feat(web): add simple auth UI + enable CORS for testing

这个提交包含在:
anygen-build-bot
2026-02-12 10:02:13 +00:00
父节点 4930a02232
当前提交 15211a99de
修改 5 个文件,包含 189 行新增58 行删除

查看文件

@@ -0,0 +1,110 @@
"use client";
import { useMemo, useState } from "react";
type AuthOk = { ok: true; user_id: number; token: string; expires_at: number };
type AuthErr = { ok: false; error: string };
type AuthResp = AuthOk | AuthErr;
export default function AuthPage() {
const apiBase = useMemo(
() => process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8080",
[]
);
const [mode, setMode] = useState<"register" | "login">("register");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [resp, setResp] = useState<AuthResp | null>(null);
async function submit() {
setLoading(true);
setResp(null);
try {
const r = await fetch(`${apiBase}/api/v1/auth/${mode}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
const j = (await r.json()) as AuthResp;
setResp(j);
} catch (e: unknown) {
setResp({ ok: false, error: String(e) });
} finally {
setLoading(false);
}
}
return (
<div className="min-h-screen bg-zinc-50 text-zinc-900">
<main className="mx-auto max-w-xl px-6 py-12">
<h1 className="text-2xl font-semibold">{mode === "register" ? "注册" : "登录"}</h1>
<p className="mt-2 text-sm text-zinc-600">
API Base: <span className="font-mono">{apiBase}</span>
</p>
<div className="mt-6 flex gap-2">
<button
className={`rounded-lg px-3 py-2 text-sm ${
mode === "register" ? "bg-zinc-900 text-white" : "bg-white border"
}`}
onClick={() => setMode("register")}
disabled={loading}
>
</button>
<button
className={`rounded-lg px-3 py-2 text-sm ${
mode === "login" ? "bg-zinc-900 text-white" : "bg-white border"
}`}
onClick={() => setMode("login")}
disabled={loading}
>
</button>
</div>
<div className="mt-6 rounded-xl border bg-white p-5">
<label className="block text-sm font-medium"></label>
<input
className="mt-2 w-full rounded-lg border px-3 py-2"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="alice"
/>
<label className="mt-4 block text-sm font-medium">6</label>
<input
type="password"
className="mt-2 w-full rounded-lg border px-3 py-2"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="password123"
/>
<button
className="mt-5 w-full rounded-lg bg-zinc-900 px-4 py-2 text-white hover:bg-zinc-800 disabled:opacity-50"
onClick={submit}
disabled={loading || !username || !password}
>
{loading ? "提交中..." : mode === "register" ? "注册" : "登录"}
</button>
</div>
<div className="mt-6 rounded-xl border bg-white p-5">
<h2 className="text-sm font-medium"></h2>
<pre className="mt-3 overflow-auto rounded-lg bg-zinc-900 p-3 text-xs text-zinc-100">
{JSON.stringify(resp, null, 2)}
</pre>
{resp && resp.ok && (
<p className="mt-3 text-xs text-zinc-600">
token
<span className="font-mono"> Authorization: Bearer {resp.token}</span>
</p>
)}
</div>
</main>
</div>
);
}