Skip to content

Instantly share code, notes, and snippets.

@panchicore
Created February 21, 2026 16:54
Show Gist options
  • Select an option

  • Save panchicore/4bb8f8ccd7a1a310b71d62afd1207e3e to your computer and use it in GitHub Desktop.

Select an option

Save panchicore/4bb8f8ccd7a1a310b71d62afd1207e3e to your computer and use it in GitHub Desktop.
TMS: Deploy to PROD plan — /setup bootstrapper + Vercel + Supabase

Plan: Production Deploy — /setup bootstrapper + Vercel + Supabase

Goal

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.

Files to modify/create

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

Implementation steps

1. Add /setup to routes config

src/config/routes.ts — add setup: '/setup' under auth.

2. Update proxy to allow /setup

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 /login

3. Create setup server action

src/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('/')

4. Create SetupForm client component

src/features/auth/components/SetupForm.tsx:

  • Same patterns as LoginForm.tsx (react-hook-form + zod + shadcn Form)
  • Uses createUserWithOrgSchema from 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

5. Create setup page

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'

6. Update build command for auto-migrations

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.

Architecture alignment

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

Verification

  1. tsc --noEmit — type check
  2. pnpm lint — architecture rules
  3. Manual test: visit /setup on blank DB → fill form → should land on dashboard
  4. Manual test: visit /setup again → should redirect to /login
  5. pnpm build — verify build succeeds with migration step
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment