Commit Graph

109 Commits

Author SHA1 Message Date
tyler 3ad2a48fc3 migrate: replace Supabase REST with Drizzle/pg in communications + marketing (wave 2 redo) 2026-06-07 03:58:26 +00:00
tyler 99a3d66636 migrate: replace Supabase REST with Drizzle/pg in billing + integrations + wholesale (wave 3) 2026-06-07 03:25:22 +00:00
tyler eb9621d238 migrate: replace Supabase REST with Drizzle/pg in core admin (wave 1) 2026-06-07 03:14:59 +00:00
tyler b8317a200e migrate: replace Supabase REST with Drizzle/pg in water-log/time-tracking/reports/etc (wave 4) 2026-06-07 03:05:00 +00:00
tyler 4b02154188 Merge remote-tracking branch 'crispygoat/main'
Deploy to route.crispygoat.com / deploy (push) Failing after 1m59s
# Conflicts:
#	src/actions/admin/password.ts
#	src/actions/brand-settings.ts
#	src/actions/stops.ts
#	src/app/change-password/page.tsx
#	src/app/login/LoginClient.tsx
#	src/app/logout/page.tsx
#	src/auth.config.ts
#	src/lib/admin-permissions.ts
#	src/lib/auth.ts
#	src/lib/db.ts
2026-06-07 01:56:43 +00:00
tyler 6e71596daf Merge branch 'feature/drizzle-rls-real-auth'
# Conflicts:
#	CLAUDE.md
#	package.json
#	src/app/api/auth/[...nextauth]/route.ts
#	src/app/login/LoginClient.tsx
#	src/auth.config.ts
#	src/components/admin/AdminSidebar.tsx
#	src/lib/admin-permissions-types.ts
#	src/lib/admin-permissions.ts
#	src/lib/auth.ts
#	src/middleware.ts
2026-06-07 01:55:06 +00:00
tyler 4da7aae5ce feat(auth): seed admin user + email/password login UI
- db/seed.ts: upserts admin@route-commerce.local with a scrypt password
  hash (default password 'admin', override with SEED_ADMIN_PASSWORD)
  and grants them the platform_admin role on the Tuxedo tenant so
  getAdminUser() resolves it for the Credentials provider's authorize
  function
- src/app/login/page.tsx: reads ?error=... from the URL and passes
  hasCredentials + seededEmail + error to the client so the form
  pre-fills in dev and surfaces 'Invalid email or password' cleanly
- src/app/login/LoginClient.tsx: adds the email + password form below
  the Google button, with a divider, dev-mode pre-fill, and local error
  handling for client-side failures (server-side failures come back
  through the ?error=... param)
2026-06-07 01:51:53 +00:00
tyler 7cd0603cfb feat: remove dev_session, add Drizzle schema + RLS + real auth
BREAKING: dev_session cookie bypass removed. Admin access now requires
a real Auth.js v5 session (Google OAuth in production). Provision users
by inserting into users + tenant_users tables.

New in this commit:
- db/migrations/0001_init.sql: 18-table SaaS schema with RLS (tenants,
  users, tenant_users, plans, add_ons, subscriptions, tenant_add_ons,
  products, product_images, stops, customers, orders, order_items,
  brand_settings, email_templates, campaigns, files, audit_log)
- db/schema/: Drizzle TypeScript mirror of every table
- db/client.ts: withTenant() / withPlatformAdmin() query wrappers that
  set Postgres GUCs (app.current_tenant_id, app.platform_admin) for
  RLS enforcement. Never query a tenant-scoped table without one.
- db/seed.ts: seeds 3 plans, 6 add-ons, 2 tenants (Tuxedo, Indian River
  Direct), brand_settings, sample products/stops/customers
- scripts/migrate.js: applies migrations in lexical order with tracking
- scripts/db-reset.js: drops + recreates DB, runs migrate + seed
- DATABASE_URL now uses rc_app (non-superuser, NOBYPASSRLS). RLS is
  enforced even for the app user. DATABASE_ADMIN_URL for migrations.
- src/lib/admin-permissions.ts: getAdminUser() reads Auth.js session,
  looks up user + tenant in Postgres. brand_id kept as alias for
  backward compat.
- src/middleware.ts: Auth.js-only route protection, dev_session gone
- src/app/login/LoginClient.tsx: Google OAuth only, no demo mode
- src/components/admin/AdminSidebar.tsx + AdminHeader.tsx: signOutAction
  replaces supabase signout
- @/db/* path aliases in tsconfig.json + vitest.config.ts
- drizzle.config.ts added
- db/auth_schema.sql removed (was a stub; replaced by real schema)
- src/app/api/dev-login/route.ts deleted
- tests: updated to remove dev_session coverage
2026-06-07 01:23:44 +00:00
tyler f96dcd01f2 feat(db+auth): add pg pool, admin_users email/provider migration, refactor auth lookup
- Create src/lib/db.ts (shared pg.Pool, query/withTx helpers, server-only)
- Add @types/pg devDep
- Migration 204: add email, auth_provider, auth_subject columns to
  admin_users; backfill from auth.users; new upsert_admin_user accepts
  multi-provider args; new get_admin_user_for_session RPC resolves
  Auth.js session id (UUID or Google sub) to an admin row
- Refactor getAdminUser() to use pg + new RPC; auto-provisions on first
  Google sign-in using session.user.email
- Refactor updatePasswordAction to call update_user_password via pg
  (drops the Supabase REST hop)
- Delete orphaned src/actions/login.ts (replaced by auth-actions.ts)
- Drop remaining DEV_FORCE_UID references in users.ts; dev path now
  uses dev_session cookie (the only cookie the dev flow can set)
- Update AdminUser type: user_id is now string | null (Google users
  have no Supabase auth id); add email + auth_provider fields
- Fix downstream type errors: StopProductAssignment.callerUid accepts
  null; pickup.ts performedBy widens to string | null
- Bump vitest config, tests/, and other earlier cleanup changes
2026-06-06 23:41:41 +00:00
tyler 3f731f7739 fix(admin): wrap post-auth data loads in try/catch to surface and survive SC render errors
Deploy to route.crispygoat.com / deploy (push) Successful in 3m9s
The /admin page was throwing a Server Components render error (digest
4266906817) in production. The exact throw point was not visible from
the browser (Next.js hides it for security) and server logs were not
available at debugging time.

The layout and page both call data-loading functions (getActiveBrandId,
listBrandsForAdmin, supabase brands lookup) that could throw on a
transient DB/network failure, and these calls had no try/catch. A
single failed call would crash the entire admin shell.

Wrap each call in try/catch with console.error so:
  1. The page renders with sensible defaults if a call fails
  2. The actual error is logged server-side (visible via the digest in
     the admin error boundary or PM2/Docker logs)
  3. The admin shell stays functional even if a single data source is
     down

No behavior change on the happy path.
2026-06-06 22:35:24 +00:00
tyler 5654ebaecd chore(auth): remove legacy rc_auth_uid/rc_uid/upsert_admin_user path; scope sign-in to ADMIN_ALLOWED_EMAILS; silence lockfile warning
Deploy to route.crispygoat.com / deploy (push) Successful in 3m14s
Cleanup after Auth.js v5 became the only sign-in path. The platform
had three overlapping auth modes (dev cookie, legacy rc_auth_uid, Auth.js
JWT) and a pile of dead-code pages/routes that only existed to support
the legacy path.

What changed:

* getAdminUser() now has only two auth paths:
    1. dev_session cookie (auto-issued by src/proxy.ts for /admin/* when
       ALLOW_DEV_LOGIN is enabled)
    2. Auth.js v5 JWT (the encrypted cookie + auth() lookup)
  The legacy rc_auth_uid/rc_uid branch and the Supabase REST fetch
  against admin_users are gone.

* The signIn callback in src/lib/auth.ts enforces ADMIN_ALLOWED_EMAILS
  when set. Unset = open mode (backward compatible with demo/dev). Dev
  credentials provider is exempt. The new env var is wired through
  .env.example and .gitea/workflows/deploy.yml (read from
  secrets.ADMIN_ALLOWED_EMAILS, written to the server .env file).

* change-password/page.tsx now uses auth() server-side instead of
  fetching the deleted /api/auth/uid endpoint. The form is split into
  page.tsx (server component, auth check) + ChangePasswordForm.tsx
  (client component, form state). updatePasswordAction now reads the
  user id from auth() instead of the rc_auth_uid cookie.

* Deleted 14 dead-code files:
    - Pages: login2, logout, auth/callback, admin/debug-auth,
      admin/test-auth
    - API routes: api/login, api/logout, api/auth/uid, api/force-admin,
      api/set-auth-cookie, api/debug-cookie, api/debug-me,
      api/debug-auth
    - Actions: src/actions/login.ts
  These were the old email/password login, the old Supabase OAuth
  callback, the old /api/auth/uid probe, and a pile of debug endpoints
  that have been superseded by the new proxy + the new /login page.

* next.config.ts: set outputFileTracingRoot: '.' to silence the
  Next.js 16 lockfile-inference warning. Without this the build
  walked up from package.json looking for a lockfile, found the
  homelab runner's stale act cache at /home/tyler/.cache/act/.../package-lock.json,
  and warned on every build. '. resolves to the project root in both
  dev and CI, so it's the right answer.

Out of scope (deferred):

* src/actions/admin/users.ts still uses rc_auth_uid internally for its
  dev-bypass logic. It works (the rc_auth_uid branch is gated on
  NODE_ENV != 'production' and DEV_FORCE_UID), but it's now genuinely
  unreachable in production. Clean up in a follow-up.

Pre-flight:
* npx tsc --noEmit: clean
* npm run lint (touched files): clean
* npm run build: clean — proxy picked up, no lockfile warning, all
  93 static pages generated.
2026-06-06 22:13:56 +00:00
tyler e499139c74 fix(login): one-button Google sign-in + middleware auto-issues dev cookie
Deploy to route.crispygoat.com / deploy (push) Failing after 3s
The dev login flow was redirecting back to /login because:
  - src/middleware.ts didn't exist, so the Auth.js authorized
    callback in auth.config.ts never ran
  - Even if it had, it only checked the Auth.js JWT, not dev_session
  - Clicking the demo buttons set the cookie via document.cookie,
    but the admin layout (via getAdminUser) was the only thing
    reading it — no edge gate

Fix:
  - New src/middleware.ts: gates /admin/* and /login at the edge.
    Auto-issues dev_session=platform_admin when ALLOW_DEV_LOGIN is
    enabled (default on, set to 'false' in prod). No buttons, no
    client-side cookie games.
  - LoginClient.tsx: stripped to a single Google OAuth button.
    Removed email/password form, dev credentials form, and the
    /login?demo=1 three-button picker.
  - Removed signInWithDev from auth-signin.ts (no longer called).
  - Removed dead /dev-login page and /api/dev-login route.

Net result: one sign-in path (Google), invisible dev auto-login
via middleware, no more three modes.
2026-06-06 20:14:08 +00:00
tyler 5477b3419f fix(build): make admin tree dynamic and catch Supabase fetch errors at build
Deploy to route.crispygoat.com / deploy (push) Failing after 4m16s
Two errors were aborting the Gitea build:

1. DYNAMIC_SERVER_USAGE on /admin/settings/square-sync (and any admin page):
   getAdminUser() reads cookies() via next/headers. The admin layout tried
   to prerender statically, so the first child page that hit cookies()
   aborted the build. Added 'export const dynamic = "force-dynamic"' to
   src/app/admin/layout.tsx so the whole admin tree opts out of static
   prerender.

2. Prerender ECONNREFUSED on /indian-river-direct/stops and the sitemap:
   getPublicStopsForBrand / getActiveStopsForSitemap / getBrandSettingsPublic
   fetch NEXT_PUBLIC_SUPABASE_URL at build time. The Gitea runner sets the
   Supabase env vars (so the existing env-var guard passes) but the URL
   is unreachable, so fetch throws ECONNREFUSED and the prerender aborts.
   Wrapped each fetch in try/catch returning [] / {success: false} so the
   prerender completes; runtime behavior is unchanged when the fetch
   succeeds.

Also added force-dynamic to the square-sync page itself as belt-and-braces
in case the layout change doesn't propagate.
2026-06-06 19:46:35 +00:00
tyler ec1506dc82 feat(auth): Auth.js v5 + Postgres adapter for local smoke test
Wire up NextAuth v5 with @auth/pg-adapter, JWT sessions (edge-friendly),
and a dev Credentials provider for local testing without Google OAuth.

Stack
- next-auth@5.0.0-beta.31, @auth/pg-adapter@1.11.2, @types/pg
- Google OAuth provider via GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
  (falls back to AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET)
- Postgres adapter wired to a single pg.Pool in src/lib/db.ts style —
  reads DATABASE_URL with SUPABASE_DB_URL / POSTGRES_URL fallbacks
- JWT session strategy (edge-safe) so the proxy can verify sessions
  without a DB round-trip

Files
- src/auth.config.ts        edge-safe config (Google + authorized cb)
- src/lib/auth.ts           server config (adapter + dev Credentials)
- src/proxy.ts              Next.js 16 proxy (was middleware.ts)
- src/app/api/auth/[...nextauth]/route.ts
                            catch-all handler
- src/app/protected-example/page.tsx
                            demo page that renders auth() session
- src/actions/auth-signin.ts
                            signInWithGoogle, signInWithDev,
                            signOutAction server actions
- src/app/login/LoginClient.tsx
                            added "Sign in with Google" + dev form
- supabase/migrations/204_authjs_tables.sql
                            users / accounts / sessions /
                            verification_token schema (UUID-keyed)
- .env.example              AUTH_SECRET, AUTH_URL, GOOGLE_CLIENT_*,
                            DATABASE_URL, ALLOW_DEV_LOGIN

Removed
- src/middleware.ts         deleted; Next.js 16 only runs one proxy
                            (the new src/proxy.ts is canonical)

Routes
- /login, /admin, /admin/*, /protected-example
                            proxy matcher
- /api/auth/{providers,csrf,signin/<provider>,callback/<provider>,
  session,signout}
                            standard Auth.js endpoints

Local dev
- npm run dev (now runs on port 4000)
- push migration 204 then visit /login
- dev signin works with any non-empty username/password
  (hidden when ALLOW_DEV_LOGIN=false)
- Google signin requires real GOOGLE_CLIENT_ID + redirect URI
  http://localhost:4000/api/auth/callback/google

Verified
- tsc --noEmit clean
- /admin, /admin/orders, /protected-example → 307 to /login
  when unauthenticated
- /api/auth/session returns user after signin
- /protected-example renders session info
- /api/auth/providers returns google + dev-login

Docs
- CLAUDE.md and MEMORY.md updated to reflect the Supabase → Postgres
  + Auth.js v5 pivot

Gradual migration in progress
- src/lib/admin-permissions.ts still uses dev_session / rc_auth_uid;
  the admin shell will show 'Access Denied' for Auth.js-only
  sessions until each page is flipped over
- @supabase/* packages remain in package.json for the same reason
- production deployment (AUTH_URL=https://, __Secure- cookies) is
  out of scope for this pass
2026-06-06 03:40:09 +00:00
tyler 69767eb250 feat(admin): redesign product form modal (Atelier des Récoltes)
Replace the basic add/edit product modal with an editorial two-column
layout featuring Fraunces display serif, IBM Plex Mono section labels,
a custom drop zone, visual type cards, and pill toggles. Add a brand
selector visible only to platform admins to fix the 'Brand ID is
required' / RLS error when creating products without an assigned brand.

- Add ProductFormModal with two-column layout (image hero + form)
- Add Fraunces / IBM Plex Mono / Inter Tight via next/font/google
- Add atelier-* design system to globals.css
- Wire brand picker through page server component -> client
- Convert ProductsClient image handling to callback shape
- Move all submit/validation/upload logic into the new modal
2026-06-04 19:54:20 +00:00
tyler 7ecb1f2fc0 Merge branch 'main' of github.com:dzinesco/route-commerce
# Conflicts:
#	src/app/admin/stops/page.tsx
#	src/app/globals.css
#	src/app/layout.tsx
2026-06-04 18:43:58 +00:00
tyler 015b1cf7b5 feat(checkout): real Stripe Express + Elements on /checkout
- Add @stripe/stripe-js + @stripe/react-stripe-js
- New src/lib/stripe-client.ts: cached loadStripe helper
- New src/actions/billing/retail-payment-intent.ts: server action
  that creates a PaymentIntent with automatic_payment_methods
- New src/components/storefront/StripeExpressCheckout.tsx: embedded
  ExpressCheckoutElement (Apple Pay, Google Pay, Link, PayPal) +
  PaymentElement (card) + hosted-checkout fallback
- /checkout form is now controlled; StripeExpressCheckout reads
  name/email/stop from form state and confirms in-page via
  stripe.confirmPayment
- /checkout/success handles both ?session_id= and ?payment_intent=
  so embedded + hosted flows both land on the same order-creation
  page using the pending_checkout sessionStorage payload
- Export StopInfo from CartContext and select 'time' on the stops
  fetch so the local Stop shape matches the context's StopInfo
- QuickCartSheet express buttons are visual shortcuts only;
  /checkout auto-renders the real Apple Pay / Google Pay buttons

Requires NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY for the embedded path;
if missing the embedded section falls back to the hosted
Stripe Checkout button. npx tsc --noEmit passes.
2026-06-04 18:38:21 +00:00
tyler 1b12a0a95d feat(admin/stops): add tabbed dashboard with calendar and locations views
Replace the table-only stops view with a three-tab 'Harvest Dispatch'
almanac dashboard.

- Calendar tab: month grid with stops as status-colored pins, day-detail
  side panel, prev/next/today nav, weekend tint, sunrise gradient on
  the current day.
- Locations tab: cards grouped by city+state with per-location stats
  (stops/active/upcoming), date range, next-stop ribbon, and inline
  expansion to list every stop at that location. Top of the tab shows
  a six-card almanac stats strip (Locations/Cities/Stops/Active/Upcoming/
  Drafts).
- List tab: condensed read-only list of all stops in date order.

Typography: load Fraunces (display), Manrope (body), and Fragment Mono
(mono) via next/font/google, wired into the Tailwind theme as
--font-display, --font-sans, --font-mono. Add typescript to devDeps
for tsc --noEmit in this environment.

Visual signature: cream parchment cards, forest green + clay accents,
Roman-numeral stat cells, paper-grain noise, binder-tab nav, route
number watermarks on location cards, harvest-pin SVG markers.

Verified with tsc --noEmit, eslint on the new files, and a 200 from
GET /admin/stops in the dev server.
2026-06-04 18:20:11 +00:00
tyler b2aa53f274 feat(storefront): dedicated Sweet Corn Box product page with 2-3 click order flow
- New route /tuxedo/products/sweet-corn-box with editorial Fraunces/JetBrains
  Mono typography, paper-grain hero, tilted 'Peak Season 2026' stamp, and
  sticky buy-box (desktop) + sticky mobile bottom bar with 48px+ tap targets
- QuickCartSheet slide-up drawer: mobile bottom sheet (spring + drag-to-close)
  vs desktop right drawer, with Apple Pay / Google Pay / Shop Pay express row
  and one-page 'Checkout' CTA — enables the user's specified 2-3 click flow
- CartContext.buyNow() — single-item cart replace + clear stop for 1-tap
  'Quick Buy' that pushes directly to /checkout
- 4-feature check list, 100% Sweetness Guarantee, FAQ accordion, dark final
  CTA strip, and proper SEO/Open Graph meta
2026-06-04 18:01:16 +00:00
tyler bdcaf0f1da feat(admin/stops): editorial redesign + calendar/route view
Reimagine the admin stops surface under a 'Harvest Almanac' editorial
direction. Establishes the type system (Fraunces serif display, Geist
sans, JetBrains Mono numerics) loaded via next/font, and a compact
modal pattern used across the new components.

Stop modal (AddStopModal)
- Compact 2-col grid: City|State, Date|Time, Address|ZIP|Cutoff
- Status replaced with slim segmented control (Draft / Publish Now)
- Tighter inputs (36px), single-line footer with Esc hint
- Auto-focus on City field, reactive submit label

GlassModal
- New 'compact' prop: tighter padding, eyebrow text, no accent bar,
  capped max-height for dense forms

Stop product assignment (StopProductAssignment)
- Reimagine the <select> dropdown as an editorial card-grid picker
- Big Fraunces numeral + small-caps eyebrow showing live count
- Click-to-toggle assign/remove; green check for assigned, red X on
  hover-to-remove intent
- Search + filter chips (All / Available / On this stop) with
  mono counters
- '/' keyboard shortcut focuses search
- Summary footer with 'Remove all' action

Stops page
- New StopsViewClient wrapper with shared search/status filter and
  Calendar/Table view toggle (default = Calendar)
- New StopsCalendarClient: month grid almanac with editorial header,
  color-coded event chips, today highlight, prev/next/Today nav
- Event popover with auto-edge-detection positioning, full stop
  details, Publish/View Route/Edit actions
- Day-route drawer: slides in from right with route spine (numbered
  markers + connecting hairline), 3-card summary (stops/live/brands),
  per-stop Publish/Edit/Duplicate actions
- StopsTableClient refactored to accept hideInternalFilterBar prop
  so the wrapper can own shared filtering

Design tokens
- New utility classes in admin-design-system.css: ha-display,
  ha-eyebrow, ha-field, ha-segment, ha-picker, ha-product-card,
  ha-calendar, ha-event-popover, ha-drawer, ha-route-stop, etc.
- All uses CSS variables (--font-fraunces, --font-geist,
  --font-jetbrains-mono) so the type system cascades
2026-06-04 17:19:46 +00:00
tyler 63842a9efc feat(admin): multi-brand admin support
Implements the design at
docs/superpowers/specs/2026-06-04-multi-brand-admin-design.md.

Adds:
- admin_user_brands junction table (m:n admin<->brand) via migration 207
- New role 'multi_brand_admin' (auto-set when an admin has 2+ brands)
- 'active_brand_id' cookie that persists the admin's currently-selected
  brand across navigations; switchable via the new BrandSelector dropdown
  in the sidebar
- Centralised brand resolution in src/lib/brand-scope.ts:
  - getActiveBrandId (URL > cookie > legacy brand_id > first of brand_ids)
  - assertBrandAccess (defence-in-depth for cases where the brandId
    comes from a URL form or RPC return)
- ~30 server actions and ~10 page server components migrated to use
  getActiveBrandId instead of the silent brandId ?? adminUser.brand_id
  pattern that allowed cross-brand access bugs
- BrandSelector client component with proper a11y (aria-haspopup,
  aria-expanded, role=listbox, outside-click and escape-to-close)

Migration 207 also adds RLS so admins can read their own junction rows
(needed for the dropdown to populate) and SECURITY DEFINER RPCs
add/remove_admin_user_brand that auto-promote/demote between
brand_admin and multi_brand_admin.

Notes:
- Migration number is 207 not 204 — 204-206 were taken in this worktree
  by the concurrent locations work.
- The legacy admin_users.brand_id column is preserved for backwards
  compat; a follow-up migration 220_* will drop it.
- Dev sessions (dev_session cookie, NEXT_PUBLIC_USE_MOCK_DATA) get
  brand_ids: []; the documented limitation is that dev store_employee
  will see <AdminAccessDenied /> if no real brands exist.
- Pages that hardcode a Tuxedo brand UUID as a fallback
  (adminUser.brand_id ?? '64294306-...') are NOT migrated in this PR —
  they still work for single-brand admins and are out of scope.

Co-authored-by: implementer subagent (cancelled mid-run), Grok orchestrator
2026-06-04 17:09:40 +00:00
tyler bc29c70e8b design(stops): cohesive card layout, count badges, row-click, merged columns
Tier 1 — Layout consistency:
* Move Stops tab action buttons (Upload Schedule, Add Stop) into the
  filter bar, matching the Locations tab. One muscle memory for
  'where the Add button is'. Removes the page-level StopsHeaderActions
  component.
* Make the card a single cohesive unit. Tabs sit INSIDE the card on
  top with a border-b, content fills the rest. No more 'rounded-b-2xl
  rounded-t-none border-t-0' shape.
* New StopsLocationsTabs: pill-style tabs with icons + count badges +
  sublabel ('269 · 7 cities'). Instant orientation. URL-driven via
  Link so refresh + deep-linking work.

Tier 2 — Easier to scan:
* New StatsStrip under the page header: '269 stops · 7 cities · 4
  states · 4 active · 5 draft · 0 inactive' (different stats for the
  Locations tab). Context before the table loads.
* Stops table collapses 8 columns → 6: drop Brand, merge City+State
  into 'Where', merge Date+Time into 'When' (date + weekday on one
  line, time on the next). 30% more breathing room per row.
* Replace font-mono for dates/times with tabular-nums — same column
  alignment, type family stays consistent.
* Click anywhere on a Stops row to edit. Redundant 'Edit' link is
  gone. Kebab menu still handles Publish/Duplicate/Delete.
* Match Locations' empty state (icon + title + subtitle) on the Stops
  side.

Polish:
* Consistent status pills (Active = emerald, Inactive = stone, Draft
  = amber) in both tabs.
* Tab-switch fade via framer-motion AnimatePresence — subtle y/opacity
  transition between Stops and Locations content.
2026-06-04 15:48:15 +00:00
tyler 49a9900d15 feat: add locations table + Locations tab in Stops & Routes
* New 'locations' table for reusable venues (Tractor Supply, Boot Barn,
  etc.) — each stop now links via stops.location_id while keeping the
  denormalized location text for backwards compat.
* 6 SECURITY DEFINER RPCs: admin_create_location, admin_create_locations_batch,
  admin_update_location, admin_delete_location, admin_attach_location_to_stop,
  get_locations_for_brand. Plus admin_list_locations for the admin tab.
* Backfill: 26 unique venues extracted from the 269 Tuxedo stops, all linked.
* Stops & Routes page now has tabs (Stops | Locations) via ?tab= query param.
  Locations tab has full CRUD UI: search, status filter, pagination, edit,
  delete, add-venue modal.
* StopsHeaderActions now tab-aware — Upload Schedule / Add Stop only show on
  Stops tab; Locations tab has its own Add Venue button inline.
2026-06-04 15:24:34 +00:00
tyler 0245aa29cc fix(buyer/billing/comms/a11y): Codex review pass round 2
Tuxedo buyer path (subagent 2):
- src/app/tuxedo/page.tsx: remove duplicate CinematicShowcase render
- src/components/storefront/CinematicShowcase.tsx: wire up useCart, Add to Cart button, brand-aware fulfillment
- src/app/tuxedo/stops/TuxedoStopsList.tsx: improved empty state with calendar icon + CTAs
- src/app/cart/CartClient.tsx: guard against empty cart checkout; 'Cart is Empty' state

Billing reconciliation (subagent 3):
- src/actions/billing/billing-overview.ts: NEW — single source of truth
- src/app/admin/settings/billing/page.tsx: use getBillingOverview
- src/app/admin/settings/billing/BillingClientPage.tsx: rewritten to consume BillingOverview (status pill, addons state, removable flags, derived invoice amounts, usage footer)
- src/app/admin/page.tsx: use getBillingOverview (aligns dashboard with billing)
- src/components/admin/DashboardClient.tsx: 'Active Products' now reads from getBillingOverview
- supabase/migrations/203_plan_usage_active_products.sql: get_brand_plan_info counts products as active=true AND deleted_at IS NULL

Harvest Reach dedup + audience preview (manual, subagent 4 didn't complete):
- src/components/admin/CommunicationsPage.tsx: add initialTab prop
- src/app/admin/communications/compose/page.tsx: now renders with initialTab='compose' (single compose experience, no duplicate edit panel)
- src/components/admin/HarvestReach/CampaignComposerPage.tsx: always-visible audience preview panel (count + sample emails), loads via previewCampaignAudience action

Layout/content consistency + a11y sweep (subagent 5):
- src/components/layout/SiteHeader.tsx: Admin link only shows for authenticated admin users
- src/components/Providers.tsx: suppress public SiteHeader/Footer for /admin, /cart, /checkout, /wholesale, /water (fixes duplicate headers)
- src/app/contact/ContactClientPage.tsx: Phone/Email now use tel:/mailto: links; dynamic year
- src/app/blog/page.tsx, changelog, privacy-policy, roadmap, security, terms-and-conditions, waitlist: dynamic year
- src/app/admin/wholesale/WholesaleClient.tsx: proper htmlFor/id, type=email/tel, autoComplete
- src/app/admin/settings/ai/AIClient.tsx: proper htmlFor/id, required + aria-required
- src/app/admin/settings/integrations/IntegrationsClient.tsx: only mask secret fields; add required + aria-required + CredentialField.required type
- src/app/admin/water-log/headgates/HeadgatesManager.tsx: htmlFor/id, aria-required
- src/components/admin/CreateUserModal.tsx: htmlFor/id, required, aria-required, aria-describedby, autoComplete
- src/app/admin/me/AdminMeClient.tsx, products/import, sales/import, water-log/settings, login, brands, tuxedo: a11y polish (ids/required/aria)
2026-06-03 16:39:19 +00:00
tyler 03ae372509 fix(home): resilient homepage animations + anchors + counters
Subagent 1 (Codex review pass):

LandingPageClient.tsx
- Guard GSAP .scroll-reveal loop to prevent 'missing target' warnings when no targets exist

brands/page.tsx
- Comment cleanup for selector scoping

tuxedo/page.tsx
- Add pageScopeRef + use as explicit GSAP context scope (fixes 'Invalid scope' warnings)
- Reduced-motion guard for counter animations
- Comments noting scope is now resilient

HeroSection.tsx
- Wrap sections in containerRef for proper GSAP context
- Reduced-motion support (final states shown immediately, no heavy anims)
- Counter animation: switched from textContent tween (unreliable, left values at 0) to proxy object pattern (val -> display)
- Added IO + rAF fallback if GSAP is unavailable
- Added section ids: #features, #stats, #reviews (matches nav anchors)
- Reduced blank scroll region (300vh -> 140vh on story section)
- Removed duplicate inline footer (LandingPageWrapper <Footer /> now sole source)

LandingPageWrapper.tsx
- Anchor comments noting ids added in HeroSection

Docs + scripts:
- docs/ADMIN_FUNCTIONALITY_CHECKLIST.md (admin app coverage notes)
- docs/pricing-assessment.md (pricing model notes)
- scripts/apply-admin-create-stop.js, check-stop-fns*.js, verify-stop-fns.js (admin stop creation tooling)
2026-06-03 15:36:40 +00:00
tyler 84ad60b4b0 feat(admin): comprehensive functionality pass across admin apps (Codex review)
- Restore admin order creation (top blocker):
  - New createAdminOrder server action wrapping create_order_with_items RPC with proper getAdminUser + can_manage_orders + brand scoping.
  - AdminOrdersPanel now supports ?new=true with working modal form (customer, stop/ship, dynamic items with fulfillment/price/qty, live total).
  - Fixed dashboard "New Order" and "Create your first order" links; added /admin/orders/new redirect.
  - Success flow: toast + redirect to list.

- Product edit reliability and admin flows hardened.

- Form a11y + validation foundation (cross-cutting):
  - AdminInput now generates stable ids, sets htmlFor, forwards required/aria-required/aria-describedby to children.
  - All admin forms benefit (labels programmatic, required semantics real).

- Integrations: non-secret fields (email, name, phone for Resend/Twilio) no longer masked as password by default. Only isSecret fields use type=password + toggle.

- Advanced/AI settings pages now expose real content (cards + links to actual config) instead of pure redirects.

- Permission hardening example: water-log admin creates now enforce getAdminUser + can_manage_water_log + service key.

- Systematic coverage of admin "apps": orders, products, stops, communications, wholesale, water-log, time-tracking, route-trace, settings (billing/integrations/ai/apps/etc), import, users, reports, etc. via exploration + targeted fixes. Empty states, toasts, scoping, quick actions improved.

- Updated MEMORY.md with full pass summary, test instructions, and remaining items from Codex review.

Non-destructive focus during pass; used dev auth for verification. Core blockers from review now addressed for testing.

See session plan.md for detailed execution guide.
2026-06-03 15:29:03 +00:00
tyler f155bf6f5c Fix CRI-17: wire handleSubmitEntry to entry form, remove dead state + unused prop
WaterFieldClient.tsx:
- Wrap entry-form section in <form onSubmit={handleSubmitEntry}>
  (handler was defined but never bound to a form or button — submit
  button did nothing). Submit button already had type="submit".
- Remove unused selectedRole state + its two setSelectedRole calls
  (state was set at role-selection but never read).

wholesale/login/page.tsx:
- Remove unused 'name' prop from BrandLogo (was passed in but
  never referenced inside the component).

Fixes CRI-17.
2026-06-03 02:13:44 +00:00
tyler 1fe5ffee8d Refactor: move public storefront stop data to server-side + parallel agent work
Server-side / caching refactor (Grok):
- New RPC get_public_stops_for_brand (migration 148) for public storefront stops
- New server action getPublicStopsForBrand with revalidate=300 + tags
- Add revalidateTag invalidation to createStopsBatch + publishStop
- Convert /tuxedo/stops and /indian-river-direct/stops to Server Components
- Extract TuxedoStopsList + IndianRiverStopsList as client islands (GSAP only)
- Removes supabase-js from browser bundle on those routes
- Both pages now statically prerendered (5m ISR)

Parallel agent changes also staged:
- AI provider model list refresh (claude-sonnet-4-5, etc.)
- ESLint directive patches for react-hooks/set-state-in-effect
- Admin + storefront + checkout + cart updates
- New admin_create_stop_rpcs migration (147)
- Misc fixes across ~90 files

Build verified: typecheck clean, lint clean on new files, production build succeeds.
2026-06-03 02:04:21 +00:00
tyler 2c95ce9a19 fix: remove duplicate headers and unused theme toggle
- Providers now excludes login page from SiteHeader/SiteFooter
- Removed ThemeToggle from SiteHeader (not being used)
- Login page uses fixed viewport height to prevent scrolling
- DemoMode also uses fixed viewport height
2026-06-02 16:31:40 +00:00
tyler 6feac9bb9a feat: redesign login page to match landing page
- Matched landing page design aesthetic (warm cream tones, organic backgrounds)
- Added header with Route Commerce logo and back link
- Added footer with logo and minimal links
- Added security trust badges section:
  - 256-bit SSL
  - SOC 2 compliance
  - Powered by Supabase
- Clean typography with Cormorant Garamond + Plus Jakarta Sans
- Subtle animations on page load
- Demo mode also styled to match
2026-06-02 16:16:17 +00:00
tyler c5509f53f9 feat: add Header with Sign In to landing page
- Wrapped LandingPageClient with LandingPageWrapper
- Header now shows Sign In button next to Get Started
- Mobile menu also includes Sign In option
- Fixed Plus Jakarta Sans font import
2026-06-02 16:07:43 +00:00
tyler 2db3375f87 feat: Apple-style scroll-driven landing page
- Add ScrollAnimations component (ScrollReveal, ParallaxLayer, FadeOnScroll)
- Add CinematicShowcase for scroll-driven product reveals
- Update Tuxedo page with cinematic product showcase and parallax layers
- Update main HeroSection with same premium scroll animations
- Add scroll progress bar, staggered reveals, counter animations
- Apply consistent Apple-style UX across landing and Tuxedo pages

Features:
- Sections pin and reveal content progressively on scroll
- Parallax depth effects on decorative elements
- Counter animations triggered by scroll
- Smooth hover states and transitions
- GSAP + Framer Motion for smooth animations
2026-06-02 15:41:48 +00:00
tyler bf4dc09e65 fix: restore full landing page with FeaturesAndStats and TestimonialsAndCTA 2026-06-02 15:29:39 +00:00
tyler 344c8741da feat: cinematic scroll-driven landing pages with GSAP ScrollTrigger
- Enhanced HeroSection with pinned scroll animations, parallax layers,
  sticky storytelling sections, and scroll progress indicator
- Rewrote TuxedoVideoHero with GSAP scroll-driven parallax video scaling,
  floating decorative orbs, and animated content reveals
- Added scroll-triggered reveals (.reveal-text, .reveal-scale, .reveal-slide)
  to Tuxedo Corn index page with counter animations
- Applied Apple-style hero sequences with smooth scrub animations
- Integrated scroll timeline with progress bar showing scroll position
2026-06-02 15:23:44 +00:00
tyler 72089d7c19 feat(storefront): add stops listing pages for both brands
- Create /tuxedo/stops page showing all Tuxedo Corn pickup stops
- Create /indian-river-direct/stops page showing all Indian River Direct stops
- Each page features: upcoming/past stop separation, date badges, GSAP animations
- Link to individual stop detail pages
- Orange accent for IRD, emerald accent for Tuxedo
2026-06-02 15:17:29 +00:00
tyler 6ec4d78404 refactor(brands): compact side-by-side card layout
- Switch from stacked vertical cards to 2-column grid
- Reduce card padding and font sizes for compact display
- Add colored accent bar at top of each card
- Shrink header, hero, and footer sections
- Both brands now fit above the fold without scrolling
2026-06-02 15:11:10 +00:00
tyler 12f9b2da99 admin: polish route trace page to match design system 2026-06-02 14:36:22 +00:00
tyler d031a9783c Polish pass: micro-interactions on admin and storefront pages
Admin design system:
- AdminCard: hover shadow elevation
- AdminTable: enhanced empty state, row hover
- AdminBadge: tracking-wide typography
- AdminButton: active scale press feedback
- AdminFormElements: input transitions
- AdminFilterBar: search input polish
- PageHeader: icon hover scale, chevron separator
- AdminToggle: glow effect, thumb shadow
- AdminEmptyState: icon hover, spacing
- AdminPagination: active solid bg, ellipsis

Storefronts (tuxedo + indian-river-direct):
- Button hover lifts (-translate-y-0.5)
- Shadow elevations on hover
- Button press feedback
- Consistent timing (duration-200)
- Product card image zoom
- FAQ empty state fix
2026-06-02 14:29:27 +00:00
tyler 8dee63fef2 feat: wire up real Supabase data to analytics dashboard
- Create src/actions/analytics.ts with real data fetching:
  - getAnalyticsMetrics() - revenue, orders, customers, AOV
  - getRevenueChart() - daily revenue data
  - getTopProducts() - product performance from reports RPC
  - getRecentOrders() - live orders from orders table
  - getCustomerGrowth() - contact growth stats
  - getConversionFunnel() - computed from order data

- Rewrite AnalyticsDashboard.tsx to:
  - Fetch real data from Supabase via brand-scoped RPCs
  - Show loading states and error handling
  - Display actual metrics from the database
  - Remove all mock data

- Add src/app/admin/analytics/page.tsx server component

All analytics now pull from real Supabase data instead of mock values.
2026-06-02 06:38:44 +00:00
tyler 7dfaba6e3d feat: complete launch & marketing layer
- Add marketing pages: blog, changelog, roadmap, waitlist, security, maintenance
- Add GDPR cookie consent banner with preference modal
- Add referral system with API routes for code generation/tracking
- Add waitlist API with referral support
- Add PWA support: manifest, service worker, install prompt
- Add onboarding flow with 6-step guided tour
- Add in-app notification center with bell dropdown
- Add admin launch checklist (32 items across 8 categories)
- Update landing page with trust badges
- Add Open Graph image and favicon
- Configure ESLint for PWA install patterns
- Add LAUNCH_CHECKLIST.md with go-to-market guide

Supabase migrations for:
- blog_posts and blog_categories tables
- waitlist_signups table
- roadmap_items table
- launch_checklist_items table
2026-06-02 06:19:56 +00:00
tyler 00deda1350 Revert Clerk - keeping existing Supabase auth, add security middleware 2026-06-02 05:35:02 +00:00
tyler 6ab52a2499 Production upgrade: Clerk auth, Stripe billing, analytics, PWA support
Backend & Auth:
- Add @clerk/nextjs for production authentication
- Create src/proxy.ts with clerkMiddleware() for route protection
- Implement multi-tenant auth with role-based access control
- Add Clerk components (Show, UserButton, SignInButton, SignUpButton)

Billing & Payments:
- Full Stripe integration (subscriptions, add-ons, customer portal)
- Plan tiers: Starter 9/mo, Farm 49/mo, Enterprise 99/mo
- Webhook handling for subscription events
- createSubscription(), createAddonSubscription(), createCustomerPortalSession()

API & Security:
- Rate limiting with @upstash/ratelimit (100 req/min API, 20 req/min checkout)
- Zod validation schemas for all endpoints (orders, products, campaigns, etc.)
- Security headers (CSP, HSTS, X-Frame-Options)
- API routes: /api/v1/ with validated, rate-limited endpoints

Monitoring:
- Sentry error tracking with performance monitoring
- PostHog analytics for feature usage, funnels, cohorts
- User activity logging and breadcrumb tracking

Admin Features:
- Analytics dashboard with revenue charts, customer growth, conversion funnel
- Onboarding flow with 6-step interactive tour
- Referral system with share tracking and reward redemption
- Changelog feed with in-app notifications

PWA & SEO:
- Web app manifest with icons and shortcuts
- Service worker for offline support and caching
- Full SEO metadata, OpenGraph, Twitter cards
- Structured data (JSON-LD) for organization and products

Database:
- Add referral_codes, changelogs, onboarding_progress tables
- Add user_activity_logs, api_keys, notification_preferences
- Comprehensive RLS policies for all new tables
- Seed data for demo brands and products
2026-06-02 05:33:42 +00:00
tyler b845d69aba feat: comprehensive frontend polish - UI/UX improvements across all pages
Public Pages:
- Landing page with server/client split and metadata export
- Cart page with ARIA accessibility and loading states
- Checkout page with form accessibility and proper design system
- Contact page with SEO metadata and improved accessibility
- Pricing page with enhanced FAQ accessibility
- Login page with Suspense boundaries and secure patterns

Brand Storefronts:
- Premium loading skeletons for Tuxedo and Indian River Direct
- Branded error boundaries with animations
- Loading.tsx for about, contact, FAQ, stops pages
- Error.tsx for all storefront subpages

Admin Dashboard:
- AdminSidebar: ARIA labels, keyboard navigation, mobile improvements
- DashboardClient: Stats cards, quick actions, usage progress
- Admin layout: Toast provider integration

Admin Orders/Products/Stops:
- Toast notification system with auto-dismiss
- Skeleton loading components (Table, Card, Stats, Form)
- Bulk actions (mark picked up, publish stops)
- Form validation with error styling

Admin Communications:
- AnalyticsDashboard: sparklines, stat cards, engagement badges
- CampaignComposerPage: step wizard, template selection, email preview
- SegmentBuilderPage: empty state, active segment handling
- ContactListPanel: loading skeletons, professional empty states
- MessageLogPanel: stats cards, engagement indicators
- HarvestReachNav: branded tab navigation
- All pages: metadata exports and loading.tsx

Admin Settings:
- SquareSyncSettingsClient: design system colors, save/cancel UX
- ShippingSettingsForm: validation, dirty state tracking
- Integrations page: proper layout with AI and communications sections
- Billing: improved plan comparison, add-on cards
- PaymentSettings: toggle components, validation

Wholesale Portal:
- Portal: loading skeletons, quantity stepper, search/filter
- Login/Register: FormField validation, success states
- Success/Cancel pages: animated checkmarks
- Employee portal: skeletons, empty states, mobile responsive

Water Log:
- FieldClient: loading step, progress indicator, spinner
- AdminClient: loading skeletons
- Admin pages: loading.tsx files

New Components:
- Toast.tsx/ToastContainer.tsx: comprehensive notification system
- Skeleton.tsx: shimmer loading components
- AdminToggle.tsx: consistent toggle/switch
- CommunicationsLoading.tsx: loading skeleton for comms
- ToastExport.ts: exports

CSS Improvements:
- Shimmer animation keyframes
- Toast slide-in animation

Accessibility:
- ARIA labels throughout
- Keyboard navigation
- Focus states
- Semantic HTML
- Screen reader support
2026-06-02 05:19:34 +00:00
tyler fb25c5ee22 fix: route trace new lot modal instead of separate page 2026-06-02 05:04:49 +00:00
tyler d7fe1ea3fd fix: admin pages CSS variables - consistent design system colors 2026-06-02 05:02:19 +00:00
tyler 3d810ee7e0 fix: restore full settings pages with tabbed interface (General, Brand, Workers, Tasks, Users) 2026-06-02 04:53:05 +00:00
tyler 12b936c634 fix: tuxedo FAQ page - add missing page.tsx and enhance SEO schemas 2026-06-02 04:48:04 +00:00
tyler 14ec2f9c71 fix: settings page redesign - tabbed interface, no sidebar dropdown
- AdminSidebar: removed dropdown menu, flat nav links, no scrolling
  - Removed SETTINGS_SUB_LINKS array
  - Removed settingsOpen state and dropdown logic
  - Settings is now a simple link to /admin/settings
  - Nav uses overflow-hidden to prevent sidebar scroll

- SettingsClient: unified tabbed interface (like CommunicationsPage)
  - New tabs: General, Add-ons, Billing, Integrations, AI, Advanced
  - Uses AdminFilterTabs for navigation
  - Each tab shows a card with link to full settings page

- Redirected pages to /admin/settings#tab:
  - /admin/settings/apps → /admin/settings#addons
  - /admin/settings/integrations → /admin/settings#integrations
  - /admin/settings/ai → /admin/settings#ai
  - /admin/advanced → /admin/settings#advanced

All TypeScript checks pass.
2026-06-02 04:42:32 +00:00
tyler 778b3fe311 fix: full Apple HIG mobile + SEO audit - all issues resolved
MOBILE RESPONSIVENESS (Apple HIG):
- HeroSection: typography scaled 7xl→4xl sm:5xl md:6xl lg:7xl, responsive px/py, rounded-2xl, active:scale-95
- Cart quantity buttons: h-8→h-11 w-8→w-11 (44px touch target), rounded-xl, active:scale-95
- StorefrontFooter newsletter: responsive w-full sm:w-52 lg:w-64, aria-label, larger touch targets
- StorefrontHeader mobile nav: padding px-6→px-4 py-6→py-5
- AdminTable: overflow-x-auto + min-w-[600px] for horizontal scroll
- AdminOrdersPanel: same table overflow fix
- AdminLayout: mobile px-4 sm:px-6 py-6 sm:py-10
- AdminFilterTabs: responsive text/text sizes
- AdminSidebar hamburger: h-10→h-11 w-10→w-11 (44px touch target)
- DashboardClient: grid gap-3 sm:gap-4, responsive stat text
- OrderEditForm: grid-cols-1 sm:grid-cols-2 (was 2, breaks on mobile)
- BillingClient: min-h-[44px] on select/button
- ProductsClient: h-32 sm:h-40 responsive image height
- StopCard: line-clamp-2 instead of line-clamp-1 on location
- CommunicationsPage: tabs wrapped in overflow-x-auto
- Checkout page: grid breakpoint md not lg
- Tuxedo page: SectionHeader mobile-first, feature grid grid-cols-1 sm:2, label visible
- Tuxedo stats: text-3xl sm:text-4xl

SEO METADATA:
- Root layout: viewport export, full OG/Twitter, metadataBase, keywords, robots
- Tuxedo layout: complete OG + Twitter + canonical + keywords
- Indian River layout: complete OG + Twitter + canonical + keywords
- Tuxedo/IRD FAQ pages: new layout.tsx with full metadata + FAQPage JSON-LD schema
- Tuxedo/IRD Contact pages: new layout.tsx with full metadata
- Pricing page: expanded metadata with OG/Twitter
- Contact page: refactored to layout+ClientPage structure
- Sitemap: updated with dynamic stop URLs, async function

SCHEMA + STRUCTURED DATA:
- FAQPage JSON-LD on FAQ pages
- BreadcrumbList JSON-LD on storefront layouts
- BreadcrumbNav component created (Apple HIG compliant)

BUG FIXES:
- Indian River: replaced raw <img> with Next.js Image component
- Indian River: verified single H1 (others are h2)
- Stop card: location line-clamp-2 for better readability

TYPE CHECK: all pass
2026-06-02 04:32:58 +00:00
tyler c73da417af fix: Apple HIG mobile responsiveness - hero typography, tables, SEO metadata
Mobile:
- HeroSection: scale typography 7xl → 4xl sm:5xl md:6xl lg:7xl, responsive px/py
- StorefrontFooter: newsletter input responsive w-full sm:w-52, larger touch targets
- AdminTable: add overflow-x-auto + min-w-[600px] for horizontal scroll
- AdminOrdersPanel: same table overflow fix
- AdminFilterTabs: increase text sizes sm:text-xs, add padding
- Tuxedo page: SectionHeader mobile-first sizing, feature grid grid-cols-1 sm:2
- Tuxedo feature labels: show on all screens (remove hidden sm:block)

SEO:
- Root layout: add viewport export, full OG/Twitter metadata, metadataBase
- Tuxedo layout: complete OG + Twitter card + canonical + keywords + robots
- Indian River layout: same complete SEO treatment

All TypeScript checks pass.
2026-06-02 04:28:03 +00:00