Skip to content

Commit aa04cf0

Browse files
committed
Always display verify domain step
1 parent 798ee78 commit aa04cf0

5 files changed

Lines changed: 73 additions & 124 deletions

File tree

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

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

@@ -11,15 +11,8 @@ import { ProfileCard } from '@/elements/ProfileCard';
1111
import { BoxIcon } from '@/icons';
1212
import { Route, Switch } from '@/router';
1313

14-
import { ConfigureSSOFlowProvider, useConfigureSSOFlow } from './ConfigureSSOContext';
15-
import {
16-
ConfigureCreateApp,
17-
ConfigureMapAttributes,
18-
ConfirmationStep,
19-
ProvideEmail,
20-
TestConfigurationStep,
21-
VerifyDomain,
22-
} from './steps';
14+
import { ConfigureSSOFlowProvider } from './ConfigureSSOContext';
15+
import { ConfigureCreateApp, ConfirmationStep, ProvideEmail, TestConfigurationStep, VerifyDomain } from './steps';
2316
import { ConfigureSSOWizard } from './wizard';
2417

2518
const ConfigureSSOInternal = () => {
@@ -132,60 +125,47 @@ const AuthenticatedContent = withCoreUserGuard(() => {
132125
);
133126
});
134127

135-
/**
136-
* The full ConfigureSSO step tree, declared inline. Each
137-
* `<ConfigureSSOWizard.Step>` is one breadcrumb entry; nested
138-
* `<ConfigureSSOWizard>` blocks declare inner sub-step routing.
139-
*
140-
* Conditional rendering on a step (`{cond && <Step ... />}`) skips
141-
* it from the breadcrumb and from `goNext`/`goPrev` traversal — no
142-
* `shouldSkip` predicate needed
143-
*/
144128
const ConfigureSSOSteps = () => {
145-
const { domainAlreadyVerified } = useConfigureSSOFlow();
129+
const { user } = useUser();
130+
const primaryEmailAddress = user?.primaryEmailAddress;
146131

147132
return (
148133
<ConfigureSSOWizard>
149-
{!domainAlreadyVerified && (
150-
<ConfigureSSOWizard.Step
151-
id='verify-email-domain'
152-
path='verify-email-domain'
153-
label='Verify domain'
154-
>
155-
<ConfigureSSOWizard>
134+
<ConfigureSSOWizard.Step
135+
id='verify-email-domain'
136+
path='verify-email-domain'
137+
label='Verify domain'
138+
>
139+
<ConfigureSSOWizard>
140+
{!primaryEmailAddress && (
156141
<ConfigureSSOWizard.Step
157142
id='provide-email'
158143
path='provide-email'
159144
>
160145
<ProvideEmail />
161146
</ConfigureSSOWizard.Step>
162-
<ConfigureSSOWizard.Step
163-
id='verify-domain'
164-
path='verify-domain'
165-
>
166-
<VerifyDomain />
167-
</ConfigureSSOWizard.Step>
168-
</ConfigureSSOWizard>
169-
</ConfigureSSOWizard.Step>
170-
)}
147+
)}
148+
<ConfigureSSOWizard.Step
149+
id='verify-domain'
150+
path='verify-domain'
151+
>
152+
<VerifyDomain />
153+
</ConfigureSSOWizard.Step>
154+
</ConfigureSSOWizard>
155+
</ConfigureSSOWizard.Step>
171156
<ConfigureSSOWizard.Step
172157
id='configure'
173158
path='configure'
174159
label='Configure'
175160
>
176161
<ConfigureSSOWizard>
162+
{/* TODO: Implement configure steps */}
177163
<ConfigureSSOWizard.Step
178164
id='create-app'
179165
path='create-app'
180166
>
181167
<ConfigureCreateApp />
182168
</ConfigureSSOWizard.Step>
183-
<ConfigureSSOWizard.Step
184-
id='map-attributes'
185-
path='map-attributes'
186-
>
187-
<ConfigureMapAttributes />
188-
</ConfigureSSOWizard.Step>
189169
</ConfigureSSOWizard>
190170
</ConfigureSSOWizard.Step>
191171
<ConfigureSSOWizard.Step

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import { useUser } from '@clerk/shared/react/index';
21
import type { EnterpriseConnectionResource } from '@clerk/shared/types';
32
import React, { type PropsWithChildren } from 'react';
43

54
/**
6-
* Shared form state for the ConfigureSSO wizard, persisted across step
7-
* route mounts. Sourced by the wizard via `useConfigureSSOFlow()` and
8-
* passed to each step's `shouldSkip` predicate
5+
* Shared form state for the ConfigureSSO wizard, persisted across steps
96
*/
107
export interface ConfigureSSOData {
11-
/**
12-
* `true` if the user primary email address domain is already verified
13-
*/
14-
domainAlreadyVerified: boolean;
158
/**
169
* The enterprise connection from the user's primary email address domain
1710
*/
@@ -39,17 +32,12 @@ export const ConfigureSSOFlowProvider = ({
3932
isLoading,
4033
children,
4134
}: PropsWithChildren<ConfigureSSOFlowProviderProps>): JSX.Element => {
42-
const { user } = useUser();
43-
44-
const domainAlreadyVerified = user?.primaryEmailAddress?.verification.status === 'verified';
45-
4635
const value = React.useMemo<ConfigureSSOContextValue>(
4736
() => ({
4837
enterpriseConnection,
49-
domainAlreadyVerified,
5038
isLoading,
5139
}),
52-
[enterpriseConnection, domainAlreadyVerified, isLoading],
40+
[enterpriseConnection, isLoading],
5341
);
5442

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

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

Lines changed: 0 additions & 23 deletions
This file was deleted.

packages/ui/src/components/ConfigureSSO/steps/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export { ConfigureCreateApp } from './ConfigureCreateAppStep';
2-
export { ConfigureMapAttributes } from './ConfigureMapAttributesStep';
32
export { ConfirmationStep } from './ConfirmationStep';
43
export { ProvideEmail } from './ProvideEmailStep';
54
export { StepLayout } from './StepLayout';

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

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ import type {
1818
ConfigureSSOWizardStepProps,
1919
} from './types';
2020

21-
/**
22-
* Marker component for a single step in `<ConfigureSSOWizard>`. The
23-
* parent wizard reads its props directly off the JSX element; the
24-
* component itself never renders independently
25-
*/
2621
const Step = (_: ConfigureSSOWizardStepProps): JSX.Element | null => null;
2722
Step.displayName = 'ConfigureSSOWizard.Step';
2823

@@ -32,23 +27,28 @@ interface RootProps {
3227

3328
/**
3429
* Walks the wizard's children and returns the descriptors for every
35-
* `<ConfigureSSOWizard.Step>` element. Anything else (`false` from
36-
* `&&`, plain text, fragments, etc.) is silently ignored, so
37-
* conditional rendering in JSX naturally drives "skipping"
30+
* `<ConfigureSSOWizard.Step>` element
3831
*/
3932
function extractSteps(children: React.ReactNode): ConfigureSSOWizardActiveStep[] {
4033
const steps: ConfigureSSOWizardActiveStep[] = [];
4134

4235
React.Children.forEach(children, child => {
43-
if (!React.isValidElement(child)) return;
36+
if (!React.isValidElement(child)) {
37+
return;
38+
}
39+
4440
// Tolerate fragments at the top level (e.g. when users factor a
4541
// group of steps into a helper component that returns one)
4642
if (child.type === React.Fragment) {
4743
const fragmentChildren = (child.props as { children?: React.ReactNode }).children;
4844
steps.push(...extractSteps(fragmentChildren));
4945
return;
5046
}
51-
if (child.type !== Step) return;
47+
48+
if (child.type !== Step) {
49+
return;
50+
}
51+
5252
const props = child.props as ConfigureSSOWizardStepProps;
5353
steps.push({
5454
id: props.id,
@@ -103,11 +103,14 @@ const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.El
103103

104104
const activeSteps = React.useMemo(() => extractSteps(children), [children]);
105105

106-
// Match the URL against non-first steps (most-specific first); the
106+
// Match the URL against non-first steps (most-specific first), the
107107
// first step is mounted as the index route and is always the
108108
// fallback when nothing else matches
109109
const currentStep = React.useMemo<ConfigureSSOWizardActiveStep | undefined>(() => {
110-
if (activeSteps.length === 0) return undefined;
110+
if (activeSteps.length === 0) {
111+
return undefined;
112+
}
113+
111114
return (
112115
activeSteps
113116
.slice(1)
@@ -130,22 +133,30 @@ const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.El
130133
);
131134

132135
const goNext = React.useCallback(() => {
133-
if (!currentStep) return;
134-
const idx = activeSteps.findIndex(s => s.id === currentStep.id);
135-
const next = activeSteps[idx + 1];
136-
if (next) return navigateTo(next);
137-
// End of *this* wizard's siblings → cascade to the parent so
138-
// the outer wizard advances past us to the next outer step
136+
if (!currentStep) {
137+
return;
138+
}
139+
140+
const index = activeSteps.findIndex(s => s.id === currentStep.id);
141+
const next = activeSteps[index + 1];
142+
if (next) {
143+
return navigateTo(next);
144+
}
145+
139146
return parentWizard?.goNext();
140147
}, [activeSteps, currentStep, navigateTo, parentWizard]);
141148

142149
const goPrev = React.useCallback(() => {
143-
if (!currentStep) return;
144-
const idx = activeSteps.findIndex(s => s.id === currentStep.id);
145-
const prev = activeSteps[idx - 1];
146-
if (prev) return navigateTo(prev);
147-
// At the first sibling — defer to the parent so the user can
148-
// step back into the previous outer step's last position
150+
if (!currentStep) {
151+
return;
152+
}
153+
154+
const index = activeSteps.findIndex(s => s.id === currentStep.id);
155+
const prev = activeSteps[index - 1];
156+
if (prev) {
157+
return navigateTo(prev);
158+
}
159+
149160
return parentWizard?.goPrev();
150161
}, [activeSteps, currentStep, navigateTo, parentWizard]);
151162

@@ -155,21 +166,19 @@ const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.El
155166
);
156167

157168
const value = React.useMemo<ConfigureSSOWizardContextValue>(() => {
158-
const idx = currentStep ? activeSteps.findIndex(s => s.id === currentStep.id) : -1;
169+
const index = currentStep ? activeSteps.findIndex(s => s.id === currentStep.id) : -1;
159170
return {
160171
activeSteps,
161172
currentStep,
162-
currentIndex: idx,
173+
currentIndex: index,
163174
totalSteps: activeSteps.length,
164-
// First/last only count as edges when there's nothing past us
165-
// to fall back on (otherwise the parent wizard absorbs the move)
166-
isFirstStep: idx <= 0 && !parentWizard,
167-
isLastStep: idx === activeSteps.length - 1 && !parentWizard,
168175
isLoading,
169176
goNext,
170177
goPrev,
171178
goToStep,
172179
isNested,
180+
isFirstStep: index <= 0 && !parentWizard,
181+
isLastStep: index === activeSteps.length - 1 && !parentWizard,
173182
};
174183
}, [activeSteps, currentStep, isLoading, goNext, goPrev, goToStep, isNested, parentWizard]);
175184

@@ -181,13 +190,10 @@ const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.El
181190
const body = <Body activeSteps={activeSteps} />;
182191

183192
if (isNested) {
184-
// Nested wizards plug into the parent's chrome; they only own
185-
// the inner routing for their step's body
186193
return <ConfigureSSOWizardContext.Provider value={value}>{body}</ConfigureSSOWizardContext.Provider>;
187194
}
188195

189-
// Outermost wizard owns the full layout: breadcrumb on top, the
190-
// step body in the middle, the shared footer at the bottom
196+
// Outermost wizard owns the full layout
191197
return (
192198
<ConfigureSSOWizardContext.Provider value={value}>
193199
<Header />
@@ -198,15 +204,15 @@ const RootInner = ({ parentWizard, isNested, children }: RootInnerProps): JSX.El
198204
};
199205

200206
/**
201-
* Renders the active step's body (or a spinner while async deps are
202-
* loading). Each step is mounted at its `path`; the first step is the
203-
* index/catch-all so the wizard's base URL renders something
207+
* Renders the active step's body
204208
*/
205209
const Body = ({ activeSteps }: { activeSteps: ConfigureSSOWizardActiveStep[] }): JSX.Element | null => {
206210
const { isLoading, isNested } = useConfigureSSOWizard();
207211

208212
if (isLoading) {
209-
if (isNested) return null;
213+
if (isNested) {
214+
return null;
215+
}
210216
return (
211217
<Flex
212218
align='center'
@@ -358,8 +364,7 @@ const SkeletonBreadcrumbStep = (): JSX.Element => (
358364
/**
359365
* Compact "Step X / Y" badge that mirrors the *nearest* wizard's
360366
* progress. Renders nothing when the nearest wizard has only one
361-
* step (i.e. there's nothing meaningful to count). Drop this inside
362-
* an inner wizard's step layout to surface inner-step progress
367+
* step
363368
*/
364369
const StepIndicator = (): JSX.Element | null => {
365370
const { totalSteps, currentIndex } = useConfigureSSOWizard();
@@ -499,10 +504,10 @@ const Footer = (props: FooterProps): JSX.Element => {
499504
*
500505
* Steps are written as JSX children: render a `<ConfigureSSOWizard.Step>`
501506
* for each step and toggle visibility with regular conditional
502-
* expressions (`{cond && <Step>...</Step>}`). Inner sub-steps are
503-
* declared by nesting another `<ConfigureSSOWizard>` inside a step's
504-
* body — the inner wizard's `goNext` cascades to the outer one when
505-
* it runs out of inner steps
507+
* expressions (`{cond && <Step>...</Step>}`)
508+
*
509+
* Inner sub-steps are declared by nesting another `<ConfigureSSOWizard>` inside
510+
* a step's body
506511
*/
507512
export const ConfigureSSOWizard = Object.assign(Root, {
508513
Step,

0 commit comments

Comments
 (0)