// ============================================================ // LoginModal — 登录 / 注册模态框 // 职责:在 LandingPage 上弹出的认证表单,复用现有 auth 系统 // ============================================================ import { useCallback, useEffect, useRef, useState } from "react"; import { useAuth } from "../../lib/auth"; import { useI18n } from "../../lib/i18n"; type AuthMode = "login" | "register"; interface LoginModalProps { initialMode?: AuthMode; onClose: () => void; } export function LoginModal({ initialMode = "login", onClose }: LoginModalProps) { const { login, register } = useAuth(); const { t } = useI18n(); const [mode, setMode] = useState(initialMode); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const overlayRef = useRef(null); // 点击遮罩关闭 const handleOverlayClick = useCallback( (e: React.MouseEvent) => { if (e.target === overlayRef.current) onClose(); }, [onClose] ); // ESC 关闭 useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [onClose]); // 阻止 body 滚动 useEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, []); const handleSubmit = useCallback( async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (username.length < 3 || username.length > 64) { setError(t("auth.error.usernameLength")); return; } if (password.length < 8 || password.length > 72) { setError(t("auth.error.passwordLength")); return; } setIsSubmitting(true); const fn = mode === "login" ? login : register; const result = await fn(username, password); setIsSubmitting(false); if (result.error) { setError(result.error); } // 登录成功时 auth 状态更新,App 自动切到主界面,modal 自然消失 }, [username, password, mode, login, register, t] ); const switchMode = useCallback(() => { setMode((m) => (m === "login" ? "register" : "login")); setError(""); }, []); return (
CamTalk
{t("auth.subtitle")}
{error &&
{error}
}
{mode === "login" ? t("auth.noAccount") : t("auth.hasAccount")}
); }