'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; 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'; // `identifier` accepts either an email address or a username (3–30 lowercase // letters / digits / dot / underscore / hyphen). The server endpoint // /api/auth/sign-in-by-identifier resolves the username server-side and // forwards to better-auth in one round-trip β€” the canonical email is never // returned to the browser, which closes the username-enumeration vector. const loginSchema = z.object({ identifier: z.string().min(1, 'Email or username is required'), password: z.string().min(1, 'Password is required'), }); type LoginFormData = z.infer; export default function LoginPage() { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); // Fresh-DB bootstrap detection: if no super-admin exists yet, /setup // owns the first-run flow. Failure of the status endpoint is silent // (login still works for everyone else). useEffect(() => { let cancelled = false; fetch('/api/v1/bootstrap/status') .then((r) => (r.ok ? (r.json() as Promise<{ data?: { needsBootstrap?: boolean } }>) : null)) .then((payload) => { if (cancelled || !payload) return; if (payload.data?.needsBootstrap) router.replace('/setup'); }) .catch(() => { /* silent β€” login UX must still work even if status check fails */ }); return () => { cancelled = true; }; }, [router]); const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(loginSchema), }); async function onSubmit(data: LoginFormData) { setIsLoading(true); try { const res = await fetch('/api/auth/sign-in-by-identifier', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identifier: data.identifier.trim(), password: data.password, }), }); if (!res.ok) { const payload = (await res.json().catch(() => ({}))) as { error?: { message?: string }; }; toast.error(payload.error?.message ?? 'Invalid credentials'); return; } router.push('/dashboard'); } catch { toast.error('Something went wrong. Please try again.'); } finally { setIsLoading(false); } } return (

Port Nimara CRM

Sign in to continue

{errors.identifier && (

{errors.identifier.message}

)}
Forgot password?
{errors.password &&

{errors.password.message}

}
); }