"use client"; import React, { Suspense, useState, useTransition } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import Link from "next/link"; import { z } from "zod"; import { toast } from "sonner"; import { authClient } from "@/lib/auth/client"; import { cn } from "@/lib/utils"; import { Eye, EyeOff, Loader2, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; // --------------------------------------------------------------------------- // Validation schema // --------------------------------------------------------------------------- const loginSchema = z.object({ email: z.string().email("Please enter a valid email address"), password: z.string().min(1, "Password is required"), }); type LoginFormValues = z.infer; type FieldErrors = Partial>; // --------------------------------------------------------------------------- // CubeAdmin logo (duplicated from sidebar so this page is self-contained) // --------------------------------------------------------------------------- function CubeIcon({ className }: { className?: string }) { return ( ); } // --------------------------------------------------------------------------- // Form field component // --------------------------------------------------------------------------- interface FormFieldProps { id: string; label: string; type?: string; value: string; onChange: (value: string) => void; error?: string; placeholder?: string; autoComplete?: string; disabled?: boolean; children?: React.ReactNode; // for additional elements (e.g., show/hide button) className?: string; } function FormField({ id, label, type = "text", value, onChange, error, placeholder, autoComplete, disabled, children, className, }: FormFieldProps) { return (
onChange((e.target as HTMLInputElement).value)} placeholder={placeholder} autoComplete={autoComplete} disabled={disabled} aria-invalid={!!error} aria-describedby={error ? `${id}-error` : undefined} className={cn( "h-9 bg-zinc-900/60 border-zinc-800 text-zinc-100 placeholder:text-zinc-600 focus-visible:border-emerald-500/50 focus-visible:ring-emerald-500/20", children && "pr-10", error && "border-red-500/50 focus-visible:border-red-500/50 focus-visible:ring-red-500/20" )} /> {children}
{error && ( )}
); } // --------------------------------------------------------------------------- // Login page // --------------------------------------------------------------------------- function LoginPageInner() { const router = useRouter(); const searchParams = useSearchParams(); const callbackUrl = searchParams.get("callbackUrl") ?? "/dashboard"; const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [fieldErrors, setFieldErrors] = useState({}); const [globalError, setGlobalError] = useState(null); const [isPending, startTransition] = useTransition(); function validate(): LoginFormValues | null { const result = loginSchema.safeParse({ email, password }); if (!result.success) { const errors: FieldErrors = {}; for (const issue of result.error.issues) { const field = issue.path[0] as keyof LoginFormValues; if (!errors[field]) errors[field] = issue.message; } setFieldErrors(errors); return null; } setFieldErrors({}); return result.data; } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setGlobalError(null); const values = validate(); if (!values) return; startTransition(async () => { try { const { error } = await authClient.signIn.email({ email: values.email, password: values.password, }); if (error) { // Map common Better Auth error codes to friendly messages const message = mapAuthError(error.code ?? error.message ?? ""); setGlobalError(message); return; } toast.success("Signed in successfully"); router.push(callbackUrl); router.refresh(); } catch (err) { setGlobalError("An unexpected error occurred. Please try again."); console.error("[login] Unexpected error:", err); } }); } return (
{/* Background grid pattern */}