import { useEffect, useState, type FormEvent } from "react"; import { useRouter, useSearch } from "@tanstack/react-router"; import * as stylex from "@stylexjs/stylex"; import { ApiError } from "@/lib/api"; import { useAuth } from "@/auth/store"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; const styles = stylex.create({ /** Login renders outside AppShell, so it paints the page ground itself. */ page: { display: "flex", minHeight: "100dvh", alignItems: "center", justifyContent: "center", backgroundColor: colors.surface, color: colors.text, padding: "1rem", }, card: { width: "100%", maxWidth: "24rem", }, heading: { fontSize: "1.5rem", lineHeight: "2rem", fontWeight: 600, }, probing: { marginTop: "1rem", color: colors.textMuted, }, form: { display: "flex", flexDirection: "column", gap: "1rem", marginTop: "1.5rem", }, fieldLabel: { display: "block", fontSize: "0.875rem", lineHeight: "1.25rem", fontWeight: 500, }, submit: { width: "100%", }, error: { marginTop: "1rem", fontSize: "0.875rem", lineHeight: "1.25rem", color: colors.danger, }, }); export function safeRedirect(raw: string | undefined): string { if (raw === undefined) return "/"; if (!/^\/(?![/\\])/.test(raw) || raw.includes("\\")) return "/"; return raw; } function errorMessage(error: unknown, remaining: number | null): string { if (error instanceof ApiError) { if (error.status === 401) return "Incorrect password."; if (error.status === 429) { return remaining !== null && remaining > 0 ? `Too many attempts. Try again in ${remaining}s.` : "Too many attempts. Try again shortly."; } if (error.status === 503) return "The server is starting or degraded. Try again shortly."; return error.message; } return "Could not reach the server."; } export default function LoginPage() { const { authRequired, probe, login } = useAuth(); const router = useRouter(); const search = useSearch({ from: "/login" }); const redirect = safeRedirect(search.redirect); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const retryAfter = error instanceof ApiError && error.status === 429 ? (error.retryAfter ?? null) : null; const [remaining, setRemaining] = useState(null); useEffect(() => { setRemaining(retryAfter); if (retryAfter === null) return; const timer = setInterval(() => setRemaining((s) => (s === null || s <= 1 ? 0 : s - 1)), 1000); return () => clearInterval(timer); }, [error, retryAfter]); const lockedOut = remaining !== null && remaining > 0; useEffect(() => { if (authRequired === false) { router.history.replace(redirect); return; } if (authRequired === null) { probe() .then((required) => { if (!required) router.history.replace(redirect); }) .catch((probeError: unknown) => setError(probeError)); } }, [authRequired, probe, redirect, router]); async function onSubmit(event: FormEvent) { event.preventDefault(); if (busy || lockedOut) return; setBusy(true); setError(null); try { await login(password); router.history.push(redirect); } catch (loginError) { setError(loginError); } finally { setBusy(false); } } return (

nxdns

{authRequired !== true ? (

Checking whether a password is required…

) : (
setPassword(event.target.value)} {...stylex.props(shared.input, shared.focusRing)} />
)} {error !== null && (

{errorMessage(error, remaining)}

)}
); }