/** * Login page. * * A single password field and submit button. On success the user is * redirected to the originally requested page (via the `?next=` query * parameter) or the dashboard. */ import { useState } from "react"; import { Button, Field, Input, makeStyles, MessageBar, MessageBarBody, Spinner, Text, tokens, } from "@fluentui/react-components"; import { useNavigate, useSearchParams } from "react-router-dom"; import type { ChangeEvent, FormEvent } from "react"; import { ApiError } from "../api/client"; import { useAuth } from "../providers/AuthProvider"; // --------------------------------------------------------------------------- // Styles // --------------------------------------------------------------------------- const useStyles = makeStyles({ root: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "100vh", backgroundColor: tokens.colorNeutralBackground2, padding: tokens.spacingHorizontalM, }, card: { width: "100%", maxWidth: "360px", backgroundColor: tokens.colorNeutralBackground1, borderRadius: tokens.borderRadiusXLarge, padding: tokens.spacingVerticalXXL, boxShadow: tokens.shadow8, }, heading: { marginBottom: tokens.spacingVerticalXS, display: "block", }, subtitle: { marginBottom: tokens.spacingVerticalXXL, color: tokens.colorNeutralForeground2, display: "block", }, field: { marginBottom: tokens.spacingVerticalM, }, submitRow: { marginTop: tokens.spacingVerticalL, }, error: { marginBottom: tokens.spacingVerticalM, }, }); // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- /** * Login page — single password input, no username. */ export function LoginPage(): React.JSX.Element { const styles = useStyles(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { login } = useAuth(); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [submitting, setSubmitting] = useState(false); const nextPath = searchParams.get("next") ?? "/"; function handlePasswordChange(ev: ChangeEvent): void { setPassword(ev.target.value); setError(null); } async function handleSubmit(ev: FormEvent): Promise { ev.preventDefault(); if (!password) { setError("Please enter a password."); return; } setSubmitting(true); setError(null); try { await login(password); navigate(nextPath, { replace: true }); } catch (err) { if (err instanceof ApiError && err.status === 401) { setError("Incorrect password. Please try again."); } else { setError("An unexpected error occurred. Please try again."); } } finally { setSubmitting(false); } } return (
BanGUI Enter your master password to continue. {error !== null && ( {error} )}
void handleSubmit(ev)}>
); }