Skip to content

Commit 480ab9b

Browse files
committed
feat(ui): add composed OrganizationProfile panels and section components
1 parent 29150fa commit 480ab9b

16 files changed

Lines changed: 1151 additions & 0 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client';
2+
3+
import { lazy, type ReactNode } from 'react';
4+
5+
import { APIKeysSection } from '../APIKeysSection';
6+
7+
const OrganizationAPIKeysPage = lazy(() =>
8+
import('../../components/OrganizationProfile/OrganizationAPIKeysPage').then(m => ({
9+
default: m.OrganizationAPIKeysPage,
10+
})),
11+
);
12+
13+
export const OrganizationProfileAPIKeysPanel = (): ReactNode => <APIKeysSection page={OrganizationAPIKeysPage} />;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use client';
2+
3+
import { lazy, type ReactNode } from 'react';
4+
5+
import { BillingSection } from '../BillingSection';
6+
7+
const OrganizationBillingPage = lazy(() =>
8+
import('../../components/OrganizationProfile/OrganizationBillingPage').then(m => ({
9+
default: m.OrganizationBillingPage,
10+
})),
11+
);
12+
13+
const OrganizationPlansPage = lazy(() =>
14+
import('../../components/OrganizationProfile/OrganizationPlansPage').then(m => ({
15+
default: m.OrganizationPlansPage,
16+
})),
17+
);
18+
19+
const OrganizationStatementPage = lazy(() =>
20+
import('../../components/OrganizationProfile/OrganizationStatementPage').then(m => ({
21+
default: m.OrganizationStatementPage,
22+
})),
23+
);
24+
25+
const OrganizationPaymentAttemptPage = lazy(() =>
26+
import('../../components/OrganizationProfile/OrganizationPaymentAttemptPage').then(m => ({
27+
default: m.OrganizationPaymentAttemptPage,
28+
})),
29+
);
30+
31+
export const OrganizationProfileBillingPanel = (): ReactNode => (
32+
<BillingSection
33+
billing={OrganizationBillingPage}
34+
plans={OrganizationPlansPage}
35+
statement={OrganizationStatementPage}
36+
paymentAttempt={OrganizationPaymentAttemptPage}
37+
/>
38+
);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use client';
2+
3+
import type { PropsWithChildren, ReactNode } from 'react';
4+
5+
import { CardStateProvider, useCardState } from '@/ui/elements/contexts';
6+
import { ProfileCard } from '@/ui/elements/ProfileCard';
7+
8+
import { OrganizationGeneralPage } from '../../components/OrganizationProfile/OrganizationGeneralPage';
9+
import { localizationKeys } from '../../customizables';
10+
import { PageContext } from '../PageContext';
11+
12+
function GeneralComposed({ children }: PropsWithChildren): ReactNode {
13+
const card = useCardState();
14+
return (
15+
<ProfileCard.Page>
16+
<ProfileCard.PagePanel
17+
pageId='organizationGeneral'
18+
titleKey={localizationKeys('organizationProfile.start.headerTitle__general')}
19+
alertContent={card.error}
20+
>
21+
{children}
22+
</ProfileCard.PagePanel>
23+
</ProfileCard.Page>
24+
);
25+
}
26+
27+
export function OrganizationProfileGeneralPanel({ children }: PropsWithChildren): ReactNode {
28+
if (!children) {
29+
return <OrganizationGeneralPage />;
30+
}
31+
32+
// The section confirmation forms (leave/delete) call useCardState(), so children must be wrapped
33+
// in a CardStateProvider — mirroring the UserProfile Account/Security composed panels.
34+
return (
35+
<PageContext.Provider value='general'>
36+
<CardStateProvider>
37+
<GeneralComposed>{children}</GeneralComposed>
38+
</CardStateProvider>
39+
</PageContext.Provider>
40+
);
41+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client';
2+
3+
import { OrganizationDeleteSection } from '../../components/OrganizationProfile/OrganizationGeneralPage';
4+
import { createSection } from '../createSection';
5+
6+
export const OrganizationProfileDeleteSection = createSection(
7+
'OrganizationProfileDeleteSection',
8+
OrganizationDeleteSection,
9+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client';
2+
3+
import { OrganizationLeaveSection } from '../../components/OrganizationProfile/OrganizationGeneralPage';
4+
import { createSection } from '../createSection';
5+
6+
export const OrganizationProfileLeaveSection = createSection(
7+
'OrganizationProfileLeaveSection',
8+
OrganizationLeaveSection,
9+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client';
2+
3+
import { OrganizationProfileSection } from '../../components/OrganizationProfile/OrganizationGeneralPage';
4+
import { createSection } from '../createSection';
5+
6+
export const OrganizationProfileProfileSection = createSection(
7+
'OrganizationProfileProfileSection',
8+
OrganizationProfileSection,
9+
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client';
2+
3+
import type { ReactNode } from 'react';
4+
5+
import { Protect } from '../../common';
6+
import { OrganizationDomainsSection } from '../../components/OrganizationProfile/OrganizationGeneralPage';
7+
import { useRequirePage } from '../useRequirePage';
8+
9+
export function OrganizationProfileDomainsSection(): ReactNode {
10+
if (!useRequirePage('OrganizationProfileDomainsSection')) {
11+
return null;
12+
}
13+
return (
14+
<Protect permission='org:sys_domains:read'>
15+
<OrganizationDomainsSection />
16+
</Protect>
17+
);
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use client';
2+
3+
import type { ReactNode } from 'react';
4+
5+
import { OrganizationMembers } from '../../components/OrganizationProfile/OrganizationMembers';
6+
7+
export const OrganizationProfileMembersPanel = (): ReactNode => <OrganizationMembers />;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use client';
2+
3+
import { useClerk, useOrganization, useUser } from '@clerk/shared/react';
4+
import type { EnvironmentResource, OrganizationProfileProps } from '@clerk/shared/types';
5+
import type { PropsWithChildren, ReactNode } from 'react';
6+
7+
import type { Appearance } from '@/ui/internal/appearance';
8+
9+
import { OrganizationProfileContext } from '../../contexts/components/OrganizationProfile';
10+
import { SubscriberTypeContext } from '../../contexts/components/SubscriberType';
11+
import { fallbackModuleManager, ProfileProviderShell } from '../ProfileProviderShell';
12+
13+
type OrganizationProfileProviderProps = PropsWithChildren<{
14+
appearance?: Appearance;
15+
afterLeaveOrganizationUrl?: OrganizationProfileProps['afterLeaveOrganizationUrl'];
16+
apiKeysProps?: OrganizationProfileProps['apiKeysProps'];
17+
}>;
18+
19+
export const OrganizationProfileProvider = (props: OrganizationProfileProviderProps): ReactNode => {
20+
const { children, appearance, afterLeaveOrganizationUrl, apiKeysProps } = props;
21+
const clerk = useClerk();
22+
const { isLoaded, user } = useUser();
23+
const { organization } = useOrganization();
24+
25+
const environment = (clerk as any).__internal_environment as EnvironmentResource | null | undefined;
26+
const moduleManager = clerk.__internal_moduleManager ?? fallbackModuleManager;
27+
28+
if (!isLoaded || !user || !organization || !environment) {
29+
return null;
30+
}
31+
32+
const orgProfileCtxValue = {
33+
componentName: 'OrganizationProfile' as const,
34+
mode: 'mounted' as const,
35+
routing: 'hash' as const,
36+
path: undefined,
37+
afterLeaveOrganizationUrl,
38+
apiKeysProps,
39+
customPages: [],
40+
};
41+
42+
return (
43+
<ProfileProviderShell
44+
clerk={clerk}
45+
environment={environment}
46+
moduleManager={moduleManager}
47+
appearanceKey='organizationProfile'
48+
flow='organizationProfile'
49+
globalAppearance={clerk.__internal_getOption('appearance')}
50+
appearance={appearance}
51+
>
52+
<SubscriberTypeContext.Provider value='organization'>
53+
<OrganizationProfileContext.Provider value={orgProfileCtxValue}>{children}</OrganizationProfileContext.Provider>
54+
</SubscriberTypeContext.Provider>
55+
</ProfileProviderShell>
56+
);
57+
};

0 commit comments

Comments
 (0)