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>
);
}

查看文件

@@ -1,63 +1,46 @@
import Image from "next/image";
import Link from "next/link";
const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8080";
async function fetchHealth() {
try {
const r = await fetch(`${API_BASE}/api/health`, { cache: "no-store" });
if (!r.ok) return { ok: false, error: `HTTP ${r.status}` } as const;
return (await r.json()) as { ok: boolean; version?: string };
} catch (e: unknown) {
return { ok: false, error: String(e) } as const;
}
}
export default async function Home() {
const health = await fetchHealth();
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
<div className="min-h-screen bg-zinc-50 text-zinc-900">
<main className="mx-auto max-w-3xl px-6 py-12">
<h1 className="text-3xl font-semibold">CSP 线MVP</h1>
<p className="mt-2 text-sm text-zinc-600">
API Base: <span className="font-mono">{API_BASE}</span>
</p>
<div className="mt-6 rounded-xl border bg-white p-5">
<h2 className="text-lg font-medium"></h2>
<pre className="mt-3 overflow-auto rounded-lg bg-zinc-900 p-3 text-xs text-zinc-100">
{JSON.stringify(health, null, 2)}
</pre>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
<div className="mt-6 flex gap-3">
<Link
className="rounded-lg bg-zinc-900 px-4 py-2 text-white hover:bg-zinc-800"
href="/auth"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
/
</Link>
</div>
<div className="mt-10 text-sm text-zinc-500">
////
</div>
</main>
</div>