"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { API_BASE, apiFetch } from "@/lib/api"; import { readToken, saveToken } from "@/lib/auth"; import { useI18nText } from "@/lib/i18n"; type AuthOk = { ok: true; user_id: number; token: string; expires_at: number }; type AuthErr = { ok: false; error: string }; type AuthResp = AuthOk | AuthErr; function passwordScore(password: string, isZh: boolean): { label: string; color: string } { if (password.length >= 12) return { label: isZh ? "强" : "Strong", color: "text-emerald-600" }; if (password.length >= 8) return { label: isZh ? "中" : "Medium", color: "text-blue-600" }; return { label: isZh ? "弱" : "Weak", color: "text-orange-600" }; } export default function AuthPage() { const { isZh, tx } = useI18nText(); const router = useRouter(); const apiBase = useMemo(() => API_BASE, []); const [checkingAuth, setCheckingAuth] = useState(true); const [mode, setMode] = useState<"register" | "login">("login"); const [username, setUsername] = useState(process.env.NEXT_PUBLIC_TEST_USERNAME ?? ""); const [password, setPassword] = useState(process.env.NEXT_PUBLIC_TEST_PASSWORD ?? ""); const [confirmPassword, setConfirmPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [loading, setLoading] = useState(false); const [resp, setResp] = useState(null); useEffect(() => { if (readToken()) { router.replace("/problems"); return; } setCheckingAuth(false); }, [router]); const usernameErr = username.trim().length < 3 ? tx("用户名至少 3 位", "Username must be at least 3 chars") : ""; const passwordErr = password.length < 6 ? tx("密码至少 6 位", "Password must be at least 6 chars") : ""; const confirmErr = mode === "register" && password !== confirmPassword ? tx("两次密码不一致", "Passwords do not match") : ""; const canSubmit = !loading && !usernameErr && !passwordErr && !confirmErr; async function submit() { if (!canSubmit) return; setLoading(true); setResp(null); try { const j = await apiFetch(`/api/v1/auth/${mode}`, { method: "POST", body: JSON.stringify({ username: username.trim(), password }), }); setResp(j); if (j.ok) { saveToken(j.token); setTimeout(() => { router.replace("/problems"); }, 350); } } catch (e: unknown) { setResp({ ok: false, error: String(e) }); } finally { setLoading(false); } } const strength = passwordScore(password, isZh); if (checkingAuth) { return (
{tx("已登录,正在跳转...", "Already signed in, redirecting...")}
); } return (

{tx("欢迎回来,开始刷题", "Welcome back, let's practice")}

{tx("登录后可提交评测、保存草稿、查看错题本和个人进度。", "After sign-in you can submit, save drafts, review wrong-book, and track your progress.")}

{tx("• 题库按 CSP-J / CSP-S / NOIP 入门组织", "• Problem sets are organized by CSP-J / CSP-S / NOIP junior")}

{tx("• 题目页支持本地草稿与试运行", "• Problem page supports local draft and run")}

{tx("• 生成式题解会异步入库,支持多解法", "• Generated solutions are queued asynchronously with multiple methods")}

API Base: {apiBase}

setUsername(e.target.value)} placeholder={tx("例如:csp_student", "e.g. csp_student")} /> {usernameErr &&

{usernameErr}

}
{tx("强度", "Strength")}: {strength.label}
setPassword(e.target.value)} placeholder={tx("至少 6 位", "At least 6 chars")} /> {passwordErr &&

{passwordErr}

}
{mode === "register" && (
setConfirmPassword(e.target.value)} placeholder={tx("再输入一次密码", "Enter password again")} /> {confirmErr &&

{confirmErr}

}
)}
{resp && (
{resp.ok ? tx("登录成功,正在跳转到题库...", "Signed in. Redirecting to problem set...") : `${tx("操作失败:", "Action failed: ")}${resp.error}`}
)}

{tx("登录后 Token 自动保存在浏览器 localStorage,可直接前往", "Token is stored in browser localStorage after sign-in. You can go to")} {tx("题库", "Problems")} {tx("与", "and")} {tx("我的", "My Account")} {tx("页面。", ".")}

); }