Deploy TMS to Vercel prod with Supabase prod DB. Provide a one-time /setup UI to create the first admin user + organization on a blank database.
| Action | File | Purpose |
|---|---|---|
| Create | src/app/(auth)/setup/page.tsx |
Server component: checks if any org exists → redirect to /login if yes, else render form |
| Create | src/features/auth/components/SetupForm.tsx |
Client component: 4-field form (fullName, email, password, organizationName) |
| Create | src/features/auth/actions/setup-actions.ts |
Server action: validates input → calls userService.createUserWithOrganization() → auto-login via signInWithPassword → redirect to / |
| Modify | src/proxy.ts |
Add /setup to public routes allowlist |
| Modify | src/config/routes.ts |
Add auth.setup: '/setup' route |
| Modify | package.json |
Change build script to run migrations first |
src/config/routes.ts — add setup: '/setup' under auth.
src/proxy.ts — change the public route check:
const isPublicPage = path.startsWith(routes.auth.login) || path.startsWith(routes.auth.setup);
if (!user && !isPublicPage) → redirect to /loginsrc/features/auth/actions/setup-actions.ts:
'use server'- Validate input with existing
createUserWithOrgSchema - Check if any organization exists (guard:
SELECT COUNT(*) FROM organizations WHERE deleted_at IS NULL)- If exists → return error "Sistema ya configurado"
- Call
userService.createUserWithOrganization(input) - Auto-login:
supabase.auth.signInWithPassword({ email, password }) revalidatePath('/', 'layout')redirect('/')
src/features/auth/components/SetupForm.tsx:
- Same patterns as
LoginForm.tsx(react-hook-form + zod + shadcn Form) - Uses
createUserWithOrgSchemafrom existing schemas - 4 fields, 2 visual sections ("Tu cuenta" / "Tu organización")
- Submit calls setup action via direct server action invocation (no React Query — one-shot)
- Loading state on button
- Error toast on failure
src/app/(auth)/setup/page.tsx:
- Server component
- Query: check if any organization exists in DB
- If exists →
redirect(routes.auth.login) - If not → render Card with SetupForm (same Card pattern as login page)
- Metadata:
title: 'Configuración Inicial | TLC Transport'
package.json:
"build": "pnpm db:migrate && next build"This makes drizzle-kit migrate && pnpm db:bootstrap run on every Vercel deploy. Both are idempotent — safe to re-run.
Note: Requires DATABASE_URL available at build time in Vercel env vars.
SetupForm (client)
→ setupAction (server action)
→ org existence check (organizationRepository or direct query)
→ userService.createUserWithOrganization() (existing)
→ supabase.auth.signInWithPassword() (auto-login)
→ redirect('/')
- No new repository methods needed
- No React Query needed (one-shot server action)
- Reuses existing
UserService,createUserWithOrgSchema,organizationRepository - Self-disabling: page redirects away if any org exists
tsc --noEmit— type checkpnpm lint— architecture rules- Manual test: visit
/setupon blank DB → fill form → should land on dashboard - Manual test: visit
/setupagain → should redirect to/login pnpm build— verify build succeeds with migration step