"use client"; import { useEffect, useState } from "react"; import { ThemeToggle } from "@/components/ui/ThemeToggle"; type LoginStage = "login" | "token" | "password" | "success"; async function postJson(path: string, body?: object) { const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body ?? {}), }); const data: { error?: string } = await response.json(); if (!response.ok) throw new Error(data.error || "Request failed"); } export default function LoginPage() { const [stage, setStage] = useState("login"); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [token, setToken] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const [setupRequired, setSetupRequired] = useState(null); useEffect(() => { fetch("/api/auth/setup-status") .then((response) => response.json()) .then((data) => setSetupRequired(data.setupRequired)) .catch(() => setSetupRequired(false)); }, []); async function run(action: () => Promise) { setError(""); setLoading(true); try { await action(); } catch (caught) { setError(caught instanceof Error ? caught.message : "Network error. Please try again."); } finally { setLoading(false); } } function handleLogin(event: React.FormEvent) { event.preventDefault(); void run(async () => { await postJson("/api/auth/login", { password }); window.location.href = "/"; }); } function beginReset() { void run(async () => { await postJson("/api/auth/password-reset/request"); setToken(""); setStage("token"); }); } function handleToken(event: React.FormEvent) { event.preventDefault(); void run(async () => { await postJson("/api/auth/password-reset/verify", { token: token.trim() }); setPassword(""); setConfirmPassword(""); setStage("password"); }); } function handlePasswordReset(event: React.FormEvent) { event.preventDefault(); void run(async () => { await postJson("/api/auth/password-reset/complete", { password, confirmPassword, }); setToken(""); setPassword(""); setConfirmPassword(""); setStage("success"); }); } function returnToLogin() { setStage("login"); setError(""); setToken(""); setPassword(""); setConfirmPassword(""); } if (setupRequired === null) { return (
); } const title = stage === "token" ? "Enter reset token" : stage === "password" ? "Choose a new password" : stage === "success" ? "Password updated" : setupRequired ? "Welcome to Study Desk" : "Welcome back"; const description = stage === "token" ? "Find the token in the Study Desk Docker logs. It expires after 15 minutes." : stage === "password" ? "Use at least eight characters and enter the password twice." : stage === "success" ? "Your old password and existing sessions are no longer valid." : setupRequired ? "Please set your admin password for the first time." : "Open your notes, decks, and practice quizzes."; return (
S

Personal workspace

{title}

{description}

{stage === "login" && (
{setupRequired ? "Save Password & Login" : "Sign In"} {!setupRequired && ( )} )} {stage === "token" && (
setToken(event.target.value)} autoComplete="one-time-code" autoFocus className="min-h-12 w-full rounded-xl border border-border bg-bg-surface-alt/60 px-4 font-mono text-text-heading placeholder:text-text-muted focus:border-primary" placeholder="Paste the token from Docker logs" />
Validate Token Back to sign in )} {stage === "password" && (
= 8} validText="Password meets the length requirement." invalidText="Password must be at least 8 characters." />
0 && confirmPassword !== password} /> {confirmPassword && ( )}
Set New Password Cancel )} {stage === "success" && (
Your password has been reset successfully.
Sign In
)}
); } function PasswordField({ id, label, value, onChange, placeholder, describedBy, invalid = false, autoFocus = false, }: { id: string; label: string; value: string; onChange: (value: string) => void; placeholder?: string; describedBy?: string; invalid?: boolean; autoFocus?: boolean; }) { return (
onChange(event.target.value)} placeholder={placeholder} autoComplete={id === "password" ? "current-password" : "new-password"} aria-describedby={describedBy} aria-invalid={invalid} autoFocus={autoFocus} className="min-h-12 w-full rounded-xl border border-border bg-bg-surface-alt/60 px-4 text-text-heading placeholder:text-text-muted focus:border-primary" />
); } function ValidationMessage({ id, valid, validText, invalidText }: { id: string; valid: boolean; validText: string; invalidText: string; }) { return (

{valid ? validText : invalidText}

); } function ErrorMessage({ message }: { message: string }) { if (!message) return null; return
{message}
; } function PrimaryButton({ loading, disabled = false, onClick, children }: { loading: boolean; disabled?: boolean; onClick?: () => void; children: React.ReactNode; }) { return ( ); } function SecondaryButton({ onClick, children }: { onClick: () => void; children: React.ReactNode }) { return ( ); }