"use client"; import Link from "next/link"; import { FormEvent, useEffect, useState } from "react"; import { PreviewNotice } from "@/components/ui"; const apiBase = process.env.NEXT_PUBLIC_API_BASE_URL ?? "/api/v1"; export function AuthForm({ mode }: { mode: "login" | "register" }) { const isLogin = mode === "login"; const [csrf, setCsrf] = useState(""); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [channel, setChannel] = useState<"email" | "phone">("email"); const [password, setPassword] = useState(""); const [confirmation, setConfirmation] = useState(""); const [verificationId, setVerificationId] = useState(""); const [verificationCode, setVerificationCode] = useState(""); const [message, setMessage] = useState(""); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); useEffect(() => { fetch(`${apiBase}/auth/csrf`, { credentials: "include" }) .then((response) => response.json()) .then((body) => setCsrf(body.csrf_token ?? "")) .catch(() => setError("暂时无法连接认证服务")); }, []); async function submit(event: FormEvent) { event.preventDefault(); setError(""); setMessage(""); if (!isLogin && password !== confirmation) { setError("两次输入的密码不一致"); return; } setBusy(true); try { const response = await fetch(`${apiBase}/auth/${isLogin ? "login" : "register"}`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", ...(csrf ? { "X-CSRF-Token": csrf } : {}) }, body: JSON.stringify(isLogin ? { identifier: email, password } : { email: channel === "email" ? email : email || null, phone: phone || null, verification_channel: channel, password }), }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new Error(body.error?.message ?? "请求失败,请稍后重试"); if (isLogin) { window.location.assign("/account"); } else { setVerificationId(body.challenge_id ?? ""); setMessage(`注册成功,验证码已发送到你的${channel === "email" ? "邮箱" : "手机"}`); } } catch (submitError) { setError(submitError instanceof Error ? submitError.message : "请求失败,请稍后重试"); } finally { setBusy(false); } } async function confirmVerification(event: FormEvent) { event.preventDefault(); setError(""); setBusy(true); try { const response = await fetch(`${apiBase}/auth/verification/confirm-registration`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", ...(csrf ? { "X-CSRF-Token": csrf } : {}) }, body: JSON.stringify({ challenge_id: verificationId, code: verificationCode }), }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new Error(body.error?.message ?? "验证码错误,请重试"); setMessage("验证成功,请登录"); window.setTimeout(() => window.location.assign("/login"), 500); } catch (confirmError) { setError(confirmError instanceof Error ? confirmError.message : "验证失败,请重试"); } finally { setBusy(false); } } async function resendVerification() { setError(""); setBusy(true); try { const response = await fetch(`${apiBase}/auth/verification/resend`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ challenge_id: verificationId }), }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new Error(body.error?.message ?? "验证码发送失败,请稍后重试"); setMessage("新的验证码已发送"); } catch (resendError) { setError(resendError instanceof Error ? resendError.message : "验证码发送失败,请稍后重试"); } finally { setBusy(false); } } return (
支持邮箱或手机二选一验证。验证码只由服务端发送和校验。 {verificationId ?
setVerificationCode(event.target.value)} placeholder="输入 6 位验证码" required value={verificationCode} />
{error ?

{error}

: null}
:
{isLogin ?
setEmail(event.target.value)} placeholder="name@example.com / 13800138000" required value={email} />
: <>
setEmail(event.target.value)} placeholder="name@example.com" required={channel === "email"} type="email" value={email} />
setPhone(event.target.value)} placeholder="中国大陆手机号" required={channel === "phone"} type="tel" value={phone} />
}
setPassword(event.target.value)} placeholder="至少 8 位字符" required type="password" value={password} />
{!isLogin ?
setConfirmation(event.target.value)} placeholder="再次输入密码" required type="password" value={confirmation} />
: null} {error ?

{error}

: null} {message ?

{message}

: null}
}

{isLogin ? "还没有账户?" : "已有账户?"}{" "}{isLogin ? "去注册" : "去登录"}

); }