fix: expose cms management entry

This commit is contained in:
flym 2026-09-11 16:52:29 +08:00
parent c439ccb2d1
commit be7e1f4e00
2 changed files with 3 additions and 2 deletions

View File

@ -7,7 +7,7 @@ import { PreviewNotice, StatusCard } from "@/components/ui";
const apiBase = process.env.NEXT_PUBLIC_API_BASE_URL ?? "/api/v1"; const apiBase = process.env.NEXT_PUBLIC_API_BASE_URL ?? "/api/v1";
type User = { email: string; role: "user" | "admin"; plan: "free" | "vip"; status: string; email_verified: boolean; phone_verified: boolean }; type User = { email: string; username: string; role: "user" | "admin"; plan: "free" | "vip"; status: string; email_verified: boolean; phone_verified: boolean };
type Usage = { period_start: string; period_end: string; limit: number; adjustment: number; used: number; reserved: number; available: number; plan: string }; type Usage = { period_start: string; period_end: string; limit: number; adjustment: number; used: number; reserved: number; available: number; plan: string };
export function AccountPanel() { export function AccountPanel() {
@ -54,7 +54,7 @@ export function AccountPanel() {
return <> return <>
<div className="mt-8 grid gap-5 md:grid-cols-2 lg:grid-cols-4"><StatusCard title="账户状态" description={user.status === "active" ? "正常" : "已停用"} tone="success" /><StatusCard title="会员计划" description={usage.plan.toUpperCase()} /><StatusCard title="可用额度" description={`${usage.available.toLocaleString()} 字符`} tone="info" /><StatusCard title="验证状态" description={`邮箱 ${user.email_verified ? "已验证" : "未验证"} · 手机 ${user.phone_verified ? "已验证" : "未验证"}`} tone="warning" /></div> <div className="mt-8 grid gap-5 md:grid-cols-2 lg:grid-cols-4"><StatusCard title="账户状态" description={user.status === "active" ? "正常" : "已停用"} tone="success" /><StatusCard title="会员计划" description={usage.plan.toUpperCase()} /><StatusCard title="可用额度" description={`${usage.available.toLocaleString()} 字符`} tone="info" /><StatusCard title="验证状态" description={`邮箱 ${user.email_verified ? "已验证" : "未验证"} · 手机 ${user.phone_verified ? "已验证" : "未验证"}`} tone="warning" /></div>
<div className="mt-8 grid gap-6 lg:grid-cols-[0.8fr_1.2fr]"> <div className="mt-8 grid gap-6 lg:grid-cols-[0.8fr_1.2fr]">
<section className="rounded-2xl border border-line bg-panel/60 p-6"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-subtle">Profile</p><h2 className="mt-3 text-xl font-semibold"></h2><dl className="mt-6 space-y-5 text-sm"><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd className="text-copy">{user.email}</dd></div><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd className="text-copy">{user.role}</dd></div>{user.role === "admin" ? <div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd><Link className="text-cyan hover:text-copy" href="/admin"></Link></dd></div> : null}<div className="flex justify-between gap-4"><dt className="text-muted"></dt><dd className="text-right text-subtle">{new Date(usage.period_start).toLocaleDateString()} {new Date(usage.period_end).toLocaleDateString()}</dd></div></dl><button className="button-ghost mt-7 w-full" onClick={logout} type="button">退 <ArrowRight /></button></section> <section className="rounded-2xl border border-line bg-panel/60 p-6"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-subtle">Profile</p><h2 className="mt-3 text-xl font-semibold"></h2><dl className="mt-6 space-y-5 text-sm"><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd className="text-copy">{user.username}</dd></div><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd className="text-copy">{user.email}</dd></div><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd className="text-copy">{user.role}</dd></div>{user.role === "admin" ? <><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd><Link className="text-cyan hover:text-copy" href="/admin"></Link></dd></div><div className="flex justify-between gap-4 border-b border-line pb-4"><dt className="text-muted"></dt><dd><Link className="text-cyan hover:text-copy" href="/cms/admin"> CMS</Link></dd></div></> : null}<div className="flex justify-between gap-4"><dt className="text-muted"></dt><dd className="text-right text-subtle">{new Date(usage.period_start).toLocaleDateString()} {new Date(usage.period_end).toLocaleDateString()}</dd></div></dl><button className="button-ghost mt-7 w-full" onClick={logout} type="button">退 <ArrowRight /></button></section>
<div className="space-y-6"><section className="rounded-2xl border border-line bg-panel/60 p-6"><div className="flex items-center justify-between gap-4"><div><p className="text-xs font-semibold uppercase tracking-[0.18em] text-subtle">Usage</p><h2 className="mt-3 text-xl font-semibold"></h2></div><Link className="text-sm text-cyan hover:text-copy" href="/tts"></Link></div><div className="mt-6 grid gap-4 sm:grid-cols-3"><StatusCard title="周期上限" description={usage.limit.toLocaleString()} /><StatusCard title="已使用" description={usage.used.toLocaleString()} /><StatusCard title="冻结" description={usage.reserved.toLocaleString()} /></div></section><section className="rounded-2xl border border-line bg-panel/60 p-6"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-subtle">Security</p><h2 className="mt-3 text-xl font-semibold"></h2><form className="mt-5 grid gap-4 sm:grid-cols-2" onSubmit={changePassword}><div><label className="field-label" htmlFor="current-password"></label><input className="field-input" id="current-password" name="current_password" required type="password" /></div><div><label className="field-label" htmlFor="new-password"></label><input className="field-input" id="new-password" minLength={8} name="new_password" required type="password" /></div>{passwordMessage ? <p className="sm:col-span-2 text-sm text-muted" role="status">{passwordMessage}</p> : null}<button className="button-secondary sm:col-span-2" type="submit"></button></form></section></div> <div className="space-y-6"><section className="rounded-2xl border border-line bg-panel/60 p-6"><div className="flex items-center justify-between gap-4"><div><p className="text-xs font-semibold uppercase tracking-[0.18em] text-subtle">Usage</p><h2 className="mt-3 text-xl font-semibold"></h2></div><Link className="text-sm text-cyan hover:text-copy" href="/tts"></Link></div><div className="mt-6 grid gap-4 sm:grid-cols-3"><StatusCard title="周期上限" description={usage.limit.toLocaleString()} /><StatusCard title="已使用" description={usage.used.toLocaleString()} /><StatusCard title="冻结" description={usage.reserved.toLocaleString()} /></div></section><section className="rounded-2xl border border-line bg-panel/60 p-6"><p className="text-xs font-semibold uppercase tracking-[0.18em] text-subtle">Security</p><h2 className="mt-3 text-xl font-semibold"></h2><form className="mt-5 grid gap-4 sm:grid-cols-2" onSubmit={changePassword}><div><label className="field-label" htmlFor="current-password"></label><input className="field-input" id="current-password" name="current_password" required type="password" /></div><div><label className="field-label" htmlFor="new-password"></label><input className="field-input" id="new-password" minLength={8} name="new_password" required type="password" /></div>{passwordMessage ? <p className="sm:col-span-2 text-sm text-muted" role="status">{passwordMessage}</p> : null}<button className="button-secondary sm:col-span-2" type="submit"></button></form></section></div>
</div> </div>
</>; </>;

View File

@ -12,6 +12,7 @@ const nav = [
{ href: "/admin", label: "概览" }, { href: "/admin", label: "概览" },
{ href: "/admin/users", label: "用户" }, { href: "/admin/users", label: "用户" },
{ href: "/admin/tts", label: "TTS 与审计" }, { href: "/admin/tts", label: "TTS 与审计" },
{ href: "/cms/admin", label: "CMS 内容管理" },
]; ];
export function AdminGate({ children }: { children: React.ReactNode }) { export function AdminGate({ children }: { children: React.ReactNode }) {