'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { BrandedAuthShell } from '@/components/shared/branded-auth-shell'; import { apiFetch } from '@/lib/api/client'; import { cn } from '@/lib/utils'; const setupSchema = z.object({ name: z.string().min(1, 'Name is required').max(120), email: z.string().email('Valid email is required').max(254), password: z.string().min(9, 'Password must be at least 9 characters').max(200), confirmPassword: z.string(), }); type SetupFormData = z.infer; interface StatusResp { data: { needsBootstrap: boolean }; } /** * First-run setup. On a fresh DB the very first visitor can claim the * super-admin account here. Once anyone claims it, future visits to * /setup redirect back to /login — the precondition is verified both * server-side (`/api/v1/bootstrap/status` + `/api/v1/bootstrap/super-admin`'s * internal recheck) and client-side here. */ export default function SetupPage() { const router = useRouter(); const [checking, setChecking] = useState(true); const [submitting, setSubmitting] = useState(false); const { register, handleSubmit, watch, formState: { errors }, } = useForm({ resolver: zodResolver(setupSchema), }); useEffect(() => { let cancelled = false; async function check() { try { const res = await apiFetch('/api/v1/bootstrap/status'); if (cancelled) return; if (!res.data.needsBootstrap) { // Already initialized — bounce to login. Replace, not push, // so back-button doesn't trap the user here. router.replace('/login'); return; } } catch { // Status endpoint failed — let the user try anyway; the POST // does its own check and will surface a 409 if the window closed. } finally { if (!cancelled) setChecking(false); } } void check(); return () => { cancelled = true; }; }, [router]); async function onSubmit(data: SetupFormData) { if (data.password !== data.confirmPassword) { toast.error('Passwords do not match'); return; } setSubmitting(true); try { await apiFetch('/api/v1/bootstrap/super-admin', { method: 'POST', body: { name: data.name, email: data.email, password: data.password, }, }); toast.success('Administrator account created — sign in to continue.'); router.replace('/login'); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to create administrator account'); } finally { setSubmitting(false); } } if (checking) { return (
Checking setup state…
); } return (

Welcome to Port Nimara CRM

No administrator account exists yet. Create one to get started — you’ll be the super-administrator for this installation.

{errors.name &&

{errors.name.message}

}
{errors.email &&

{errors.email.message}

}
{errors.password && (

{errors.password.message}

)}
0 && 'border-destructive', )} />

This screen is only available until the first administrator is created. After that, subsequent users are added through Admin → Users.

); }