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,68 @@
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { clearToken, readToken } from "@/lib/auth";
const links = [
["首页", "/"],
["登录", "/auth"],
["题库", "/problems"],
["提交", "/submissions"],
["错题本", "/wrong-book"],
["比赛", "/contests"],
["知识库", "/kb"],
["导入任务", "/imports"],
["在线运行", "/run"],
["我的", "/me"],
["排行榜", "/leaderboard"],
["API文档", "/api-docs"],
] as const;
export function AppNav() {
const [hasToken, setHasToken] = useState<boolean>(() => Boolean(readToken()));
useEffect(() => {
const refresh = () => setHasToken(Boolean(readToken()));
window.addEventListener("storage", refresh);
window.addEventListener("focus", refresh);
return () => {
window.removeEventListener("storage", refresh);
window.removeEventListener("focus", refresh);
};
}, []);
return (
<header className="border-b bg-white">
<div className="mx-auto flex max-w-6xl flex-wrap items-center gap-2 px-4 py-3">
{links.map(([label, href]) => (
<Link
key={href}
href={href}
className="rounded-md border px-3 py-1 text-sm hover:bg-zinc-100"
>
{label}
</Link>
))}
<div className="ml-auto flex items-center gap-2 text-sm">
<span className={hasToken ? "text-emerald-700" : "text-zinc-500"}>
{hasToken ? "已登录" : "未登录"}
</span>
{hasToken && (
<button
onClick={() => {
clearToken();
setHasToken(false);
}}
className="rounded-md border px-3 py-1 hover:bg-zinc-100"
>
退
</button>
)}
</div>
</div>
</header>
);
}