Skip to content

Commit ceca5e5

Browse files
committed
Render route in OrganizationProfile
1 parent a043a15 commit ceca5e5

9 files changed

Lines changed: 137 additions & 2 deletions

File tree

packages/localizations/src/en-US.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ export const enUS: LocalizationResource = {
768768
navbar: {
769769
apiKeys: 'API keys',
770770
billing: 'Billing',
771+
selfServeSso: 'Self-Serve SSO',
771772
description: 'Manage your organization.',
772773
general: 'General',
773774
members: 'Members',

packages/shared/src/internal/clerk-js/componentGuards.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const noOrganizationExists: ComponentGuard = clerk => {
1818
return !clerk.organization;
1919
};
2020

21+
// TODO -> Update with per org check
2122
export const disabledOrganizationsFeature: ComponentGuard = (_, environment) => {
2223
return !environment?.organizationSettings.enabled;
2324
};

packages/shared/src/types/localization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,7 @@ export type __internal_LocalizationResource = {
10211021
members: LocalizationValue;
10221022
billing: LocalizationValue;
10231023
apiKeys: LocalizationValue;
1024+
selfServeSso: LocalizationValue;
10241025
};
10251026
badge__unverified: LocalizationValue;
10261027
badge__automaticInvitation: LocalizationValue;

packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,22 @@ const OrganizationPaymentAttemptPage = lazy(() =>
3737
})),
3838
);
3939

40+
const OrganizationSelfServeSsoPage = lazy(() =>
41+
import(/* webpackChunkName: "op-self-serve-sso-page"*/ './OrganizationSelfServeSsoPage').then(module => ({
42+
default: module.OrganizationSelfServeSsoPage,
43+
})),
44+
);
45+
4046
export const OrganizationProfileRoutes = () => {
4147
const {
4248
pages,
4349
isMembersPageRoot,
4450
isGeneralPageRoot,
4551
isBillingPageRoot,
4652
isAPIKeysPageRoot,
53+
isSelfServeSsoPageRoot,
4754
shouldShowBilling,
55+
shouldShowSelfServeSso,
4856
apiKeysProps,
4957
} = useOrganizationProfileContext();
5058

@@ -142,6 +150,15 @@ export const OrganizationProfileRoutes = () => {
142150
</Route>
143151
</Protect>
144152
)}
153+
{shouldShowSelfServeSso ? (
154+
<Route path={isSelfServeSsoPageRoot ? undefined : 'organization-self-serve-sso'}>
155+
<Switch>
156+
<Route index>
157+
<OrganizationSelfServeSsoPage />
158+
</Route>
159+
</Switch>
160+
</Route>
161+
) : null}
145162
</Route>
146163
</Switch>
147164
);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useOrganization } from '@clerk/shared/react';
2+
3+
import { Col } from '@/ui/customizables';
4+
import { Header } from '@/ui/elements/Header';
5+
6+
export const OrganizationSelfServeSsoPage = () => {
7+
const { organization } = useOrganization();
8+
9+
if (!organization) {
10+
// We should never reach this point, but we'll return null to make TS happy
11+
return null;
12+
}
13+
14+
return (
15+
<Col gap={4}>
16+
<Header.Root>
17+
<Header.Title
18+
title='Self-Serve SSO'
19+
textVariant='h2'
20+
/>
21+
</Header.Root>
22+
</Col>
23+
);
24+
};

packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,68 @@ describe('OrganizationProfile', () => {
476476
});
477477
});
478478

479+
describe('Self-Serve SSO visibility', () => {
480+
it('includes Self-Serve SSO when self-serve SSO is enabled in environment settings', async () => {
481+
const { wrapper } = await createFixtures(f => {
482+
f.withEnterpriseSso({ selfServeSso: true });
483+
f.withOrganizations();
484+
f.withUser({
485+
email_addresses: ['test@clerk.com'],
486+
organization_memberships: [
487+
{
488+
name: 'Org1',
489+
permissions: ['org:sys_entconns:manage'],
490+
},
491+
],
492+
});
493+
});
494+
495+
render(<OrganizationProfile />, { wrapper });
496+
expect(await screen.findByText('Self-Serve SSO')).toBeDefined();
497+
});
498+
499+
it('does not include Self-Serve SSO when self-serve SSO is disabled in environment settings', async () => {
500+
const { wrapper } = await createFixtures(f => {
501+
f.withEnterpriseSso({ selfServeSso: false });
502+
f.withOrganizations();
503+
f.withUser({
504+
email_addresses: ['test@clerk.com'],
505+
organization_memberships: [
506+
{
507+
name: 'Org1',
508+
permissions: ['org:sys_entconns:manage'],
509+
},
510+
],
511+
});
512+
});
513+
514+
const { queryByText } = render(<OrganizationProfile />, { wrapper });
515+
await waitFor(() => expect(queryByText('Self-Serve SSO')).toBeNull());
516+
});
517+
518+
// We keep showing the section but the internal page displays a warning for missing permission
519+
520+
it('includes Self-Serve SSO even when the user does not have the manage enterprise connections permission', async () => {
521+
const { wrapper } = await createFixtures(f => {
522+
f.withEnterpriseSso({ selfServeSso: true });
523+
f.withOrganizations();
524+
f.withUser({
525+
email_addresses: ['test@clerk.com'],
526+
organization_memberships: [
527+
{
528+
name: 'Org1',
529+
permissions: [],
530+
},
531+
],
532+
});
533+
});
534+
535+
// TODO -> Add assertions for page content warning
536+
render(<OrganizationProfile />, { wrapper });
537+
expect(await screen.findByText('Self-Serve SSO')).toBeDefined();
538+
});
539+
});
540+
479541
it('removes member nav item if user is lacking permissions', async () => {
480542
const { wrapper } = await createFixtures(f => {
481543
f.withOrganizations();

packages/ui/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID = {
1212
MEMBERS: 'members',
1313
BILLING: 'billing',
1414
API_KEYS: 'apiKeys',
15+
SELF_SERVE_SSO: 'selfServeSso',
1516
};
1617

1718
export const USER_BUTTON_ITEM_ID = {

packages/ui/src/contexts/components/OrganizationProfile.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ export type OrganizationProfileContextType = OrganizationProfileCtx & {
2525
isGeneralPageRoot: boolean;
2626
isBillingPageRoot: boolean;
2727
isAPIKeysPageRoot: boolean;
28+
isSelfServeSsoPageRoot: boolean;
2829
shouldShowBilling: boolean;
30+
shouldShowSelfServeSso: boolean;
2931
};
3032

3133
export const OrganizationProfileContext = createContext<OrganizationProfileCtx | null>(null);
@@ -56,6 +58,9 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType
5658
// The C2 had a subscription in the past
5759
Boolean(statements.data.length > 0);
5860

61+
// TODO -> Check for org level as well
62+
const shouldShowSelfServeSso = environment.userSettings.enterpriseSSO.self_serve_sso;
63+
5964
const pages = useMemo(
6065
() => createOrganizationProfileCustomPages(customPages || [], clerk, shouldShowBilling, environment),
6166
[customPages, shouldShowBilling],
@@ -68,6 +73,7 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType
6873
const isGeneralPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.GENERAL;
6974
const isBillingPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.BILLING;
7075
const isAPIKeysPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.API_KEYS;
76+
const isSelfServeSsoPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SELF_SERVE_SSO;
7177
const navigateToGeneralPageRoot = () =>
7278
navigate(isGeneralPageRoot ? '../' : isMembersPageRoot ? './organization-general' : '../organization-general');
7379

@@ -81,6 +87,8 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType
8187
isGeneralPageRoot,
8288
isBillingPageRoot,
8389
isAPIKeysPageRoot,
90+
isSelfServeSsoPageRoot,
8491
shouldShowBilling,
92+
shouldShowSelfServeSso,
8593
};
8694
};

packages/ui/src/utils/createCustomPages.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
disabledOrganizationAPIKeysFeature,
33
disabledOrganizationBillingFeature,
4+
disabledSelfServeSSOFeature,
45
disabledUserAPIKeysFeature,
56
disabledUserBillingFeature,
67
} from '@clerk/shared/internal/clerk-js/componentGuards';
@@ -9,7 +10,7 @@ import type { CustomPage, EnvironmentResource, LoadedClerk } from '@clerk/shared
910

1011
import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID, USER_PROFILE_NAVBAR_ROUTE_ID } from '../constants';
1112
import type { NavbarRoute } from '../elements/Navbar';
12-
import { Code, CreditCard, Organization, TickShield, User, Users } from '../icons';
13+
import { Code, CreditCard, LinkIcon, Organization, TickShield, User, Users } from '../icons';
1314
import { localizationKeys } from '../localization';
1415
import { ExternalElementMounter } from './ExternalElementMounter';
1516
import { isDevelopmentSDK } from './runtimeEnvironment';
@@ -48,7 +49,15 @@ type GetDefaultRoutesReturnType = {
4849

4950
type CreateCustomPagesParams = {
5051
customPages: CustomPage[];
51-
getDefaultRoutes: ({ commerce, apiKeys }: { commerce: boolean; apiKeys: boolean }) => GetDefaultRoutesReturnType;
52+
getDefaultRoutes: ({
53+
commerce,
54+
apiKeys,
55+
selfServeSso,
56+
}: {
57+
commerce: boolean;
58+
apiKeys: boolean;
59+
selfServeSso: boolean;
60+
}) => GetDefaultRoutesReturnType;
5261
setFirstPathToRoot: (routes: NavbarRoute[]) => NavbarRoute[];
5362
excludedPathsFromDuplicateWarning: string[];
5463
};
@@ -106,6 +115,7 @@ const createCustomPages = (
106115
apiKeys: organization
107116
? !disabledOrganizationAPIKeysFeature(clerk, environment)
108117
: !disabledUserAPIKeysFeature(clerk, environment),
118+
selfServeSso: organization ? !disabledSelfServeSSOFeature(clerk, environment) : false,
109119
});
110120

111121
if (isDevelopmentSDK(clerk)) {
@@ -315,9 +325,11 @@ const getUserProfileDefaultRoutes = ({
315325
const getOrganizationProfileDefaultRoutes = ({
316326
commerce,
317327
apiKeys,
328+
selfServeSso,
318329
}: {
319330
commerce: boolean;
320331
apiKeys: boolean;
332+
selfServeSso: boolean;
321333
}): GetDefaultRoutesReturnType => {
322334
const INITIAL_ROUTES: NavbarRoute[] = [
323335
{
@@ -349,6 +361,14 @@ const getOrganizationProfileDefaultRoutes = ({
349361
path: 'organization-api-keys',
350362
});
351363
}
364+
if (selfServeSso) {
365+
INITIAL_ROUTES.push({
366+
name: localizationKeys('organizationProfile.navbar.selfServeSso'),
367+
id: ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SELF_SERVE_SSO,
368+
icon: LinkIcon,
369+
path: 'organization-self-serve-sso',
370+
});
371+
}
352372

353373
const pageToRootNavbarRouteMap: Record<string, NavbarRoute> = {
354374
'invite-members': INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS) as NavbarRoute,

0 commit comments

Comments
 (0)