Files
browser/platform_web/components/AuthView.tsx
T
2026-07-11 14:56:10 +08:00

133 lines
5.3 KiB
TypeScript

import { HeartHandshake, Loader2, MoonStar, Sparkles, WandSparkles } from "lucide-react";
import { type FormEvent, useState } from "react";
import type { SessionState } from "../stores/session";
import { cx } from "../utils/classes";
interface AuthViewProps {
session: SessionState;
}
export function AuthView({ session }: AuthViewProps) {
const isRegister = session.auth.mode === "register";
const [draft, setDraft] = useState({
account: "",
displayName: "",
email: "",
password: "",
phone: "",
qq: ""
});
function updateDraft(key: keyof typeof draft, value: string) {
setDraft((current) => ({ ...current, [key]: value }));
}
async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (isRegister) {
await session.register({
displayName: draft.displayName.trim() || draft.account.trim() || "待审核玩家",
email: draft.email.trim(),
password: draft.password,
phone: draft.phone.trim(),
qq: draft.qq.trim()
});
return;
}
await session.login({ account: draft.account.trim(), password: draft.password });
}
return (
<main className="auth-shell">
<section className="auth-card" aria-label={isRegister ? "注册账号" : "登录账号"}>
<div className="auth-brand">
<span className="app-brand-mark">
<WandSparkles size={18} />
</span>
<div>
<p className="page-kicker">Mecha Ops Console</p>
<h1>{isRegister ? "申请进入服务器工作台" : "登录机甲运维工作台"}</h1>
</div>
</div>
<p className="auth-copy">
{isRegister
? "新账号默认进入待审核或服务器范围,不会获得平台管理员权限。"
: "登录后会按你的角色进入默认工作区:平台管理员看概览,服务器用户看服务器列表。"}
</p>
<div className="auth-mode-tabs" role="tablist" aria-label="认证方式">
<button type="button" className={cx(!isRegister && "auth-mode-active")} onClick={() => session.switchAuthMode("login")}>
<MoonStar size={14} />
<span>登录</span>
</button>
<button type="button" className={cx(isRegister && "auth-mode-active")} onClick={() => session.switchAuthMode("register")}>
<HeartHandshake size={14} />
<span>注册</span>
</button>
</div>
<form className="auth-form" onSubmit={submit}>
{isRegister ? (
<>
<label>
<span>昵称</span>
<input value={draft.displayName} required onChange={(event) => updateDraft("displayName", event.target.value)} />
</label>
<label>
<span>邮箱</span>
<input value={draft.email} type="email" required onChange={(event) => updateDraft("email", event.target.value)} />
</label>
<label>
<span>手机号</span>
<input value={draft.phone} inputMode="tel" onChange={(event) => updateDraft("phone", event.target.value)} />
</label>
<label>
<span>QQ</span>
<input value={draft.qq} inputMode="numeric" onChange={(event) => updateDraft("qq", event.target.value)} />
</label>
</>
) : (
<label>
<span>账号 / 邮箱</span>
<input value={draft.account} required onChange={(event) => updateDraft("account", event.target.value)} />
</label>
)}
<label>
<span>密码</span>
<input value={draft.password} type="password" required minLength={6} onChange={(event) => updateDraft("password", event.target.value)} />
</label>
{session.auth.error && (
<div className="auth-result auth-result-error" role="alert">
<strong>操作失败</strong>
<span>{session.auth.error}</span>
</div>
)}
{session.auth.success && (
<div className="auth-result auth-result-success" role="status">
<strong>已记录</strong>
<span>{session.auth.success}</span>
</div>
)}
{session.authUnavailable && session.localFallbackAvailable && (
<div className="auth-result" role="status">
<strong>本地回退可用</strong>
<span>当前身份 API 不可用,可以进入服务器范围的本地演示工作台进行 UI 验收。</span>
</div>
)}
<button type="submit" className="profile-save-button auth-submit" disabled={session.auth.pending}>
{session.auth.pending ? <Loader2 size={14} className="state-spinner" /> : <Sparkles size={14} />}
<span>{isRegister ? "提交注册" : "登录"}</span>
</button>
{session.authUnavailable && session.localFallbackAvailable && (
<button type="button" className="state-action auth-local-action" onClick={session.continueWithLocalFallback}>
<MoonStar size={14} />
<span>进入本地回退工作台</span>
</button>
)}
</form>
</section>
</main>
);
}