diff --git a/src/app/admin/studio/layout.tsx b/src/app/admin/studio/layout.tsx index 09b6bd74..ad69d286 100644 --- a/src/app/admin/studio/layout.tsx +++ b/src/app/admin/studio/layout.tsx @@ -1,158 +1,21 @@ -"use client"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { StudioLayoutClient } from "./studio-layout-client"; -import { - BadgeCheck, - Cat, - ChevronDown, - ClipboardCheck, - Copy, - Dumbbell, - Image as ImageIcon, - type LucideIcon, - Puzzle, - RefreshCw, - Shirt, - Upload, - Wrench, -} from "lucide-react"; -import { usePathname } from "next/navigation"; -import { useState } from "react"; -import Link from "@/components/link"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { cn } from "@/lib/utils"; - -type StudioLink = { href: string; label: string; icon: LucideIcon }; -const assets: StudioLink[] = [ - { href: "/admin/studio/furni", label: "Furniture", icon: Puzzle }, - { href: "/admin/studio/badges", label: "Badges", icon: BadgeCheck }, - { href: "/admin/studio/clothing", label: "Clothing", icon: Shirt }, - { href: "/admin/studio/effects", label: "Effects", icon: Dumbbell }, - { href: "/admin/studio/pets", label: "Pets", icon: Cat }, -]; -const imports: StudioLink[] = [ - { href: "/admin/studio/upload", label: "Upload files", icon: Upload }, - { href: "/admin/studio/clone", label: "Import from a source", icon: Copy }, - { href: "/admin/studio/sync", label: "Sync & repository", icon: RefreshCw }, -]; -const maintenance: StudioLink[] = [ - { href: "/admin/studio/audit", label: "Catalog audit", icon: ClipboardCheck }, - { - href: "/admin/studio/repair-icons", - label: "Repair icons", - icon: ImageIcon, - }, - { - href: "/admin/studio/maintenance", - label: "Catalog maintenance", - icon: Wrench, - }, -]; -const activePath = (path: string, href: string) => - path === href || path.startsWith(`${href}/`); - -function StudioMenu({ - label, - links, - pathname, -}: { - label: string; - links: StudioLink[]; - pathname: string; -}) { - const [open, setOpen] = useState(false); - const active = links.find((link) => activePath(pathname, link.href)); - return ( - - - {label} - - - - {links.map(({ href, label: title, icon: Icon }) => ( - setOpen(false)} - aria-current={activePath(pathname, href) ? "page" : undefined} - className="flex min-h-11 items-center gap-3 rounded-md px-3 text-sm hover:bg-[var(--admin-accent)]/10 aria-[current=page]:bg-[var(--admin-accent)]/15" - > - - {title} - - ))} - - - ); -} - -export default function StudioLayout({ +export default async function StudioLayout({ children, }: { children: React.ReactNode; }) { - const pathname = usePathname() ?? ""; + const { session, permissions } = await getAdminContext(); + const canEditCatalog = canAccess( + permissions, + PERMS.CATALOG_EDIT, + session.user.rank, + ); + return ( -
- -
{children}
-
+ + {children} + ); } diff --git a/src/app/admin/studio/studio-layout-client.tsx b/src/app/admin/studio/studio-layout-client.tsx new file mode 100644 index 00000000..9a65d237 --- /dev/null +++ b/src/app/admin/studio/studio-layout-client.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { + BadgeCheck, + Cat, + ChevronDown, + ClipboardCheck, + Copy, + Dumbbell, + Image as ImageIcon, + type LucideIcon, + Puzzle, + RefreshCw, + Shirt, + Upload, + Wrench, +} from "lucide-react"; +import { usePathname } from "next/navigation"; +import { useState } from "react"; +import { CatalogManagerDialog } from "@/app/admin/catalog/catalog-manager-dialog-wrapper"; +import Link from "@/components/link"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; + +type StudioLink = { href: string; label: string; icon: LucideIcon }; +const assets: StudioLink[] = [ + { href: "/admin/studio/furni", label: "Furniture", icon: Puzzle }, + { href: "/admin/studio/badges", label: "Badges", icon: BadgeCheck }, + { href: "/admin/studio/clothing", label: "Clothing", icon: Shirt }, + { href: "/admin/studio/effects", label: "Effects", icon: Dumbbell }, + { href: "/admin/studio/pets", label: "Pets", icon: Cat }, +]; +const imports: StudioLink[] = [ + { href: "/admin/studio/upload", label: "Upload files", icon: Upload }, + { href: "/admin/studio/clone", label: "Import from a source", icon: Copy }, + { href: "/admin/studio/sync", label: "Sync & repository", icon: RefreshCw }, +]; +const maintenance: StudioLink[] = [ + { href: "/admin/studio/audit", label: "Catalog audit", icon: ClipboardCheck }, + { + href: "/admin/studio/repair-icons", + label: "Repair icons", + icon: ImageIcon, + }, + { + href: "/admin/studio/maintenance", + label: "Catalog maintenance", + icon: Wrench, + }, +]; +const activePath = (path: string, href: string) => + path === href || path.startsWith(`${href}/`); + +function StudioMenu({ + label, + links, + pathname, +}: { + label: string; + links: StudioLink[]; + pathname: string; +}) { + const [open, setOpen] = useState(false); + const active = links.find((link) => activePath(pathname, link.href)); + return ( + + + {label} + + + + {links.map(({ href, label: title, icon: Icon }) => ( + setOpen(false)} + aria-current={activePath(pathname, href) ? "page" : undefined} + className="flex min-h-11 items-center gap-3 rounded-md px-3 text-sm hover:bg-[var(--admin-accent)]/10 aria-[current=page]:bg-[var(--admin-accent)]/15" + > + + {title} + + ))} + + + ); +} + +export function StudioLayoutClient({ + canEditCatalog, + children, +}: { + canEditCatalog: boolean; + children: React.ReactNode; +}) { + const pathname = usePathname() ?? ""; + return ( +
+ +
{children}
+
+ ); +} diff --git a/src/components/admin/catalog-manager/catalog-manager-dialog.tsx b/src/components/admin/catalog-manager/catalog-manager-dialog.tsx index 5b2d3e7f..6ba82e13 100644 --- a/src/components/admin/catalog-manager/catalog-manager-dialog.tsx +++ b/src/components/admin/catalog-manager/catalog-manager-dialog.tsx @@ -74,11 +74,13 @@ export function CatalogManagerDialog({ catalogType = "normal", canEdit = false, totals = null, + triggerLabel, }: { initialRoots?: RootTab[]; catalogType?: CatalogManagerMode; canEdit?: boolean; totals?: CatalogStatsTotals | null; + triggerLabel?: string; }) { const [open, setOpen] = useState(false); const [mounted, setMounted] = useState(false); @@ -115,7 +117,7 @@ export function CatalogManagerDialog({ onClick={() => setOpen(true)} > - {title} + {triggerLabel ?? title} {mounted && open ? createPortal(