Files
Genarrative/apps/admin-web/src/app/AdminShell.tsx
2026-05-01 20:29:09 +08:00

111 lines
2.9 KiB
TypeScript

import {
Bug,
LayoutDashboard,
LogOut,
ShieldCheck,
TicketCheck,
TicketPercent,
} from 'lucide-react';
import type {ReactNode} from 'react';
import type {AdminSessionPayload} from '../api/adminApiTypes';
import type {AdminRouteId} from './adminRoutes';
import {adminRoutes} from './adminRoutes';
interface AdminShellProps {
admin: AdminSessionPayload;
routeId: AdminRouteId;
children: ReactNode;
onRouteChange: (routeId: AdminRouteId) => void;
onLogout: () => void;
}
const routeIcons = {
overview: LayoutDashboard,
debug: Bug,
redeem: TicketPercent,
invite: TicketCheck,
} satisfies Record<AdminRouteId, typeof LayoutDashboard>;
export function AdminShell({
admin,
routeId,
children,
onRouteChange,
onLogout,
}: AdminShellProps) {
return (
<div className="admin-shell">
<aside className="admin-sidebar">
<div className="admin-brand">
<div className="admin-brand-icon">
<ShieldCheck size={20} aria-hidden="true" />
</div>
<div>
<strong></strong>
<span>Admin</span>
</div>
</div>
<nav className="admin-nav" aria-label="后台导航">
{adminRoutes.map((route) => {
const Icon = routeIcons[route.id];
return (
<button
className="admin-nav-button"
data-active={route.id === routeId}
key={route.id}
title={route.label}
type="button"
onClick={() => onRouteChange(route.id)}
>
<Icon size={18} aria-hidden="true" />
<span>{route.label}</span>
</button>
);
})}
</nav>
</aside>
<div className="admin-main">
<header className="admin-topbar">
<div className="admin-user">
<span>{admin.displayName || admin.username}</span>
<small>{admin.roles.join(' / ')}</small>
</div>
<button
className="admin-icon-button"
title="退出登录"
type="button"
onClick={onLogout}
>
<LogOut size={18} aria-hidden="true" />
<span>退</span>
</button>
</header>
<main className="admin-content">{children}</main>
</div>
<nav className="admin-bottom-nav" aria-label="后台导航">
{adminRoutes.map((route) => {
const Icon = routeIcons[route.id];
return (
<button
className="admin-bottom-nav-button"
data-active={route.id === routeId}
key={route.id}
title={route.label}
type="button"
onClick={() => onRouteChange(route.id)}
>
<Icon size={19} aria-hidden="true" />
<span>{route.label}</span>
</button>
);
})}
</nav>
</div>
);
}