"use client"; import React, { useState } from "react"; import { Mail, Lock, AlertCircle } from "lucide-react"; import { useTranslations } from "../../../../i18n/provider"; import { Button } from "../../../../components/ui/Button"; import { Logo } from "../../../../components/Logo"; import { ThemeToggle } from "../../../../components/ThemeToggle"; export default function UserLoginPage() { const t = useTranslations(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); /** * One step, because the mailbox is one step. * * Reading your own mail is behind your own password; the second factor guards * the console, and is collected on the way in there. This screen used to have * a code step as well, so an account that had enrolled a factor could not open * its inbox without its phone. */ const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setLoading(true); try { const res = await fetch("/api/v1/auth/user/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); const data = await res.json(); if (!res.ok) throw new Error(data.message || t("auth.invalidCredentials")); // The destination the middleware attached when it sent an unauthenticated // visitor here, so a deep link survives the sign-in. // A path on this origin only: "//evil.test" also begins with a slash, and // following it would turn the sign-in into an open redirect. const next = new URLSearchParams(window.location.search).get("next"); const safe = next && next.startsWith("/") && !next.startsWith("//"); window.location.href = safe ? next : "/user/mail/inbox"; } catch (err: unknown) { setError(err instanceof Error ? err.message : t("auth.genericError")); } finally { setLoading(false); } }; const field = "w-full min-h-[38px] rounded-xl bg-dark-card py-2.5 pl-[38px] pr-3 text-[13.5px] text-slate-100 placeholder-slate-500 shadow-edge transition-shadow focus:outline-none focus-visible:shadow-edge-accent"; return ( /* No ambient glow behind the card any more: two blurred coloured circles under a sign-in form are decoration that costs a full-viewport paint and says nothing. Nocturne's depth is the surface and its edge. */

{t("auth.userLoginTitle")}

{t("common.appName")} · {t("common.userPortal")}

{error && (
{error}
)}
setEmail(e.target.value)} placeholder="user@domain.com" required className={field} />
setPassword(e.target.value)} placeholder="************" required className={field} />
); }