Skip to content

Commit 11fb804

Browse files
committed
feat(ui): replace the ConfigureSSO confirmation step with an Activate step
The self-serve SSO setup wizard now ends on an explicit Activate step instead of a static confirmation summary. After configuring and testing a connection, the user activates it or skips for now; an already-active connection shows an active state with a Done action. The wizard step labels are renamed to Domains, Connection, Test, and Activate, and the step uses a duotone shield-check icon.
1 parent c84f8df commit 11fb804

17 files changed

Lines changed: 388 additions & 363 deletions

File tree

.changeset/moody-carrots-make.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/localizations': patch
3+
'@clerk/shared': patch
4+
'@clerk/ui': patch
5+
---
6+
7+
The SSO setup flow now ends on an explicit Activate step: after configuring and testing a connection you confirm activation with an Activate SSO action (or skip and activate later) instead of a static confirmation summary.

packages/localizations/src/en-US.ts

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,16 @@ export const enUS: LocalizationResource = {
211211
yearPerUnit: 'Year per {{unitName}}',
212212
},
213213
configureSSO: {
214+
activate: {
215+
activateButton: 'Activate SSO',
216+
activeSubtitle: 'Anyone signing in with {{domain}} must use your identity provider.',
217+
activeTitle: 'SSO connection is active',
218+
doneButton: 'Done',
219+
skipButton: 'Skip for now',
220+
subtitle:
221+
'Your SSO connection is ready. Once activated, anyone signing in with {{domain}} must use your identity provider.',
222+
title: 'SSO connection configured',
223+
},
214224
configureStep: {
215225
attributeMappingTable: {
216226
badges: {
@@ -635,35 +645,6 @@ export const enUS: LocalizationResource = {
635645
mainHeaderTitle: 'Configure Okta Workforce',
636646
},
637647
},
638-
confirmation: {
639-
configurationSection: {
640-
configureAgainLink: 'Configure again',
641-
issuerLabel: 'Issuer',
642-
ssoUrlLabel: 'Sign on URL',
643-
title: 'Configuration details',
644-
},
645-
domainSection: {
646-
title: 'Domain',
647-
},
648-
enableSection: {
649-
title: 'Enable SSO',
650-
},
651-
inactiveBanner: {
652-
title: 'SSO is inactive and you need to enable it to authenticate',
653-
},
654-
resetSection: {
655-
confirmationFieldLabel: 'Type "{{name}}" to confirm',
656-
submitButton: 'Reset connection',
657-
title: 'Reset connection',
658-
warning:
659-
'This will permanently remove the SSO configuration. Members will no longer be able to sign in with SSO.',
660-
},
661-
statusSection: {
662-
activeBadge: 'Active',
663-
inactiveBadge: 'Inactive',
664-
title: 'SSO Successfully configured',
665-
},
666-
},
667648
missingManageEnterpriseConnectionsPermission: {
668649
subtitle: "Contact your organization's administrator to upgrade your permissions.",
669650
title: 'You do not have permission to manage Single Sign-on (SSO)',

packages/shared/src/types/localization.ts

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,33 +1830,14 @@ export type __internal_LocalizationResource = {
18301830
};
18311831
};
18321832
};
1833-
confirmation: {
1834-
statusSection: {
1835-
title: LocalizationValue;
1836-
activeBadge: LocalizationValue;
1837-
inactiveBadge: LocalizationValue;
1838-
};
1839-
enableSection: {
1840-
title: LocalizationValue;
1841-
};
1842-
domainSection: {
1843-
title: LocalizationValue;
1844-
};
1845-
configurationSection: {
1846-
title: LocalizationValue;
1847-
ssoUrlLabel: LocalizationValue;
1848-
issuerLabel: LocalizationValue;
1849-
configureAgainLink: LocalizationValue;
1850-
};
1851-
resetSection: {
1852-
title: LocalizationValue;
1853-
warning: LocalizationValue;
1854-
confirmationFieldLabel: LocalizationValue<'name'>;
1855-
submitButton: LocalizationValue;
1856-
};
1857-
inactiveBanner: {
1858-
title: LocalizationValue;
1859-
};
1833+
activate: {
1834+
title: LocalizationValue;
1835+
subtitle: LocalizationValue<'domain'>;
1836+
activateButton: LocalizationValue;
1837+
skipButton: LocalizationValue;
1838+
activeTitle: LocalizationValue;
1839+
activeSubtitle: LocalizationValue<'domain'>;
1840+
doneButton: LocalizationValue;
18601841
};
18611842
};
18621843
apiKeys: {

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { ConfigureSSOHeader } from './ConfigureSSOHeader';
77
import { areAllOrganizationDomainsVerified } from './domain/organizationEnterpriseConnection';
88
import { Wizard, type WizardStepConfig } from './elements/Wizard';
99
import {
10+
ActivateStep,
1011
ConfigureStep,
11-
ConfirmationStep,
1212
OrganizationDomainsStep,
1313
SelectProviderStep,
1414
TestConfigurationStep,
@@ -26,11 +26,11 @@ export const ConfigureSSOWizard = ({ title, forceInitialStep, ...props }: Config
2626

2727
const steps = React.useMemo<WizardStepConfig[]>(
2828
() => [
29-
{ id: 'verify-domain', label: 'Verify domain' },
29+
{ id: 'verify-domain', label: 'Domains' },
3030
{ id: 'select-provider', guard: () => allDomainsVerified },
31-
{ id: 'configure', label: 'Configure', guard: () => c.hasConnection },
31+
{ id: 'configure', label: 'Connection', guard: () => c.hasConnection },
3232
{ id: 'test', label: 'Test', guard: () => c.hasMinimumConfiguration || c.isActive },
33-
{ id: 'confirmation', label: 'Confirmation', guard: () => c.hasSuccessfulTestRun || c.isActive },
33+
{ id: 'activate', label: 'Activate', guard: () => c.hasSuccessfulTestRun || c.isActive },
3434
],
3535
[c, allDomainsVerified],
3636
);
@@ -69,9 +69,9 @@ export const ConfigureSSOWizard = ({ title, forceInitialStep, ...props }: Config
6969
</CardStateProvider>
7070
</Wizard.Match>
7171

72-
<Wizard.Match id='confirmation'>
72+
<Wizard.Match id='activate'>
7373
<CardStateProvider>
74-
<ConfirmationStep />
74+
<ActivateStep />
7575
</CardStateProvider>
7676
</Wizard.Match>
7777
</Wizard>

packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.navigation.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,30 @@ describe('ConfigureSSO wizard navigation (integration)', () => {
225225
expect(queryByText(/at least one successful test run/i)).not.toBeInTheDocument();
226226
});
227227
});
228+
229+
// The labelled steps surface in the stepper under their renamed labels, and an
230+
// active connection short-circuits to the (reachable) activate step.
231+
it('renders the renamed stepper labels and reaches the activate step', async () => {
232+
const { wrapper, fixtures } = await createFixtures(withAdminOrgUser);
233+
234+
fixtures.clerk.organization?.getEnterpriseConnections.mockResolvedValue([
235+
{ ...configuredConnection, id: 'ent_active', active: true } as any,
236+
]);
237+
fixtures.clerk.organization?.getEnterpriseConnectionTestRuns.mockResolvedValue({
238+
data: [],
239+
total_count: 0,
240+
} as any);
241+
mockVerifiedDomains(fixtures);
242+
243+
const { findByText, getByText } = render(<ConfigureSSO />, { wrapper });
244+
245+
// The activate step body renders (active connection short-circuits to the already-active variant).
246+
await findByText(/sso connection is active/i);
247+
248+
// The stepper carries the renamed labels (select-provider stays unlabelled).
249+
expect(getByText('Domains')).toBeInTheDocument();
250+
expect(getByText('Connection')).toBeInTheDocument();
251+
expect(getByText('Test')).toBeInTheDocument();
252+
expect(getByText('Activate')).toBeInTheDocument();
253+
});
228254
});

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

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ describe('ConfigureSSO', () => {
158158
expect(queryByText(/select your identity provider/i)).not.toBeInTheDocument();
159159
});
160160

161-
it('short-circuits to the confirmation step for an active connection', async () => {
161+
it('short-circuits to the activate step for an active connection', async () => {
162162
const { wrapper, fixtures } = await createFixtures(f => {
163163
f.withEnterpriseSso({ selfServeSSO: true });
164164
f.withEmailAddress();
@@ -177,7 +177,7 @@ describe('ConfigureSSO', () => {
177177
active: true,
178178
// Owned by the active organization (matches the membership above), so
179179
// the domain is not "taken by another org" and the machine can
180-
// short-circuit to confirmation.
180+
// short-circuit to activate.
181181
organizationId: 'Org1',
182182
domains: ['clerk.com'],
183183
samlConnection: {
@@ -195,8 +195,8 @@ describe('ConfigureSSO', () => {
195195

196196
const { findByText, queryByText } = render(<ConfigureSSO />, { wrapper });
197197

198-
// An active connection lands on confirmation even if never tested.
199-
await findByText(/configuration/i);
198+
// An active connection lands on the activate-step's already-active variant.
199+
await findByText(/sso connection is active/i);
200200
expect(queryByText(/select your identity provider/i)).not.toBeInTheDocument();
201201
});
202202

@@ -229,7 +229,7 @@ describe('ConfigureSSO', () => {
229229
} as any,
230230
]);
231231
mockOrganizationDomains(fixtures, [verifiedDomain]);
232-
// No successful run yet, so the confirmation guard fails and the
232+
// No successful run yet, so the activate guard fails and the
233233
// furthest-reachable step is `test`.
234234
fixtures.clerk.organization?.getEnterpriseConnectionTestRuns.mockResolvedValue({
235235
data: [],
@@ -239,12 +239,12 @@ describe('ConfigureSSO', () => {
239239
const { findByText, queryByText } = render(<ConfigureSSO />, { wrapper });
240240

241241
// Configured + inactive + no successful run ⇒ lands on the test step, not
242-
// confirmation.
242+
// activate.
243243
await findByText(/test your sso connection/i);
244-
expect(queryByText(/configuration details/i)).not.toBeInTheDocument();
244+
expect(queryByText(/sso connection configured/i)).not.toBeInTheDocument();
245245
});
246246

247-
it('mounts on confirmation for a configured-but-inactive connection that has a successful test run', async () => {
247+
it('mounts on activate for a configured-but-inactive connection that has a successful test run', async () => {
248248
const { wrapper, fixtures } = await createFixtures(f => {
249249
f.withEnterpriseSso({ selfServeSSO: true });
250250
f.withEmailAddress();
@@ -272,10 +272,10 @@ describe('ConfigureSSO', () => {
272272
} as any,
273273
]);
274274
mockOrganizationDomains(fixtures, [verifiedDomain]);
275-
// A successful run satisfies the confirmation guard (`hasSuccessfulTestRun`)
275+
// A successful run satisfies the activate guard (`hasSuccessfulTestRun`)
276276
// even though the connection is still inactive — the success-filtered probe
277277
// returns a row, so the furthest-reachable step clears `test` and lands on
278-
// confirmation. Distinct from the active short-circuit above (here
278+
// activate. Distinct from the active short-circuit above (here
279279
// `active: false`, so it is the test run — not activation — that carries the
280280
// wizard past the test step).
281281
fixtures.clerk.organization?.getEnterpriseConnectionTestRuns.mockResolvedValue({
@@ -285,9 +285,8 @@ describe('ConfigureSSO', () => {
285285

286286
const { findByText, queryByText } = render(<ConfigureSSO />, { wrapper });
287287

288-
// Lands on confirmation (the inactive badge + configuration details render),
289-
// not the test step.
290-
await findByText(/configuration details/i);
288+
// Lands on the activate step (not-active variant: "SSO connection configured" renders).
289+
await findByText(/sso connection configured/i);
291290
expect(queryByText(/test your sso connection/i)).not.toBeInTheDocument();
292291
});
293292
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ const FooterReset = (): JSX.Element | null => {
222222
size='sm'
223223
colorScheme='danger'
224224
onClick={() => setIsOpen(true)}
225-
localizationKey={localizationKeys('configureSSO.confirmation.resetSection.title')}
225+
localizationKey={localizationKeys('configureSSO.resetConnectionDialog.resetButton')}
226226
sx={{ marginInlineEnd: 'auto' }}
227227
/>
228228
<ResetConnectionDialog

0 commit comments

Comments
 (0)