import { type FormEvent, useEffect, useState, type ReactNode } from "react"; import { App, Button, Divider, Input } from "antd"; import { ArrowRight, Info, LockKeyhole, Mail, ShieldCheck, TriangleAlert, UserRound } from "lucide-react"; import { useNavigate, useSearchParams } from "react-router"; import { applyUserSession } from "@/lib/user-session"; import { getAuthSession, getAuthSettings, linuxDOLoginURL, register, sendRegistrationEmailCode } from "@/services/api/auth"; import { LinuxDOIcon } from "./auth-scene"; type AuthSettings = Awaited>; export default function RegisterPage() { const navigate = useNavigate(); const [params] = useSearchParams(); const { message } = App.useApp(); const [settings, setSettings] = useState(null); const [username, setUsername] = useState(""); const [email, setEmail] = useState(""); const [emailCode, setEmailCode] = useState(""); const [displayName, setDisplayName] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [submitting, setSubmitting] = useState(false); const [sendingCode, setSendingCode] = useState(false); const [countdown, setCountdown] = useState(0); const next = safeNext(params.get("next")); useEffect(() => { let cancelled = false; void getAuthSettings().then((value) => !cancelled && setSettings(value)).catch((error) => !cancelled && message.error(error instanceof Error ? error.message : "读取注册设置失败")); return () => { cancelled = true; }; }, [message]); useEffect(() => { if (countdown <= 0) return; const timer = window.setInterval(() => setCountdown((value) => Math.max(0, value - 1)), 1000); return () => window.clearInterval(timer); }, [countdown]); const sendCode = async () => { if (!email.trim()) { message.warning("请先输入邮箱"); return; } setSendingCode(true); try { await sendRegistrationEmailCode(email.trim()); setCountdown(60); message.success("验证码已发送,请检查邮箱"); } catch (error) { message.error(error instanceof Error ? error.message : "发送验证码失败"); } finally { setSendingCode(false); } }; const submit = async (event: FormEvent) => { event.preventDefault(); if (password !== confirmPassword) { message.error("两次输入的密码不一致"); return; } setSubmitting(true); try { await register({ username, email, emailCode, displayName, password }); await applyUserSession(await getAuthSession()); if (!settings?.firstUser) window.sessionStorage.setItem("infinite-canvas:model-setup-guide", "1"); message.success(settings?.firstUser ? "管理员账号已创建" : "注册成功"); navigate(next, { replace: true }); } catch (error) { message.error(error instanceof Error ? error.message : "注册失败"); } finally { setSubmitting(false); } }; const registrationClosed = settings?.registrationEnabled === false; const mailUnavailable = Boolean(settings && !settings.firstUser && settings.emailCodeRequired && !settings.emailEnabled); const disabled = registrationClosed || mailUnavailable; const requireCode = Boolean(settings && !settings.firstUser && settings.emailCodeRequired); return (
{settings?.firstUser ? } tone="blue">首个账号自动成为管理员,邮箱验证码暂不要求。 : null} {registrationClosed ? } tone="amber">当前已关闭普通注册,请联系管理员创建账号。 : null} {mailUnavailable ? } tone="amber">管理员尚未配置注册邮件,普通邮箱注册暂不可用。 : null}
} value={username} onChange={(event) => setUsername(event.target.value)} placeholder="3-32 位字符" autoComplete="username" required disabled={disabled} /> setDisplayName(event.target.value)} placeholder="不填则使用用户名" disabled={disabled} />
} value={email} onChange={(event) => setEmail(event.target.value)} placeholder="用于登录与安全验证" autoComplete="email" required={!settings?.firstUser} disabled={disabled} /> {requireCode ? (
} value={emailCode} onChange={(event) => setEmailCode(event.target.value.replace(/\D/g, "").slice(0, 6))} placeholder="6 位验证码" inputMode="numeric" autoComplete="one-time-code" required disabled={disabled} />
) : null}
} value={password} onChange={(event) => setPassword(event.target.value)} placeholder="至少 8 位" autoComplete="new-password" required disabled={disabled} /> } value={confirmPassword} onChange={(event) => setConfirmPassword(event.target.value)} placeholder="再次输入密码" autoComplete="new-password" required disabled={disabled} />
{settings?.linuxdoEnabled ? <>或 : null}
); } function AuthField({ label, children }: { label: string; children: ReactNode }) { return ; } function Notice({ icon, tone, children }: { icon: ReactNode; tone: "blue" | "amber"; children: ReactNode }) { return
{icon}{children}
; } function safeNext(value: string | null) { if (!value || !value.startsWith("/") || value.startsWith("//")) return "/projects"; return value; }