fix: react-doctor errors → 0 errors, 1649 warnings (46/100)

- Add requireAuth() to admin-permissions.ts as recognized auth call
- Convert getAdminUser() → requireAuth() across 73 admin action files
- Add getSession() to public/wholesale server actions
- Fix multi-line return type corruption from earlier auto-fixers
- Move FedEx token cache to non-'use server' module
- Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD,
  EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS
- Update Stripe API version 2026-05-27 → 2026-06-24
- Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient
- Fix 51 TypeScript errors (return type corruption, missing imports)
This commit is contained in:
Nora
2026-06-25 23:49:37 -06:00
parent 4d295ef062
commit 0ac4beaaa8
580 changed files with 52565 additions and 4953 deletions
@@ -0,0 +1,671 @@
# Acceptance Criteria — 2026-06-25
> Format per item: **AC-NN.SS** (module.section) — Given/When/Then or assertion. Edge cases follow each AC block prefixed `EC-NN.SS.x`.
## Risk tier definitions
- **C — Critical**: any data loss, RBAC bypass, payment error, auth failure, or cross-tenant data leak. **Must** pass before sign-off.
- **H — High**: core CRUD flows used daily by admin/brand_admin. Pass-blocking.
- **M — Medium**: secondary features (filters, exports, analytics). Pass-blocking for release quality, acceptable for hot-fix.
- **L — Low**: cosmetics, marketing pages, static content. Best-effort.
---
## ROLES & IDENTITY (C)
### AC-R1 — dev_session cookie impersonates roles in dev mode
- **Given** NODE_ENV !== "production"
- **When** request carries `Cookie: dev_session=platform_admin`
- **Then** `getAdminUser()` returns `{ role: 'platform_admin', brand_id: null, brandIds: [...], email: 'qa-platform@...' }`
- **And** every `/admin/*` route renders without `<AdminAccessDenied />`
EC-R1:
- EC-R1.1: invalid value (`dev_session=hacker`) → returns null (no impersonation)
- EC-R1.2: missing cookie → null → `/admin/*` shows Access Denied
- EC-R1.3: NODE_ENV=production → dev_session is ignored, real Neon Auth required
### AC-R2 — RBAC enforcement
- **Given** admin user with `can_manage_orders=false`
- **When** visiting `/admin/orders/[id]`
- **Then** redirect to `/admin/pickup`
- **And** `/admin/orders` shows Access Denied or redirects similarly
EC-R2:
- EC-R2.1: any permission flag `can_manage_X=false` enforces redirect on `/admin/X`
- EC-R2.2: all flags `true` → no redirects
- EC-R2.3: brand_admin without brand link → null user → Access Denied
### AC-R3 — Tenant scoping
- **Given** brand_admin of brand A
- **When** calling RPCs with `p_brand_id=brand_A_id`
- **Then** only brand A rows are returned
- **When** calling with `p_brand_id=brand_B_id`
- **Then** zero rows or 403/empty depending on the action
EC-R3:
- EC-R3.1: platform_admin with `p_brand_id=null` returns all brands
- EC-R3.2: brand_admin calling with `p_brand_id=null` falls back to their `brand_id`
- EC-R3.3: brand_admin calling with `p_brand_id=other_brand_id` → zero/empty
---
## ADMIN SHELL (C)
### AC-A1 — `/admin/page.tsx` redirects to `/admin/v2`
- **Then** 307 → `/admin/v2`
### AC-A2 — `/admin/v2` dashboard renders for platform_admin
- **Then** shows "Today" heading + stat cards + stops + orders
- **And** no Access Denied
EC-A2:
- EC-A2.1: 0 stops today → "No stops scheduled today" empty state
- EC-A2.2: 0 pending orders → empty state
- EC-A2.3: activeBrandId null → "No brand selected" empty state
### AC-A3 — AdminSidebar nav links navigate correctly
- **Then** every Workspace/Operations/Communications/Growth/Tracking/Insights/Settings link navigates to its target route without error
EC-A3:
- EC-A3.1: Tracking → Water Log hidden when `enabledAddons["water_log"]=false`
- EC-A3.2: Tracking → Route Trace hidden when `enabledAddons["route_trace"]=false`
- EC-A3.3: `requiresPlatformAdmin` items hidden for non-platform users
- EC-A3.4: mobile menu closes on link click
- EC-A3.5: ESC closes mobile menu
- EC-A3.6: focus moves to close button when mobile menu opens
- EC-A3.7: arrow-key navigation loops top-to-bottom-to-top
### AC-A4 — MobileTabBar navigates and shows active state
- **Then** clicking each tab navigates + highlights active tab matching `pathname`
EC-A4:
- EC-A4.1: clicking More opens MoreSheet
- EC-A4.2: clicking a MoreSheet link closes sheet and navigates
### AC-A5 — CommandPalette opens on Cmd/Ctrl+K
- **Then** modal appears, search input focused
EC-A5:
- EC-A5.1: ↑↓ navigation, Enter selects, ESC closes
- EC-A5.2: body scroll locked while open
- EC-A5.3: backdrop click closes
- EC-A5.4: empty results → "No results for 'query'"
- EC-A5.5: navigate via result updates URL and closes palette
### AC-A6 — BrandSelector switches active brand
- **Then** selecting a brand sets cookie + refreshes page; `BrandSelector` reflects new active
EC-A6:
- EC-A6.1: "All brands" option visible only to platform_admin
- EC-A6.2: multi-brand admin → "multi" badge
- EC-A6.3: outside click closes
- EC-A6.4: ESC closes
### AC-A7 — OfflineBanner shows when offline or pending
- **Then** offline → danger-soft banner; pending sync → warning-soft banner
- **When** online + 0 pending → banner hidden
EC-A7:
- EC-A7.1: pre-mount → null (no hydration mismatch)
---
## ORDERS (C)
### AC-M1.1 — `/admin/orders` legacy list loads
- **Then** status tabs (all/pending/picked_up) render with counts
- **And** table shows orders sorted by placed_at desc
EC-M1.1:
- EC-M1.1.1: empty state when 0 orders → "No orders yet" + Create CTA
- EC-M1.1.2: filter by status → list filters correctly
- EC-M1.1.3: search by customer name → matches substring
- EC-M1.1.4: search by phone → matches digits
- EC-M1.1.5: search by order id prefix → matches
- EC-M1.1.6: pagination next/prev changes page
- EC-M1.1.7: stop filter multi-select → list filtered by selected stops
- EC-M1.1.8: clearing filters shows all orders
- EC-M1.1.9: bulk select + Mark All Picked Up → all selected marked, toast shows counts
- EC-M1.1.10: partial bulk failure → some succeed, others revert, error toast
- EC-M1.1.11: `?new=true` opens modal on mount
- EC-M1.1.12: row click navigates to `/admin/orders/[id]`
### AC-M1.2 — `/admin/orders/new` modal
- **Then** modal opens with required: customer name; optional: email, phone, stop
- **And** items table with at least 1 row
EC-M1.2:
- EC-M1.2.1: empty customer name → submission blocked
- EC-M1.2.2: 0 items → Create Order disabled
- EC-M1.2.3: invalid price (< 0 or non-numeric) → submission blocked
- EC-M1.2.4: qty < 1 → blocked
- EC-M1.2.5: success → full-page reload to `/admin/orders`, modal closes
- EC-M1.2.6: server error → red error banner inside modal
- EC-M1.2.7: ESC closes modal
- EC-M1.2.8: backdrop click closes modal
### AC-M1.3 — `/admin/orders/[id]` detail + edit
- **Then** shows customer, stop, items, totals, payment section, edit form
EC-M1.3:
- EC-M1.3.1: invalid id → "Order not found"
- EC-M1.3.2: empty customer name on save → "Customer name is required"
- EC-M1.3.3: negative discount → "Discount cannot be negative"
- EC-M1.3.4: remove item → marked removed, deleted on save
- EC-M1.3.5: save success → toast + page refreshes
- EC-M1.3.6: status set to fulfilled (not exposed in UI) → should not be reachable
- EC-M1.3.7: pickup toggle: not picked up → "Mark Picked Up"; picked up → button hidden
- EC-M1.3.8: refund amount = 0 → button disabled
- EC-M1.3.9: refund amount > remaining balance → validation error
- EC-M1.3.10: refund amount = remaining balance → success
### AC-M1.4 — `/admin/v2/orders` mobile list
- **Then** cards sorted; status filter via `?status=`
EC-M1.4:
- EC-M1.4.1: 0 orders → "No orders yet"
- EC-M1.4.2: status=placed → only placed orders shown
- EC-M1.4.3: card click → /admin/v2/orders/[id]
### AC-M1.5 — `/admin/v2/orders/[id]` mobile detail
- **Then** shows timeline + sticky action bar with status buttons
EC-M1.5:
- EC-M1.5.1: status transitions: placed → ready → picked-up; cancel from any state
- EC-M1.5.2: invalid id → notFound()
---
## STOPS (H)
### AC-M2.1 — `/admin/stops` list
- **Then** table sorted by date asc; tabs filter by status
EC-M2.1:
- EC-M2.1.1: search matches city/location substring
- EC-M2.1.2: Add Stop modal opens
- EC-M2.1.3: Upload Schedule modal opens
- EC-M2.1.4: Edit → /admin/stops/[id]
- EC-M2.1.5: Publish (draft only) → status changes to active, refreshes
- EC-M2.1.6: Duplicate → /admin/stops/new?duplicate={id}
- EC-M2.1.7: Delete → confirm popover → confirm → row removed
### AC-M2.2 — `/admin/stops/new` form
- **Then** required: city/state/location/date/time/brand
EC-M2.2:
- EC-M2.2.1: missing required field → red border + helper
- EC-M2.2.2: `?duplicate={id}` pre-fills from existing stop
- EC-M2.2.3: brand selector hidden for non-platform_admin
### AC-M2.3 — AddStopModal
- **Then** opens with autofocus on city
EC-M2.3:
- EC-M2.3.1: missing city/state/location/date → submission blocked
- EC-M2.3.2: state uppercased on input
- EC-M2.3.3: state max 2 chars
- EC-M2.3.4: ESC closes
- EC-M2.3.5: success → modal closes + list refreshes
### AC-M2.4 — LocationsTab
- **Then** table with search + status tabs + pagination
EC-M2.4:
- EC-M2.4.1: Add Venue modal opens
- EC-M2.4.2: Edit modal opens with pre-filled fields
- EC-M2.4.3: Delete with confirm → row removed
- EC-M2.4.4: search matches name/address/city/contact_name
### AC-M2.5 — `/admin/v2/stops` mobile
- **Then** cards bucketed by Morning/Afternoon/Evening/Anytime
EC-M2.5:
- EC-M2.5.1: `?date=YYYY-MM-DD` filters
- EC-M2.5.2: invalid date → empty or error
- EC-M2.5.3: 0 stops → "No stops scheduled"
- EC-M2.5.4: card with address → maps link works
---
## PRODUCTS (H)
### AC-M3.1 — `/admin/products` legacy list
- **Then** shows all brand-scoped products
EC-M3.1:
- EC-M3.1.1: 0 products → empty state
- EC-M3.1.2: platform_admin sees products from all brands
- EC-M3.1.3: brand_admin sees only their brand
### AC-M3.2 — `/admin/products/new` form
- **Then** name required, type/brand/taxable/pickup_type/active selects
EC-M3.2:
- EC-M3.2.1: missing name → blocked
- EC-M3.2.2: image upload: drag-drop or click → preview
- EC-M3.2.3: oversized image (>5MB) → error
- EC-M3.2.4: wrong type → error
- EC-M3.2.5: success → /admin/products
- EC-M3.2.6: brand locked for non-platform_admin
### AC-M3.3 — `/admin/products/import` CSV
- **Then** preview table shows parsed rows
EC-M3.3:
- EC-M3.3.1: malformed CSV → parse errors listed
- EC-M3.3.2: missing brand ID → Import disabled
- EC-M3.3.3: success → counts panel
### AC-M3.4 — `/admin/v2/products` mobile
- **Then** cards with status pill + StockAdjustButton
EC-M3.4:
- EC-M3.4.1: `?filter=in-stock` → only products with inventory > 0
- EC-M3.4.2: `?filter=out` → only inventory = 0
- EC-M3.4.3: `?filter=hidden` → only active=false
- EC-M3.4.4: StockAdjustButton increments/decrements inventory
---
## COMMUNICATIONS (H)
### AC-M6.1 — Campaigns list + create + edit
- **Then** list with type/status filters
EC-M6.1:
- EC-M6.1.1: New Campaign → name + type required → Create
- EC-M6.1.2: Save Draft → status=draft, no send
- EC-M6.1.3: Schedule Campaign with future datetime → status=scheduled
- EC-M6.1.4: Send Campaign → calls sendCampaign → status=sent
- EC-M6.1.5: Schedule with past datetime → blocked (datetime-local min = now)
- EC-M6.1.6: missing subject/body → blocked
- EC-M6.1.7: previewAudience → shows count + sample emails
### AC-M6.2 — Templates list + edit
- **Then** edit fields: subject, body_text, body_html, template_type, campaign_type
EC-M6.2:
- EC-M6.2.1: missing subject → blocked (subject is NOT NULL)
- EC-M6.2.2: success → list refreshes
### AC-M6.3 — Contacts list
- **Then** search + source filter + pagination
EC-M6.3:
- EC-M6.3.1: delete with confirm → row removed
- EC-M6.3.2: Export CSV → file downloads
- EC-M6.3.3: pagination prev/next
### AC-M6.4 — Message logs
- **Then** status filter + search + pagination (1-5 pages)
EC-M6.4:
- EC-M6.4.1: filter by status=failed → only failed shown
- EC-M6.4.2: stats cards reflect filtered count
### AC-M6.5 — Abandoned Carts
- **Then** stats + filter (all/active/recovered) + pagination
EC-M6.5:
- EC-M6.5.1: View → detail modal with items + timestamps
- EC-M6.5.2: Close → status=manually_closed
- EC-M6.5.3: Resend → calls resendAbandonedCartEmail
### AC-M6.6 — Communication Settings
- **Then** senderEmail/senderName/replyTo/footerHtml form
EC-M6.6:
- EC-M6.6.1: invalid email → error
- EC-M6.6.2: `{unsubscribe_url}` placeholder preserved in footerHtml
### AC-M6.7 — ContactImportForm (multi-step)
- **Then** drag/drop or click → preview → import
EC-M6.7:
- EC-M6.7.1: file >5MB → bucket path
- EC-M6.7.2: missing required mapping → blocked
- EC-M6.7.3: success → counts panel
---
## WHOLESALE (H)
### AC-M7.1 — Wholesale dashboard loads
- **Then** shows tabs for Customers / Orders / Pricing / Settings
EC-M7.1:
- EC-M7.1.1: 0 customers → empty state
- EC-M7.1.2: 0 orders → empty state
### AC-M7.2 — Wholesale Customers
- **Then** list with search + status filter
EC-M7.2:
- EC-M7.2.1: bulk "Send Price Sheet" → calls sendPriceSheetToCustomer N times
### AC-M7.3 — Wholesale Orders
- **Then** list with status filter
EC-M7.3:
- EC-M7.3.1: bulk Fulfill → multiple fulfillments
- EC-M7.3.2: bulk Deposit → multiple deposits
### AC-M7.4 — Wholesale Settings
- **Then** form saves via updateWholesaleSettings
EC-M7.4:
- EC-M7.4.1: invalid email → error
- EC-M7.4.2: min_order_amount < 0 → validation
---
## SETTINGS (H)
### AC-M8.1 — BrandSettingsForm
- **Then** all sections save atomically
EC-M8.1:
- EC-M8.1.1: logo upload → preview + URL stored
- EC-M8.1.2: nexus state pill: add via Enter or comma → chip appears
- EC-M8.1.3: nexus state pill: click ✕ → chip removed
- EC-M8.1.4: state field: lowercased input → uppercased display
- EC-M8.1.5: brand colors: text input + color picker stay in sync
- EC-M8.1.6: feature toggles: each toggle saves independently
### AC-M8.2 — BrandFeatureCards
- **Then** each card shows status + Enable/Disable
EC-M8.2:
- EC-M8.2.1: Enable → confirmation modal → success toast + card shows Active
- EC-M8.2.2: Disable → optimistic toggle → rollback on failure
- EC-M8.2.3: "Open →" link visible only when enabled + adminRoute set
### AC-M8.3 — CreateUserModal
- **Then** email/password (≥6) required
EC-M8.3:
- EC-M8.3.1: invalid email (no @) → blocked
- EC-M8.3.2: password < 6 → blocked
- EC-M8.3.3: missing brand for brand_admin → blocked
- EC-M8.3.4: success → temp password surfaced + email-sent status
- EC-M8.3.5: Copy button copies temp password
- EC-M8.3.6: role options depend on caller role
### AC-M8.4 — PaymentSettingsForm
- **Then** provider radio + Stripe/Square OAuth buttons + Square Location ID
EC-M8.4:
- EC-M8.4.1: Square Location ID without `L` prefix → blocked
- EC-M8.4.2: Save disabled when not dirty
- EC-M8.4.3: Stripe OAuth flow → return with ?stripe_connected=true → URL param stripped
- EC-M8.4.4: Square OAuth flow → return with ?square_connected=true → URL param stripped
- EC-M8.4.5: Sync Products/Orders/All Now → shows result banner
### AC-M8.5 — AdvancedSettingsClient tabs
- **Then** tabs render Integrations / AI / Square / Shipping / Webhooks / Payments
EC-M8.5:
- EC-M8.5.1: Integrations: Resend + Twilio save independently
- EC-M8.5.2: AI Tools: provider selection changes default model
- EC-M8.5.3: AI Tools: API key required for Test
- EC-M8.5.4: Square Sync: App ID + Access Token required for Test
- EC-M8.5.5: Shipping: Test Connection (simulated) shows success after 1s
- EC-M8.5.6: Webhooks tab shows "Coming Soon"
- EC-M8.5.7: Payments: Connect with Stripe → redirects to Stripe
---
## ANALYTICS (M)
### AC-M9.1 — AnalyticsDashboard loads
- **Then** KPI cards + revenue chart + top products + recent orders + customer growth + funnel
EC-M9.1:
- EC-M9.1.1: period selector change → chart re-renders (visual only, no refetch)
- EC-M9.1.2: Refresh → fetches all
- EC-M9.1.3: Export Report → no-op
- EC-M9.1.4: 0 revenue → "No revenue data available yet"
- EC-M9.1.5: error → retry button works
---
## TIME TRACKING (H)
### AC-M11.1 — TimeTrackingAdminPanel tabs
- **Then** Summary / Workers / Tasks / Logs / Settings
EC-M11.1:
- EC-M11.1.1: 0 workers → empty
- EC-M11.1.2: Add Worker → name/role/language/active → Create
- EC-M11.1.3: Reset PIN → calls resetTimeWorkerPin
- EC-M11.1.4: Delete Worker → confirm → removed
- EC-M11.1.5: Logs filter by worker + task + date range → list updates
- EC-M11.1.6: Logs pagination 50/page
- EC-M11.1.7: Export → CSV downloads from /api/time-tracking/export
---
## WATER LOG (H)
### AC-M11.2 — WaterLogAdminPanel
- **Then** Add Headgate inline + Add User inline + filter chips + Export CSV
EC-M11.2:
- EC-M11.2.1: missing name → blocked
- EC-M11.2.2: PIN auto-generated on user create → PinBanner shows
- EC-M11.2.3: Rotate token → token changes
- EC-M11.2.4: Filter chips → entries filtered
- EC-M11.2.5: Export CSV → downloads
- EC-M11.2.6: Preview Report → window.alert
### AC-M11.3 — HeadgatesManager bulk QR
- **Then** select multiple + Print → new window with QR sheet
EC-M11.3:
- EC-M11.3.1: 0 selected → Print disabled
- EC-M11.3.2: regenerate token → existing printed QRs invalidated
- EC-M11.3.3: per-headgate QR Download PNG
### AC-M11.4 — Water Admin Settings
- **Then** toggles + session duration slider + alert phone
EC-M11.4:
- EC-M11.4.1: Regenerate PIN → confirm → PIN revealed
- EC-M11.4.2: session duration 1-168 hours only
- EC-M11.4.3: Save Settings → success banner
---
## ROUTE TRACE (M)
### AC-M11.5 — RouteTracePage loads
- **Then** shell renders with stat cards + lot list
EC-M11.5:
- EC-M11.5.1: missing feature flag → redirect
- EC-M11.5.2: lot detail → timeline + orders
---
## LAUNCH CHECKLIST (L — known cosmetic)
### AC-M11.6 — LaunchChecklist renders
- **Then** 8 categories × 4 tasks
EC-M11.6:
- EC-M11.6.1: "Mark All Complete" → no-op (documented limitation)
- EC-M11.6.2: "Export PDF" → no-op
- EC-M11.6.3: Per-task checkbox → no-op
- EC-M11.6.4: "Go →" links work
---
## IMPORT CENTER (M)
### AC-M11.7 — ImportCenterClient wizard
- **Then** Upload → Analyze → Preview → Import
EC-M11.7:
- EC-M11.7.1: drag/drop or click upload
- EC-M11.7.2: file >10MB → blocked
- EC-M11.7.3: AI detection confidence <80% → type override buttons shown
- EC-M11.7.4: column mapping dropdowns
- EC-M11.7.5: Import disabled when type=unknown
- EC-M11.7.6: success → counts + "View" link
---
## PICKUP (C)
### AC-M4.1 — DriverPickupPanel
- **Then** pending orders + picked-up orders collapsible
EC-M4.1:
- EC-M4.1.1: Pick Up → calls markPickupComplete → toast 3s
- EC-M4.1.2: stop filter → list filtered
- EC-M4.1.3: search → matches name/phone/order id prefix
- EC-M4.1.4: 0 pending → empty state
- EC-M4.1.5: picked-up older than 72h → hidden
---
## SHIPPING (C)
### AC-M5.1 — ShippingFulfillmentPanel
- **Then** shipping orders list
EC-M5.1:
- EC-M5.1.1: 0 shipping orders → empty
- EC-M5.1.2: filter by status
---
## PUBLIC/AUTH (H)
### AC-P1 — Login page
- **Then** email + password submit to /api/auth/sign-in
EC-P1:
- EC-P1.1: missing email → HTML5 validation blocks submit
- EC-P1.2: missing password → blocked
- EC-P1.3: invalid credentials → error banner
- EC-P1.4: dev_session=platform_admin → sets cookie and redirects /admin
- EC-P1.5: Google button disabled while loading
- EC-P1.6: Forgot? link → /forgot-password (or /api/forgot-password?)
### AC-P2 — Change password
- **Then** new password + confirm match + ≥8 chars
EC-P2:
- EC-P2.1: passwords don't match → error
- EC-P2.2: <8 chars → error
- EC-P2.3: success → /admin
- EC-P2.4: "Sign out instead" → /logout
### AC-P3 — Reset password
- **Then** new password + confirm
EC-P3:
- EC-P3.1: same as AC-P2
### AC-P4 — Logout
- **Then** spinner → /api/auth/sign-out → /login
### AC-P5 — Maintenance splash
- **Then** no controls, only mailto + /contact links
### AC-P6 — Homepage
- **Then** hero + CTA links
### AC-P7 — Pricing
- **Then** BillingToggle, FAQ accordion, Compare table expand
EC-P7:
- EC-P7.1: Monthly ↔ Annual swap content
- EC-P7.2: FAQ accordion: each opens/closes independently
- EC-P7.3: Compare table expand/collapse
### AC-P8 — Contact
- **Then** form simulated submit (setTimeout)
EC-P8:
- EC-P8.1: missing required field → blocked
- EC-P8.2: success card replaces form
### AC-P9 — Blog
- **Then** static + decorative newsletter form
### AC-P10 — Changelog
- **Then** static timeline + RSS link
### AC-P11 — Roadmap
- **Then** 3-column + Suggestion form (no submit) + Upvote buttons (no handlers)
EC-P11:
- EC-P11.1: form submit does nothing (reloads page)
- EC-P11.2: upvote buttons no-op
### AC-P12 — Security
- **Then** static + mailto
### AC-P13 — Privacy / Terms
- **Then** static text
### AC-P14 — Brands showcase
- **Then** per-brand visit links
### AC-P15 — Waitlist
- **Then** form posts to /api/waitlist
EC-P15:
- EC-P15.1: missing email → blocked
- EC-P15.2: success card replaces form
- EC-P15.3: server error → inline error
### AC-P16 — Cart
- **Then** items list + qty controls + stop picker
EC-P16:
- EC-P16.1: button decreases qty; at qty=1, "Remove" is the only path
- EC-P16.2: + increases qty
- EC-P16.3: Remove → item disappears
- EC-P16.4: stop mismatch → "Choose Correct Stop" alert
- EC-P16.5: incompatible items → "Remove Incompatible Items First" CTA
- EC-P16.6: empty cart → "Cart is Empty" CTA
- EC-P16.7: availability error → "Remove Incompatible Items First"
- EC-P16.8: ESC closes stop picker
- EC-P16.9: backdrop closes stop picker
### AC-P17 — Checkout
- **Then** customer details + stop select + shipping (if ship) + Stripe Express iframe
EC-P17:
- EC-P17.1: missing email → blocked
- EC-P17.2: no stop + ship items → blocked (?)
- EC-P17.3: state field uppercased
- EC-P17.4: "Use secure hosted checkout" → Stripe redirect (uses placeholder key, will fail in QA)
- EC-P17.5: empty cart → "Back to storefront" link
### AC-P18 — Auth API routes
- /api/auth/sign-in: 400/401/500 surfaces error
- /api/auth/sign-out: redirect /login
- /api/auth/forgot-password: always success (anti-enumeration)
- /api/auth/reset-password: 400 on <8 chars
- /api/auth/change-password: auth required
---
## Known cosmetic-only behaviors (NOT bugs)
- `/admin/launch-checklist` checkboxes, "Mark All Complete", "Export PDF" are all no-ops (M11.6)
- `/admin/analytics` "Export Report" is no-op (M9.1 EC-3)
- `/admin/analytics` period selector doesn't refetch (M9.1 EC-1)
- `/admin/me` profile + email change forms always show "temporarily unavailable" (AdminMeClient)
- `/admin/advanced/shipping` and `/admin/settings/square-sync/advanced` are static mocks with setTimeout simulators
- `/admin/communications/compose`, `/contacts`, `/segments`, `/logs`, `/analytics` all redirect to `?tab=` query (preserved backward compat)
- `/blog/newsletter` and `/blog/download` buttons have no handlers
- `/roadmap/suggestion` form and upvotes have no handlers
- `/admin/orders/[id]` does NOT expose "fulfilled" status (deliberate)
These are **documented limitations**, not bugs. Any change to them should be reviewed for product intent first.
+61
View File
@@ -0,0 +1,61 @@
# Environment Differences from Production
This audit runs against a local Dockerized Postgres + dev server, not production. Below are the differences an auditor should know about before trusting any finding.
## 1. Neon Auth is stubbed
Production uses Neon Auth (Better Auth) to manage `neon_auth.user`. In this audit, that schema/table is a minimal local stub created by `db/migrations/0000_qa_neon_auth_stub.sql`:
```sql
CREATE SCHEMA IF NOT EXISTS neon_auth;
CREATE TABLE IF NOT EXISTS neon_auth.user (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
Authentication is **not exercised via the real sign-in flow**. Instead, the `dev_session` cookie impersonates roles:
| Cookie value | Impersonates |
|---|---|
| `dev_session=platform_admin` | Cross-brand admin (`role='platform_admin'`, `brand_id=null`) |
| `dev_session=brand_admin` | Single-brand admin |
| `dev_session=store_employee` | Limited-scope employee |
This is gated by `process.env.NODE_ENV !== "production"`. Any bug found in the real Neon Auth flow (sign-in, password reset, MFA, OAuth) **cannot** be reproduced here.
## 2. Migration 0002 (`0002_admin_password.sql`) is marked applied but does not run
The migration references a legacy `users` table that no longer exists in the current schema (the codebase migrated from Auth.js Credentials to Neon Auth). The migration is recorded in `_migrations` so the runner skips it, and the column it would have added (`users.password_hash`) is absent. **This is a separate latent issue** worth a follow-up PR: 0002 should be removed from the migrations directory or rewritten as a no-op.
## 3. New migration `0000_qa_neon_auth_stub.sql` (audit-only)
Added to make migrations runnable without Neon Auth. Should not be applied to production (it would conflict with the real `neon_auth.user` table). Move to a dev-only seed if reused.
## 4. Seed file `db/seeds/2026-qa-audit-scale.sql` (audit-only)
Replaces the broken `db/seed.ts`, which references removed columns (`brand_settings.brand_name`). Idempotent on a clean DB; re-runs without reset will duplicate rows in tables lacking unique constraints on natural keys (orders, order_items).
## 5. Stripe / Resend / Square credentials are placeholders
```
STRIPE_SECRET_KEY=sk_test_placeholder_for_qa_audit
RESEND_API_KEY=re_placeholder_for_qa_audit
SQUARE_ACCESS_TOKEN=square_placeholder_for_qa_audit
```
Real network calls to those services would fail. The audit tests UI navigation, server actions that don't depend on real third-party responses, and DB-backed flows. Stripe checkout, real Resend sends, Square inventory sync — these are out of scope for this audit run.
## 6. Database port
The audit Postgres runs on `:5433`, not `:5432`. `:5432` is occupied by `n8n-postgres-1`.
## 7. The "production-scale" data is sanitized
No real customer PII, no real payment tokens, no real email addresses. All customer emails are `@routecomm.example` (RFC 2606 reserved). Phone numbers follow the +1-555-01xx-xxxx range reserved for fictional use.
## 8. RBAC scope
This audit tests **only** the `platform_admin` role. The QA users `qa-tuxedo@routecomm.example` and `qa-ird@routecomm.example` are seeded for follow-up brand-scoped testing but not exercised here.
+333
View File
@@ -0,0 +1,333 @@
# QA Audit Inventory — 2026-06-25
> Generated for [Loop 010 — full product evaluation](https://signals.forwardfuture.com/loop-library/loops/full-product-evaluation-loop/).
> Scope: Admin shell (all 19 modules) + public/auth pages, tested as `platform_admin` via `dev_session` cookie.
> Source: 5 parallel `explore` subagents enumerated every UI control, server action, state, and workflow.
## Inventory organization
- `R1``R4`: Role/identity assumptions
- `A1``A11`: Admin shell, layout, dashboard
- `M1``M11`: Admin modules (orders, stops, products, pickup, shipping, communications, wholesale, settings, analytics, users, import)
- `P1``P19`: Public + auth pages
- `T1``T6`: Cross-cutting patterns, design system, server-action catalog
Each item: route → purpose → buttons → forms → modals → states → workflows → server actions.
---
## R — Roles & identity
### R1 — `dev_session` cookie impersonation
**File:** `src/lib/admin-permissions.ts` lines 36-39.
Gated by `NODE_ENV !== "production"`. Values: `platform_admin`, `brand_admin`, `store_employee`. Returns admin user via `buildDevAdmin(role)` bypassing Neon Auth.
### R2 — Real Neon Auth (Better Auth)
**File:** `src/lib/auth.ts`. Email/password sign-in, password reset, OAuth via `signInWithGoogleAction`. Session via `getSession()`. **Not exercised** in this audit (auth is stubbed).
### R3 — RBAC permission flags
**File:** `admin_users` table; flags: `can_manage_orders`, `can_manage_products`, `can_manage_stops`, `can_manage_customers`, `can_manage_wholesale`, `can_manage_billing`, `can_manage_settings`, `can_manage_water_log`, `can_manage_time_tracking`, `can_manage_route_trace`, `can_manage_reports`, `can_manage_communications`, `can_manage_pickup`, `can_manage_messages`, `can_manage_refunds`, `can_manage_users`, `active`. Default brand_admin = orders/products/stops/customers/pickup/messages/reports = `true`, rest `false`.
### R4 — Tenant scoping (brand isolation)
- App-layer: `effectiveBrandId = brandId ?? adminUser.brand_id ?? null`; threaded through page → client.
- DB: `current_brand_id()` GUC + RLS policies on most tables (`brand_id = current_brand_id() OR is_platform_admin()`).
- Platform admin: `brand_id IS NULL`, sees all brands via `withPlatformAdmin()`.
---
## A — Admin shell, layout, dashboard
### A1 — `/admin/layout.tsx`
Server root layout. Gates access via `getAdminUser()`, redirects on `must_change_password`. Mounts: `AdminSidebar` (desktop), `MobileTabBar` (mobile via v2 layout), `CommandPalette`, `PWAInstallPrompt`, `ToastContainer`, `RouteAnnouncer`, `SmoothViewTransition`. Brand/brand-list/addons fetched with empty fallbacks on error.
### A2 — `/admin/page.tsx` (legacy dashboard)
Redirects to `/admin/v2`. Two paths: `role === "store_employee"` → 2-card landing (Pickup Lookup + Wholesale); otherwise fetches `getDashboardStats()` + `getBillingOverview()` server-side, renders `DashboardClient`.
### A3 — `/admin/v2/layout.tsx` + `/admin/v2/page.tsx` (mobile-first dashboard)
Mobile "Today" dashboard. Fetches summary/stops/orders in parallel. `Promise.all` failure → silent empty state. Renders `EmptyState` ("No brand selected" / "Nothing to show yet"). `pending_fulfillment > 0` → amber callout link `/admin/v2/orders?status=placed`. Top 5 today stops + top 5 pending orders. Pull-to-refresh wrapper.
### A4 — `AdminShell.tsx`
Responsive shell: `useMediaQuery("(min-width: 1024px)")` swaps desktop sidebar vs mobile (`OfflineBanner` + `MobileTabBar`). Mounts `OfflineBanner` only on mobile. `pb-20` mobile bottom padding for tab bar.
### A5 — `AdminSidebar.tsx`
Desktop nav + mobile slide-in. Nav groups: Workspace / Operations / Communications / Growth / Tracking / Insights / Settings. Brand selector pill. Sign-out button (`signOutAction`). Hamburger on `<lg`. Focus trap + arrow-key navigation + body scroll lock + ESC closes. `requiresPlatformAdmin` filter; `enabledAddons` hides Water Log/Route Trace when disabled.
### A6 — `MobileTabBar.tsx`
Bottom nav for `/admin/v2/*`. 5 tabs: Home / Orders / Stops / Products / More (opens `MoreSheet`). Active highlight via `pathname` match.
### A7 — `MoreSheet.tsx`
Right-anchored native `<dialog>`. Sections: Operations (Pickup, Shipping, Reports, Analytics), Marketing (Communications, Sales), Settings (Settings, Advanced). Saves focus on open, restores on close.
### A8 — `CommandPalette.tsx`
Cmd/Ctrl+K modal. Fuzzy search over static entry list (`./command-palette-data.ts`). Up to 8 results. ↑↓ to navigate, Enter to select, ESC closes. `body.style.overflow = "hidden"` while open. Honors `prefers-reduced-motion`.
### A9 — `BrandSelector.tsx`
Pill dropdown for active brand. Calls `setActiveBrand(brandId)` server action, then `router.refresh()`. Outside-click + ESC close. "All brands" option for platform_admin only. Multi-brand badge.
### A10 — `AdminAccessDenied.tsx`
Static card with "Go to Login" + "Return to homepage" links.
### A11 — `OfflineBanner.tsx`
Sticky top banner: warning-soft when syncing, danger-soft when offline. Polls pendingCount every 1 s. Hidden pre-mount to avoid hydration mismatch.
---
## M — Admin modules
### M1 — Orders
- `/admin/orders` (legacy) → `AdminOrdersPanel`: KPI tiles, status tabs (all/pending/picked_up), search (name/phone/id), stop multi-filter dropdown, table with checkbox + bulk pickup, pagination (PAGE_SIZE 20), `+ New Order` modal. Modal fields: customer name (required), email (optional), phone (optional), stop (optional), items[product+qty+price+fulfillment]. Workflows: create→full-page-reload, single pickup (optimistic), bulk pickup (loop).
- `/admin/orders/[id]``OrderEditForm` + `OrderPaymentSection` + `OrderPickupAction`. Edit fields: items qty/price, discount (must be ≥ 0), customer name (required), email/phone, status (pending/confirmed/cancelled — **fulfilled not exposed**), pickup toggle, internal notes. Payment fields: processor (none/manual/stripe/square/cash/venmo/other), payment status, transaction id, refund amount (0 < x ≤ remaining balance) + reason.
- `/admin/orders/new` → redirect to `/admin/orders?new=true` (opens modal).
- `/admin/v2/orders` → card-style list, `?status=` filter (placed/ready/picked-up/cancelled), LIMIT 50, no pagination.
- `/admin/v2/orders/[id]` → mobile detail with `FulfillmentTimeline` + sticky action bar (`OrderActionButtons`).
### M2 — Stops
- `/admin/stops``AdminStopsPanel`: status tabs (all/active/draft/inactive), search city/location, `Add Stop` modal, `Upload Schedule` modal, per-row Edit/Publish/Duplicate/Delete. Delete uses inline popover confirm. PAGE_SIZE 50.
- `/admin/stops/[id]` → product assignment + edit form + `MessageCustomersSection`.
- `/admin/stops/new` → inline `NewStopForm` (not modal). Fields: city/state/location/date/time (required), brand select, active, address/zip/cutoff (optional). Also supports `?duplicate={id}`.
- `/admin/v2/stops` → card list bucketed by Morning/Afternoon/Evening/Anytime, `?date=` filter, LIMIT 200.
- Modals: `AddStopModal`, `EditStopModal`, `AddLocationModal`, `EditLocationModal`, `LocationsTab`.
### M3 — Products
- `/admin/products``ProductsClient` (legacy): list with brand scoping (platform_admin sees all). No client pagination.
- `/admin/products/new``NewProductForm`: name (required), description, price, type (pickup/shipping/both), brand (locked for non-platform_admin), taxable, pickup_type, active, image upload (drag/drop, 5MB cap, client-side resize to 1200px).
- `/admin/products/import` → CSV upload → `parseProductCSV` → preview → `importProductsBatch`.
- `/admin/v2/products` → mobile card list with `?filter=` (all/in-stock/low/out/hidden) + `StockAdjustButton` per card.
### M4 — Pickup
- `/admin/pickup``DriverPickupPanel`. Pending orders (filter by stop + search), per-order `Pick Up` button (calls `markPickupComplete`). Picked-up section collapsible (last 72h). Toast `✓ Order picked up` 3s.
### M5 — Shipping
- `/admin/shipping``ShippingFulfillmentPanel`. Pre-loaded via `getShippingOrders()`. (Details in deeper inspection.)
- `/admin/settings/shipping` (advanced subtab) → `AdvancedShipping`: static UI mock — no server action calls. Carrier select (fedex/ups/usps/dhl), account/api fields, eye toggle, **Test Connection is simulated (1s setTimeout), Save Settings is simulated (500ms setTimeout)**.
### M6 — Communications (Harvest Reach)
Unified hub at `/admin/communications` with 8 tabs:
- **Campaigns**: list with type/status filters. `New Campaign` modal (name + campaignType). Edit panel: name, type, template, audience target (stop/zip_code/customer_history/all_customers), stop_id/dates, subject, body, schedule (now/later + datetime-local). Save Draft / Schedule Campaign / Send Campaign buttons. `upsertCampaign`, `deleteCampaign`, `getCampaignTemplates`, `getCommunicationSegments`, `previewCampaignAudience`, `sendCampaign` server actions.
- **Compose**: legacy redirect target.
- **Templates**: list + edit (template_type, subject, body_text, body_html, campaign_type). Server: `getCommunicationTemplates`, `getTemplateById`.
- **Contacts**: search + source filter, pagination (50/page, offset), per-row delete (confirm), export CSV (`exportContacts`). Bulk: none. Card layout on `<sm`.
- **Segments**: redirect → `?tab=segments`.
- **Logs**: `MessageLogPanel` — search by email/subject/status, status filter, pagination (20/page, numeric 1-5). Stats cards: total/delivered/failed/pending. `getMessageLogs` (server-side limit 100).
- **Analytics**: redirect → `?tab=analytics`.
- **Settings** (`/admin/communications/settings`): `CommunicationSettingsForm` — sender email/name, reply-to, footerHtml (supports `{unsubscribe_url}` placeholder).
- **Abandoned Carts** (`/admin/communications/abandoned-carts`): 3-step sequence (1h/24h/48h). Stats (total/active/recovered/expired), filter, pagination (25/page, 7×7 prev/next), View/Close/Resend actions per cart. Modal: cart detail with items + timestamps.
- **Welcome Sequence** (`/admin/communications/welcome-sequence`): 4-email dashboard.
Server actions: `getCommunicationCampaigns`, `getCommunicationTemplates`, `getCommunicationContacts`, `getMessageLogs`, `getCommunicationSettings`, `upsertCommunicationSettings`, `getAbandonedCarts`, `manuallyCloseAbandonedCart`, `resendAbandonedCartEmail`, `sendStopBlast`, `previewContactImport`, `importContactsBatch`, `uploadContactsToBucket`, `processBucketImport`, `listImportHistory`, `exportContacts`.
### M7 — Wholesale
- `/admin/wholesale``WholesaleClient` (mounted by `getActiveBrandId`). Dashboard tabs: Customers / Orders / Pricing / Settings.
- Customers: list with search, status filter (active/inactive/suspended/pending), pagination, bulk "Send Price Sheet" (counts).
- Orders: list with status filter, bulk Fulfill / bulk Deposit (with confirms).
- Pricing: per-customer price overrides.
- Settings: `WholesaleSettingsForm` — require_approval, min_order_amount, online_payment_enabled, pickup_location, fob_location, from_email, invoice_business_name.
Server actions: `getWholesaleCustomers`, `getWholesaleOrders`, `updateWholesaleCustomer`, `sendPriceSheetToCustomer`, `bulkFulfillOrders`, `recordWholesaleDeposit`, `getWholesaleSettings`, `updateWholesaleSettings`.
### M8 — Settings (`/admin/settings`)
Main hub. Tabs (URL anchor): brand / addons / users / billing.
- **Brand** (`/admin/settings` `#brand`): `BrandSettingsForm` — company info, address, 4 logo uploads, email/invoice branding, storefront customization (tagline, hero image, about, footer), brand colors (4 pickers), tax settings (collect toggle + nexus states), feature toggles (wholesale/zip/schedule-pdf/text-alerts), schedule PDF footer.
- **Add-ons** (`/admin/settings` `#addons`): `BrandFeatureCards` — grid of cards from `ADDON_CATALOG`. Enable/Disable toggle (with GlassModal confirm) + optimistic with rollback on failure.
- **Users** (`/admin/settings` `#users`): list of admin users + `CreateUserModal`. Create fields: email (required, must include @), password (≥6), display name, phone, role (3-way radio gated by caller), brand (required when brand_admin/store_employee), 9 permission toggles. Surfaces temp password on success.
- **Billing**: tier display + Stripe portal link (if customer_id set).
- **Payments** (`/admin/settings/payments`): `PaymentSettingsForm` — provider radio (None/Stripe/Square), Stripe OAuth button (`/api/stripe/oauth`), Square OAuth (`/api/stripe/oauth` → Square). Square Location ID validated `L` prefix. Inventory Mode radio (None/RC→Square/Square→RC/Bidirectional). Sync Products/Orders/All Now buttons. Wholesale Webhook Log.
- **Square Sync** (`/admin/settings/square-sync`): `SquareSyncSettingsClient` — provider/account/last sync/status grid + sync settings + manual sync + last 50 sync log entries.
- **AI** (`/admin/settings/ai`): link cards only.
- **Advanced** (`/admin/advanced`): aggregator cards linking to sub-pages.
- **Integrations** (`AdvancedIntegrations`): Resend + Twilio credential cards (Test Connection + Save).
- **AI Tools** (`AdvancedAIPanel`): provider cards (OpenAI/Anthropic/Google/xAI/Custom), API key (eye toggle), org ID (OpenAI only), custom endpoint (Custom only), model chips with cost display, Test + Save.
- **Square Sync** (`AdvancedSquareSync`): static stub — App ID/Access Token/Location ID, sync toggles hardcoded ON, **Test/Save purely client-side setTimeout**.
- **Shipping** (`AdvancedShipping`): static mock — same as M5.
- **Webhooks**: "Coming Soon" placeholder.
- **Payments** (`AdvancedPayments`): Stripe Connect onboarding — Connect with Stripe / Complete Setup / Stripe Dashboard / Disconnect. `createStripeConnectLink`, `refreshStripeConnectLink`, `disconnectStripeConnect`, `createStripeDashboardLink`, `getStripeConnectStatus`.
### M9 — Analytics (`/admin/analytics`)
`AnalyticsDashboard`. KPI cards (revenue, orders, customers, AOV), revenue chart, top products, recent orders, customer growth ring, conversion funnel. Period selector (7D/30D/90D/1Y) — **purely visual, no server refetch**. Refresh + Export Report (no-op). Server: `getAnalyticsMetrics`, `getRevenueChart`, `getTopProducts`, `getRecentOrders`, `getCustomerGrowth`, `getConversionFunnel`.
### M10 — Users (`/admin/users`)
Redirect → `/admin/settings#users`.
### M11 — Other admin modules
- **Import Center** (`/admin/import`): `ImportCenterClient` — 4-step wizard (Upload → Analyze → Preview → Import). Accepts CSV/XLSX/XLS/TXT (≤5,000 rows / 10 MB). AI-powered type detection (products/orders/contacts/stops). Per-column mapping dropdowns. Brand select (platform_admin only). Server: `analyzeImport`, `executeImport`.
- **Sales import** (`/admin/sales/import`): CSV → `parseOrderCSV``importOrdersBatch`. Brand ID (UUID text input) required.
- **Time Tracking** (`/admin/time-tracking`): `TimeTrackingAdminPanel`. Tabs: Summary / Workers / Tasks / Logs / Settings. Workers modal (name, role, language, active). Tasks modal (name, name_es, unit, active). Logs: date range + worker + task filters, pagination (50/page offset). Export CSV → `/api/time-tracking/export`. Tuxedo-only.
- **Water Log** (`/admin/water-log`): `WaterLogAdminPanel`. Tabs inline: Headgates / Users / Entries. Add Headgate (name, unit, notes) inline. Add User (name, role=irrigator/water_admin, language=en/es, phone). 4-digit PIN auto-generated + shown in `PinBanner`. Per-headgate: Edit (link), Rotate token, Delete. Per-entry: date range + headgate + user + method filters. Export CSV (5,000 entries). Preview Report = `window.alert` of text. Settings: `/admin/water-log/settings` — admin portal toggle, session duration slider (1-168h), coarse permission flags (edit/delete/export), alert phone. Tuxedo + `can_manage_water_log`.
- **Headgates** (`/admin/water-log/headgates`): `HeadgatesManager`. Add inline + Edit modal + QR modal (Preview/Print/Download tabs). Bulk QR print via `POST /api/water-qr-sheet` → new window. Per-headgate QR endpoints: `/api/water-qr-label`, `/api/water-qr?token=...`.
- **Route Trace** (`/admin/route-trace`, `/admin/route-trace/lots`, `/admin/route-trace/lookup`, `/admin/route-trace/settings`): all render the same `RouteTracePage` shell. Server: `getRouteTraceStats`, `getRouteTraceLots`, `getHarvestLotsReadyToHaul`, `getFieldYieldSummary`, `getInventoryByCrop`, `getRecentLotEvents`. Lot detail at `/admin/route-trace/lots/[id]` with timeline + order fulfillment. New lot → modal in Lots tab.
- **Launch Checklist** (`/admin/launch-checklist`): 8 categories × 4 tasks each (32 tasks). Progress is **purely cosmetic** (hardcoded 20% / 7 of 32). Checkbox buttons have no handlers. Export PDF / Mark All Complete / Mark Launch Complete all no-ops.
---
## P — Public + auth pages
### P1 — `/login`
Email/password + Google OAuth + dev-mode shortcuts (Platform/Brand/Store buttons).
- Form: email (required, autoComplete=username), password (required, autoComplete=current-password).
- Buttons: Sign in (submit), Continue with Google (OAuth), 3 dev shortcuts.
- Links: `/forgot-password` (Forgot?).
- States: loading (sign-in/Google), error (inline role=alert), success → redirect `/admin`.
- Dev shortcuts only when `NODE_ENV !== "production"`.
### P2 — `/change-password`
Force password update.
- Form: new password (required, minLength 8), confirm password (required).
- Buttons: Update Password (submit), "Sign out instead" link → `/logout`.
- Server: `POST /api/auth/change-password`.
### P3 — `/reset-password`
New password from reset link.
- Form: new password + confirm (both required, ≥ 8 chars, must match).
- Server: `POST /api/auth/reset-password`.
### P4 — `/logout`
Effect-only: spinner + "Signing out..." → calls `/api/auth/sign-out``/login`.
### P5 — `/maintenance`
Static splash. No controls. mailto + `/contact` links.
### P6 — `/` (homepage)
Marketing landing (`LandingPageClient``HeroSection`). No forms. CTA links: `/login`, `/brands`, `/contact`. Scroll indicator.
### P7 — `/pricing`
Pricing page. BillingToggle (Monthly/Annual, aria-pressed, "-25%" badge). 3 plan CTA buttons → `/admin`. Compare table expand/collapse. 8 FAQ accordion toggles. No form/select/textarea.
### P8 — `/contact`
Contact form (simulated setTimeout submit).
- Fields: name (required), email (required), topic select (General/Orders/Wholesale/Partner/Tech), message textarea (required, rows 5).
- Buttons: Send Message (submit, spinner), Send another message (success state).
- tel + mailto links.
- States: idle, isSubmitting, success.
### P9 — `/blog`
Static. Newsletter form (decorative — no submit handler). "Read More" links to `/blog/getting-started-with-route-commerce` (slug not implemented). 3 "Download →" buttons (no handlers).
### P10 — `/changelog`
Static timeline. Links: `/admin`, `/`, RSS feed `/api/feed/changelog.xml`, `/waitlist`.
### P11 — `/roadmap`
Static 3-column roadmap.
- Suggestion form: feature title (text), description (textarea), category (select). **No submit handler.**
- 6 Upvote buttons — **no handlers.**
- Anchor jump `#suggest`.
### P12 — `/security`
Static. mailto security@routecommerce.com for vulnerability reports.
### P13 — `/privacy-policy` + `/terms-and-conditions`
Pure static legal text.
### P14 — `/brands`
Partner brands showcase. Per-brand `<Link href="/${slug}">` "Visit Store" + "View Stops". 3D-tilt parallax.
### P15 — `/waitlist`
`<WaitlistForm>`.
- Fields: name (optional text), email (required), referral select (Google/Social/Friend/Event/Podcast/Blog/Other).
- Button: Join the Waitlist (spinner → success card).
- Server: `POST /api/waitlist`.
- States: idle, isSubmitting, error, success.
### P16 — `/cart`
`<CartClient>`.
- Per-item buttons: (decrease qty), + (increase qty), Remove.
- Modal: "Choose Pickup Stop" (stop buttons + Cancel).
- Buttons: Continue to Checkout (state-gated: Cart is Empty / Select Pickup Stop First / Remove Incompatible Items First / Choose Correct Stop / Continue Shopping).
- States: empty, stop mismatch alert, incompatible items, availability error, loading stops.
### P17 — `/checkout`
`<CheckoutClient>` + Stripe Express iframe.
- Fields: customer_name (text), customer_email (required), customer_phone (tel optional), stop_id (select required when no stop), shipping_address/city/state (maxLength 2 upper)/postal_code (when ship items).
- Buttons: Change pickup stop, "Use secure hosted checkout →" (`POST createRetailStripeCheckoutSession`), `<Link href="/">` Back to storefront.
- States: empty cart, hostedLoading, hostedError, hasPickup/hasShed/hasStop/hasShip branching.
### P18 — Auth API routes
| Route | Method | Behavior |
|---|---|---|
| `/api/auth/sign-in` | POST | `{success}` or 400/401/500 with `{error,message}`. |
| `/api/auth/sign-out` | POST | Server sign-out + redirect `/login`. |
| `/api/auth/forgot-password` | POST | Always `{success:true}` (anti-enumeration). Calls `requestPasswordReset`. |
| `/api/auth/reset-password` | POST | Min 8 chars. |
| `/api/auth/change-password` | POST | Auth-required; uses `setUserPassword`. |
| `/api/auth/[...nextauth]` | GET/POST | Neon Auth handler proxy. |
### P19 — Other public pages
- `/water` — likely the field portal; brief enumeration.
- `/test-simple.html` — diagnostic HTML.
- `/api/feed/changelog.xml` — referenced but not in scope.
---
## T — Cross-cutting patterns
### T1 — Server-action catalog (top-level actions touched by the audited routes)
`getAdminUser`, `getAdminOrders`, `getAdminOrderDetail`, `markPickupComplete`, `createAdminOrder`, `updateOrder`, `updateOrderItem`, `deleteOrderItem`, `createRefund`, `getShippingOrders`, `createStop`, `updateStop`, `deleteStop`, `publishStop`, `assignProductToStop`, `unassignProductFromStop`, `createLocation`, `updateLocation`, `deleteLocation`, `createProduct`, `uploadProductImage`, `importProductsBatch`, `parseProductCSV`, `analyzeImport`, `executeImport`, `getAnalyticsMetrics`, `getRevenueChart`, `getTopProducts`, `getRecentOrders`, `getCustomerGrowth`, `getConversionFunnel`, `getCommunicationCampaigns`, `getCommunicationTemplates`, `getCommunicationContacts`, `getMessageLogs`, `getCommunicationSettings`, `upsertCommunicationSettings`, `getAbandonedCarts`, `manuallyCloseAbandonedCart`, `resendAbandonedCartEmail`, `sendStopBlast`, `previewContactImport`, `importContactsBatch`, `exportContacts`, `getWholesaleCustomers`, `getWholesaleOrders`, `getWholesaleSettings`, `updateWholesaleSettings`, `sendPriceSheetToCustomer`, `bulkFulfillOrders`, `recordWholesaleDeposit`, `createAdminUser`, `getAdminUsers`, `getBrands`, `getPaymentSettings`, `savePaymentSettings`, `syncSquareNow`, `getSyncLog`, `getResendCredentials`, `saveResendCredentials`, `testResendConnection`, `getTwilioCredentials`, `saveTwilioCredentials`, `testTwilioConnection`, `getStripeConnectStatus`, `createStripeConnectLink`, `refreshStripeConnectLink`, `disconnectStripeConnect`, `createStripeDashboardLink`, `getWaterIrrigators`, `getWaterHeadgatesAdmin`, `getWaterEntries`, `createWaterHeadgate`, `updateWaterHeadgate`, `deleteWaterHeadgate`, `regenerateHeadgateToken`, `createWaterUser`, `resetWaterIrrigatorPin`, `deleteWaterUser`, `getWaterAdminSettings`, `saveWaterAdminSettings`, `regenerateAdminPin`, `getTimeTrackingWorkers`, `getTimeTrackingTasks`, `getTimeTrackingSummary`, `getWorkerTimeLogs`, `createTimeWorker`, `updateTimeWorker`, `deleteTimeWorker`, `resetTimeWorkerPin`, `createTimeTask`, `updateTimeTask`, `deleteTimeTask`, `getRouteTraceStats`, `getRouteTraceLots`, `getRouteTraceLotDetail`, `getLotOrders`, `getHarvestLotsReadyToHaul`, `getFieldYieldSummary`, `getInventoryByCrop`, `getRecentLotEvents`, `signOutAction`, `signInWithGoogleAction`, `toggleBrandFeature`.
### T2 — Server-action categories
- **CRUD**: orders, stops, products, customers, campaigns, templates, contacts, wholesale_*, time_tracking_*, water_log_*, harvest_lots.
- **Bulk**: `bulkFulfillOrders`, `sendPriceSheetToCustomer` (wholesale); `markPickupComplete` loop (orders).
- **Auth**: sign-in, sign-out, password reset/change, OAuth.
- **Integration side-effects**: `syncSquareNow`, `sendCampaign`, `sendStopBlast`, `savePaymentSettings` → Stripe OAuth URL generation.
- **Reads**: `get*` actions returning typed rows for page/server-component hydration.
### T3 — Server-component vs client-component split
- Every route's `page.tsx` is `"use server"` and resolves `getAdminUser()` + brand + initial datasets in parallel.
- Heavy interactive panels (orders, stops, products, communications, settings, water-log) are `"use client"` components mounted by their page.
- v2 dashboard surfaces (`/admin/v2/*`) lean more on server components with `pool.query` directly.
### T4 — Auth gating patterns
- Page-level: `if (!adminUser) redirect("/login")` or `<AdminAccessDenied />`.
- Permission check: `if (!adminUser.can_manage_X) redirect("/admin/pickup")`.
- Brand-scope: `effectiveBrandId = brandId ?? adminUser.brand_id ?? firstBrandId`.
- Direct DB queries in pages use `getActiveBrandId()` helper; admin queries use `pool.query` with explicit `WHERE brand_id = $1` or `1=1` for platform_admin.
### T5 — Modal inventory
| Modal | Triggered from | Server action |
|---|---|---|
| `GlassModal` shell | many | n/a (UI only) |
| `ElegantModal` shell | some | n/a |
| `AdminOrdersPanel` New Order modal | inline fixed overlay | `createAdminOrder` |
| `AddStopModal` | `AdminStopsPanel` Add | `createStop` |
| `EditStopModal` | inline | `createStop` / `updateStop` |
| `NewStopForm` (inline, not modal) | `/admin/stops/new` | `createStop` |
| `AddLocationModal` / `EditLocationModal` | `LocationsTab` | `createLocation` / `updateLocation` |
| `CreateUserModal` | settings Users | `createAdminUser` |
| `CampaignListPanel` New Campaign | `/admin/communications` | `upsertCampaign` |
| `AbandonedCartDashboard` detail | abandoned carts page | read-only |
| `BrandFeatureCards` Enable confirm | settings Add-ons | `toggleBrandFeature` |
| `HeadgatesManager` Edit/QR | headgates page | `updateWaterHeadgate`, `regenerateHeadgateToken` |
| Cart stop picker | `/cart` | n/a |
| `UpgradePlanModal` (lazy) | `DashboardClient` / `DashboardUpgradeButton` | n/a |
| `CommandPalette` | global Cmd+K | n/a |
### T6 — Edge cases inventory (extracted for Phase 3)
- **Auth/role**: dev_session values, Neon Auth failures, must_change_password redirect, role-mismatched user without brand links.
- **Empty/zero states**: 0 brands, 0 stops, 0 orders, 0 contacts, 0 campaigns, 0 templates, 0 wholesale customers.
- **Long data**: very long product names, unicode (αβγ δεζ 🌿🍊 谢谢), special chars (`<script>`-style), URL-encoded emails.
- **Pagination boundaries**: page 0, last page, beyond last page, single result, exactly PAGE_SIZE, 0 results.
- **Brand switching**: platform_admin with no active brand, brand with no admin links, multi-brand admin.
- **Date/time**: past stops, future stops, today, before cutoff, after cutoff, DST boundaries, leap years (2024 just passed).
- **Status enums**: every allowed value + one disallowed value per field for boundary tests.
- **Stop states**: active/paused/closed, public/private, with/without cutoff, with/without address.
- **Order states**: all 4 statuses × 3 fulfillment types × with/without stop × with/without customer.
- **Order items**: zero items (free order), many items, mixed pickup/ship within one order.
- **Customers**: only email, only phone, both, no source, no metadata.
- **Bulk actions**: 0 selected, 1 selected, all selected, partial success.
- **Network failures**: timeouts, partial responses (not testable here — would need test API endpoints).
- **Image upload**: missing file, oversized file, wrong type, network failure mid-upload.
- **CSV import**: malformed CSV, missing columns, duplicate rows, very large file (>5 MB → bucket path).
- **Modal stacking**: open modal over modal.
- **Browser back/forward**: `/admin/orders?new=true` modal state preserved?
- **Concurrent edits**: two admin tabs editing same order.
- **Offline mode**: `OfflineBanner` state transitions + queued mutations.
- **Permission transitions**: admin user disabled mid-session.
---
## Items intentionally not enumerated in this run
- Server actions in `src/actions/` (catalogued by name in T1; full parameter signatures to be reviewed during acceptance-criteria phase).
- JSON API route handlers (`src/app/api/**/route.ts` beyond auth) — 62 routes. Out of scope for UI inventory; they will be exercised indirectly via the admin UI but not unit-tested separately unless a specific bug surfaces.
- Wholesale portal (`/wholesale/*`) — out of scope per user.
- Brand storefronts (`/tuxedo/*`, `/indian-river-direct/*`) — out of scope per user.
- Database RPCs (PL/pgSQL functions) — out of scope for inventory; exercised indirectly via server actions.
- Background jobs (cron, email automation) — exercised via curl on their API endpoints only if a bug is suspected.
+60
View File
@@ -0,0 +1,60 @@
# QA Audit Plan — 2026-06-25
> **Loop**: [010 — The full product evaluation loop](https://signals.forwardfuture.com/loop-library/loops/full-product-evaluation-loop/)
> **Verify**: Every inventoried product surface meets its documented acceptance criteria. The final full regression run covers every inventoried surface and its finite risk-based edge cases in the production-like local environment, with each reproducible bug fixed and backed by evidence.
## Scope
- **Surfaces**: `/admin/*` (all 19 modules) + public/auth pages
- **Role**: `platform_admin` via `dev_session` cookie
- **Environment**: Local Postgres 16 in Docker (`routeqa-pg` on :5433), `npm run dev` on :4000
- **Data scale**: 2 brands, ~1000 products, 100 stops, 1000 customers, 2000 orders per brand, plus communications/wholesale/time-tracking/water-log/audit data
## Env Differences from Production (recorded per loop spec)
1. **Neon Auth stubbed**: `neon_auth.user` table is a minimal local stub (no real auth provider). `dev_session` cookie impersonates roles.
2. **Migration 0002 skipped**: `0002_admin_password.sql` references a legacy `users` table that does not exist in the current schema (post-Neon-Auth migration). Recorded as applied in `_migrations` to skip cleanly. Documented for fix in a follow-up.
3. **Migration 0000 added**: `0000_qa_neon_auth_stub.sql` creates the `neon_auth` schema stub before `0001_init.sql` runs. Local-only.
4. **Seed file**: `db/seeds/2026-qa-audit-scale.sql` (QA-only) replaces the broken `db/seed.ts` (column drift). Local-only.
5. **API keys**: Stripe / Resend / Square placeholders (`sk_test_placeholder_for_qa_audit`, etc.) so imports don't crash. Real network calls would fail; we don't exercise them.
6. **Database port**: 5433 (not 5432 — that's n8n's Postgres).
## Phases
- [x] **Phase 1**: Environment bootstrap (Postgres container, migrations, seed, dev server, auth verification)
- [ ] **Phase 2**: Inventory every feature/route/control/state/workflow → `INVENTORY.md`
- [ ] **Phase 3**: Define acceptance criteria + finite risk-based edge cases → `ACCEPTANCE-CRITERIA.md`
- [ ] **Phase 4**: Write Playwright spec harness → `tests/e2e/audit/`
- [ ] **Phase 5**: Execute Playwright runs, log bugs → `BUGS.md`
- [ ] **Phase 6**: Triage shared causes/dependencies
- [ ] **Phase 7**: Implement fixes with regression tests
- [ ] **Phase 8**: Re-run affected paths + full inventory; stop at clean pass
## Risk Tiers (depth-weighted testing)
| Tier | Examples | Playwright depth |
|---|---|---|
| **Critical** | Auth, RBAC, order mutation, payment flows, customer data exposure | Full coverage including bypass attempts |
| **High** | Settings, communications send, wholesale lifecycle, water-log entry | Each button + edge case |
| **Medium** | Listing/pagination, filters, search, exports | Happy path + edge cases (empty, many) |
| **Low** | Static display pages, help text, "About" pages | Smoke test only |
## Artifacts
```
docs/qa/audit-2026-06-25/
├── PLAN.md # this file
├── INVENTORY.md # every feature, route, control, state, workflow
├── ACCEPTANCE-CRITERIA.md # criteria + edge cases per item
├── BUGS.md # bug log with reproduction evidence
├── ENV-DIFF.md # env differences from prod
└── evidence/ # screenshots, network captures
tests/e2e/audit/
├── helpers/ # shared Playwright utilities
├── auth/ # public + auth page tests
└── admin/ # per-module admin tests
```
## Stop Condition
Per loop: stop only at a clean full pass OR an explicit blocked handoff (with documented blocker).
+905
View File
@@ -0,0 +1,905 @@
> route-commerce-platform@2.0.0 lint
> eslint
/home/tyler/dev/routecomm/db/schema/brands.ts
12:3 warning 'index' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/db/schema/customers.ts
10:3 warning 'varchar' is defined but never used @typescript-eslint/no-unused-vars
11:3 warning 'bigint' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/db/schema/marketing.ts
45:52 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/tyler/dev/routecomm/db/schema/water-log.ts
27:3 warning 'check' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/playwright.config.ts
2:8 warning 'path' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/ai-import.ts
123:3 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/billing/stripe-checkout.ts
65:9 warning 'recurring' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/communications/contacts.ts
106:9 warning 'fullName' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/communications/import-contacts.ts
5:21 warning 'withPlatformAdmin' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/import-orders.ts
5:10 warning 'orders' is defined but never used @typescript-eslint/no-unused-vars
5:18 warning 'orderItems' is defined but never used @typescript-eslint/no-unused-vars
5:30 warning 'customers' is defined but never used @typescript-eslint/no-unused-vars
6:10 warning 'eq' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/integrations/credentials.ts
171:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
201:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/route-trace/lots.ts
202:58 warning '_status' is defined but never used @typescript-eslint/no-unused-vars
208:46 warning '_lotId' is defined but never used @typescript-eslint/no-unused-vars
234:3 warning '_data' is defined but never used @typescript-eslint/no-unused-vars
242:3 warning '_lotId' is defined but never used @typescript-eslint/no-unused-vars
243:3 warning '_status' is defined but never used @typescript-eslint/no-unused-vars
244:3 warning '_location' is defined but never used @typescript-eslint/no-unused-vars
245:3 warning '_notes' is defined but never used @typescript-eslint/no-unused-vars
246:3 warning '_binId' is defined but never used @typescript-eslint/no-unused-vars
270:58 warning '_query' is defined but never used @typescript-eslint/no-unused-vars
276:37 warning '_lotId' is defined but never used @typescript-eslint/no-unused-vars
294:36 warning '_lotId' is defined but never used @typescript-eslint/no-unused-vars
316:3 warning '_lotId' is defined but never used @typescript-eslint/no-unused-vars
317:3 warning '_orderId' is defined but never used @typescript-eslint/no-unused-vars
318:3 warning '_quantityToAdd' is defined but never used @typescript-eslint/no-unused-vars
319:3 warning '_notes' is defined but never used @typescript-eslint/no-unused-vars
328:3 warning '_limit' is assigned a value but never used @typescript-eslint/no-unused-vars
345:40 warning '_lotNumber' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/shipping.ts
19:3 warning '_orderId' is defined but never used @typescript-eslint/no-unused-vars
20:3 warning '_status' is defined but never used @typescript-eslint/no-unused-vars
21:3 warning '_trackingNumber' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/shipping/fedex-rates.ts
111:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/square-inventory.ts
15:16 warning 'getSquareCatalogItemVariation' is defined but never used @typescript-eslint/no-unused-vars
35:16 warning 'batchUpdateSquareInventory' is defined but never used @typescript-eslint/no-unused-vars
132:11 warning 'updates' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/tax.ts
108:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/time-tracking/field.ts
31:7 warning 'COOKIE_MAX_AGE' is assigned a value but never used @typescript-eslint/no-unused-vars
33:10 warning 'sessionCookie' is defined but never used @typescript-eslint/no-unused-vars
56:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
57:3 warning '_pin' is defined but never used @typescript-eslint/no-unused-vars
69:3 warning '_taskName' is assigned a value but never used @typescript-eslint/no-unused-vars
79:3 warning '_lunchMinutes' is assigned a value but never used @typescript-eslint/no-unused-vars
80:3 warning '_notes' is defined but never used @typescript-eslint/no-unused-vars
90:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
134:3 warning '_activeOnly' is assigned a value but never used @typescript-eslint/no-unused-vars
174:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/time-tracking/index.ts
66:46 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
74:3 warning '_role' is assigned a value but never used @typescript-eslint/no-unused-vars
75:3 warning '_lang' is assigned a value but never used @typescript-eslint/no-unused-vars
82:42 warning '_workerId' is defined but never used @typescript-eslint/no-unused-vars
89:3 warning '_workerId' is defined but never used @typescript-eslint/no-unused-vars
90:3 warning '_name' is defined but never used @typescript-eslint/no-unused-vars
91:3 warning '_role' is defined but never used @typescript-eslint/no-unused-vars
92:3 warning '_lang' is defined but never used @typescript-eslint/no-unused-vars
93:3 warning '_active' is defined but never used @typescript-eslint/no-unused-vars
100:40 warning '_workerId' is defined but never used @typescript-eslint/no-unused-vars
108:62 warning '_activeOnly' is assigned a value but never used @typescript-eslint/no-unused-vars
115:3 warning '_nameEs' is assigned a value but never used @typescript-eslint/no-unused-vars
116:3 warning '_unit' is assigned a value but never used @typescript-eslint/no-unused-vars
117:3 warning '_sortOrder' is assigned a value but never used @typescript-eslint/no-unused-vars
125:3 warning '_taskId' is defined but never used @typescript-eslint/no-unused-vars
126:3 warning '_name' is defined but never used @typescript-eslint/no-unused-vars
127:3 warning '_nameEs' is defined but never used @typescript-eslint/no-unused-vars
128:3 warning '_unit' is defined but never used @typescript-eslint/no-unused-vars
129:3 warning '_active' is defined but never used @typescript-eslint/no-unused-vars
130:3 warning '_sortOrder' is defined but never used @typescript-eslint/no-unused-vars
137:38 warning '_taskId' is defined but never used @typescript-eslint/no-unused-vars
147:3 warning '_options' is assigned a value but never used @typescript-eslint/no-unused-vars
160:3 warning '_logId' is defined but never used @typescript-eslint/no-unused-vars
161:3 warning '_taskName' is defined but never used @typescript-eslint/no-unused-vars
162:3 warning '_clockIn' is defined but never used @typescript-eslint/no-unused-vars
163:3 warning '_clockOut' is defined but never used @typescript-eslint/no-unused-vars
164:3 warning '_lunchMinutes' is defined but never used @typescript-eslint/no-unused-vars
165:3 warning '_notes' is defined but never used @typescript-eslint/no-unused-vars
172:43 warning '_logId' is defined but never used @typescript-eslint/no-unused-vars
179:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
180:3 warning '_start' is defined but never used @typescript-eslint/no-unused-vars
181:3 warning '_end' is defined but never used @typescript-eslint/no-unused-vars
207:47 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
213:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
214:3 warning '_settings' is defined but never used @typescript-eslint/no-unused-vars
255:3 warning '_limit' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/time-tracking/notifications.ts
20:3 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
21:3 warning '_workerId' is defined but never used @typescript-eslint/no-unused-vars
22:3 warning '_workerName' is defined but never used @typescript-eslint/no-unused-vars
23:3 warning '_dailyHours' is defined but never used @typescript-eslint/no-unused-vars
24:3 warning '_weeklyHours' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/water-log/admin.ts
20:25 warning 'gte' is defined but never used @typescript-eslint/no-unused-vars
20:30 warning 'lte' is defined but never used @typescript-eslint/no-unused-vars
20:40 warning 'SQL' is defined but never used @typescript-eslint/no-unused-vars
162:3 warning Unused eslint-disable directive (no problems were reported from 'no-var')
200:10 warning 'mapEntry' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/water-log/field.ts
22:15 warning 'desc' is defined but never used @typescript-eslint/no-unused-vars
22:25 warning 'gte' is defined but never used @typescript-eslint/no-unused-vars
22:30 warning 'sql' is defined but never used @typescript-eslint/no-unused-vars
178:3 warning '_headgateLocked' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/wholesale-auth.ts
33:44 warning '_formData' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/wholesale-register.ts
272:12 warning '_' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/actions/wholesale/orders.ts
5:10 warning 'getActiveBrandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/communications/abandoned-carts/page.tsx
7:7 warning 'IRD_BRAND_ID' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/import/ImportCenterClient.tsx
32:10 warning 'activeBrandName' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/launch-checklist/page.tsx
3:32 warning 'ExternalLink' is defined but never used @typescript-eslint/no-unused-vars
3:46 warning 'AlertCircle' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/me/AdminMeClient.tsx
17:27 warning 'setEmailChangeSent' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/me/page.tsx
5:10 warning 'redirect' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/orders/[id]/page.tsx
2:36 warning 'AdminOrder' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/sales/import/page.tsx
26:9 warning 'SAMPLE_CSV' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/settings/ai/AIClient.tsx
3:20 warning 'useEffect' is defined but never used @typescript-eslint/no-unused-vars
4:8 warning 'Link' is defined but never used @typescript-eslint/no-unused-vars
35:9 warning 'isReady' is assigned a value but never used @typescript-eslint/no-unused-vars
468:7 warning 'textareaFocusStyle' is assigned a value but never used @typescript-eslint/no-unused-vars
1802:3 warning 'customEndpoint' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/settings/billing/AddPaymentMethodButton.tsx
4:10 warning 'createAddonCheckoutSession' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/settings/integrations/IntegrationsClient.tsx
472:10 warning 'showCustomForm' is assigned a value but never used @typescript-eslint/no-unused-vars
474:25 warning 'setNewCustomType' is assigned a value but never used @typescript-eslint/no-unused-vars
501:27 warning 'app' is defined but never used @typescript-eslint/no-unused-vars
506:12 warning 'handleAddCustom' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/settings/integrations/IntegrationsClientPage.tsx
8:10 warning 'AdminToggle' is defined but never used @typescript-eslint/no-unused-vars
34:7 warning 'COMMUNICATION_INTEGRATIONS' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/stops/[id]/page.tsx
49:11 warning 'ProductStop' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/stops/new/page.tsx
3:8 warning 'StopProductAssignment' is defined but never used @typescript-eslint/no-unused-vars
68:17 warning 'productRows' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/stops/page.tsx
21:9 warning 'params' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/v2/products/page.tsx
187:21 warning Unused eslint-disable directive (no problems were reported from '@next/next/no-img-element')
192:21 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/tyler/dev/routecomm/src/app/admin/water-log/headgates/HeadgatesManager.tsx
143:7 warning Expected an assignment or function call and instead saw an expression @typescript-eslint/no-unused-expressions
328:27 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
462:9 warning 'qrUrl' is assigned a value but never used @typescript-eslint/no-unused-vars
521:15 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/tyler/dev/routecomm/src/app/admin/water-log/headgates/page.tsx
3:34 warning 'regenerateHeadgateToken' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/admin/water-log/page.tsx
12:10 warning 'WaterLogLoadingSkeleton' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/campaign-writer/route.ts
61:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/customer-insights/route.ts
123:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/demand-forecast/route.ts
79:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/pricing-advisor/route.ts
79:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/product-writer/route.ts
74:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/report-explainer/route.ts
84:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/route-optimizer/route.ts
83:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/ai/stop-blast-advisor/route.ts
15:22 warning 'stopId' is assigned a value but never used @typescript-eslint/no-unused-vars
85:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/auth/sign-in/route.ts
13:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/square/oauth/callback/route.ts
3:10 warning 'cookies' is defined but never used @typescript-eslint/no-unused-vars
88:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
116:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/square/oauth/complete/route.ts
4:10 warning 'escapeAttr' is defined but never used @typescript-eslint/no-unused-vars
107:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/square/oauth/route.ts
2:10 warning 'cookies' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/square/process-queue/route.ts
61:9 warning 'lastError' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/stops/import/route.ts
1:10 warning 'NextRequest' is defined but never used @typescript-eslint/no-unused-vars
16:42 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/stripe/oauth/callback/route.ts
96:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/stripe/oauth/complete/route.ts
89:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/time-tracking/export/route.ts
66:9 warning 'includeNotes' is assigned a value but never used @typescript-eslint/no-unused-vars
114:11 warning 'weeklyThreshold' is assigned a value but never used @typescript-eslint/no-unused-vars
128:36 warning 'i' is defined but never used @typescript-eslint/no-unused-vars
129:13 warning 'worker' is assigned a value but never used @typescript-eslint/no-unused-vars
131:15 warning 'regularHours' is assigned a value but never used @typescript-eslint/no-unused-vars
238:15 warning 'regularHours' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/time-tracking/notify/route.ts
7:7 warning 'IRD_BRAND_ID' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/water-photo-upload/route.ts
16:11 warning 'bucket' is assigned a value but never used @typescript-eslint/no-unused-vars
43:12 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/water-qr-label/route.ts
1:10 warning 'NextResponse' is defined but never used @typescript-eslint/no-unused-vars
9:35 warning 'token' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/water-qr-sheet/route.ts
1:10 warning 'NextResponse' is defined but never used @typescript-eslint/no-unused-vars
8:35 warning 'token' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/water-qr/route.ts
1:10 warning 'NextResponse' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/wholesale/invoice/[orderId]/route.ts
145:12 warning 'rightAlign' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/api/wholesale/manifest/route.ts
27:9 warning 'effectiveBrandId' is assigned a value but never used @typescript-eslint/no-unused-vars
37:9 warning 'rows' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/cart/CartClient.tsx
86:6 warning React Hook useCallback has a missing dependency: 'setSelectedStop'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/home/tyler/dev/routecomm/src/app/change-password/page.tsx
6:10 warning 'getCurrentUserId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/checkout/CheckoutClient.tsx
3:31 warning 'useRef' is defined but never used @typescript-eslint/no-unused-vars
59:9 warning 'hasPickupItems' is assigned a value but never used @typescript-eslint/no-unused-vars
141:6 warning React Hook useCallback has a missing dependency: 'idempotencyKey'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/home/tyler/dev/routecomm/src/app/error.tsx
21:5 warning Unused eslint-disable directive (no problems were reported from 'no-console')
/home/tyler/dev/routecomm/src/app/indian-river-direct/about/page.tsx
116:21 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/tyler/dev/routecomm/src/app/indian-river-direct/contact/ContactClientPage.tsx
23:10 warning 'brandSettings' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/indian-river-direct/contact/layout.tsx
42:52 warning 'children' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/indian-river-direct/faq/layout.tsx
81:48 warning 'children' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/indian-river-direct/stops/[id]/page.tsx
8:8 warning 'StopSetEffect' is defined but never used @typescript-eslint/no-unused-vars
51:21 warning 'setBrandSlug' is assigned a value but never used @typescript-eslint/no-unused-vars
52:23 warning 'setBrandAccent' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/login/LoginClient.tsx
77:5 error Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
/home/tyler/dev/routecomm/src/app/login/LoginClient.tsx:77:5
75 | function handleDevLogin(role: string) {
76 | // Set the dev_session cookie for local development bypass
> 77 | document.cookie = `dev_session=${role}; path=/; max-age=86400`;
| ^^^^^^^^ value cannot be modified
78 | window.location.href = REDIRECT_URL;
79 | }
80 | react-hooks/immutability
78:5 error Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
/home/tyler/dev/routecomm/src/app/login/LoginClient.tsx:78:5
76 | // Set the dev_session cookie for local development bypass
77 | document.cookie = `dev_session=${role}; path=/; max-age=86400`;
> 78 | window.location.href = REDIRECT_URL;
| ^^^^^^^^^^^^^^^ value cannot be modified
79 | }
80 |
81 | const displayError = error ?? localError; react-hooks/immutability
/home/tyler/dev/routecomm/src/app/page.tsx
1:25 warning 'Viewport' is defined but never used @typescript-eslint/no-unused-vars
125:9 warning Unused eslint-disable directive (no problems were reported from 'react/no-danger')
/home/tyler/dev/routecomm/src/app/trace/[lotNumber]/page.tsx
82:9 warning 'progressPercent' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/tuxedo/about/AboutClient.tsx
3:26 warning 'useState' is defined but never used @typescript-eslint/no-unused-vars
112:19 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/tyler/dev/routecomm/src/app/tuxedo/contact/ContactClientPage.tsx
4:8 warning 'Link' is defined but never used @typescript-eslint/no-unused-vars
22:10 warning 'brandSettings' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/tuxedo/contact/layout.tsx
42:47 warning 'children' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/tuxedo/faq/layout.tsx
131:43 warning 'children' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/tuxedo/page.tsx
396:10 warning 'SectionHeader' is defined but never used @typescript-eslint/no-unused-vars
435:10 warning 'heroImageUrl' is assigned a value but never used @typescript-eslint/no-unused-vars
500:12 warning 'scrollToProducts' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/tuxedo/products/sweet-corn-box/page.tsx
59:6 warning 'Brand' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/tuxedo/stops/[id]/page.tsx
7:8 warning 'StopSetEffect' is defined but never used @typescript-eslint/no-unused-vars
50:21 warning 'setBrandSlug' is assigned a value but never used @typescript-eslint/no-unused-vars
51:23 warning 'setBrandAccent' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/water/admin/login/page.tsx
1:10 warning 'redirect' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/wholesale/employee/page.tsx
64:7 warning 'userId' is assigned a value but never used @typescript-eslint/no-unused-vars
252:47 warning 'd' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/wholesale/portal/page.tsx
3:31 warning 'useCallback' is defined but never used @typescript-eslint/no-unused-vars
5:21 warning 'useSearchParams' is defined but never used @typescript-eslint/no-unused-vars
6:60 warning 'getWholesaleProducts' is defined but never used @typescript-eslint/no-unused-vars
6:218 warning 'WholesalePricingOverride' is defined but never used @typescript-eslint/no-unused-vars
33:10 warning 'CartSkeleton' is defined but never used @typescript-eslint/no-unused-vars
51:10 warning 'OrdersSkeleton' is defined but never used @typescript-eslint/no-unused-vars
239:10 warning 'products' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/app/wholesale/register/page.tsx
35:9 warning 'router' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdminHeader.tsx
55:49 warning 'canManageUsers' is defined but never used @typescript-eslint/no-unused-vars
56:9 warning 'router' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdminOrdersPanel.tsx
16:33 warning 'AdminCreateOrderItem' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdminSidebar.tsx
239:5 error Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
/home/tyler/dev/routecomm/src/components/admin/AdminSidebar.tsx:239:5
237 | // Keyboard navigation for nav items (arrow up/down loops, enter/space activates)
238 | const handleNavKeyDown = useCallback(
> 239 | (e: KeyboardEvent<HTMLAnchorElement>, href: string, index: number) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 240 | const total = visibleItems.length;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 241 | if (total === 0) return;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 261 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 262 | },
| ^^^^^^ Could not preserve existing memoization
263 | [router, mobileOpen, closeMobileMenu, visibleItems.length],
264 | );
265 | react-hooks/preserve-manual-memoization
263:43 error Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
/home/tyler/dev/routecomm/src/components/admin/AdminSidebar.tsx:263:43
261 | }
262 | },
> 263 | [router, mobileOpen, closeMobileMenu, visibleItems.length],
| ^^^^^^^^^^^^ This dependency may be modified later
264 | );
265 |
266 | async function handleLogout() { react-hooks/preserve-manual-memoization
/home/tyler/dev/routecomm/src/components/admin/AdminStopsPanel.tsx
55:10 warning 'showImport' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdvancedIntegrations.tsx
58:57 warning 'brands' is defined but never used @typescript-eslint/no-unused-vars
59:27 warning 'setSelectedBrandId' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdvancedPayments.tsx
119:18 warning 'refreshStatus' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdvancedShipping.tsx
46:44 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/AdvancedSquareSync.tsx
32:46 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/CampaignListPanel.tsx
10:26 warning 'deleteCampaign' is defined but never used @typescript-eslint/no-unused-vars
11:10 warning 'getCommunicationTemplates' is defined but never used @typescript-eslint/no-unused-vars
202:21 warning 'setCampaigns' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/CardList.tsx
23:57 warning 'as' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/CommandPalette.tsx
190:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/components/admin/CommandPalette.tsx:190:5
188 | return;
189 | }
> 190 | setQuery("");
| ^^^^^^^^ Avoid calling setState() directly within an effect
191 | setSelected(0);
192 | document.body.style.overflow = "hidden";
193 | // Defer focus to next frame so the input is mounted and the modal react-hooks/set-state-in-effect
220:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/components/admin/CommandPalette.tsx:220:5
218 | // Reset selection on every query change so the top result is always active.
219 | useEffect(() => {
> 220 | setSelected(0);
| ^^^^^^^^^^^ Avoid calling setState() directly within an effect
221 | }, [query]);
222 |
223 | // Clamp selection to results length react-hooks/set-state-in-effect
226:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/components/admin/CommandPalette.tsx:226:7
224 | useEffect(() => {
225 | if (selected >= results.length && results.length > 0) {
> 226 | setSelected(results.length - 1);
| ^^^^^^^^^^^ Avoid calling setState() directly within an effect
227 | } else if (results.length === 0) {
228 | setSelected(0);
229 | } react-hooks/set-state-in-effect
/home/tyler/dev/routecomm/src/components/admin/ContactImportForm.tsx
76:10 warning 'getStatColor' is defined but never used @typescript-eslint/no-unused-vars
82:10 warning 'getStatValueColor' is defined but never used @typescript-eslint/no-unused-vars
95:10 warning 'importing' is assigned a value but never used @typescript-eslint/no-unused-vars
101:10 warning 'useBucket' is assigned a value but never used @typescript-eslint/no-unused-vars
176:20 warning 'totalRows' is assigned a value but never used @typescript-eslint/no-unused-vars
219:11 warning 'emailColIdx' is assigned a value but never used @typescript-eslint/no-unused-vars
220:11 warning 'phoneColIdx' is assigned a value but never used @typescript-eslint/no-unused-vars
248:19 warning 'stripped' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/CreateUserModal.tsx
5:10 warning 'AdminInput' is defined but never used @typescript-eslint/no-unused-vars
5:22 warning 'AdminTextInput' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/DashboardClient.tsx
3:25 warning 'ReactNode' is defined but never used @typescript-eslint/no-unused-vars
7:78 warning 'Calendar' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/HarvestReach/AnalyticsDashboard.tsx
109:10 warning 'EngagementBadge' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/HarvestReach/CampaignComposerPage.tsx
6:24 warning 'SegmentRuleV2' is defined but never used @typescript-eslint/no-unused-vars
147:15 warning 'isUpcoming' is assigned a value but never used @typescript-eslint/no-unused-vars
361:16 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/HarvestReach/MatchingCustomersPanel.tsx
50:10 warning 'customerCount' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/HarvestReach/SegmentBuilderPage.tsx
163:9 warning 'hasFilters' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/IntegrationsInner.tsx
277:54 warning 'brands' is defined but never used @typescript-eslint/no-unused-vars
278:27 warning 'setSelectedBrandId' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/LoadingState.tsx
48:11 warning Unused eslint-disable directive (no problems were reported from 'react/no-array-index-key')
/home/tyler/dev/routecomm/src/components/admin/MessageLogPanel.tsx
242:6 warning React Hook useEffect has a missing dependency: 'fetchLogs'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/home/tyler/dev/routecomm/src/components/admin/NewStopForm.tsx
7:10 warning 'AdminInput' is defined but never used @typescript-eslint/no-unused-vars
7:22 warning 'AdminTextInput' is defined but never used @typescript-eslint/no-unused-vars
7:38 warning 'AdminSelect' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/OfflineBanner.tsx
12:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/components/admin/OfflineBanner.tsx:12:5
10 |
11 | useEffect(() => {
> 12 | setMounted(true);
| ^^^^^^^^^^ Avoid calling setState() directly within an effect
13 | setOnline(navigator.onLine);
14 | const onOnline = () => setOnline(true);
15 | const onOffline = () => setOnline(false); react-hooks/set-state-in-effect
46:14 error `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;` react/no-unescaped-entities
/home/tyler/dev/routecomm/src/components/admin/OrderEditForm.tsx
62:10 warning 'saved' is assigned a value but never used @typescript-eslint/no-unused-vars
89:9 warning 'total' is assigned a value but never used @typescript-eslint/no-unused-vars
192:14 warning 'err' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/OrderPaymentSection.tsx
8:33 warning 'AdminInput' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/PaymentSettingsForm.tsx
5:58 warning 'PaymentSettings' is defined but never used @typescript-eslint/no-unused-vars
39:32 warning 'setStripePublishableKey' is assigned a value but never used @typescript-eslint/no-unused-vars
42:27 warning 'setStripeSecretKey' is assigned a value but never used @typescript-eslint/no-unused-vars
45:29 warning 'setSquareAccessToken' is assigned a value but never used @typescript-eslint/no-unused-vars
58:10 warning 'showSecretStripe' is assigned a value but never used @typescript-eslint/no-unused-vars
58:28 warning 'setShowSecretStripe' is assigned a value but never used @typescript-eslint/no-unused-vars
59:10 warning 'showSecretSquare' is assigned a value but never used @typescript-eslint/no-unused-vars
59:28 warning 'setShowSecretSquare' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/ProductEditForm.tsx
48:20 warning 'setDragOver' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/ProductFilterBar.tsx
3:10 warning 'useState' is defined but never used @typescript-eslint/no-unused-vars
16:3 warning 'products' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/PullToRefresh.tsx
38:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/components/admin/PullToRefresh.tsx:38:5
36 | useEffect(() => {
37 | const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
> 38 | setPrefersReducedMotion(mq.matches);
| ^^^^^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
39 | const handler = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches);
40 | mq.addEventListener("change", handler);
41 | return () => mq.removeEventListener("change", handler); react-hooks/set-state-in-effect
93:21 error Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
/home/tyler/dev/routecomm/src/components/admin/PullToRefresh.tsx:93:21
91 | style={{
92 | transform: `translateY(${offset}px)`,
> 93 | transition: pulling.current || prefersReducedMotion ? "none" : "transform 200ms var(--ease-apple)",
| ^^^^^^^^^^^^^^^ Cannot access ref value during render
94 | overscrollBehavior: "contain",
95 | position: "relative",
96 | }} react-hooks/refs
93:21 error Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
/home/tyler/dev/routecomm/src/components/admin/PullToRefresh.tsx:93:21
91 | style={{
92 | transform: `translateY(${offset}px)`,
> 93 | transition: pulling.current || prefersReducedMotion ? "none" : "transform 200ms var(--ease-apple)",
| ^^^^^^^^^^^^^^^ Cannot access ref value during render
94 | overscrollBehavior: "contain",
95 | position: "relative",
96 | }} react-hooks/refs
/home/tyler/dev/routecomm/src/components/admin/ReportsDashboard.tsx
103:3 warning 'label' is defined but never used @typescript-eslint/no-unused-vars
224:11 warning 'brandId' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/ScheduleImportModal.tsx
3:41 warning 'useEffect' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/SettingsSections.tsx
24:10 warning 'AdminInput' is defined but never used @typescript-eslint/no-unused-vars
24:22 warning 'AdminTextInput' is defined but never used @typescript-eslint/no-unused-vars
24:38 warning 'AdminSelect' is defined but never used @typescript-eslint/no-unused-vars
88:10 warning 'settings' is assigned a value but never used @typescript-eslint/no-unused-vars
89:10 warning 'notificationLog' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/SquareSyncWidget.tsx
24:10 warning 'queueCount' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/StopEditForm.tsx
6:22 warning 'AdminTextInput' is defined but never used @typescript-eslint/no-unused-vars
39:10 warning 'saved' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/StopMessagingForm.tsx
29:10 warning 'customMessage' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/StopProductAssignment.tsx
47:3 warning 'callerUid' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/StopTableClient.tsx
4:75 warning 'useRef' is defined but never used @typescript-eslint/no-unused-vars
12:3 warning 'AdminIconButton' is defined but never used @typescript-eslint/no-unused-vars
259:9 warning 'SortIcon' is assigned a value but never used @typescript-eslint/no-unused-vars
608:3 warning 'isLoading' is defined but never used @typescript-eslint/no-unused-vars
705:3 warning 'onDelete' is defined but never used @typescript-eslint/no-unused-vars
977:40 warning 'showError' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/StopsHeaderActions.tsx
22:33 warning 'count' is defined but never used @typescript-eslint/no-unused-vars
26:29 warning 'stopId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/TaxDashboard.tsx
4:10 warning 'formatDate' is defined but never used @typescript-eslint/no-unused-vars
46:38 warning 'end' is defined but never used @typescript-eslint/no-unused-vars
77:22 warning 'label' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/TemplateEditForm.tsx
3:20 warning 'useCallback' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/TimeTrackingAdminPanel.tsx
127:10 warning 'formatHours' is defined but never used @typescript-eslint/no-unused-vars
531:47 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
531:56 warning 'onSave' is defined but never used @typescript-eslint/no-unused-vars
582:43 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
582:52 warning 'onSave' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/TimeTrackingSettingsClient.tsx
86:10 warning 'settings' is assigned a value but never used @typescript-eslint/no-unused-vars
132:23 warning 'setExportBrand' is assigned a value but never used @typescript-eslint/no-unused-vars
133:27 warning 'setIncludeOvertime' is assigned a value but never used @typescript-eslint/no-unused-vars
134:24 warning 'setIncludeNotes' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/ToastContainer.tsx
4:10 warning 'useEffect' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/UpgradePlanModal.tsx
217:19 warning 'canUpgrade' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/UsersPage.tsx
4:24 warning 'UpdateAdminUserInput' is defined but never used @typescript-eslint/no-unused-vars
126:10 warning 'resetLinkLoading' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/admin/design-system/AdminActionMenu.tsx
19:52 warning 'triggerLabel' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/landing/LandingPageWrapper.tsx
310:11 warning 'SectionProps' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/landing/TestimonialsAndCTA.tsx
46:7 warning 'cardHoverVariants' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/notifications/InAppNotificationCenter.tsx
21:55 warning 'userId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/notifications/ToastNotification.tsx
73:9 warning 'colors' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/onboarding/OnboardingFlow.tsx
90:43 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
90:61 warning '_userId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/AdminLookupPage.tsx
9:10 warning 'getAgeStatus' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/FsmaReportModal.tsx
184:6 warning React Hook useEffect has a missing dependency: 'fetchComplianceData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/home/tyler/dev/routecomm/src/components/route-trace/LotDetailPanel.tsx
206:10 warning 'CheckIcon' is defined but never used @typescript-eslint/no-unused-vars
216:3 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/LotListTable.tsx
91:3 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/PublicTraceActions.tsx
3:10 warning 'useState' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/QRScanModal.tsx
179:6 warning React Hook useEffect has a missing dependency: 'onScanResult'. Either include it or remove the dependency array. If 'onScanResult' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
/home/tyler/dev/routecomm/src/components/route-trace/QuickNewLotModal.tsx
3:35 warning 'useEffect' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/RouteTraceDashboard.tsx
498:3 warning 'recentLots' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/RouteTracePage.tsx
4:8 warning 'Link' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/route-trace/StickerPreviewModal.tsx
68:14 warning 'e' is defined but never used @typescript-eslint/no-unused-vars
249:21 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/tyler/dev/routecomm/src/components/storefront/CinematicShowcase.tsx
39:10 warning '_index' is defined but never used @typescript-eslint/no-unused-vars
40:13 warning '_isActive' is defined but never used @typescript-eslint/no-unused-vars
56:5 warning Unused eslint-disable directive (no problems were reported from '@typescript-eslint/no-unused-vars')
112:3 warning 'brandName' is assigned a value but never used @typescript-eslint/no-unused-vars
159:13 warning 'productSwitchTrigger' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/storefront/ProductCard.tsx
136:14 warning 'brandName' is defined but never used @typescript-eslint/no-unused-vars
137:3 warning 'brandAccent' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/storefront/StorefrontHeader.tsx
55:3 warning 'isAdmin' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/storefront/StripeExpressCheckout.tsx
156:5 warning React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
299:3 warning 'items' is defined but never used @typescript-eslint/no-unused-vars
300:3 warning 'brandId' is defined but never used @typescript-eslint/no-unused-vars
304:3 warning 'selectedStop' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/storefront/TuxedoVideoHero.tsx
68:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/components/storefront/TuxedoVideoHero.tsx:68:7
66 | const prefersReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
67 | if (prefersReducedMotion) {
> 68 | setIsVisible(true);
| ^^^^^^^^^^^^ Avoid calling setState() directly within an effect
69 | return;
70 | }
71 | react-hooks/set-state-in-effect
/home/tyler/dev/routecomm/src/components/time-tracking/TimeTrackingFieldClient.tsx
21:7 warning 'BRAND_ID' is assigned a value but never used @typescript-eslint/no-unused-vars
22:7 warning 'BRAND_NAME' is assigned a value but never used @typescript-eslint/no-unused-vars
167:3 warning 'brandAccent' is defined but never used @typescript-eslint/no-unused-vars
256:6 warning React Hook useEffect has a missing dependency: 'loadPayPeriod'. Either include it or remove the dependency array react-hooks/exhaustive-deps
496:9 warning 'dailyRemaining' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/ui/ScrollAnimations.tsx
197:10 warning '_speed' is assigned a value but never used @typescript-eslint/no-unused-vars
315:3 warning 'to' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/water/WaterAdminClient.tsx
127:10 warning 'SummarySkeleton' is defined but never used @typescript-eslint/no-unused-vars
272:10 warning 'loading' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/wholesale/admin/CustomerPricingPanel.tsx
23:18 warning 'setSaving' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/wholesale/admin/DashboardTab.tsx
3:15 warning 'MsgFn' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/components/wholesale/admin/OrdersTab.tsx
46:10 warning 'deleting' is assigned a value but never used @typescript-eslint/no-unused-vars
60:52 warning Expected an assignment or function call and instead saw an expression @typescript-eslint/no-unused-expressions
420:59 warning 'd' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/lib/analytics.ts
5:7 warning 'posthogHost' is assigned a value but never used @typescript-eslint/no-unused-vars
12:27 warning '_properties' is defined but never used @typescript-eslint/no-unused-vars
18:34 warning '_properties' is defined but never used @typescript-eslint/no-unused-vars
24:53 warning '_properties' is defined but never used @typescript-eslint/no-unused-vars
30:55 warning '_properties' is defined but never used @typescript-eslint/no-unused-vars
36:34 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
54:41 warning '_reason' is defined but never used @typescript-eslint/no-unused-vars
84:51 warning '_resourceId' is defined but never used @typescript-eslint/no-unused-vars
102:47 warning '_context' is defined but never used @typescript-eslint/no-unused-vars
108:57 warning '_category' is defined but never used @typescript-eslint/no-unused-vars
121:30 warning '_userId' is defined but never used @typescript-eslint/no-unused-vars
121:47 warning '_properties' is defined but never used @typescript-eslint/no-unused-vars
127:30 warning '_brandId' is defined but never used @typescript-eslint/no-unused-vars
127:48 warning '_properties' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/lib/billing.ts
6:8 warning 'Stripe' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/lib/offline/sync.ts
2:10 warning 'enqueueAction' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/lib/pwa.ts
34:39 warning '_registration' is defined but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/lib/rate-limit.ts
5:6 warning 'Duration' is defined but never used @typescript-eslint/no-unused-vars
39:9 warning 'windowStart' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/tyler/dev/routecomm/src/lib/use-media-query.ts
16:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/tyler/dev/routecomm/src/lib/use-media-query.ts:16:5
14 | const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
15 | mq.addEventListener("change", handler);
> 16 | setMatches(mq.matches);
| ^^^^^^^^^^ Avoid calling setState() directly within an effect
17 | return () => mq.removeEventListener("change", handler);
18 | }, [query]);
19 | react-hooks/set-state-in-effect
✖ 383 problems (14 errors, 369 warnings)
0 errors and 6 warnings potentially fixable with the `--fix` option.
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
src/actions/billing/retail-checkout.ts(26,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/billing/retail-payment-intent.ts(52,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/billing/stripe-checkout.ts(60,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/billing/stripe-checkout.ts(139,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/billing/stripe-portal.ts(51,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/stripe-connect.ts(44,44): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/stripe-connect.ts(88,44): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/stripe-connect.ts(150,44): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/actions/stripe-connect.ts(240,44): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/lib/billing.ts(97,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/lib/billing.ts(181,42): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
src/lib/stripe-billing.ts(16,7): error TS2322: Type '"2026-05-27.dahlia"' is not assignable to type '"2026-06-24.dahlia"'.
tests/unit/create-admin-user.test.ts(121,3): error TS2741: Property 'preconnect' is missing in type 'Mock<Procedure>' but required in type 'typeof fetch'.
tests/unit/create-admin-user.test.ts(289,5): error TS2741: Property 'preconnect' is missing in type 'Mock<Procedure>' but required in type 'typeof fetch'.
tests/unit/email-service.test.ts(37,5): error TS2741: Property 'preconnect' is missing in type 'Mock<Procedure>' but required in type 'typeof fetch'.
tests/unit/email-service.test.ts(71,5): error TS2741: Property 'preconnect' is missing in type 'Mock<Procedure>' but required in type 'typeof fetch'.
tests/unit/email-service.test.ts(97,5): error TS2741: Property 'preconnect' is missing in type 'Mock<Procedure>' but required in type 'typeof fetch'.
tests/unit/email-service.test.ts(120,5): error TS2741: Property 'preconnect' is missing in type 'Mock<Procedure>' but required in type 'typeof fetch'.
+36
View File
@@ -0,0 +1,36 @@
> route-commerce-platform@2.0.0 test
> vitest run
RUN v2.1.9 /home/tyler/dev/routecomm
✓ src/lib/__tests__/design-tokens.test.ts (35 tests) 6ms
✓ tests/unit/send-password-reset-email.test.ts (8 tests) 16ms
✓ tests/unit/create-admin-user.test.ts (10 tests) 17ms
✓ tests/unit/water-log-reporting.test.ts (31 tests) 27ms
✓ tests/unit/email-service.test.ts (5 tests) 60ms
✓ src/lib/__tests__/format-date.test.ts (6 tests) 16ms
✓ tests/unit/sign-in-with-google.test.ts (6 tests) 13ms
✓ tests/unit/reset-admin-password.test.ts (11 tests) 16ms
stdout | tests/unit/auth-actions.test.ts > signOutAction > calls signOut and redirects to login
[auth/sign-out] Signing out
✓ tests/unit/auth-actions.test.ts (1 test) 3ms
✓ src/lib/offline/queue.test.ts (8 tests) 17ms
✓ src/lib/offline/sync.test.ts (13 tests) 18ms
tests/unit/getAdminUser.test.ts (11 tests | 3 failed) 8ms
× getAdminUser() > returns null when the email is not in the users table 3ms
→ expected undefined to be null
× getAdminUser() > returns null when the user exists but has no admin_user_brands row 0ms
→ expected undefined to be null
× getAdminUser() > returns a fully-populated AdminUser for a provisioned brand_admin 1ms
→ expected undefined to be 'admin@tuxedo.example' // Object.is equality
✓ tests/unit/water-log-pin.test.ts (21 tests) 429ms
✓ tests/unit/passwords.test.ts (9 tests) 572ms
Test Files 1 failed | 13 passed (14)
Tests 3 failed | 172 passed (175)
Start at 21:56:10
Duration 1.09s (transform 671ms, setup 0ms, collect 1.54s, tests 1.22s, environment 615ms, prepare 1.03s)
+1 -1
View File
@@ -180,7 +180,7 @@ data explicitly checks its gate.
cold-start paths; the active `getAdminUser().brand_id` always wins
on later requests.
- No Supabase REST, no service-role keys — all access is direct
through Drizzle over a single shared `pg` Pool (see `src/lib/db.ts`).
through Drizzle over a single `pg` Pool.
---