Skip to content

Commit 5010686

Browse files
committed
refactor(ui): switch ConfigureSSO to layered wizard architecture
Wires the new ConfigureSSO surface into the layered components shipped in the previous commits: - ConfigureSSO.tsx adopts the new shape — Flow.Part wrap inside Flow.Root for convention parity with OrganizationProfile, an initial load gate at the root that returns a centered spinner inside ConfigureSSOLayout, and a FooterActionsProvider wrap around the layout. The inline ProfileCard / NavBar / Col chrome moves out; ConfigureSSOLayout owns it now. - ConfigureSSOSteps switches its wizard imports from the old wizard/ folder to the new elements/Wizard/ primitive. The declarative step tree shape is preserved. - isLoading is lifted out of ConfigureSSOFlowContext and out of the wizard's context value. The root-level gate handles initial loading; subsequent re-fetches no longer flip the wizard into a loading state in context. ConfigureSSOFooter drops the isLoading-driven disable. - The Wizard primitive no longer imports useConfigureSSOFlow — it's a fully generic primitive at the navigation layer now. - The unused legacy wizard folder also drops its isLoading reads (spinner branch in Body, skeleton in Header, force-disable in Footer) so the typechecker stays clean while it sits on disk. The old wizard/ folder is unused after this commit but stays on disk for a final cleanup commit.
1 parent 8172b22 commit 5010686

7 files changed

Lines changed: 130 additions & 290 deletions

File tree

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

Lines changed: 63 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,67 @@
1-
import { __internal_useUserEnterpriseConnections, useOrganization, useUser } from '@clerk/shared/react';
1+
import { __internal_useUserEnterpriseConnections, useUser } from '@clerk/shared/react';
22
import type { __experimental_ConfigureSSOProps } from '@clerk/shared/types';
33
import React from 'react';
44

5-
import { useEnvironment, withCoreUserGuard } from '@/contexts';
6-
import { Box, Col, descriptors, Flex, Flow, Icon, localizationKeys, Text, useAppearance } from '@/customizables';
7-
import { ApplicationLogo } from '@/elements/ApplicationLogo';
5+
import { withCoreUserGuard } from '@/contexts';
6+
import { descriptors, Flex, Flow, Spinner } from '@/customizables';
87
import { withCardStateProvider } from '@/elements/contexts';
9-
import { NavBar, NavbarContextProvider } from '@/elements/Navbar';
10-
import { ProfileCard } from '@/elements/ProfileCard';
11-
import { BoxIcon } from '@/icons';
128
import { Route, Switch } from '@/router';
139

1410
import { ConfigureSSOFlowProvider } from './ConfigureSSOContext';
11+
import { ConfigureSSOFooter } from './ConfigureSSOFooter';
12+
import { ConfigureSSOHeader } from './ConfigureSSOHeader';
13+
import { ConfigureSSOLayout } from './ConfigureSSOLayout';
14+
import { FooterActionsProvider, Wizard } from './elements/Wizard';
1515
import { ConfigureCreateApp, ConfirmationStep, ProvideEmail, TestConfigurationStep, VerifyDomainStep } from './steps';
16-
import { ConfigureSSOWizard } from './wizard';
1716

1817
const ConfigureSSOInternal = () => {
1918
return (
2019
<Flow.Root flow='configureSSO'>
21-
<Switch>
22-
<Route>
23-
<AuthenticatedContent />
24-
</Route>
25-
</Switch>
20+
<Flow.Part>
21+
<Switch>
22+
<Route>
23+
<AuthenticatedContent />
24+
</Route>
25+
</Switch>
26+
</Flow.Part>
2627
</Flow.Root>
2728
);
2829
};
2930

3031
const AuthenticatedContent = withCoreUserGuard(() => {
31-
const contentRef = React.useRef<HTMLDivElement>(null);
32-
const { applicationName, logoImageUrl } = useEnvironment().displayConfig;
33-
const { organizationSettings } = useEnvironment();
34-
const { parsedOptions } = useAppearance();
35-
const hasLogo = Boolean(parsedOptions.logoImageUrl || logoImageUrl);
36-
37-
const { data: enterpriseConnections, isLoading: isLoadingEnterpriseConnections } =
38-
__internal_useUserEnterpriseConnections({ enabled: true });
32+
const { data: enterpriseConnections, isLoading } = __internal_useUserEnterpriseConnections({ enabled: true });
3933
// Currently FAPI only supports one enterprise connection per user
4034
const enterpriseConnection = enterpriseConnections?.[0];
4135

42-
return (
43-
<ProfileCard.Root
44-
sx={t => ({ display: 'grid', gridTemplateColumns: '1fr 3fr', height: t.sizes.$176, overflow: 'hidden' })}
45-
>
46-
<NavbarContextProvider contentRef={contentRef}>
47-
<NavBar
48-
header={
49-
<Flex
50-
align='center'
51-
sx={t => ({
52-
gap: t.space.$2,
53-
padding: `${t.space.$none} ${t.space.$3}`,
54-
maxWidth: '100%',
55-
})}
56-
>
57-
{hasLogo ? (
58-
<ApplicationLogo
59-
sx={t => ({ width: t.space.$9, height: t.space.$9, borderRadius: t.radii.$md, overflow: 'hidden' })}
60-
/>
61-
) : (
62-
<Box
63-
sx={t => ({
64-
width: t.space.$9,
65-
height: t.space.$9,
66-
flexShrink: 0,
67-
borderRadius: t.radii.$md,
68-
backgroundColor: t.colors.$primary500,
69-
color: t.colors.$colorPrimaryForeground,
70-
display: 'flex',
71-
alignItems: 'center',
72-
justifyContent: 'center',
73-
})}
74-
aria-hidden
75-
>
76-
<Icon
77-
icon={BoxIcon}
78-
sx={t => ({ width: t.sizes.$4, height: t.sizes.$4 })}
79-
/>
80-
</Box>
81-
)}
82-
83-
<Col sx={{ minWidth: 0 }}>
84-
<Text
85-
as='p'
86-
truncate
87-
>
88-
{applicationName}
89-
</Text>
90-
{organizationSettings.enabled && <OrganizationSidebarSubtitle />}
91-
</Col>
92-
</Flex>
93-
}
94-
titleSx={t => ({ fontSize: t.fontSizes.$lg })}
95-
title={localizationKeys('configureSSO.navbar.title')}
96-
routes={[]}
97-
contentRef={contentRef}
98-
/>
99-
<Col
100-
ref={contentRef}
101-
elementDescriptor={descriptors.scrollBox}
102-
sx={t => ({
103-
backgroundColor: t.colors.$colorBackground,
104-
position: 'relative',
105-
borderRadius: t.radii.$lg,
106-
width: '100%',
107-
overflow: 'hidden',
108-
borderWidth: t.borderWidths.$normal,
109-
borderStyle: t.borderStyles.$solid,
110-
borderColor: t.colors.$borderAlpha150,
111-
marginBlock: '-1px',
112-
marginInlineEnd: '-1px',
113-
flex: 1,
114-
})}
36+
// Initial-load gate at root — wizard never sees isLoading
37+
if (isLoading && !enterpriseConnections) {
38+
return (
39+
<ConfigureSSOLayout>
40+
<Flex
41+
align='center'
42+
justify='center'
43+
sx={{ flex: 1 }}
11544
>
116-
<ConfigureSSOFlowProvider
117-
enterpriseConnection={enterpriseConnection}
118-
isLoading={isLoadingEnterpriseConnections}
119-
>
120-
<ConfigureSSOSteps />
121-
</ConfigureSSOFlowProvider>
122-
</Col>
123-
</NavbarContextProvider>
124-
</ProfileCard.Root>
45+
<Spinner
46+
size='xs'
47+
colorScheme='neutral'
48+
elementDescriptor={descriptors.spinner}
49+
/>
50+
</Flex>
51+
</ConfigureSSOLayout>
52+
);
53+
}
54+
55+
return (
56+
<ConfigureSSOFlowProvider enterpriseConnection={enterpriseConnection}>
57+
<FooterActionsProvider>
58+
<ConfigureSSOLayout>
59+
<ConfigureSSOHeader />
60+
<ConfigureSSOSteps />
61+
<ConfigureSSOFooter />
62+
</ConfigureSSOLayout>
63+
</FooterActionsProvider>
64+
</ConfigureSSOFlowProvider>
12565
);
12666
});
12767

@@ -131,77 +71,59 @@ const ConfigureSSOSteps = () => {
13171
const primaryEmailAddress = user?.primaryEmailAddress;
13272

13373
return (
134-
<ConfigureSSOWizard>
135-
<ConfigureSSOWizard.Step
74+
<Wizard>
75+
<Wizard.Step
13676
id='verify-email-domain'
13777
path='verify-email-domain'
13878
label='Verify domain'
13979
>
140-
<ConfigureSSOWizard>
80+
<Wizard>
14181
{!primaryEmailAddress && (
142-
<ConfigureSSOWizard.Step
82+
<Wizard.Step
14383
id='provide-email'
14484
path='provide-email'
14585
>
14686
<ProvideEmail />
147-
</ConfigureSSOWizard.Step>
87+
</Wizard.Step>
14888
)}
149-
<ConfigureSSOWizard.Step
89+
<Wizard.Step
15090
id='verify-domain'
15191
path='verify-domain'
15292
>
15393
<VerifyDomainStep />
154-
</ConfigureSSOWizard.Step>
155-
</ConfigureSSOWizard>
156-
</ConfigureSSOWizard.Step>
157-
<ConfigureSSOWizard.Step
94+
</Wizard.Step>
95+
</Wizard>
96+
</Wizard.Step>
97+
<Wizard.Step
15898
id='configure'
15999
path='configure'
160100
label='Configure'
161101
>
162-
<ConfigureSSOWizard>
102+
<Wizard>
163103
{/* TODO: Implement configure steps */}
164-
<ConfigureSSOWizard.Step
104+
<Wizard.Step
165105
id='create-app'
166106
path='create-app'
167107
>
168108
<ConfigureCreateApp />
169-
</ConfigureSSOWizard.Step>
170-
</ConfigureSSOWizard>
171-
</ConfigureSSOWizard.Step>
172-
<ConfigureSSOWizard.Step
109+
</Wizard.Step>
110+
</Wizard>
111+
</Wizard.Step>
112+
<Wizard.Step
173113
id='test'
174114
path='test'
175115
label='Test'
176116
>
177117
<TestConfigurationStep />
178-
</ConfigureSSOWizard.Step>
179-
<ConfigureSSOWizard.Step
118+
</Wizard.Step>
119+
<Wizard.Step
180120
id='confirmation'
181121
path='confirmation'
182122
label='Confirmation'
183123
>
184124
<ConfirmationStep />
185-
</ConfigureSSOWizard.Step>
186-
</ConfigureSSOWizard>
187-
);
188-
};
189-
190-
const OrganizationSidebarSubtitle = () => {
191-
const { organization } = useOrganization();
192-
193-
if (!organization) {
194-
return null;
195-
}
196-
197-
return (
198-
<Text
199-
as='span'
200-
truncate
201-
sx={t => ({ color: t.colors.$colorMutedForeground })}
202-
>
203-
{organization?.name}
204-
</Text>
125+
</Wizard.Step>
126+
</Wizard>
205127
);
206128
};
207129

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,28 @@ export interface ConfigureSSOData {
1111
enterpriseConnection: EnterpriseConnectionResource | undefined;
1212
}
1313

14-
export interface ConfigureSSOContextValue extends ConfigureSSOData {
15-
/**
16-
* `true` while the parent is still fetching the user's enterprise
17-
* connection
18-
*/
19-
isLoading: boolean;
20-
}
21-
2214
interface ConfigureSSOFlowProviderProps {
2315
enterpriseConnection: EnterpriseConnectionResource | undefined;
24-
isLoading: boolean;
2516
}
2617

27-
const ConfigureSSOFlowContext = React.createContext<ConfigureSSOContextValue | null>(null);
18+
const ConfigureSSOFlowContext = React.createContext<ConfigureSSOData | null>(null);
2819
ConfigureSSOFlowContext.displayName = 'ConfigureSSOFlowContext';
2920

3021
export const ConfigureSSOFlowProvider = ({
3122
enterpriseConnection,
32-
isLoading,
3323
children,
3424
}: PropsWithChildren<ConfigureSSOFlowProviderProps>): JSX.Element => {
35-
const value = React.useMemo<ConfigureSSOContextValue>(
25+
const value = React.useMemo<ConfigureSSOData>(
3626
() => ({
3727
enterpriseConnection,
38-
isLoading,
3928
}),
40-
[enterpriseConnection, isLoading],
29+
[enterpriseConnection],
4130
);
4231

4332
return <ConfigureSSOFlowContext.Provider value={value}>{children}</ConfigureSSOFlowContext.Provider>;
4433
};
4534

46-
export const useConfigureSSOFlow = (): ConfigureSSOContextValue => {
35+
export const useConfigureSSOFlow = (): ConfigureSSOData => {
4736
const ctx = React.useContext(ConfigureSSOFlowContext);
4837
if (!ctx) {
4938
throw new Error('useConfigureSSOFlow called outside <ConfigureSSOFlowProvider>.');

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button, descriptors, Flex, Icon, useLocalizations } from '@/customizables';
22
import { CaretLeft, CaretRight } from '@/icons';
33

4-
import { useFooterActions, useWizard } from './elements/Wizard';
4+
import { useFooterActions } from './elements/Wizard';
55

66
interface ConfigureSSOFooterProps {
77
/** Override label for the Previous button */
@@ -26,11 +26,10 @@ export const ConfigureSSOFooter = ({
2626
hidePrevious = false,
2727
isDisabled = false,
2828
}: ConfigureSSOFooterProps): JSX.Element => {
29-
const { isLoading } = useWizard();
3029
const { continueAction, deepestWizardRef } = useFooterActions();
3130
const { t } = useLocalizations();
3231

33-
const isForceDisabled = isDisabled || isLoading;
32+
const isForceDisabled = isDisabled;
3433
const deepest = deepestWizardRef.current?.current;
3534
const isFirstStep = deepest?.isFirstStep ?? true;
3635
const isLastStep = deepest?.isLastStep ?? true;

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React from 'react';
22

33
import { Route, Switch, useRouter } from '@/router';
44

5-
import { useConfigureSSOFlow } from '../../ConfigureSSOContext';
65
import { FooterActionsProvider, useRegisterWizard } from './FooterActionsContext';
76
import type { WizardActiveStep, WizardContextValue, WizardStepProps } from './types';
87
import { WizardContext } from './WizardContext';
@@ -89,8 +88,6 @@ interface RootInnerProps {
8988

9089
const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.Element => {
9190
const router = useRouter();
92-
const flow = useConfigureSSOFlow();
93-
const { isLoading } = flow;
9491

9592
const activeSteps = React.useMemo(() => extractSteps(children), [children]);
9693

@@ -163,15 +160,14 @@ const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.El
163160
currentStep,
164161
currentIndex: index,
165162
totalSteps: activeSteps.length,
166-
isLoading,
167163
goNext,
168164
goPrev,
169165
goToStep,
170166
isNested,
171167
isFirstStep: index <= 0 && (!parentWizard || parentWizard.isFirstStep),
172168
isLastStep: index === activeSteps.length - 1 && (!parentWizard || parentWizard.isLastStep),
173169
};
174-
}, [activeSteps, currentStep, isLoading, goNext, goPrev, goToStep, isNested, parentWizard]);
170+
}, [activeSteps, currentStep, goNext, goPrev, goToStep, isNested, parentWizard]);
175171

176172
// Push this wizard onto the chrome stack so the shared footer can
177173
// dispatch Continue / Previous to the *deepest* mounted wizard,

packages/ui/src/components/ConfigureSSO/elements/Wizard/types.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,6 @@ export interface WizardContextValue {
105105
* wizard scope and there is no parent wizard to fall back on
106106
*/
107107
isLastStep: boolean;
108-
/**
109-
* `true` while the parent flow is still loading async dependencies.
110-
* The header renders a skeleton breadcrumb, the content renders a
111-
* centered spinner, and the footer's buttons are disabled
112-
*/
113-
isLoading: boolean;
114108
/**
115109
* Navigate forward. Within this wizard, advances to the next active
116110
* sibling. On the last sibling, falls through to the parent

0 commit comments

Comments
 (0)