Reorganize admin into tabbed hubs with a condensed sidebar.
Local Build and Deploy / deploy (push) Successful in 57s

Replace the long flat nav with hub entries, shared AdminHubChrome tabs, and AdminPageShell. Existing URLs stay stable; Settings/Radio and other sections now share one chrome.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-15 21:29:36 +02:00
1 parent 367f69a439
commit 363c9b3d56
30 files changed
+854 -432

No files matched your search

+435
View File
@@ -0,0 +1,435 @@
import type { LucideIcon } from "lucide-react";
import {
Ban,
Calendar,
Cog,
Compass,
LayoutDashboard,
Monitor,
Newspaper,
Radio,
Settings,
Shield,
ShoppingCart,
Store,
Users,
Wrench,
} from "lucide-react";
export interface AdminHubTab {
href: string;
label: string;
match?: string[];
}
export interface AdminHubDefinition {
id: string;
title: string;
subtitle: string;
icon: LucideIcon;
/** Path prefixes belonging to this hub (for chrome + sidebar active). */
prefixes: string[];
tabs: AdminHubTab[];
}
export interface AdminNavItem {
href: string;
labelKey: string;
icon: LucideIcon;
/** When set, sidebar item is active if pathname matches any prefix. */
matchPrefixes?: string[];
}
export interface AdminNavGroup {
labelKey: string;
icon: LucideIcon;
items: AdminNavItem[];
}
export const ADMIN_HUBS: AdminHubDefinition[] = [
{
id: "news-media",
title: "News & media",
subtitle: "Articles, photos, banners, ads and media library",
icon: Newspaper,
prefixes: [
"/admin/articles",
"/admin/photos",
"/admin/banners",
"/admin/ads",
"/admin/media",
],
tabs: [
{
href: "/admin/articles",
label: "Articles",
match: ["/admin/articles"],
},
{ href: "/admin/photos", label: "Photos" },
{ href: "/admin/banners", label: "Banners" },
{ href: "/admin/ads", label: "Ads", match: ["/admin/ads"] },
{ href: "/admin/media", label: "Media" },
],
},
{
id: "site-content",
title: "Site content",
subtitle: "Navigation, help center, boxes, tags and prefixes",
icon: Compass,
prefixes: [
"/admin/navigation",
"/admin/help-questions",
"/admin/writeable-boxes",
"/admin/tags",
"/admin/prefixes",
],
tabs: [
{ href: "/admin/navigation", label: "Navigator" },
{
href: "/admin/help-questions",
label: "Help",
match: ["/admin/help-questions"],
},
{ href: "/admin/writeable-boxes", label: "Boxes" },
{ href: "/admin/tags", label: "Tags" },
{ href: "/admin/prefixes", label: "Prefixes" },
],
},
{
id: "engagement",
title: "Engagement",
subtitle: "Events, polls and support tickets",
icon: Calendar,
prefixes: ["/admin/events", "/admin/polls", "/admin/tickets"],
tabs: [
{ href: "/admin/events", label: "Events", match: ["/admin/events"] },
{ href: "/admin/polls", label: "Polls", match: ["/admin/polls"] },
{
href: "/admin/tickets",
label: "Tickets",
match: ["/admin/tickets"],
},
],
},
{
id: "users",
title: "Users",
subtitle: "Directory, online players, applications and teams",
icon: Users,
prefixes: [
"/admin/users",
"/admin/online",
"/admin/applications",
"/admin/teams",
],
tabs: [
{ href: "/admin/users", label: "Directory", match: ["/admin/users"] },
{ href: "/admin/online", label: "Online" },
{ href: "/admin/applications", label: "Applications" },
{ href: "/admin/teams", label: "Teams" },
],
},
{
id: "access",
title: "Access & security",
subtitle: "Bans, IP, VPN, permissions and word filter",
icon: Shield,
prefixes: [
"/admin/bans",
"/admin/ip",
"/admin/vpn",
"/admin/permissions",
"/admin/wordfilter",
],
tabs: [
{ href: "/admin/bans", label: "Bans" },
{ href: "/admin/ip", label: "IP" },
{ href: "/admin/vpn", label: "VPN" },
{
href: "/admin/permissions",
label: "Permissions",
match: ["/admin/permissions"],
},
{ href: "/admin/wordfilter", label: "Word filter" },
],
},
{
id: "moderation",
title: "Moderation",
subtitle: "Reports, actions and moderation team",
icon: Shield,
prefixes: ["/admin/moderation"],
tabs: [
{ href: "/admin/moderation", label: "Overview" },
{ href: "/admin/moderation/actions", label: "Actions" },
{
href: "/admin/moderation/cfh",
label: "CFH",
match: ["/admin/moderation/cfh"],
},
{ href: "/admin/moderation/team", label: "Team" },
],
},
{
id: "catalog",
title: "Catalog & rares",
subtitle: "Catalog, rares, badges, achievements and sounds",
icon: Store,
prefixes: [
"/admin/catalog",
"/admin/rare-values",
"/admin/badges",
"/admin/achievements",
"/admin/sounds",
],
tabs: [
{ href: "/admin/catalog", label: "Catalog", match: ["/admin/catalog"] },
{ href: "/admin/rare-values", label: "Rares" },
{ href: "/admin/badges", label: "Badges" },
{ href: "/admin/achievements", label: "Achievements" },
{ href: "/admin/sounds", label: "Sounds" },
],
},
{
id: "shop",
title: "Shop & payments",
subtitle: "Shop, transactions, vouchers, subscriptions and calendar",
icon: ShoppingCart,
prefixes: [
"/admin/shop",
"/admin/transactions",
"/admin/vouchers",
"/admin/subscriptions",
"/admin/calendar",
],
tabs: [
{ href: "/admin/shop", label: "Shop" },
{ href: "/admin/transactions", label: "Transactions" },
{ href: "/admin/vouchers", label: "Vouchers" },
{ href: "/admin/subscriptions", label: "Subscriptions" },
{
href: "/admin/calendar",
label: "Calendar",
match: ["/admin/calendar"],
},
],
},
{
id: "radio",
title: "Radio",
subtitle: "Stream, DJ tools, embed and monitoring",
icon: Radio,
prefixes: ["/admin/radio"],
tabs: [
{ href: "/admin/radio", label: "Overview" },
{ href: "/admin/radio/history", label: "History" },
{ href: "/admin/radio/banners", label: "Banners" },
{ href: "/admin/radio/embed", label: "Embed" },
{ href: "/admin/radio/api-keys", label: "API keys" },
{ href: "/admin/radio/moderation", label: "Moderation" },
{ href: "/admin/radio/settings", label: "Settings" },
{ href: "/admin/radio/monitoring", label: "Monitoring" },
{ href: "/admin/radio/points", label: "Points" },
{ href: "/admin/radio/ranks", label: "Ranks" },
{ href: "/admin/radio/autodj", label: "Auto DJ" },
],
},
{
id: "settings",
title: "Settings",
subtitle: "CMS config, theme, maintenance and related tools",
icon: Cog,
prefixes: [
"/admin/settings",
"/admin/theme",
"/admin/maintenance",
"/admin/favicon",
"/admin/emulator",
"/admin/email-templates",
"/admin/housekeeping",
],
tabs: [
{ href: "/admin/settings", label: "CMS" },
{ href: "/admin/theme", label: "Theme" },
{ href: "/admin/maintenance", label: "Maintenance" },
{ href: "/admin/favicon", label: "Favicon" },
{ href: "/admin/emulator", label: "Emulator" },
{ href: "/admin/email-templates", label: "Email" },
{ href: "/admin/housekeeping", label: "Housekeeping" },
],
},
{
id: "tools",
title: "Tools",
subtitle: "RCON, rooms, import and translations",
icon: Wrench,
prefixes: [
"/admin/commandocentrum",
"/admin/rooms",
"/admin/import",
"/admin/translations",
],
tabs: [
{ href: "/admin/commandocentrum", label: "Commando" },
{ href: "/admin/rooms", label: "Rooms", match: ["/admin/rooms"] },
{ href: "/admin/import", label: "Import", match: ["/admin/import"] },
{
href: "/admin/translations",
label: "Translations",
match: ["/admin/translations"],
},
],
},
{
id: "observability",
title: "Observability",
subtitle: "Logs, analytics, DevOps and hotel alerts",
icon: Monitor,
prefixes: [
"/admin/logs",
"/admin/analytics",
"/admin/devops",
"/admin/alerts",
],
tabs: [
{ href: "/admin/logs", label: "Logs", match: ["/admin/logs"] },
{ href: "/admin/logs/audit", label: "Audit" },
{
href: "/admin/analytics",
label: "Analytics",
match: ["/admin/analytics"],
},
{ href: "/admin/devops", label: "DevOps", match: ["/admin/devops"] },
{ href: "/admin/alerts", label: "Alerts" },
],
},
];
export function findAdminHub(pathname: string): AdminHubDefinition | null {
return (
ADMIN_HUBS.find((hub) =>
hub.prefixes.some((p) => pathname === p || pathname.startsWith(`${p}/`)),
) ?? null
);
}
function hubPrefixes(id: string): string[] {
return ADMIN_HUBS.find((h) => h.id === id)?.prefixes ?? [];
}
/** Condensed sidebar — one entry per hub (plus dashboard). */
export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
{
labelKey: "overview",
icon: LayoutDashboard,
items: [
{
href: "/admin",
labelKey: "dashboard",
icon: LayoutDashboard,
},
],
},
{
labelKey: "content",
icon: Newspaper,
items: [
{
href: "/admin/articles",
labelKey: "hubNewsMedia",
icon: Newspaper,
matchPrefixes: hubPrefixes("news-media"),
},
{
href: "/admin/navigation",
labelKey: "hubSiteContent",
icon: Compass,
matchPrefixes: hubPrefixes("site-content"),
},
{
href: "/admin/events",
labelKey: "hubEngagement",
icon: Calendar,
matchPrefixes: hubPrefixes("engagement"),
},
],
},
{
labelKey: "usersAndAccess",
icon: Shield,
items: [
{
href: "/admin/users",
labelKey: "hubUsers",
icon: Users,
matchPrefixes: hubPrefixes("users"),
},
{
href: "/admin/bans",
labelKey: "hubAccess",
icon: Ban,
matchPrefixes: hubPrefixes("access"),
},
{
href: "/admin/moderation",
labelKey: "moderation",
icon: Shield,
matchPrefixes: hubPrefixes("moderation"),
},
],
},
{
labelKey: "economy",
icon: ShoppingCart,
items: [
{
href: "/admin/catalog",
labelKey: "hubCatalog",
icon: Store,
matchPrefixes: hubPrefixes("catalog"),
},
{
href: "/admin/shop",
labelKey: "hubShop",
icon: ShoppingCart,
matchPrefixes: hubPrefixes("shop"),
},
],
},
{
labelKey: "radio",
icon: Radio,
items: [
{
href: "/admin/radio",
labelKey: "radio",
icon: Radio,
matchPrefixes: ["/admin/radio"],
},
],
},
{
labelKey: "system",
icon: Settings,
items: [
{
href: "/admin/settings",
labelKey: "settings",
icon: Cog,
matchPrefixes: hubPrefixes("settings"),
},
{
href: "/admin/commandocentrum",
labelKey: "hubTools",
icon: Wrench,
matchPrefixes: hubPrefixes("tools"),
},
{
href: "/admin/logs",
labelKey: "hubObservability",
icon: Monitor,
matchPrefixes: hubPrefixes("observability"),
},
],
},
];