- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject,
deleteObject, presigned URL helpers, and BUCKETS constant
- Wire product images, brand logos, and water log photos to MinIO via the
new storage client
- Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call)
- Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge
Function proxy)
- Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass
brand_settings.logo_url from all call sites
- Update email templates to use dynamic logoUrl instead of hardcoded Supabase
bucket URLs
- Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage,
TimeTrackingFieldClient; use brand_settings props + local public/ fallback
- Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to
public/ for local development
- Add MinIO env vars to .env.example (endpoint, access key, secret, buckets)
- Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props
- Fix admin/users.ts logoUrl type (null → undefined for optional string)
- Remove stale sb- cookie from wholesale-auth
- Migrate tuxedo/about page to remove supabase import and use pool query for
wholesale_settings lookup
Next.js 16 enforces 'one of middleware.ts OR proxy.ts' - both can't
coexist. Our src/middleware.ts (Auth.js v5) is the canonical one;
src/proxy.ts was the legacy dev_session auto-issuer.
Next.js 16 renamed middleware.ts to proxy.ts and only allows one of the
two. Delete src/middleware.ts and rewrite src/proxy.ts to do the dev
auto-login + /login bounce + /admin gate explicitly (no NextAuth auth()
wrapper — auth.handler() returns a NextMiddleware taking (req, event),
which makes wrapping it awkward when we also need to set cookies on the
response).
Drop the now-dead 'authorized' callback from auth.config.ts (it was only
fired by the NextAuth wrapper, which we no longer use).
Migration 209: add a defensive unique constraint on admin_users.user_id
so the ON CONFLICT (user_id) clause in the new RPC resolves regardless
of whether the Supabase-dashboard-created table shipped with one.
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