Skip to content

Commit 0c08efd

Browse files
committed
refactor(ui): consolidate ConfigureSSO flow state and provider groups
Move ProviderType and WizardStepId into a shared types module and lift initialStepId derivation into the flow context so consumers read a single value instead of recomputing it. Replace the flat PROVIDER_OPTIONS list with grouped PROVIDER_GROUPS to support multiple provider categories, switch connection creation to __internal_useUserEnterpriseConnections, and drop the unused clearProvider/goPrev affordances on the Select Provider step.
1 parent 287033f commit 0c08efd

5 files changed

Lines changed: 69 additions & 63 deletions

File tree

packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { ConfigureSSOFlowProvider, useConfigureSSOFlow } from './ConfigureSSOCon
1414
import { ConfigureSSOHeader } from './ConfigureSSOHeader';
1515
import { ConfigureSSONavbar } from './ConfigureSSONavbar';
1616
import { ConfigureSSOSkeleton } from './ConfigureSSOSkeleton';
17-
import { deriveInitialStep } from './deriveInitialStep';
1817
import { ProfileCardFooter, ProfileCardHeader } from './elements/ProfileCard';
1918
import { Step } from './elements/Step';
2019
import { Wizard } from './elements/Wizard';
@@ -85,8 +84,7 @@ const ConfigureSSOCardContent = () => {
8584
};
8685

8786
const ConfigureSSOSteps = () => {
88-
const { enterpriseConnection } = useConfigureSSOFlow();
89-
const initialStepId = deriveInitialStep(enterpriseConnection);
87+
const { initialStepId } = useConfigureSSOFlow();
9088

9189
return (
9290
<Wizard initialStepId={initialStepId}>

packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { useReverification, useSession, useUser } from '@clerk/shared/react';
1+
import { __internal_useUserEnterpriseConnections, useSession, useUser } from '@clerk/shared/react';
22
import type { EnterpriseConnectionResource } from '@clerk/shared/types';
33
import React, { type PropsWithChildren } from 'react';
44

5-
export type ProviderType = 'saml_okta' | 'saml_custom';
5+
import { deriveInitialStep } from './deriveInitialStep';
6+
import type { ProviderType, WizardStepId } from './types';
67

78
/**
89
* Shared form state for the ConfigureSSO wizard, persisted across steps
910
*/
1011
export interface ConfigureSSOData {
12+
initialStepId: WizardStepId;
1113
/**
1214
* The enterprise connection from the user's primary email address domain
1315
*/
@@ -27,11 +29,6 @@ export interface ConfigureSSOData {
2729
* connection has been created.
2830
*/
2931
setProvider: (provider: ProviderType) => void;
30-
/**
31-
* Clears the local provider selection. Used by Reset on the Confirmation
32-
* step (wired up by a follow-up PR).
33-
*/
34-
clearProvider: () => void;
3532
/**
3633
* Creates the enterprise connection from the current provider selection,
3734
* the user's primary email domain, and the session's active organization.
@@ -56,11 +53,15 @@ export const ConfigureSSOFlowProvider = ({
5653
}: PropsWithChildren<ConfigureSSOFlowProviderProps>): JSX.Element => {
5754
const { user } = useUser();
5855
const { session } = useSession();
59-
const [localProvider, setLocalProvider] = React.useState<ProviderType | undefined>(undefined);
56+
const [provider, setProvider] = React.useState<ProviderType | undefined>(
57+
enterpriseConnection?.provider as ProviderType,
58+
);
59+
60+
const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections();
6061

61-
const provider = (enterpriseConnection?.provider as ProviderType | undefined) ?? localProvider;
62+
const initialStepId = deriveInitialStep(enterpriseConnection);
6263

63-
const createConnectionFetcher = React.useCallback(async () => {
64+
const createConnection = React.useCallback(async () => {
6465
if (enterpriseConnection) {
6566
return;
6667
}
@@ -74,33 +75,23 @@ export const ConfigureSSOFlowProvider = ({
7475
const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1];
7576
const organizationId = session?.lastActiveOrganizationId ?? null;
7677

77-
await user.createEnterpriseConnection({
78-
provider,
78+
await createEnterpriseConnection({
79+
provider: 'saml_okta',
7980
name: emailDomain,
8081
organizationId,
8182
});
82-
}, [enterpriseConnection, provider, user, session]);
83-
84-
const createConnection = useReverification(createConnectionFetcher);
85-
86-
const setProvider = React.useCallback((next: ProviderType) => {
87-
setLocalProvider(next);
88-
}, []);
89-
90-
const clearProvider = React.useCallback(() => {
91-
setLocalProvider(undefined);
92-
}, []);
83+
}, [enterpriseConnection, provider, user, session, createEnterpriseConnection]);
9384

9485
const value = React.useMemo<ConfigureSSOData>(
9586
() => ({
87+
initialStepId,
9688
enterpriseConnection,
9789
isLoading,
9890
provider,
9991
setProvider,
100-
clearProvider,
10192
createConnection,
10293
}),
103-
[enterpriseConnection, isLoading, provider, setProvider, clearProvider, createConnection],
94+
[initialStepId, enterpriseConnection, isLoading, provider, setProvider, createConnection],
10495
);
10596

10697
return <ConfigureSSOFlowContext.Provider value={value}>{children}</ConfigureSSOFlowContext.Provider>;

packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { EnterpriseConnectionResource } from '@clerk/shared/types';
22

3-
export type WizardStepId = 'select-provider' | 'verify-domain' | 'configure' | 'test' | 'confirmation';
3+
import type { WizardStepId } from './types';
44

55
/**
66
* Decides where the ConfigureSSO wizard should mount on (re)load based on

packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,34 @@ import { useCardState } from '@/elements/contexts';
1717
import { Alert } from '@/ui/elements/Alert';
1818
import { handleError } from '@/utils/errorHandler';
1919

20-
import { type ProviderType, useConfigureSSOFlow } from '../ConfigureSSOContext';
20+
import { useConfigureSSOFlow } from '../ConfigureSSOContext';
2121
import { Step } from '../elements/Step';
2222
import { useWizard } from '../elements/Wizard';
23+
import type { ProviderType } from '../types';
2324

24-
const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: LocalizationKey; iconId: string }> = [
25-
{ id: 'saml_okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' },
25+
const PROVIDER_GROUPS: ReadonlyArray<{
26+
id: 'saml';
27+
label: LocalizationKey;
28+
options: ReadonlyArray<{ id: ProviderType; label: LocalizationKey; iconId: string }>;
29+
}> = [
2630
{
27-
id: 'saml_custom',
28-
label: localizationKeys('configureSSO.selectProviderStep.saml.customSaml'),
29-
iconId: 'saml',
31+
id: 'saml',
32+
label: localizationKeys('configureSSO.selectProviderStep.saml.groupLabel'),
33+
options: [
34+
{ id: 'saml_okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' },
35+
{
36+
id: 'saml_custom',
37+
label: localizationKeys('configureSSO.selectProviderStep.saml.customSaml'),
38+
iconId: 'saml',
39+
},
40+
],
3041
},
3142
];
3243

3344
export const SelectProviderStep = (): JSX.Element => {
34-
const { goNext, goPrev, isFirstStep, isLastStep } = useWizard();
35-
const { setProvider, createConnection } = useConfigureSSOFlow();
3645
const card = useCardState();
46+
const { goNext, isLastStep } = useWizard();
47+
const { setProvider, createConnection } = useConfigureSSOFlow();
3748
const [selected, setSelected] = React.useState<ProviderType | null>(null);
3849

3950
const handleContinue = async () => {
@@ -84,29 +95,34 @@ export const SelectProviderStep = (): JSX.Element => {
8495
/>
8596
</Col>
8697

87-
<Col sx={theme => ({ gap: theme.space.$3 })}>
88-
<Text
89-
as='label'
90-
variant='subtitle'
91-
sx={theme => ({ color: theme.colors.$colorForeground })}
92-
localizationKey={localizationKeys('configureSSO.selectProviderStep.saml.groupLabel')}
93-
/>
94-
95-
<Grid
96-
gap={3}
97-
columns={2}
98+
{PROVIDER_GROUPS.map(group => (
99+
<Col
100+
key={group.id}
101+
sx={theme => ({ gap: theme.space.$3 })}
98102
>
99-
{PROVIDER_OPTIONS.map(option => (
100-
<ProviderCard
101-
key={option.id}
102-
iconId={option.iconId}
103-
label={option.label}
104-
isSelected={selected === option.id}
105-
onClick={() => setSelected(option.id)}
106-
/>
107-
))}
108-
</Grid>
109-
</Col>
103+
<Text
104+
as='label'
105+
variant='subtitle'
106+
sx={theme => ({ color: theme.colors.$colorForeground })}
107+
localizationKey={group.label}
108+
/>
109+
110+
<Grid
111+
gap={3}
112+
columns={2}
113+
>
114+
{group.options.map(option => (
115+
<ProviderCard
116+
key={option.id}
117+
iconId={option.iconId}
118+
label={option.label}
119+
isSelected={selected === option.id}
120+
onClick={() => setSelected(option.id)}
121+
/>
122+
))}
123+
</Grid>
124+
</Col>
125+
))}
110126

111127
<Alert
112128
variant='warning'
@@ -126,10 +142,8 @@ export const SelectProviderStep = (): JSX.Element => {
126142
</Step.Body>
127143

128144
<Step.Footer>
129-
<Step.Footer.Previous
130-
onClick={() => goPrev()}
131-
isDisabled={isFirstStep}
132-
/>
145+
<Step.Footer.Previous isDisabled />
146+
133147
<Step.Footer.Continue
134148
onClick={handleContinue}
135149
isLoading={card.isLoading}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type ProviderType = 'saml_okta' | 'saml_custom';
2+
3+
export type WizardStepId = 'select-provider' | 'verify-domain' | 'configure' | 'test' | 'confirmation';

0 commit comments

Comments
 (0)