1"use client";23import { useRouter, useSearchParams } from "next/navigation";4import { Suspense, useEffect, useState } from "react";5import { api, ApiError } from "@/lib/api";67function LoginForm() {8 const router = useRouter();9 const params = useSearchParams();10 const [needsSetup, setNeedsSetup] = useState(false);11 const [email, setEmail] = useState("");12 const [password, setPassword] = useState("");13 const [confirm, setConfirm] = useState("");14 const [error, setError] = useState<string | null>(null);15 const [busy, setBusy] = useState(false);1617 useEffect(() => {18 api.get<{ needs_setup: boolean; authenticated: boolean }>("/api/auth/status").then((s) => {19 setNeedsSetup(s.needs_setup);20 if (s.authenticated) router.replace(params.get("next") || "/");21 }).catch(() => {});22 }, [router, params]);2324 const submit = async (e: React.FormEvent) => {25 e.preventDefault();26 setError(null);27 if (needsSetup && password !== confirm) return setError("Passwords do not match.");28 setBusy(true);29 try {30 await api.post(needsSetup ? "/api/auth/setup" : "/api/auth/login", { email, password });31 router.replace(params.get("next") || "/");32 } catch (err) {33 setError(err instanceof ApiError ? err.message : "Login failed");34 } finally {35 setBusy(false);36 }37 };3839 return (40 <div className="min-h-screen grid place-items-center p-6">41 <form onSubmit={submit} className="card w-full max-w-sm p-7 flex flex-col gap-4">42 <div className="flex items-center gap-2.5 mb-1">43 <div className="h-8 w-8 rounded-lg bg-accent grid place-items-center text-white font-bold">λ</div>44 <div>45 <div className="font-semibold tracking-tight">LLM API</div>46 <div className="text-xs text-ink-3">{needsSetup ? "Create the administrator account" : "Sign in to the console"}</div>47 </div>48 </div>49 <label className="flex flex-col gap-1 text-sm">50 <span className="label">Email</span>51 <input className="input" type="email" autoComplete="username" value={email} onChange={(e) => setEmail(e.target.value)} required />52 </label>53 <label className="flex flex-col gap-1 text-sm">54 <span className="label">Password</span>55 <input className="input" type="password" autoComplete={needsSetup ? "new-password" : "current-password"} value={password} onChange={(e) => setPassword(e.target.value)} required minLength={needsSetup ? 10 : 1} />56 </label>57 {needsSetup && (58 <label className="flex flex-col gap-1 text-sm">59 <span className="label">Confirm password</span>60 <input className="input" type="password" autoComplete="new-password" value={confirm} onChange={(e) => setConfirm(e.target.value)} required />61 </label>62 )}63 {error && <div className="text-sm text-bad">{error}</div>}64 <button className="btn btn-primary h-9" disabled={busy}>{busy ? "…" : needsSetup ? "Create account" : "Sign in"}</button>65 <div className="text-[11px] text-ink-3 text-center">Private system · all access is logged</div>66 </form>67 </div>68 );69}7071export default function LoginPage() {72 return <Suspense><LoginForm /></Suspense>;73}74