'use client'; import { 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 { authClient } from '@/lib/auth/client'; 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 page resolves usernames // to the canonical Better-Auth email via /api/auth/resolve-identifier before // the actual sign-in call. 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); const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(loginSchema), }); async function onSubmit(data: LoginFormData) { setIsLoading(true); try { // Resolve username β†’ email when the input isn't already an email. // The endpoint always returns SOMETHING (the input itself on miss) // so the auth call below fails uniformly with "invalid credentials" // either way β€” no username enumeration. const identifier = data.identifier.trim(); let email = identifier; if (!identifier.includes('@')) { const res = await fetch('/api/auth/resolve-identifier', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identifier }), }); const payload = (await res.json().catch(() => ({}))) as { email?: string }; email = payload.email?.trim() || identifier; } const result = await authClient.signIn.email({ email, password: data.password, }); if (result.error) { toast.error(result.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}

}
); }