Reorganize admin nav by feature and soften light mode.

Sidebar lists one entry per feature; keep hub tabs only for sibling views (events, tickets, ranks/permissions, moderation, radio, analytics, devops). Soft cool-gray light canvas/surface replaces harsh white.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-18 22:06:41 +02:00
1 parent 022fbfa223
commit dd806d2527
9 files changed
+344 -267

No files matched your search

+1
View File
@@ -116,6 +116,7 @@ async function Sidebar({
key={`${group.labelKey}-${item.href}`} key={`${group.labelKey}-${item.href}`}
href={item.href} href={item.href}
matchPrefixes={item.matchPrefixes} matchPrefixes={item.matchPrefixes}
matchExcludePrefixes={item.matchExcludePrefixes}
icon={<ItemIcon size={16} />} icon={<ItemIcon size={16} />}
> >
{t(item.labelKey)} {t(item.labelKey)}
+1 -1
View File
@@ -30,7 +30,7 @@ export function AdminHubChrome({ children }: { children: ReactNode }) {
icon={hub.icon} icon={hub.icon}
title={t(hub.titleKey)} title={t(hub.titleKey)}
subtitle={t(hub.subtitleKey)} subtitle={t(hub.subtitleKey)}
tabs={<AdminSectionTabs tabs={tabs} />} tabs={tabs.length > 1 ? <AdminSectionTabs tabs={tabs} /> : undefined}
className="admin-hub-panel" className="admin-hub-panel"
> >
{children} {children}
+9 -1
View File
@@ -13,18 +13,26 @@ export function AdminNavLink({
href, href,
icon, icon,
matchPrefixes, matchPrefixes,
matchExcludePrefixes,
children, children,
}: { }: {
href: string; href: string;
icon?: ReactNode; icon?: ReactNode;
matchPrefixes?: string[]; matchPrefixes?: string[];
matchExcludePrefixes?: string[];
children: ReactNode; children: ReactNode;
}) { }) {
const pathname = usePathname() ?? ""; const pathname = usePathname() ?? "";
const { close } = useAdminMobile(); const { close } = useAdminMobile();
const excluded = matchExcludePrefixes?.some(
(p) => pathname === p || pathname.startsWith(`${p}/`),
);
let active: boolean; let active: boolean;
if (href === "/admin") { if (excluded) {
active = false;
} else if (href === "/admin") {
active = pathname === "/admin"; active = pathname === "/admin";
} else if (matchPrefixes?.length) { } else if (matchPrefixes?.length) {
active = matchPrefixes.some( active = matchPrefixes.some(
+242 -245
View File
@@ -1,19 +1,42 @@
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import { import {
Activity,
AlertTriangle,
BadgeCheck,
Ban, Ban,
Calendar, Calendar,
CalendarDays,
ClipboardList,
Cog, Cog,
Compass, Compass,
FileText,
Filter,
Gavel, Gavel,
HelpCircle,
Image,
Import,
KeyRound,
Languages,
LayoutDashboard, LayoutDashboard,
Megaphone,
Monitor, Monitor,
Newspaper, Newspaper,
Package,
Radio, Radio,
Server,
Settings, Settings,
Shield, Shield,
ShoppingCart, ShoppingCart,
Sparkles,
Store, Store,
Tags,
Terminal,
Ticket,
Trophy,
Users, Users,
Volume2,
Vote,
Wifi,
Wrench, Wrench,
} from "lucide-react"; } from "lucide-react";
@@ -43,6 +66,8 @@ export interface AdminNavItem {
icon: LucideIcon; icon: LucideIcon;
/** When set, sidebar item is active if pathname matches any prefix. */ /** When set, sidebar item is active if pathname matches any prefix. */
matchPrefixes?: string[]; matchPrefixes?: string[];
/** Prefixes that must NOT count as active (e.g. /admin/users vs multi-accounts). */
matchExcludePrefixes?: string[];
} }
export interface AdminNavGroup { export interface AdminNavGroup {
@@ -51,65 +76,29 @@ export interface AdminNavGroup {
items: AdminNavItem[]; items: AdminNavItem[];
} }
/**
* Hubs with real sibling tabs only. Standalone features have no hub chrome
* (sidebar links go straight to the page).
*/
export const ADMIN_HUBS: AdminHubDefinition[] = [ export const ADMIN_HUBS: AdminHubDefinition[] = [
{ {
id: "news-media", id: "events",
titleKey: "newsMedia", titleKey: "events",
subtitleKey: "newsMediaSubtitle", subtitleKey: "eventsSubtitle",
icon: Newspaper,
prefixes: [
"/admin/articles",
"/admin/photos",
"/admin/banners",
"/admin/ads",
"/admin/media",
],
tabs: [
{
href: "/admin/articles",
labelKey: "articles",
match: ["/admin/articles"],
},
{ href: "/admin/photos", labelKey: "photos" },
{ href: "/admin/banners", labelKey: "banners" },
{ href: "/admin/ads", labelKey: "ads", match: ["/admin/ads"] },
{ href: "/admin/media", labelKey: "media" },
],
},
{
id: "site-content",
titleKey: "siteContent",
subtitleKey: "siteContentSubtitle",
icon: Compass,
prefixes: [
"/admin/navigation",
"/admin/help-questions",
"/admin/writeable-boxes",
"/admin/tags",
"/admin/prefixes",
],
tabs: [
{ href: "/admin/navigation", labelKey: "navigator" },
{
href: "/admin/help-questions",
labelKey: "help",
match: ["/admin/help-questions"],
},
{ href: "/admin/writeable-boxes", labelKey: "boxes" },
{ href: "/admin/tags", labelKey: "tags" },
{ href: "/admin/prefixes", labelKey: "prefixes" },
],
},
{
id: "engagement",
titleKey: "engagement",
subtitleKey: "engagementSubtitle",
icon: Calendar, icon: Calendar,
prefixes: ["/admin/events", "/admin/polls", "/admin/tickets"], prefixes: ["/admin/events"],
tabs: [ tabs: [
{ href: "/admin/events", labelKey: "events", match: ["/admin/events"] }, { href: "/admin/events", labelKey: "events", match: ["/admin/events"] },
{ href: "/admin/events/types", labelKey: "eventTypes" }, { href: "/admin/events/types", labelKey: "eventTypes" },
{ href: "/admin/polls", labelKey: "polls", match: ["/admin/polls"] }, ],
},
{
id: "tickets",
titleKey: "tickets",
subtitleKey: "ticketsSubtitle",
icon: Ticket,
prefixes: ["/admin/tickets"],
tabs: [
{ {
href: "/admin/tickets", href: "/admin/tickets",
labelKey: "tickets", labelKey: "tickets",
@@ -119,50 +108,19 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [
], ],
}, },
{ {
id: "users", id: "staff-access",
titleKey: "users", titleKey: "staffAccess",
subtitleKey: "usersSubtitle", subtitleKey: "staffAccessSubtitle",
icon: Users, icon: KeyRound,
prefixes: [ prefixes: ["/admin/teams", "/admin/permissions", "/admin/housekeeping"],
"/admin/users",
"/admin/online",
"/admin/applications",
"/admin/teams",
],
tabs: [ tabs: [
{
href: "/admin/users",
labelKey: "directory",
match: ["/admin/users"],
},
{ href: "/admin/users/multi-accounts", labelKey: "multiAccounts" },
{ href: "/admin/online", labelKey: "online" },
{ href: "/admin/applications", labelKey: "applications" },
{ href: "/admin/teams", labelKey: "teams" }, { href: "/admin/teams", labelKey: "teams" },
],
},
{
id: "access",
titleKey: "access",
subtitleKey: "accessSubtitle",
icon: Shield,
prefixes: [
"/admin/bans",
"/admin/ip",
"/admin/vpn",
"/admin/permissions",
"/admin/wordfilter",
],
tabs: [
{ href: "/admin/bans", labelKey: "bans" },
{ href: "/admin/ip", labelKey: "ip" },
{ href: "/admin/vpn", labelKey: "vpn" },
{ {
href: "/admin/permissions", href: "/admin/permissions",
labelKey: "permissions", labelKey: "permissions",
match: ["/admin/permissions"], match: ["/admin/permissions"],
}, },
{ href: "/admin/wordfilter", labelKey: "wordFilter" }, { href: "/admin/housekeeping", labelKey: "housekeeping" },
], ],
}, },
{ {
@@ -182,50 +140,6 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [
{ href: "/admin/moderation/team", labelKey: "team" }, { href: "/admin/moderation/team", labelKey: "team" },
], ],
}, },
{
id: "catalog",
titleKey: "catalog",
subtitleKey: "catalogSubtitle",
icon: Store,
prefixes: [
"/admin/catalog",
"/admin/rare-values",
"/admin/badges",
"/admin/achievements",
"/admin/sounds",
],
tabs: [
{ href: "/admin/catalog", labelKey: "catalogTab", match: ["/admin/catalog"] },
{ href: "/admin/rare-values", labelKey: "rares" },
{ href: "/admin/badges", labelKey: "badges" },
{ href: "/admin/achievements", labelKey: "achievements" },
{ href: "/admin/sounds", labelKey: "sounds" },
],
},
{
id: "shop",
titleKey: "shop",
subtitleKey: "shopSubtitle",
icon: ShoppingCart,
prefixes: [
"/admin/shop",
"/admin/transactions",
"/admin/vouchers",
"/admin/subscriptions",
"/admin/calendar",
],
tabs: [
{ href: "/admin/shop", labelKey: "shopTab" },
{ href: "/admin/transactions", labelKey: "transactions" },
{ href: "/admin/vouchers", labelKey: "vouchers" },
{ href: "/admin/subscriptions", labelKey: "subscriptions" },
{
href: "/admin/calendar",
labelKey: "calendar",
match: ["/admin/calendar"],
},
],
},
{ {
id: "radio", id: "radio",
titleKey: "radio", titleKey: "radio",
@@ -245,7 +159,11 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [
labelKey: "monitoring", labelKey: "monitoring",
group: "primary", group: "primary",
}, },
{ href: "/admin/radio/settings", labelKey: "settingsTab", group: "primary" }, {
href: "/admin/radio/settings",
labelKey: "settingsTab",
group: "primary",
},
{ href: "/admin/radio/banners", labelKey: "banners", group: "tools" }, { href: "/admin/radio/banners", labelKey: "banners", group: "tools" },
{ href: "/admin/radio/embed", labelKey: "embed", group: "tools" }, { href: "/admin/radio/embed", labelKey: "embed", group: "tools" },
{ href: "/admin/radio/api-keys", labelKey: "apiKeys", group: "tools" }, { href: "/admin/radio/api-keys", labelKey: "apiKeys", group: "tools" },
@@ -255,68 +173,12 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [
], ],
}, },
{ {
id: "settings", id: "analytics",
titleKey: "settings", titleKey: "analytics",
subtitleKey: "settingsSubtitle", subtitleKey: "analyticsSubtitle",
icon: Cog, icon: Activity,
prefixes: [ prefixes: ["/admin/analytics"],
"/admin/settings",
"/admin/theme",
"/admin/maintenance",
"/admin/favicon",
"/admin/emulator",
"/admin/email-templates",
"/admin/housekeeping",
],
tabs: [ tabs: [
{ href: "/admin/settings", labelKey: "cms" },
{ href: "/admin/theme", labelKey: "theme" },
{ href: "/admin/maintenance", labelKey: "maintenance" },
{ href: "/admin/favicon", labelKey: "favicon" },
{ href: "/admin/emulator", labelKey: "emulator" },
{ href: "/admin/email-templates", labelKey: "email" },
{ href: "/admin/housekeeping", labelKey: "housekeeping" },
],
},
{
id: "tools",
titleKey: "tools",
subtitleKey: "toolsSubtitle",
icon: Wrench,
prefixes: [
"/admin/commandocentrum",
"/admin/rooms",
"/admin/import",
"/admin/translations",
],
tabs: [
{ href: "/admin/commandocentrum", labelKey: "commando" },
{ href: "/admin/rooms", labelKey: "rooms", match: ["/admin/rooms"] },
{ href: "/admin/import", labelKey: "import", match: ["/admin/import"] },
{
href: "/admin/translations",
labelKey: "translations",
match: ["/admin/translations"],
},
],
},
{
id: "observability",
titleKey: "observability",
subtitleKey: "observabilitySubtitle",
icon: Monitor,
prefixes: [
"/admin/logs",
"/admin/analytics",
"/admin/devops",
"/admin/alerts",
],
tabs: [
{ href: "/admin/logs", labelKey: "logs" },
{ href: "/admin/logs/audit", labelKey: "audit" },
{ href: "/admin/logs/chat", labelKey: "chat" },
{ href: "/admin/logs/commands", labelKey: "commands" },
{ href: "/admin/logs/trades", labelKey: "trades" },
{ {
href: "/admin/analytics", href: "/admin/analytics",
labelKey: "analytics", labelKey: "analytics",
@@ -324,26 +186,38 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [
}, },
{ href: "/admin/analytics/activity", labelKey: "activity" }, { href: "/admin/analytics/activity", labelKey: "activity" },
{ href: "/admin/analytics/economy", labelKey: "economy" }, { href: "/admin/analytics/economy", labelKey: "economy" },
],
},
{
id: "devops",
titleKey: "devops",
subtitleKey: "devopsSubtitle",
icon: Server,
prefixes: ["/admin/devops"],
tabs: [
{ href: "/admin/devops", labelKey: "devops", match: ["/admin/devops"] }, { href: "/admin/devops", labelKey: "devops", match: ["/admin/devops"] },
{ href: "/admin/devops/errors", labelKey: "errors" }, { href: "/admin/devops/errors", labelKey: "errors" },
{ href: "/admin/alerts", labelKey: "alerts" },
], ],
}, },
]; ];
/** Longest-prefix match so nested routes (e.g. /admin/logs/audit) win. */
export function findAdminHub(pathname: string): AdminHubDefinition | null { export function findAdminHub(pathname: string): AdminHubDefinition | null {
return ( let best: AdminHubDefinition | null = null;
ADMIN_HUBS.find((hub) => let bestLen = -1;
hub.prefixes.some((p) => pathname === p || pathname.startsWith(`${p}/`)), for (const hub of ADMIN_HUBS) {
) ?? null for (const prefix of hub.prefixes) {
); const hit = pathname === prefix || pathname.startsWith(`${prefix}/`);
if (hit && prefix.length > bestLen) {
best = hub;
bestLen = prefix.length;
}
}
}
return best;
} }
function hubPrefixes(id: string): string[] { /** Feature-first sidebar: one entry per feature; hubs only for sibling tabs. */
return ADMIN_HUBS.find((h) => h.id === id)?.prefixes ?? [];
}
/** Condensed sidebar — one entry per hub (plus dashboard). */
export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
{ {
labelKey: "overview", labelKey: "overview",
@@ -362,45 +236,100 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
items: [ items: [
{ {
href: "/admin/articles", href: "/admin/articles",
labelKey: "hubNewsMedia", labelKey: "articles",
icon: Newspaper, icon: Newspaper,
matchPrefixes: hubPrefixes("news-media"), matchPrefixes: ["/admin/articles"],
},
{ href: "/admin/photos", labelKey: "photos", icon: Image },
{ href: "/admin/banners", labelKey: "banners", icon: Megaphone },
{
href: "/admin/ads",
labelKey: "advertisements",
icon: FileText,
matchPrefixes: ["/admin/ads"],
},
{ href: "/admin/media", labelKey: "media", icon: Image },
{ href: "/admin/navigation", labelKey: "navigator", icon: Compass },
{
href: "/admin/help-questions",
labelKey: "helpCenter",
icon: HelpCircle,
matchPrefixes: ["/admin/help-questions"],
}, },
{ {
href: "/admin/navigation", href: "/admin/writeable-boxes",
labelKey: "hubSiteContent", labelKey: "writeableBoxes",
icon: Compass, icon: Package,
matchPrefixes: hubPrefixes("site-content"),
}, },
{ href: "/admin/tags", labelKey: "tags", icon: Tags },
{ href: "/admin/prefixes", labelKey: "prefixes", icon: Sparkles },
],
},
{
labelKey: "community",
icon: Calendar,
items: [
{ {
href: "/admin/events", href: "/admin/events",
labelKey: "hubEngagement", labelKey: "events",
icon: Calendar, icon: Calendar,
matchPrefixes: hubPrefixes("engagement"), matchPrefixes: ["/admin/events"],
},
{
href: "/admin/polls",
labelKey: "polls",
icon: Vote,
matchPrefixes: ["/admin/polls"],
},
{
href: "/admin/tickets",
labelKey: "tickets",
icon: Ticket,
matchPrefixes: ["/admin/tickets"],
}, },
], ],
}, },
{ {
labelKey: "usersAndAccess", labelKey: "usersAndAccess",
icon: Shield, icon: Users,
items: [ items: [
{ {
href: "/admin/users", href: "/admin/users",
labelKey: "hubUsers", labelKey: "users",
icon: Users, icon: Users,
matchPrefixes: hubPrefixes("users"), matchPrefixes: ["/admin/users"],
matchExcludePrefixes: ["/admin/users/multi-accounts"],
}, },
{ {
href: "/admin/bans", href: "/admin/users/multi-accounts",
labelKey: "hubAccess", labelKey: "multiAccounts",
icon: Ban, icon: Users,
matchPrefixes: hubPrefixes("access"),
}, },
{ href: "/admin/online", labelKey: "onlineUsers", icon: Wifi },
{
href: "/admin/applications",
labelKey: "applications",
icon: ClipboardList,
},
{
href: "/admin/teams",
labelKey: "staffAccess",
icon: KeyRound,
matchPrefixes: [
"/admin/teams",
"/admin/permissions",
"/admin/housekeeping",
],
},
{ href: "/admin/bans", labelKey: "bans", icon: Ban },
{ href: "/admin/ip", labelKey: "ipManagement", icon: Shield },
{ href: "/admin/vpn", labelKey: "vpn", icon: Shield },
{ href: "/admin/wordfilter", labelKey: "wordFilter", icon: Filter },
{ {
href: "/admin/moderation", href: "/admin/moderation",
labelKey: "moderation", labelKey: "moderation",
icon: Gavel, icon: Gavel,
matchPrefixes: hubPrefixes("moderation"), matchPrefixes: ["/admin/moderation"],
}, },
], ],
}, },
@@ -410,15 +339,27 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
items: [ items: [
{ {
href: "/admin/catalog", href: "/admin/catalog",
labelKey: "hubCatalog", labelKey: "catalog",
icon: Store, icon: Store,
matchPrefixes: hubPrefixes("catalog"), matchPrefixes: ["/admin/catalog"],
},
{ href: "/admin/rare-values", labelKey: "rareValues", icon: Sparkles },
{ href: "/admin/badges", labelKey: "badges", icon: BadgeCheck },
{ href: "/admin/achievements", labelKey: "achievements", icon: Trophy },
{ href: "/admin/sounds", labelKey: "sounds", icon: Volume2 },
{ href: "/admin/shop", labelKey: "shop", icon: ShoppingCart },
{ href: "/admin/transactions", labelKey: "transactions", icon: Activity },
{ href: "/admin/vouchers", labelKey: "vouchers", icon: Ticket },
{
href: "/admin/subscriptions",
labelKey: "subscriptions",
icon: ClipboardList,
}, },
{ {
href: "/admin/shop", href: "/admin/calendar",
labelKey: "hubShop", labelKey: "calendar",
icon: ShoppingCart, icon: CalendarDays,
matchPrefixes: hubPrefixes("shop"), matchPrefixes: ["/admin/calendar"],
}, },
], ],
}, },
@@ -438,24 +379,80 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
labelKey: "system", labelKey: "system",
icon: Settings, icon: Settings,
items: [ items: [
{ href: "/admin/settings", labelKey: "settings", icon: Cog },
{ href: "/admin/theme", labelKey: "theme", icon: Sparkles },
{ href: "/admin/maintenance", labelKey: "maintenance", icon: Wrench },
{ href: "/admin/favicon", labelKey: "favicon", icon: Image },
{ href: "/admin/emulator", labelKey: "emulator", icon: Server },
{ {
href: "/admin/settings", href: "/admin/email-templates",
labelKey: "settings", labelKey: "emailTemplates",
icon: Cog, icon: FileText,
matchPrefixes: hubPrefixes("settings"),
},
{
href: "/admin/commandocentrum",
labelKey: "hubTools",
icon: Wrench,
matchPrefixes: hubPrefixes("tools"),
},
{
href: "/admin/logs",
labelKey: "hubObservability",
icon: Monitor,
matchPrefixes: hubPrefixes("observability"),
}, },
], ],
}, },
{
labelKey: "tools",
icon: Wrench,
items: [
{
href: "/admin/commandocentrum",
labelKey: "commandocentrum",
icon: Terminal,
},
{
href: "/admin/rooms",
labelKey: "rooms",
icon: Store,
matchPrefixes: ["/admin/rooms"],
},
{
href: "/admin/import",
labelKey: "import",
icon: Import,
matchPrefixes: ["/admin/import"],
},
{
href: "/admin/translations",
labelKey: "translations",
icon: Languages,
matchPrefixes: ["/admin/translations"],
},
],
},
{
labelKey: "monitoring",
icon: Monitor,
items: [
{
href: "/admin/logs",
labelKey: "logs",
icon: FileText,
matchPrefixes: ["/admin/logs"],
matchExcludePrefixes: [
"/admin/logs/audit",
"/admin/logs/chat",
"/admin/logs/commands",
"/admin/logs/trades",
],
},
{ href: "/admin/logs/audit", labelKey: "auditLog", icon: ClipboardList },
{ href: "/admin/logs/chat", labelKey: "chatLog", icon: FileText },
{ href: "/admin/logs/commands", labelKey: "commandLog", icon: Terminal },
{ href: "/admin/logs/trades", labelKey: "tradeLog", icon: Activity },
{
href: "/admin/analytics",
labelKey: "analytics",
icon: Activity,
matchPrefixes: ["/admin/analytics"],
},
{
href: "/admin/devops",
labelKey: "devops",
icon: Server,
matchPrefixes: ["/admin/devops"],
},
{ href: "/admin/alerts", labelKey: "alerts", icon: AlertTriangle },
],
},
]; ];
+4 -4
View File
@@ -131,7 +131,7 @@ describe("deriveAdminPalette", () => {
expect(admin.surface).not.toBe("#00ff00"); expect(admin.surface).not.toBe("#00ff00");
}); });
it("in light mode mirrors readable public surfaces", () => { it("in light mode softens harsh white public surfaces", () => {
const admin = deriveAdminPalette( const admin = deriveAdminPalette(
{ {
color_background: "#f8fafc", color_background: "#f8fafc",
@@ -150,8 +150,8 @@ describe("deriveAdminPalette", () => {
}, },
"light", "light",
); );
expect(admin.canvas).toBe("#f8fafc"); expect(admin.canvas).toBe("#eef1f4");
expect(admin.surface).toBe("#ffffff"); expect(admin.surface).toBe("#f5f7fa");
expect(contrastRatio(admin.text, admin.canvas)).toBeGreaterThanOrEqual(4.5); expect(contrastRatio(admin.text, admin.canvas)).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(admin.sidebarText, admin.sidebar)).toBeGreaterThanOrEqual( expect(contrastRatio(admin.sidebarText, admin.sidebar)).toBeGreaterThanOrEqual(
4.5, 4.5,
@@ -169,7 +169,7 @@ describe("deriveAdminPalette", () => {
{ canvas: "#0b0f19", text: "#f8fafc" }, { canvas: "#0b0f19", text: "#f8fafc" },
"light", "light",
); );
expect(admin.canvas).toBe("#ffffff"); expect(admin.canvas).toBe("#eef1f4");
expect(admin.text.toLowerCase()).not.toBe("#f8fafc"); expect(admin.text.toLowerCase()).not.toBe("#f8fafc");
}); });
+22 -5
View File
@@ -27,6 +27,19 @@ export interface AdminPalette {
focusRing: string; focusRing: string;
} }
/** Pure / near-white public surfaces → soft admin paper (less eye strain). */
function softLightSurface(
value: string | undefined,
softDefault: string,
): string {
if (!value) return softDefault;
const v = value.trim().toLowerCase();
if (v === "#fff" || v === "#ffffff" || v === "white") return softDefault;
// Common slate/zinc paper whites (#f8fafc, #fafafa, #f9fafb, …)
if (/^#f[89a-f][0-9a-f]{4}$/i.test(v)) return softDefault;
return value;
}
export function deriveAdminPalette( export function deriveAdminPalette(
colors: PublicThemeColors, colors: PublicThemeColors,
overrides?: Partial<AdminPalette>, overrides?: Partial<AdminPalette>,
@@ -34,13 +47,14 @@ export function deriveAdminPalette(
): AdminPalette { ): AdminPalette {
const accent = overrides?.accent ?? colors.color_primary ?? "#f59e0b"; const accent = overrides?.accent ?? colors.color_primary ?? "#f59e0b";
// Soft cool gray-blue paper (not pure white) — easier on the eyes in light HK.
const lightDefaults: AdminPalette = { const lightDefaults: AdminPalette = {
canvas: colors.color_background ?? "#f8fafc", canvas: softLightSurface(colors.color_background, "#eef1f4"),
surface: colors.color_surface ?? "#ffffff", surface: softLightSurface(colors.color_surface, "#f5f7fa"),
surfaceElevated: colors.color_dropdown ?? "#f1f5f9", surfaceElevated: softLightSurface(colors.color_dropdown, "#fafbfc"),
text: colors.color_text ?? "#0f172a", text: colors.color_text ?? "#0f172a",
muted: colors.color_text_muted ?? "#64748b", muted: colors.color_text_muted ?? "#64748b",
border: "#e2e8f0", border: "#d8dee6",
accent, accent,
accentText: accent, accentText: accent,
accentForeground: "#ffffff", accentForeground: "#ffffff",
@@ -48,7 +62,10 @@ export function deriveAdminPalette(
warning: colors.color_warning ?? "#eab308", warning: colors.color_warning ?? "#eab308",
error: colors.color_error ?? "#ef4444", error: colors.color_error ?? "#ef4444",
info: colors.color_info ?? "#0ea5e9", info: colors.color_info ?? "#0ea5e9",
sidebar: colors.color_navbar ?? colors.color_surface ?? "#ffffff", sidebar: softLightSurface(
colors.color_navbar ?? colors.color_surface,
"#e8ecf1",
),
sidebarText: colors.color_navbar_text ?? colors.color_text ?? "#0f172a", sidebarText: colors.color_navbar_text ?? colors.color_text ?? "#0f172a",
overlay: "rgba(15, 23, 42, 0.45)", overlay: "rgba(15, 23, 42, 0.45)",
focusRing: accent, focusRing: accent,
+20 -2
View File
@@ -781,7 +781,15 @@
"backToSite": "Back to site", "backToSite": "Back to site",
"housekeeping": "Housekeeping", "housekeeping": "Housekeeping",
"hotelManagementPanel": "Hotel management panel", "hotelManagementPanel": "Hotel management panel",
"rankLabel": "Rank {rank}" "rankLabel": "Rank {rank}",
"community": "Community",
"monitoring": "Monitoring",
"staffAccess": "Ranks & permissions",
"multiAccounts": "Multi-accounts",
"chatLog": "Chat log",
"commandLog": "Command log",
"tradeLog": "Trade log",
"tools": "Tools"
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
@@ -2153,7 +2161,17 @@
"errors": "Errors", "errors": "Errors",
"alerts": "Alerts", "alerts": "Alerts",
"toolsGroup": "Tools" "toolsGroup": "Tools"
} },
"events": "Events",
"eventsSubtitle": "Hotel events and event types",
"tickets": "Tickets",
"ticketsSubtitle": "Support tickets and reply templates",
"staffAccess": "Ranks & permissions",
"staffAccessSubtitle": "Staff ranks, ACL permissions and housekeeping access",
"analytics": "Analytics",
"analyticsSubtitle": "Hotel activity, retention and economy",
"devops": "DevOps",
"devopsSubtitle": "Service health and emulator errors"
}, },
"wordfilter": { "wordfilter": {
"title": "Word Filter", "title": "Word Filter",
+24 -6
View File
@@ -673,7 +673,7 @@
"content": "Contenuti", "content": "Contenuti",
"articles": "Articoli", "articles": "Articoli",
"tags": "Tag", "tags": "Tag",
"navigator": "Navigatore", "navigator": "Navigazione",
"helpCenter": "Centro assistenza", "helpCenter": "Centro assistenza",
"advertisements": "Pubblicità", "advertisements": "Pubblicità",
"writeableBoxes": "Box modificabili", "writeableBoxes": "Box modificabili",
@@ -737,15 +737,23 @@
"backToSite": "Torna al sito", "backToSite": "Torna al sito",
"hotelManagementPanel": "Pannello di gestione hotel", "hotelManagementPanel": "Pannello di gestione hotel",
"rankLabel": "Grado {rank}", "rankLabel": "Grado {rank}",
"hubNewsMedia": "News & media", "hubNewsMedia": "Notizie e media",
"hubSiteContent": "Site content", "hubSiteContent": "Contenuti sito",
"hubEngagement": "Engagement", "hubEngagement": "Community",
"hubUsers": "Users", "hubUsers": "Users",
"hubAccess": "Access & security", "hubAccess": "Access & security",
"hubCatalog": "Catalog & rares", "hubCatalog": "Catalog & rares",
"hubShop": "Shop & payments", "hubShop": "Shop & payments",
"hubTools": "Tools", "hubTools": "Tools",
"hubObservability": "Observability" "hubObservability": "Observability",
"community": "Community",
"monitoring": "Monitoraggio",
"staffAccess": "Gradi e permessi",
"multiAccounts": "Multi-account",
"chatLog": "Log chat",
"commandLog": "Log comandi",
"tradeLog": "Log trade",
"tools": "Strumenti"
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
@@ -2396,7 +2404,17 @@
"errors": "Errori", "errors": "Errori",
"alerts": "Alert", "alerts": "Alert",
"toolsGroup": "Strumenti" "toolsGroup": "Strumenti"
} },
"events": "Eventi",
"eventsSubtitle": "Eventi hotel e tipi evento",
"tickets": "Ticket",
"ticketsSubtitle": "Ticket di supporto e template risposte",
"staffAccess": "Gradi e permessi",
"staffAccessSubtitle": "Gradi staff, permessi ACL e accesso housekeeping",
"analytics": "Analitiche",
"analyticsSubtitle": "Attività hotel, retention ed economia",
"devops": "DevOps",
"devopsSubtitle": "Salute servizi ed errori emulator"
}, },
"settings": { "settings": {
"title": "Impostazioni CMS", "title": "Impostazioni CMS",
+21 -3
View File
@@ -712,7 +712,7 @@
"navigator": "Navigator", "navigator": "Navigator",
"helpCenter": "Helpcentrum", "helpCenter": "Helpcentrum",
"advertisements": "Advertenties", "advertisements": "Advertenties",
"writeableBoxes": "Beschrijfbare boxes", "writeableBoxes": "Bewerkbare boxes",
"photos": "Foto's", "photos": "Foto's",
"tickets": "Tickets", "tickets": "Tickets",
"events": "Events", "events": "Events",
@@ -781,7 +781,15 @@
"hubCatalog": "Catalog & rares", "hubCatalog": "Catalog & rares",
"hubShop": "Shop & payments", "hubShop": "Shop & payments",
"hubTools": "Tools", "hubTools": "Tools",
"hubObservability": "Observability" "hubObservability": "Observability",
"community": "Community",
"monitoring": "Monitoring",
"staffAccess": "Rangen & rechten",
"multiAccounts": "Multi-accounts",
"chatLog": "Chatlog",
"commandLog": "Commandolog",
"tradeLog": "Tradelog",
"tools": "Tools"
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
@@ -2443,7 +2451,17 @@
"errors": "Fouten", "errors": "Fouten",
"alerts": "Meldingen", "alerts": "Meldingen",
"toolsGroup": "Tools" "toolsGroup": "Tools"
} },
"events": "Evenementen",
"eventsSubtitle": "Hotel-evenementen en evenementtypes",
"tickets": "Tickets",
"ticketsSubtitle": "Supporttickets en antwoordtemplates",
"staffAccess": "Rangen & rechten",
"staffAccessSubtitle": "Staffrangen, ACL-rechten en housekeeping-toegang",
"analytics": "Analytics",
"analyticsSubtitle": "Hotelactiviteit, retentie en economie",
"devops": "DevOps",
"devopsSubtitle": "Servicegezondheid en emulatorfouten"
}, },
"tickets": { "tickets": {
"title": "Tickets", "title": "Tickets",