feat: rebuild CSP practice workflow, UX and automation

这个提交包含在:
Codex CLI
2026-02-13 15:49:05 +08:00
父节点 d33deed4c5
当前提交 e2ab522b78
修改 105 个文件,包含 15669 行新增428 行删除

查看文件

@@ -0,0 +1,67 @@
"use client";
import { useEffect, useState } from "react";
import { apiFetch } from "@/lib/api";
type Row = {
user_id: number;
username: string;
rating: number;
created_at: number;
};
export default function LeaderboardPage() {
const [items, setItems] = useState<Row[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
const load = async () => {
setLoading(true);
setError("");
try {
const data = await apiFetch<Row[]>("/api/v1/leaderboard/global?limit=200");
setItems(data);
} catch (e: unknown) {
setError(String(e));
} finally {
setLoading(false);
}
};
void load();
}, []);
return (
<main className="mx-auto max-w-4xl px-6 py-8">
<h1 className="text-2xl font-semibold"></h1>
{loading && <p className="mt-3 text-sm text-zinc-500">...</p>}
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
<div className="mt-4 overflow-x-auto rounded-xl border bg-white">
<table className="min-w-full text-sm">
<thead className="bg-zinc-100 text-left">
<tr>
<th className="px-3 py-2"></th>
<th className="px-3 py-2"></th>
<th className="px-3 py-2">Rating</th>
<th className="px-3 py-2"></th>
</tr>
</thead>
<tbody>
{items.map((row, i) => (
<tr key={row.user_id} className="border-t">
<td className="px-3 py-2">{i + 1}</td>
<td className="px-3 py-2">{row.username}</td>
<td className="px-3 py-2">{row.rating}</td>
<td className="px-3 py-2">
{new Date(row.created_at * 1000).toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</main>
);
}