SettingsSection now carries titleKey/descriptionKey instead of title/description, and the sidebar and content pane resolve them. The section arrays are module-level consts holding JSX content, so they cannot call a hook — the data holds keys and the renderers translate. Sidebar search matches the RESOLVED words, so it works in the language on screen. Capital K in titleKey is load-bearing: docs-check-settings.ts greps key:\s*'…' for route segments, and a lowercase title_key would have fed it twenty bogus paths. Verified 0 bogus, 26 declared, 20 offered, 0 missing. Section titles now share one key with the Spotlight rows that name the same section, per 'one key per meaning'. Two casings had to converge, so Settings shows Spotlight's: 'Change Password' now reads 'Change password'. Settings sidebar search placeholder was 'Search...'; it is now 'Search settings…', which is what the skeleton catalog already said and what distinguishes it from the global Spotlight.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Users, UserCog, ShieldCheck } from 'lucide-react';
|
|
import type { LayoutNode, PanelComponents } from 'officerdev';
|
|
import { WorkspaceLayout } from 'officerdev';
|
|
import { SettingsRoute, createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
|
|
import { UsersSection } from './UsersSection';
|
|
import { PermissionsSection } from './PermissionsSection';
|
|
|
|
// Owner-only. Every endpoint behind this screen is gated by ownerGate in users-router.ts, and the
|
|
// global backstop already confines a non-owner token to /api/auth + /api/music — so a Member reaching
|
|
// this route sees the list fail to load rather than someone else's accounts. The route is not hidden
|
|
// from them, because hiding a screen is not access control and pretending otherwise invites someone to
|
|
// rely on it.
|
|
const sections: SettingsSection[] = [
|
|
{
|
|
key: 'users',
|
|
icon: Users,
|
|
titleKey: 'settings.sections.accounts.label',
|
|
descriptionKey: 'settings.sections.accounts.description',
|
|
content: <UsersSection />,
|
|
},
|
|
{
|
|
key: 'permissions',
|
|
icon: ShieldCheck,
|
|
titleKey: 'settings.sections.permissions.label',
|
|
descriptionKey: 'settings.sections.permissions.description',
|
|
content: <PermissionsSection />,
|
|
},
|
|
];
|
|
|
|
const BASE_PATH = '/settings/user-management';
|
|
|
|
const { Sidebar, Content } = createSettingsPanelComponents({
|
|
basePath: BASE_PATH,
|
|
sidebarIcon: UserCog,
|
|
sidebarLabelKey: 'settings.nav.users',
|
|
sections,
|
|
});
|
|
|
|
const layout: LayoutNode = {
|
|
type: 'group',
|
|
id: 'user-management-root',
|
|
direction: 'horizontal',
|
|
children: [
|
|
{ node: { type: 'panel', id: 'user-management-left', appType: null }, size: 20 },
|
|
{ node: { type: 'panel', id: 'user-management-right', appType: null }, size: 80 },
|
|
],
|
|
};
|
|
|
|
export const UserManagementSettings = () => {
|
|
const panelComponents: PanelComponents = useMemo(
|
|
() => ({ 'user-management-left': Sidebar, 'user-management-right': Content }),
|
|
[],
|
|
);
|
|
|
|
return (
|
|
<SettingsRoute basePath={BASE_PATH} sections={sections}>
|
|
<div className="h-full w-full pt-2">
|
|
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
|
</div>
|
|
</SettingsRoute>
|
|
);
|
|
};
|