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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user