Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions packages/app/src/TeamPage.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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 (
<Stack gap="lg" pt="lg">
Expand All @@ -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 },
Expand Down Expand Up @@ -88,7 +85,7 @@ export default function TeamPage() {
[refetchTeam, setTeamName],
);

const tabs: TeamTab[] = [
const baseTabs: TeamTab[] = [
{
value: 'data',
label: 'Data',
Expand Down Expand Up @@ -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)
Expand Down
42 changes: 42 additions & 0 deletions packages/app/src/extensions/teamSettings.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
Loading