put the settings section in the url
Five settings pages moved from a `*_SELECTED` global to `/settings/:page/:section`. The sidebar entry is a react-router `<NavLink>` rather than a button holding the key in its onClick closure, so a section is linkable, cmd-clickable and gets its active state from the router; each page renders one `SettingsRoute` guard that canonicalises both the bare route and a section that does not exist. Integrations needed more than the shared factory. It builds its own sidebar, and it kept the Enterprise/Personal tab in a second global — which is why a deep link to a Personal section could never have worked: the link set the section, the tab stayed on Enterprise, and the content pane said "Select a section" about a section that existed. The tab is derived from the section key now. Also removes the `/settings/resources` menu item (audit M8) and its two locale keys: there has never been such a route, so it bounced to the catch-all and out to `/`.
This commit is contained in:
@@ -30,11 +30,20 @@ export function App() {
|
||||
<Dashboard.DashboardLayout>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard.HomeScreen />} />
|
||||
{/* Route pairs, like /headscale and /photos below: the bare page route exists so the
|
||||
existing links into it keep working, and its `SettingsRoute` guard redirects to the
|
||||
first section. The section is the URL rather than a `*_SELECTED` global, so a settings
|
||||
sub-page is linkable, bookmarkable and cmd-clickable. */}
|
||||
<Route path="/settings/profile" element={<Dashboard.ProfileSettings />} />
|
||||
<Route path="/settings/profile/:section" element={<Dashboard.ProfileSettings />} />
|
||||
<Route path="/settings/ai" element={<Dashboard.AISettings />} />
|
||||
<Route path="/settings/ai/:section" element={<Dashboard.AISettings />} />
|
||||
<Route path="/settings/system" element={<Dashboard.SystemSettings />} />
|
||||
<Route path="/settings/system/:section" element={<Dashboard.SystemSettings />} />
|
||||
<Route path="/settings/integrations" element={<Dashboard.IntegrationsSettings />} />
|
||||
<Route path="/settings/integrations/:section" element={<Dashboard.IntegrationsSettings />} />
|
||||
<Route path="/settings/user-management" element={<Dashboard.UserManagementSettings />} />
|
||||
<Route path="/settings/user-management/:section" element={<Dashboard.UserManagementSettings />} />
|
||||
{/* A project group is a working directory, so it is spelled as a path, not ?cwd= — see
|
||||
apps/ChatHistory/chat-routes.ts. The splat has to be last in a pattern, which is why
|
||||
"new" comes before the group rather than after it. A session carries no group: the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Link } from 'react-router';
|
||||
import * as Dropdown from '@/components/ui/dropdown-menu';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { User, LogOut, Settings, Package, Puzzle, Sun, Moon, Bot, UserCog } from 'lucide-react';
|
||||
import { User, LogOut, Settings, Puzzle, Sun, Moon, Bot, UserCog } from 'lucide-react';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
import { useColorMode } from '@/components/ui/ThemeProvider';
|
||||
@@ -47,12 +47,9 @@ export function UserMenu() {
|
||||
{t('header.userMenu.systemSettings')}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/resources">
|
||||
<Package className="mr-2 h-4 w-4" />
|
||||
{t('header.userMenu.resources')}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{/* No Resources entry. `/settings/resources` has never been a route, so this item sent you to
|
||||
the catch-all and out to `/` — a menu entry that looks like navigation and is a bounce.
|
||||
(navigation-audit M8.) */}
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/ai">
|
||||
<Bot className="mr-2 h-4 w-4" />
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
|
||||
import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
||||
import { SettingsRoute, createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
||||
import { useSettings } from 'state/useSettings';
|
||||
import type { UserSettings } from 'state/useSettings';
|
||||
import { useModels, useVisibleModels, modelKey, getProviderDisplayName, type ModelOption } from 'state/useModels';
|
||||
@@ -462,6 +462,8 @@ function useAISettingsGroups(): SettingsSectionGroup[] {
|
||||
}, []);
|
||||
}
|
||||
|
||||
const BASE_PATH = '/settings/ai';
|
||||
|
||||
const layout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'ai-root',
|
||||
@@ -475,10 +477,10 @@ const layout: LayoutNode = {
|
||||
export const AISettings = () => {
|
||||
const groups = useAISettingsGroups();
|
||||
|
||||
const { Sidebar, Content } = useMemo(
|
||||
const { Sidebar, Content, allSections } = useMemo(
|
||||
() =>
|
||||
createSettingsPanelComponents({
|
||||
globalKey: 'AI_SETTINGS_SELECTED',
|
||||
basePath: BASE_PATH,
|
||||
sidebarIcon: Bot,
|
||||
sidebarLabel: 'AI',
|
||||
groups,
|
||||
@@ -495,8 +497,10 @@ export const AISettings = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
<SettingsRoute basePath={BASE_PATH} sections={allSections}>
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
</SettingsRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { Puzzle, KeyRound, UserCircle, Globe, Wrench, Mail, CalendarDays } from 'lucide-react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
|
||||
import { SettingsSidebar, SettingsContent, type SettingsSection } from '../SettingsPanel';
|
||||
import { SettingsRoute, SettingsSidebar, SettingsContent, type SettingsSection } from '../SettingsPanel';
|
||||
import { GoogleOAuthConfig } from './GoogleOAuthConfig';
|
||||
// import { GoogleAccount } from './GoogleAccount'; // hidden — email uses IMAP app password now
|
||||
import { BrowserRelay } from './BrowserRelay';
|
||||
@@ -14,8 +14,7 @@ import { ApifyConfig } from './ApifyConfig';
|
||||
import { EmailAccounts } from './EmailAccounts';
|
||||
import { DavAppPasswords } from './DavAppPasswords';
|
||||
|
||||
const GLOBAL_KEY = 'INTEGRATIONS_SETTINGS_SELECTED';
|
||||
const TAB_KEY = 'INTEGRATIONS_SETTINGS_TAB';
|
||||
const BASE_PATH = '/settings/integrations';
|
||||
|
||||
const enterpriseSections: SettingsSection[] = [
|
||||
{
|
||||
@@ -67,10 +66,27 @@ const personalSections: SettingsSection[] = [
|
||||
},
|
||||
];
|
||||
|
||||
// The Enterprise/Personal tab is not a second thing to remember — it is a fact about which section is
|
||||
// open, so it is derived from the URL rather than kept in its own global. That also fixes the deep link:
|
||||
// with the tab held separately, `/settings/integrations/email-accounts` would have arrived with the tab
|
||||
// still on Enterprise and the content pane showing "Select a section".
|
||||
const allSections = [...enterpriseSections, ...personalSections];
|
||||
const tabForSection = (section: string | undefined) =>
|
||||
personalSections.some((s) => s.key === section) ? 'personal' : 'enterprise';
|
||||
|
||||
const IntegrationsSidebar = () => {
|
||||
const [tab, setTab] = useGlobal<string>(TAB_KEY, 'enterprise');
|
||||
const { section } = useParams<{ section?: string }>();
|
||||
const navigate = useNavigate();
|
||||
const tab = tabForSection(section);
|
||||
const sections = tab === 'enterprise' ? enterpriseSections : personalSections;
|
||||
|
||||
// Switching tab is a navigation, so it lands on that tab's first section rather than leaving the
|
||||
// content pane showing a section the sidebar no longer lists.
|
||||
const setTab = (next: string) => {
|
||||
const first = (next === 'enterprise' ? enterpriseSections : personalSections)[0];
|
||||
if (first) navigate(`${BASE_PATH}/${first.key}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="p-3 pb-2">
|
||||
@@ -91,17 +107,14 @@ const IntegrationsSidebar = () => {
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<SettingsSidebar globalKey={GLOBAL_KEY} icon={Puzzle} label="Integrations" sections={sections} hideHeader />
|
||||
<SettingsSidebar basePath={BASE_PATH} icon={Puzzle} label="Integrations" sections={sections} hideHeader />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const IntegrationsContent = () => {
|
||||
const [tab] = useGlobal<string>(TAB_KEY, 'enterprise');
|
||||
const sections = tab === 'enterprise' ? enterpriseSections : personalSections;
|
||||
|
||||
return <SettingsContent globalKey={GLOBAL_KEY} sections={sections} />;
|
||||
};
|
||||
// Both tabs' sections, because the URL alone says which one is open and the pane no longer has to agree
|
||||
// with the sidebar about the tab to find it.
|
||||
const IntegrationsContent = () => <SettingsContent sections={allSections} />;
|
||||
|
||||
const layout: LayoutNode = {
|
||||
type: 'group',
|
||||
@@ -123,8 +136,10 @@ export const IntegrationsSettings = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
<SettingsRoute basePath={BASE_PATH} sections={allSections}>
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
</SettingsRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,14 +3,14 @@ import { User, Lock, Globe, LayoutGrid, Volume2 } from 'lucide-react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
|
||||
import { createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
|
||||
import { SettingsRoute, createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
|
||||
import { UserData } from './UserData';
|
||||
import { ChangePassword } from './ChangePassword';
|
||||
import { Languages } from './Languages';
|
||||
import { DockSettings } from './DockSettings';
|
||||
import { VoicePreference } from './VoicePreference';
|
||||
|
||||
const GLOBAL_KEY = 'PROFILE_SETTINGS_SELECTED';
|
||||
const BASE_PATH = '/settings/profile';
|
||||
|
||||
const sections: SettingsSection[] = [
|
||||
{ key: 'profile', icon: User, title: 'Profile', description: 'Avatar, name, and email', content: <UserData /> },
|
||||
@@ -45,7 +45,7 @@ const sections: SettingsSection[] = [
|
||||
];
|
||||
|
||||
const { Sidebar, Content } = createSettingsPanelComponents({
|
||||
globalKey: GLOBAL_KEY,
|
||||
basePath: BASE_PATH,
|
||||
sidebarIcon: User,
|
||||
sidebarLabel: 'Profile',
|
||||
sections,
|
||||
@@ -71,8 +71,10 @@ export const ProfileSettings = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
<SettingsRoute basePath={BASE_PATH} sections={sections}>
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
</SettingsRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComponentType, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { NavLink, Navigate, useParams } from 'react-router';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
export type SettingsSection = {
|
||||
@@ -19,7 +19,7 @@ export type SettingsSectionGroup = {
|
||||
};
|
||||
|
||||
type SettingsSidebarProps = {
|
||||
globalKey: string;
|
||||
basePath: string;
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
sections: SettingsSection[];
|
||||
@@ -27,33 +27,33 @@ type SettingsSidebarProps = {
|
||||
hideHeader?: boolean;
|
||||
};
|
||||
|
||||
const SectionButton = ({
|
||||
section,
|
||||
isActive,
|
||||
onClick,
|
||||
}: {
|
||||
section: SettingsSection;
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
}) => (
|
||||
<button
|
||||
key={section.key}
|
||||
onClick={onClick}
|
||||
className={`flex items-start gap-2.5 py-2 px-3 rounded-lg text-left cursor-pointer transition-colors ${
|
||||
isActive ? 'bg-duck-teal/10 text-duck-dark dark:text-foreground' : 'text-duck-dark/70 dark:text-foreground/70 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 hover:text-duck-dark dark:hover:text-foreground'
|
||||
}`}
|
||||
/**
|
||||
* A real link, not a button with an onClick. The section is in the URL, so cmd-click opens it in a tab,
|
||||
* the browser knows it has been visited, and `isActive` comes from the router instead of from a global
|
||||
* that the content pane had to be told about separately.
|
||||
*/
|
||||
const SectionLink = ({ section, to }: { section: SettingsSection; to: string }) => (
|
||||
<NavLink
|
||||
to={to}
|
||||
className={({ isActive }) =>
|
||||
`flex items-start gap-2.5 py-2 px-3 rounded-lg text-left cursor-pointer transition-colors ${
|
||||
isActive ? 'bg-duck-teal/10 text-duck-dark dark:text-foreground' : 'text-duck-dark/70 dark:text-foreground/70 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 hover:text-duck-dark dark:hover:text-foreground'
|
||||
}`
|
||||
}
|
||||
>
|
||||
<section.icon className={`h-3.5 w-3.5 shrink-0 mt-0.5 ${isActive ? 'text-duck-teal' : 'text-duck-dark/40 dark:text-foreground/40'}`} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium truncate">{section.title}</div>
|
||||
<div className="text-xs text-duck-dark/40 dark:text-foreground/40 truncate">{section.description}</div>
|
||||
</div>
|
||||
</button>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<section.icon className={`h-3.5 w-3.5 shrink-0 mt-0.5 ${isActive ? 'text-duck-teal' : 'text-duck-dark/40 dark:text-foreground/40'}`} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium truncate">{section.title}</div>
|
||||
<div className="text-xs text-duck-dark/40 dark:text-foreground/40 truncate">{section.description}</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
export const SettingsSidebar = ({ globalKey, icon: Icon, label, sections, groups, hideHeader }: SettingsSidebarProps) => {
|
||||
const allSections = groups ? groups.flatMap((g) => g.sections) : sections;
|
||||
const [selectedKey, setSelectedKey] = useGlobal<string | null>(globalKey, allSections[0]?.key ?? null);
|
||||
export const SettingsSidebar = ({ basePath, icon: Icon, label, sections, groups, hideHeader }: SettingsSidebarProps) => {
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const query = search.toLowerCase();
|
||||
@@ -87,23 +87,13 @@ export const SettingsSidebar = ({ globalKey, icon: Icon, label, sections, groups
|
||||
</span>
|
||||
</div>
|
||||
{filtered.map((s) => (
|
||||
<SectionButton
|
||||
key={s.key}
|
||||
section={s}
|
||||
isActive={selectedKey === s.key}
|
||||
onClick={() => setSelectedKey(s.key)}
|
||||
/>
|
||||
<SectionLink key={s.key} section={s} to={`${basePath}/${s.key}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: sections.filter(matchesSearch).map((s) => (
|
||||
<SectionButton
|
||||
key={s.key}
|
||||
section={s}
|
||||
isActive={selectedKey === s.key}
|
||||
onClick={() => setSelectedKey(s.key)}
|
||||
/>
|
||||
<SectionLink key={s.key} section={s} to={`${basePath}/${s.key}`} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,12 +101,14 @@ export const SettingsSidebar = ({ globalKey, icon: Icon, label, sections, groups
|
||||
};
|
||||
|
||||
type SettingsContentProps = {
|
||||
globalKey: string;
|
||||
sections: SettingsSection[];
|
||||
};
|
||||
|
||||
export const SettingsContent = ({ globalKey, sections }: SettingsContentProps) => {
|
||||
const [selectedKey] = useGlobal<string | null>(globalKey, sections[0]?.key ?? null);
|
||||
export const SettingsContent = ({ sections }: SettingsContentProps) => {
|
||||
// The sidebar and this pane are two sibling panels that used to agree via a shared `useGlobal` key.
|
||||
// They agree via the URL now, so neither has to be told about the other — and neither can be the one
|
||||
// that seeds the slot, which is what made the old defaulting order matter (see §5.6).
|
||||
const { section: selectedKey } = useParams<{ section?: string }>();
|
||||
const section = sections.find((s) => s.key === selectedKey);
|
||||
|
||||
if (!section) {
|
||||
@@ -139,19 +131,35 @@ export const SettingsContent = ({ globalKey, sections }: SettingsContentProps) =
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The one guard each settings screen renders, per the route-pair convention: it canonicalises both the
|
||||
* bare page route and a section that does not exist, so `/settings/ai` and `/settings/ai/nonsense` both
|
||||
* land on the first section instead of on "Select a section".
|
||||
*
|
||||
* Place it after the screen's hooks — it returns early.
|
||||
*/
|
||||
type SettingsRouteProps = { basePath: string; sections: SettingsSection[]; children: ReactNode };
|
||||
|
||||
export const SettingsRoute = ({ basePath, sections, children }: SettingsRouteProps) => {
|
||||
const { section } = useParams<{ section?: string }>();
|
||||
const first = sections[0];
|
||||
if (first && !sections.some((s) => s.key === section)) return <Navigate to={`${basePath}/${first.key}`} replace />;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
type CreateSettingsPanelParams = {
|
||||
globalKey: string;
|
||||
basePath: string;
|
||||
sidebarIcon: LucideIcon;
|
||||
sidebarLabel: string;
|
||||
sections?: SettingsSection[];
|
||||
groups?: SettingsSectionGroup[];
|
||||
};
|
||||
|
||||
export const createSettingsPanelComponents = ({ globalKey, sidebarIcon, sidebarLabel, sections = [], groups }: CreateSettingsPanelParams) => {
|
||||
export const createSettingsPanelComponents = ({ basePath, sidebarIcon, sidebarLabel, sections = [], groups }: CreateSettingsPanelParams) => {
|
||||
const allSections = groups ? groups.flatMap((g) => g.sections) : sections;
|
||||
const Sidebar: ComponentType = () => (
|
||||
<SettingsSidebar globalKey={globalKey} icon={sidebarIcon} label={sidebarLabel} sections={allSections} groups={groups} />
|
||||
<SettingsSidebar basePath={basePath} icon={sidebarIcon} label={sidebarLabel} sections={allSections} groups={groups} />
|
||||
);
|
||||
const Content: ComponentType = () => <SettingsContent globalKey={globalKey} sections={allSections} />;
|
||||
return { Sidebar, Content };
|
||||
const Content: ComponentType = () => <SettingsContent sections={allSections} />;
|
||||
return { Sidebar, Content, allSections };
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Bot, Settings, Mail, Volume2, Mic, ScanText } from 'lucide-react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
|
||||
import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
||||
import { SettingsRoute, createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
||||
import { SMTPSection } from './ServerSettings/SMTPSection';
|
||||
import { TTSSection } from './ServerSettings/TTSSection';
|
||||
import { STTSection } from './ServerSettings/STTSection';
|
||||
@@ -40,8 +40,10 @@ const groups: SettingsSectionGroup[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const { Sidebar, Content } = createSettingsPanelComponents({
|
||||
globalKey: 'SYSTEM_SETTINGS_SELECTED',
|
||||
const BASE_PATH = '/settings/system';
|
||||
|
||||
const { Sidebar, Content, allSections } = createSettingsPanelComponents({
|
||||
basePath: BASE_PATH,
|
||||
sidebarIcon: Settings,
|
||||
sidebarLabel: 'System',
|
||||
groups,
|
||||
@@ -73,8 +75,10 @@ export const SystemSettings = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={baseLayout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
<SettingsRoute basePath={BASE_PATH} sections={allSections}>
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={baseLayout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
</SettingsRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
||||
import { Users, UserCog, ShieldCheck } from 'lucide-react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
import { createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
|
||||
import { SettingsRoute, createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
|
||||
import { UsersSection } from './UsersSection';
|
||||
import { PermissionsSection } from './PermissionsSection';
|
||||
|
||||
@@ -28,8 +28,10 @@ const sections: SettingsSection[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const BASE_PATH = '/settings/user-management';
|
||||
|
||||
const { Sidebar, Content } = createSettingsPanelComponents({
|
||||
globalKey: 'USER_MANAGEMENT_SELECTED',
|
||||
basePath: BASE_PATH,
|
||||
sidebarIcon: UserCog,
|
||||
sidebarLabel: 'Users',
|
||||
sections,
|
||||
@@ -52,8 +54,10 @@ export const UserManagementSettings = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
<SettingsRoute basePath={BASE_PATH} sections={sections}>
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} components={panelComponents} />
|
||||
</div>
|
||||
</SettingsRoute>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
"userMenu": {
|
||||
"profile": "Profile",
|
||||
"systemSettings": "System Settings",
|
||||
"resources": "Resources",
|
||||
"signOut": "Sign Out"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
"userMenu": {
|
||||
"profile": "Perfil",
|
||||
"systemSettings": "Definições do Sistema",
|
||||
"resources": "Recursos",
|
||||
"signOut": "Terminar Sessão"
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user