import { useState } from "react"; import { Alert, Button, Checkbox, Form } from "tdesign-react"; import { useAppDispatch, useAppSelector, clearSessionError, loginThunk } from "../../store"; import type { Admin } from "../../types/auth"; import { LoginUserIcon, StarterPasswordInput } from "./StarterAuth"; import AccessibleInput from "../../components/AccessibleInput"; const REMEMBERED_USERNAME_KEY = "tallynote.login.username"; export interface LoginPageProps { notice?: string; onSuccess?: (admin: Admin) => void; } function getRememberedUsername(): string { if (typeof window === "undefined") return ""; try { return window.localStorage.getItem(REMEMBERED_USERNAME_KEY) ?? ""; } catch { return ""; } } export default function LoginPage({ notice, onSuccess }: LoginPageProps) { const dispatch = useAppDispatch(); const { status, error } = useAppSelector((state) => state.session); const [username, setUsername] = useState(getRememberedUsername); const [password, setPassword] = useState(""); const [rememberAccount, setRememberAccount] = useState(Boolean(username)); const [fieldErrors, setFieldErrors] = useState<{ username?: string; password?: string }>({}); const busy = status === "loading"; const updateUsername = (value: string) => { setUsername(value); setFieldErrors(current => ({ ...current, username: undefined })); if (error) dispatch(clearSessionError()); }; const updatePassword = (value: string) => { setPassword(value); setFieldErrors(current => ({ ...current, password: undefined })); if (error) dispatch(clearSessionError()); }; const submit = async () => { const normalizedUsername = username.trim(); const nextErrors: { username?: string; password?: string } = {}; if (!normalizedUsername) nextErrors.username = "请输入用户名"; if (!password) nextErrors.password = "请输入密码"; if (Object.keys(nextErrors).length) { setFieldErrors(nextErrors); const focusInvalid = () => document.querySelector("[aria-invalid='true'], [aria-invalid='true'] input, [aria-invalid='true'] textarea")?.focus(); requestAnimationFrame(focusInvalid); window.setTimeout(focusInvalid, 60); return; } setFieldErrors({}); try { if (rememberAccount) { window.localStorage.setItem(REMEMBERED_USERNAME_KEY, normalizedUsername); } else { window.localStorage.removeItem(REMEMBERED_USERNAME_KEY); } } catch { // Private browsing or a locked-down browser may reject localStorage. } try { const admin = await dispatch(loginThunk({ username: normalizedUsername, password })).unwrap(); onSuccess?.(admin); } catch { // The rejected thunk exposes its server message through session.error. } }; return (

登录 TallyNote 工作台

{ context.e?.preventDefault(); void submit(); }} > {notice &&
} {error &&
} updateUsername(String(value))} clearable disabled={busy} autocomplete="username" autofocus prefixIcon={} placeholder="请输入用户名" /> {fieldErrors.username && {fieldErrors.username}} {fieldErrors.password && {fieldErrors.password}}
记住账号
); }