Skip to content

Commit 8a10a9a

Browse files
committed
refactor(ui): make ConfigureSSO Wizard state-driven and render children
Replaces the route-based wizard primitive with a state-driven version: - Wizard renders {children} directly. Header, Footer, and step descriptors all render as siblings inside the wizard scope. The Switch / Route / useRouter routing layer is gone. - Wizard.Step is a self-rendering component now: it registers itself with the parent wizard via useLayoutEffect on mount, then renders its children only when its id matches the wizard's currentStep. Inactive steps render null. - Active-step state is internal — first registered step becomes the default; goNext / goPrev / goToStep mutate that state. Nested wizards still bubble to parent.goNext on inner-last-step continue. - Step descriptors no longer carry a path; navigation is purely by id. - Footer now reads the deepest wizard's value from reactive context state (not a ref), so navigation inside a nested wizard re-renders the Footer and isFirstStep / isLastStep stay in sync. Also picks up earlier in-flight work in ConfigureSSO: rename ConfigureSSOLayout to ConfigureSSOCard (the inner Col flex wrapper was dropped — the body Col on the card scrollbox owns sizing now), and Header / Steps / Footer mount as siblings inside the wizard scope so the breadcrumb and footer chrome render alongside the active step body.
1 parent 5010686 commit 8a10a9a

7 files changed

Lines changed: 269 additions & 246 deletions

File tree

packages/clerk-js/sandbox/template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@
456456
<script
457457
type="text/javascript"
458458
src="/<%= htmlRspackPlugin.files.js[0] %>"
459-
data-clerk-publishable-key="pk_test_c3VtbWFyeS1tYW4tOTkuY2xlcmsuYWNjb3VudHNzdGFnZS5kZXYk"
459+
data-clerk-publishable-key="pk_test_bW9kZWwtbWFzdGlmZi04Ny5jbGVyay5hY2NvdW50c3N0YWdlLmRldiQ"
460460
></script>
461461
<script
462462
type="text/javascript"

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

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,125 +5,117 @@ import React from 'react';
55
import { withCoreUserGuard } from '@/contexts';
66
import { descriptors, Flex, Flow, Spinner } from '@/customizables';
77
import { withCardStateProvider } from '@/elements/contexts';
8+
import { ProfileCard } from '@/elements/ProfileCard';
89
import { Route, Switch } from '@/router';
910

10-
import { ConfigureSSOFlowProvider } from './ConfigureSSOContext';
1111
import { ConfigureSSOFooter } from './ConfigureSSOFooter';
1212
import { ConfigureSSOHeader } from './ConfigureSSOHeader';
13-
import { ConfigureSSOLayout } from './ConfigureSSOLayout';
14-
import { FooterActionsProvider, Wizard } from './elements/Wizard';
13+
import { Wizard } from './elements/Wizard';
1514
import { ConfigureCreateApp, ConfirmationStep, ProvideEmail, TestConfigurationStep, VerifyDomainStep } from './steps';
1615

1716
const ConfigureSSOInternal = () => {
1817
return (
1918
<Flow.Root flow='configureSSO'>
20-
<Flow.Part>
21-
<Switch>
22-
<Route>
23-
<AuthenticatedContent />
24-
</Route>
25-
</Switch>
26-
</Flow.Part>
19+
<Switch>
20+
<Route>
21+
<AuthenticatedContent />
22+
</Route>
23+
</Switch>
2724
</Flow.Root>
2825
);
2926
};
3027

3128
const AuthenticatedContent = withCoreUserGuard(() => {
29+
return (
30+
<ProfileCard.Root
31+
sx={t => ({ display: 'grid', gridTemplateColumns: '1fr 3fr', height: t.sizes.$176, overflow: 'hidden' })}
32+
>
33+
<ConfigureSSOCardContent />
34+
</ProfileCard.Root>
35+
);
36+
});
37+
38+
const ConfigureSSOCardContent = () => {
3239
const { data: enterpriseConnections, isLoading } = __internal_useUserEnterpriseConnections({ enabled: true });
3340
// Currently FAPI only supports one enterprise connection per user
3441
const enterpriseConnection = enterpriseConnections?.[0];
3542

3643
// Initial-load gate at root — wizard never sees isLoading
37-
if (isLoading && !enterpriseConnections) {
44+
if (isLoading && !enterpriseConnection) {
3845
return (
39-
<ConfigureSSOLayout>
40-
<Flex
41-
align='center'
42-
justify='center'
43-
sx={{ flex: 1 }}
44-
>
45-
<Spinner
46-
size='xs'
47-
colorScheme='neutral'
48-
elementDescriptor={descriptors.spinner}
49-
/>
50-
</Flex>
51-
</ConfigureSSOLayout>
46+
<Flex
47+
align='center'
48+
justify='center'
49+
sx={{ flex: 1 }}
50+
>
51+
<Spinner
52+
size='xs'
53+
colorScheme='neutral'
54+
elementDescriptor={descriptors.spinner}
55+
/>
56+
</Flex>
5257
);
5358
}
5459

5560
return (
56-
<ConfigureSSOFlowProvider enterpriseConnection={enterpriseConnection}>
57-
<FooterActionsProvider>
58-
<ConfigureSSOLayout>
59-
<ConfigureSSOHeader />
60-
<ConfigureSSOSteps />
61-
<ConfigureSSOFooter />
62-
</ConfigureSSOLayout>
63-
</FooterActionsProvider>
64-
</ConfigureSSOFlowProvider>
61+
<Wizard>
62+
<ConfigureSSOHeader />
63+
64+
<ConfigureSSOSteps />
65+
66+
<ConfigureSSOFooter />
67+
</Wizard>
6568
);
66-
});
69+
};
6770

6871
const ConfigureSSOSteps = () => {
6972
const { user } = useUser();
7073

7174
const primaryEmailAddress = user?.primaryEmailAddress;
7275

7376
return (
74-
<Wizard>
77+
<>
7578
<Wizard.Step
7679
id='verify-email-domain'
77-
path='verify-email-domain'
7880
label='Verify domain'
7981
>
8082
<Wizard>
8183
{!primaryEmailAddress && (
82-
<Wizard.Step
83-
id='provide-email'
84-
path='provide-email'
85-
>
84+
<Wizard.Step id='provide-email'>
8685
<ProvideEmail />
8786
</Wizard.Step>
8887
)}
89-
<Wizard.Step
90-
id='verify-domain'
91-
path='verify-domain'
92-
>
88+
<Wizard.Step id='verify-domain'>
9389
<VerifyDomainStep />
9490
</Wizard.Step>
9591
</Wizard>
9692
</Wizard.Step>
93+
9794
<Wizard.Step
9895
id='configure'
99-
path='configure'
10096
label='Configure'
10197
>
10298
<Wizard>
103-
{/* TODO: Implement configure steps */}
104-
<Wizard.Step
105-
id='create-app'
106-
path='create-app'
107-
>
99+
<Wizard.Step id='create-app'>
108100
<ConfigureCreateApp />
109101
</Wizard.Step>
110102
</Wizard>
111103
</Wizard.Step>
104+
112105
<Wizard.Step
113106
id='test'
114-
path='test'
115107
label='Test'
116108
>
117109
<TestConfigurationStep />
118110
</Wizard.Step>
111+
119112
<Wizard.Step
120113
id='confirmation'
121-
path='confirmation'
122114
label='Confirmation'
123115
>
124116
<ConfirmationStep />
125117
</Wizard.Step>
126-
</Wizard>
118+
</>
127119
);
128120
};
129121

packages/ui/src/components/ConfigureSSO/ConfigureSSOLayout.tsx renamed to packages/ui/src/components/ConfigureSSO/ConfigureSSOCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { BoxIcon } from '@/icons';
1414
* body Col with flex sizing so the wizard / pre-wizard gates can fill
1515
* the available space without needing their own sizing chrome
1616
*/
17-
export const ConfigureSSOLayout = ({ children }: PropsWithChildren): JSX.Element => {
17+
export const ConfigureSSOCard = ({ children }: PropsWithChildren): JSX.Element => {
1818
const contentRef = React.useRef<HTMLDivElement>(null);
1919
const { applicationName, logoImageUrl } = useEnvironment().displayConfig;
2020
const { organizationSettings } = useEnvironment();
@@ -95,7 +95,7 @@ export const ConfigureSSOLayout = ({ children }: PropsWithChildren): JSX.Element
9595
flex: 1,
9696
})}
9797
>
98-
<Col sx={{ flex: 1, minHeight: 0 }}>{children}</Col>
98+
{children}
9999
</Col>
100100
</NavbarContextProvider>
101101
</ProfileCard.Root>

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@ export const ConfigureSSOFooter = ({
2626
hidePrevious = false,
2727
isDisabled = false,
2828
}: ConfigureSSOFooterProps): JSX.Element => {
29-
const { continueAction, deepestWizardRef } = useFooterActions();
29+
const { continueAction, deepestWizard } = useFooterActions();
3030
const { t } = useLocalizations();
3131

3232
const isForceDisabled = isDisabled;
33-
const deepest = deepestWizardRef.current?.current;
34-
const isFirstStep = deepest?.isFirstStep ?? true;
35-
const isLastStep = deepest?.isLastStep ?? true;
33+
const isFirstStep = deepestWizard?.isFirstStep ?? true;
34+
const isLastStep = deepestWizard?.isLastStep ?? true;
3635

3736
const continueLabelToShow =
3837
typeof continueAction?.label === 'string'
@@ -46,11 +45,11 @@ export const ConfigureSSOFooter = ({
4645
void continueAction.handler();
4746
return;
4847
}
49-
void deepestWizardRef.current?.current.goNext();
48+
void deepestWizard?.goNext();
5049
};
5150

5251
const handlePrevious = () => {
53-
void deepestWizardRef.current?.current.goPrev();
52+
void deepestWizard?.goPrev();
5453
};
5554

5655
return (

0 commit comments

Comments
 (0)