"use client" import type React from "react" import { useEffect, useRef, useState } from "react" import { useRouter } from "next/navigation" import { Mail, Lock, KeyRound, ArrowLeft, CheckCircle2, Eye, EyeOff, Flag, } from "lucide-react" import { TwoFactorSetupForm, TwoFactorVerifyForm } from "@/components/two-factor-flow" // Locked "Forest Green" palette (V0-redesign 2026-08-01, basert på Stitch -- // se DESIGN_SYSTEM.md sitt notat om denne runden). Anvendt inline med vilje, // slik at ingenting tolkes om ved en senere V0-reeksport. const C = { card: "rgba(255, 255, 255, 0.72)", ink: "#012c11", // primærtekst + primærknapp-bakgrunn inkOn: "#ffffff", accent: "#1a4325", // mørkegrønne aksenter (header-gradient) accentMuted: "#bfeec4", // dempet tekst PÅ mørkegrønn -- ALDRI #84b089/#a4a5a5 her (målt ~4.5:1, under vårt AAA-mål) successBg: "#91f78e", successInk: "#00731e", muted: "#424941", // dempet brødtekst på hvit bakgrunn border: "#c1c9bf", fieldBg: "#ffffff", } as const const RESEND_COOLDOWN = 30 // seconds type SessionUser = { id: string email: string display_name: string preferred_locale: string } type LoginResult = { status: "success" | "2fa_required" | "2fa_setup_required" user?: SessionUser two_factor_method?: "totp" | "email" } // ADR-021 Beslutning E: primær-autentisering (magic-link ELLER passord) kan // returnere tre ulike utfall -- delt mellom denne komponenten og // verify-form.tsx sin håndtering av magic-link-svaret. type PostAuthMode = "2fa-verify" | "2fa-setup" | null type Mode = "default" | "sent" | "password" | "join" | "verify" function isValidEmail(value: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim()) } export function LoginForm() { const router = useRouter() const [mode, setMode] = useState("default") const [postAuth, setPostAuth] = useState(null) const [twoFactorMethod, setTwoFactorMethod] = useState<"totp" | "email" | null>(null) const [email, setEmail] = useState("") const [emailTouched, setEmailTouched] = useState(false) const [sending, setSending] = useState(false) const [cooldown, setCooldown] = useState(0) const [error, setError] = useState(null) const emailValid = isValidEmail(email) const emailError = emailTouched && email.length > 0 && !emailValid function handleLoginResult(result: LoginResult) { if (result.status === "success") { router.replace("/dashboard") } else if (result.status === "2fa_required") { setTwoFactorMethod(result.two_factor_method ?? "totp") setPostAuth("2fa-verify") } else { setPostAuth("2fa-setup") } } function handleTwoFactorSuccess() { router.replace("/dashboard") } // Countdown timer for the resend cooldown. useEffect(() => { if (cooldown <= 0) return const id = setInterval(() => setCooldown((c) => (c <= 1 ? 0 : c - 1)), 1000) return () => clearInterval(id) }, [cooldown]) async function sendLink() { setSending(true) setError(null) try { // Alltid samme suksess-respons uansett om e-posten finnes (anti- // enumerering, se ADR-009) -- kun nettverks-/serverfeil havner i catch. const res = await fetch("/auth/request-link", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ email: email.trim(), locale: "nb" }), }) if (!res.ok) throw new Error(`request-link: ${res.status}`) setMode("sent") setCooldown(RESEND_COOLDOWN) } catch { setError("Klarte ikke å sende lenken. Sjekk tilkoblingen og prøv igjen.") } finally { setSending(false) } } function submitDefault(e: React.FormEvent) { e.preventDefault() setEmailTouched(true) if (!emailValid || sending) return void sendLink() } function resend() { if (cooldown > 0 || sending) return void sendLink() } function backToDefault() { setMode("default") setEmailTouched(false) setError(null) } return (
{/* Soft green glow behind the card so the glass blur has something to work against */}