diff --git a/packages/app/src/TeamPage.tsx b/packages/app/src/TeamPage.tsx index 4a9c2bffd4..411f229359 100644 --- a/packages/app/src/TeamPage.tsx +++ b/packages/app/src/TeamPage.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; import { SubmitHandler, useForm } from 'react-hook-form'; @@ -16,6 +16,12 @@ import { import { notifications } from '@mantine/notifications'; import { IconPencil } from '@tabler/icons-react'; +import { + type TeamTab, + useTeamAdminAccess, + useTeamSettingsTabs, +} from '@/extensions/teamSettings'; + import { PageHeader } from './components/PageHeader'; import ApiKeysSection from './components/TeamSettings/ApiKeysSection'; import ConnectionsSection from './components/TeamSettings/ConnectionsSection'; @@ -28,15 +34,6 @@ import { useBrandDisplayName } from './theme/ThemeProvider'; import api from './api'; import { withAppNav } from './layout'; -type TeamTab = { - value: string; - label: string; - sections: { - id: string; - content: ReactNode; - }[]; -}; - function TeamTabContent({ sections }: { sections: TeamTab['sections'] }) { return ( @@ -57,7 +54,7 @@ export default function TeamPage() { const allowedAuthMethods = team?.allowedAuthMethods ?? []; const hasAllowedAuthMethods = allowedAuthMethods.length > 0; - const hasAdminAccess = true; + const hasAdminAccess = useTeamAdminAccess(); const [isEditingTeamName, setIsEditingTeamName] = useState(false); const form = useForm<{ name: string }>({ defaultValues: { name: team?.name }, @@ -88,7 +85,7 @@ export default function TeamPage() { [refetchTeam, setTeamName], ); - const tabs: TeamTab[] = [ + const baseTabs: TeamTab[] = [ { value: 'data', label: 'Data', @@ -157,6 +154,8 @@ export default function TeamPage() { }, ]; + const tabs = useTeamSettingsTabs(baseTabs); + const queryTab = typeof router.query.tab === 'string' ? router.query.tab : null; const activeTab = tabs.some(tab => tab.value === queryTab) diff --git a/packages/app/src/extensions/teamSettings.tsx b/packages/app/src/extensions/teamSettings.tsx new file mode 100644 index 0000000000..9c3d68f83a --- /dev/null +++ b/packages/app/src/extensions/teamSettings.tsx @@ -0,0 +1,42 @@ +import { ReactNode } from 'react'; + +/** + * Team settings extension points. + * + * `TeamPage` builds its tabs from a declarative list and renders them + * generically. The two hooks below are the extension seam: builds that ship + * additional team-settings surfaces can supply their own implementations of + * this module (resolved via the `@/extensions` import) to contribute or + * rearrange tabs and to gate administrative affordances, without editing + * `TeamPage` itself. The defaults here preserve the standard behavior. + */ + +export type TeamTab = { + value: string; + label: string; + sections: { + id: string; + content: ReactNode; + }[]; +}; + +/** + * Transform the team-settings tabs before they are rendered. Receives the + * base tabs and returns the tabs to display. Default: unchanged. + * + * This is a hook so implementations may read state (current team, feature + * flags, etc.) when deciding which tabs and sections to show. + */ +// eslint-disable-next-line @eslint-react/no-unnecessary-use-prefix -- hook by contract: override implementations call hooks +export function useTeamSettingsTabs(tabs: TeamTab[]): TeamTab[] { + return tabs; +} + +/** + * Whether the current user may administer the team (controls the team-name + * edit affordance). Default: always allowed. + */ +// eslint-disable-next-line @eslint-react/no-unnecessary-use-prefix -- hook by contract: override implementations call hooks +export function useTeamAdminAccess(): boolean { + return true; +}