digital-human-monitor-v2/components/account-auth-form.tsx
2026-04-17 12:58:38 +08:00

189 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useRouter } from "next/navigation";
import { startTransition, useState } from "react";
type AccountAuthFormProps = {
redirectTo: string;
};
type AuthMode = "login" | "register";
export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
const router = useRouter();
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(() => {
router.replace(payload?.redirectTo || redirectTo);
router.refresh();
});
} catch (error) {
setNotice(
error instanceof Error ? error.message : "操作失败,请稍后再试。",
);
} finally {
setSubmitting(false);
}
}
return (
<div className="rounded-[32px] border border-white/75 bg-white/84 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)] backdrop-blur sm:p-7">
<div className="flex flex-wrap gap-3">
{[
{ 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={`inline-flex h-11 items-center justify-center rounded-full px-4 text-sm font-semibold transition ${
active
? "bg-[var(--ink)] text-white shadow-[0_12px_30px_rgba(36,27,20,0.18)]"
: "border border-[var(--line)] bg-white text-[var(--ink)] hover:bg-[var(--paper-soft)]"
}`}
>
{item.label}
</button>
);
})}
</div>
<form className="mt-6 space-y-4" onSubmit={handleSubmit}>
<div>
<label className="text-sm font-semibold text-[var(--ink)]" htmlFor="caregiver-username">
</label>
<input
id="caregiver-username"
value={username}
onChange={(event) => setUsername(event.target.value)}
autoComplete="username"
placeholder="例如family_guardian"
className="mt-2 h-13 w-full rounded-[22px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
/>
<p className="mt-2 text-xs leading-6 text-[var(--muted)]">
使 3 24 线线便
</p>
</div>
<div>
<label className="text-sm font-semibold text-[var(--ink)]" 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="mt-2 h-13 w-full rounded-[22px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
/>
<p className="mt-2 text-xs leading-6 text-[var(--muted)]">
8
</p>
</div>
{mode === "register" ? (
<div>
<label className="text-sm font-semibold text-[var(--ink)]" 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="mt-2 h-13 w-full rounded-[22px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
/>
</div>
) : null}
<button
type="submit"
disabled={submitting}
className="inline-flex h-12 w-full items-center justify-center rounded-full bg-[var(--copper)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--copper-deep)] disabled:cursor-not-allowed disabled:opacity-60"
>
{submitting
? mode === "login"
? "正在登录"
: "正在创建账号"
: mode === "login"
? "登录并继续"
: "创建账号并继续"}
</button>
</form>
{notice ? (
<p className="mt-4 rounded-[20px] bg-[var(--paper-soft)] px-4 py-3 text-sm leading-7 text-[var(--muted)]">
{notice}
</p>
) : null}
</div>
);
}