feat: add local auth billing and usage management

This commit is contained in:
inman committed 2026-08-12 12:13:06 +08:00
1 parent f642b5e71f
commit 196fdde83f
119 files changed
+15695 -2650

No files matched your search

+13 -12
View File
@@ -5,37 +5,41 @@ import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
Archive,
BarChart3,
ShieldCheck,
LogIn,
LogOut,
ScrollText,
Settings,
Sparkles,
UserCircle
WalletCards
} from "lucide-react";
import clsx from "clsx";
import { revealChildren, runScopedMotion } from "@/lib/ui/motion";
import type { AuthUser } from "@/lib/auth/session";
import { AccountUsageMenu } from "@/components/account-usage-menu";
const nav = [
{ href: "/create", label: "创作", icon: Sparkles },
{ href: "/assets", label: "结果", icon: Archive },
{ href: "/logs", label: "日志", icon: ScrollText, adminOnly: true },
{ href: "/settings", label: "设置", icon: Settings, adminOnly: true },
{ href: "/accounts", label: "账号", icon: ShieldCheck, adminOnly: true }
{ href: "/logs", label: "日志", icon: ScrollText, requiredRole: "super" as const },
{ href: "/settings", label: "设置", icon: Settings, requiredRole: "super" as const },
{ href: "/accounts", label: "账号", icon: ShieldCheck },
{ href: "/usage", label: "用量", icon: BarChart3, requiredRole: "admin" as const },
{ href: "/billing", label: "计费", icon: WalletCards }
];
export function AppShell({
children,
user,
authRequired,
isAdmin
isAdmin,
isSuperAdmin
}: {
children: React.ReactNode;
user?: AuthUser | null;
authRequired?: boolean;
isAdmin?: boolean;
isSuperAdmin?: boolean;
}) {
const pathname = usePathname();
const shellRef = useRef<HTMLDivElement | null>(null);
@@ -60,7 +64,7 @@ export function AppShell({
</div>
</Link>
<nav className="nav top-nav" aria-label="主导航">
{nav.filter((item) => !item.adminOnly || isAdmin).map((item) => {
{nav.filter((item) => !item.requiredRole || (item.requiredRole === "super" && isSuperAdmin) || (item.requiredRole === "admin" && isAdmin)).map((item) => {
const Icon = item.icon;
const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
return (
@@ -79,10 +83,7 @@ export function AppShell({
<div className="topbar-account">
{user ? (
<>
<span className="account-chip" title={user.username || user.subject}>
<UserCircle aria-hidden="true" />
<span>{user.displayName}</span>
</span>
<AccountUsageMenu user={user} />
<form action="/api/auth/logout" method="post">
<button className="icon-button logout-button" type="submit" aria-label="退出登录" title="退出登录">
<LogOut aria-hidden="true" />