digital-human-monitor-v2/components/account-auth-form.tsx
2026-07-11 23:37:48 +08:00

179 lines
5.2 KiB
TypeScript

"use client";
import { useRouter } from "next/navigation";
import { startTransition, useState } from "react";
import { useNavigationProgress } from "@/components/navigation-progress";
type AccountAuthFormProps = {
redirectTo: string;
};
type AuthMode = "login" | "register";
export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
const router = useRouter();
const { beginNavigationProgress } = useNavigationProgress();
const [mode, setMode] = useState<AuthMode>("login");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [submitting, setSubmitting] = useState(false);
const [notice, setNotice] = useState<string | null>(null);
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!username.trim()) {
setNotice("请先输入用户名。");
return;
}
if (!password) {
setNotice("请先输入密码。");
return;
}
if (mode === "register" && password !== confirmPassword) {
setNotice("两次输入的密码还不一致,请再检查一次。");
return;
}
setSubmitting(true);
setNotice(mode === "login" ? "正在登录账号……" : "正在创建账号……");
try {
const response = await fetch(
mode === "login" ? "/api/account/login" : "/api/account/register",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username.trim(),
password,
redirectTo,
}),
},
);
const payload = (await response.json().catch(() => null)) as
| { error?: string; redirectTo?: string }
| null;
if (!response.ok) {
throw new Error(
payload?.error ||
(mode === "login"
? "登录失败,请稍后再试。"
: "创建账号失败,请稍后再试。"),
);
}
setNotice(mode === "login" ? "登录成功,正在进入……" : "账号已创建,正在进入……");
startTransition(() => {
beginNavigationProgress();
router.replace(payload?.redirectTo || redirectTo);
router.refresh();
});
} catch (error) {
setNotice(
error instanceof Error ? error.message : "操作失败,请稍后再试。",
);
} finally {
setSubmitting(false);
}
}
return (
<div>
<div className="segment">
{[
{ id: "login", label: "已有账号,直接登录" },
{ id: "register", label: "第一次使用,创建账号" },
].map((item) => {
const active = mode === item.id;
return (
<button
key={item.id}
type="button"
onClick={() => {
setMode(item.id as AuthMode);
setNotice(null);
}}
className={active ? "active" : ""}
>
{item.label}
</button>
);
})}
</div>
<form style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: 12 }} onSubmit={handleSubmit}>
<div>
<label className="sr-only" htmlFor="caregiver-username"></label>
<input
id="caregiver-username"
value={username}
onChange={(event) => setUsername(event.target.value)}
autoComplete="username"
placeholder="用户名"
className="field"
/>
</div>
<div>
<label className="sr-only" htmlFor="caregiver-password"></label>
<input
id="caregiver-password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
autoComplete={mode === "login" ? "current-password" : "new-password"}
placeholder="请输入密码"
className="field"
/>
</div>
{mode === "register" ? (
<div>
<label className="sr-only" htmlFor="caregiver-password-confirm"></label>
<input
id="caregiver-password-confirm"
type="password"
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
autoComplete="new-password"
placeholder="再次输入密码"
className="field"
/>
</div>
) : null}
<button
type="submit"
disabled={submitting}
className="btn primary block"
style={{ minHeight: 44 }}
>
{submitting
? mode === "login"
? "正在登录"
: "正在创建账号"
: mode === "login"
? "登录并继续"
: "创建账号并继续"}
</button>
</form>
{notice ? (
<p style={{ margin: "10px 0 0", borderRadius: 8, background: "var(--surface)", padding: "10px 12px", color: "var(--muted)", fontSize: 12, lineHeight: 1.6 }}>
{notice}
</p>
) : null}
</div>
);
}