spb/tools-spboucher-ai
Public
1import Link from "next/link";2import type { Metadata } from "next";3import { requireUserPage } from "@/lib/auth/guard";4import { csrfToken, maybeRotate } from "@/lib/auth/session";5import { ThemeToggle } from "@/components/theme-toggle";6import { LogoutButton } from "@/components/admin/logout-button";7import { LayoutDashboard, Library, ClipboardCheck, Copy, Upload, Link2, ListChecks, ScrollText, UserRound, BookMarked, Settings, ExternalLink } from "lucide-react";8export const metadata: Metadata = { title: "Admin", robots: { index: false, follow: false } };9export const dynamic = "force-dynamic";10const NAV = [["/admin", "Dashboard", LayoutDashboard], ["/admin/tools", "Tools", Library], ["/admin/review", "Review queue", ClipboardCheck], ["/admin/duplicates", "Duplicates", Copy], ["/admin/collections", "Collections", BookMarked], ["/admin/import", "Import / export", Upload], ["/admin/links", "Links", Link2], ["/admin/jobs", "Jobs & crawler", ListChecks], ["/admin/owner", "Owner dashboard", UserRound], ["/admin/audit", "Audit log", ScrollText], ["/admin/settings", "Settings & security", Settings]] as const;11export default async function AdminLayout({ children }: { children: React.ReactNode }) {12 const s = await requireUserPage(); await maybeRotate(s);13 return (14 <>15 <meta name="csrf-token" content={csrfToken(s)} />16 <div className="flex min-h-dvh">17 <aside className="hidden w-60 shrink-0 border-r border-line bg-bg-2 lg:block">18 <div className="flex h-14 items-center gap-2 border-b border-line px-4"><Link href="/admin" className="display text-lg">Tools <span className="text-fg-3">· admin</span></Link></div>19 <nav aria-label="Admin" className="p-2">{NAV.map(([href, l, I]) => <Link key={href} href={href} className="flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-fg-2 hover:bg-bg-3 hover:text-fg"><I className="h-4 w-4" aria-hidden />{l}</Link>)}</nav>20 <div className="mt-4 border-t border-line p-3 text-xs text-fg-3"><p className="truncate">{s.email}</p><p className="mt-0.5">{s.totpEnabled ? "MFA on" : <Link href="/admin/settings" className="text-warn">MFA off — enable</Link>}</p></div>21 </aside>22 <div className="flex min-w-0 flex-1 flex-col">23 <header className="sticky top-0 z-30 flex h-14 items-center gap-2 border-b border-line bg-bg/90 px-4 backdrop-blur">24 <details className="relative lg:hidden"><summary className="btn-ghost h-9 cursor-pointer list-none px-3 text-sm">Menu</summary><nav className="absolute left-0 mt-2 w-56 rounded-xl border border-line bg-bg-2 p-1 shadow-lift">{NAV.map(([href, l]) => <Link key={href} href={href} className="block rounded-lg px-3 py-2 text-sm hover:bg-bg-3">{l}</Link>)}</nav></details>25 <p className="text-sm text-fg-3">Private area · no-index</p>26 <div className="ml-auto flex items-center gap-2"><Link href="/" className="btn-ghost h-9 px-3 text-sm" target="_blank">Public site <ExternalLink className="h-3.5 w-3.5" aria-hidden /></Link><ThemeToggle /><LogoutButton /></div>27 </header>28 <main id="main" className="flex-1 p-4 sm:p-6">{children}</main>29 </div>30 </div>31 </>32 );33}34