import { useEffect, useRef, useState } from "react"; import type { FormEvent } from "react"; import { Eye, EyeOff, Info, LockKeyhole, UserRound } from "lucide-react"; import { api, getRemember, setRemember as persistRemember } from "../api"; import type { Team, User } from "../types"; import type { AuthMode } from "./route-config"; type LoginProgress = "checking" | "entering"; const LOGIN_PROGRESS_COPY: Record = { checking: "正在验证用户名和密码…", entering: "登录成功,正在进入 Airshelf" }; type FieldErrors = { username?: string; password?: string; }; export function AuthScreen({ initialMode: _initialMode, onModeChange: _onModeChange, onAuthed }: { initialMode: AuthMode; onModeChange: (mode: AuthMode) => void; onAuthed: (payload: { token: string; user: User; team: Team; remember?: boolean }) => void | Promise; }) { const remembered = getRemember(); const [username, setUsername] = useState(remembered?.username || ""); const [password, setPassword] = useState(""); const [remember, setRemember] = useState(Boolean(remembered)); const [showPwd, setShowPwd] = useState(false); const [fieldErrors, setFieldErrors] = useState({}); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); const [toast, setToast] = useState<{ title: string; sub: string } | null>(null); const [loginProgress, setLoginProgress] = useState(null); const toastTimer = useRef | null>(null); useEffect(() => { const path = window.location.pathname.replace(/\/+$/, "").toLowerCase(); if (path === "/register" || window.location.hash === "#register") { window.history.replaceState(null, "", "/login"); } }, []); useEffect(() => () => { if (toastTimer.current) clearTimeout(toastTimer.current); }, []); function showToast(title: string, sub: string) { setToast({ title, sub }); if (toastTimer.current) clearTimeout(toastTimer.current); toastTimer.current = setTimeout(() => setToast(null), 2800); } function setFieldValue(field: keyof FieldErrors, value: string) { if (field === "username") setUsername(value); if (field === "password") setPassword(value); setError(""); setLoginProgress(null); setFieldErrors((current) => ({ ...current, [field]: undefined })); } function validateLogin() { const next: FieldErrors = {}; if (!username.trim()) next.username = "请输入用户名"; if (!password) next.password = "请输入密码"; setFieldErrors(next); if (Object.keys(next).length > 0) { setError("请补全必填字段后再登录"); return false; } return true; } async function submit(event?: FormEvent) { event?.preventDefault(); if (busy) return; if (!validateLogin()) return; setBusy(true); setError(""); setLoginProgress("checking"); try { const payload = await api.login({ username: username.trim(), password }); // 记住我:勾选则存用户名 + 7 天有效期,token 落 localStorage;否则只存当前会话 persistRemember(username.trim(), remember); // 凭证验过即进:不再做任何人为延迟动画。"进入"态由真实的 onAuthed(身份+数据水合)驱动。 setLoginProgress("entering"); await onAuthed({ ...payload, remember }); } catch (err) { setLoginProgress(null); const raw = err instanceof Error ? err.message : ""; const friendly = /invalid credentials|unable to log in|non_field_errors/i.test(raw) ? "用户名或密码不正确" : raw || "登录失败,请稍后重试"; setError(friendly); } finally { setBusy(false); } } return (

登录

// LOGIN
{loginProgress && !error && (
{LOGIN_PROGRESS_COPY[loginProgress]}
)} {error &&
{error}
}
{toast && (
{toast.title}// {toast.sub}
)}
); }