// --------------------------------------------------------------------------- // Folder: components/auth // File: LoginForm.jsx // Relative Path: components/auth/LoginForm.jsx // --------------------------------------------------------------------------- "use client"; import React from "react"; import { useRouter } from "next/navigation"; import { login } from "@/lib/frontend/apiClient"; import { sanitizeNext } from "@/lib/frontend/authRedirect"; import { getLoginErrorMessage, getLoginReasonAlert, } from "@/lib/frontend/authMessages"; import { homePath } from "@/lib/frontend/routes"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, } from "@/components/ui/card"; /** * LoginForm (RHL-020) * * Responsibilities: * - Render login UI using shadcn/ui primitives. * - Show a reason banner (session expired / logged out) if present. * - Submit credentials via apiClient.login(). * - On success: redirect to `nextPath` (if safe) or "/" (default). * - On failure: show a safe, user-friendly error message. * * Clean code notes: * - "reason" and "nextPath" parsing/sanitization is centralized in helpers. * - Error message mapping is centralized in lib/frontend/authMessages.js. * * @param {{ reason: string|null, nextPath: string|null }} props */ export default function LoginForm({ reason, nextPath }) { const router = useRouter(); // Controlled inputs: keep state explicit and predictable. const [username, setUsername] = React.useState(""); const [password, setPassword] = React.useState(""); // UX state: disable submit while in-flight. const [isSubmitting, setIsSubmitting] = React.useState(false); // UI-safe error message shown above the form. const [errorMessage, setErrorMessage] = React.useState(""); // Informational banner (optional). const reasonAlert = getLoginReasonAlert(reason); // Defensive: sanitize again on the client (even though server parsing already does it). const safeNext = sanitizeNext(nextPath) || homePath(); async function onSubmit(e) { e.preventDefault(); // Minimal validation to avoid unnecessary network calls. const u = username.trim(); const p = password; if (!u || !p) { setErrorMessage("Please enter username and password."); return; } setIsSubmitting(true); setErrorMessage(""); try { // Backend sets an HTTP-only cookie on success. await login({ username: u, password: p }); // Replace history entry so "Back" does not return to login. router.replace(safeNext); } catch (err) { setErrorMessage(getLoginErrorMessage(err)); setIsSubmitting(false); } } return ( Sign in Enter your credentials to access the delivery note browser. {reasonAlert ? ( {reasonAlert.title} {reasonAlert.description} ) : null} {errorMessage ? ( Login error {errorMessage} ) : null}
setUsername(e.target.value)} disabled={isSubmitting} placeholder="e.g. branchuser" />
setPassword(e.target.value)} disabled={isSubmitting} placeholder="••••••••" />
); }