From 8f36c9c6cd270ec20aa3cb2bd0a5e5f30dd12093 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 7 May 2026 17:00:35 -0300 Subject: [PATCH 01/26] feat(ui): add provider selection grid to ConfigureSSO step Replace placeholder text in SelectProviderStep with a SAML provider selection UI featuring Okta Workforce and Custom SAML Provider cards. Introduces a ProviderCard component with selected state styling and adjusts Step header alignment to top-align with refined spacing. --- .../components/ConfigureSSO/elements/Step.tsx | 4 +- .../ConfigureSSO/steps/SelectProviderStep.tsx | 92 ++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/elements/Step.tsx b/packages/ui/src/components/ConfigureSSO/elements/Step.tsx index a3fc56adebd..10d39a377d1 100644 --- a/packages/ui/src/components/ConfigureSSO/elements/Step.tsx +++ b/packages/ui/src/components/ConfigureSSO/elements/Step.tsx @@ -54,11 +54,11 @@ const Header = ({ title, description, children }: StepHeaderProps): JSX.Element })} > ({ gap: theme.space.$4 })} > - ({ gap: theme.space.$1x5, minWidth: 0 })}> + ({ gap: theme.space.$2, minWidth: 0 })}> ({ color: theme.colors.$colorForeground, fontSize: theme.fontSizes.$lg })} diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index f27de65c496..faaba8e356b 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -1,4 +1,6 @@ -import { descriptors, Flow, Text } from '@/customizables'; +import type { PropsWithChildren } from 'react'; + +import { Box, Col, descriptors, Flow, Grid, SimpleButton, Text } from '@/customizables'; import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; @@ -18,8 +20,42 @@ export const SelectProviderStep = (): JSX.Element => { /> - - UI goes here + ({ gap: theme.space.$5 })}> + ({ gap: theme.space.$1x5 })}> + ({ color: theme.colors.$colorForeground })} + > + Select your identity provider + + + ({ color: theme.colors.$colorMutedForeground })} + > + We'll guide you through the detailed setup process next. + + + + ({ gap: theme.space.$3 })}> + ({ color: theme.colors.$colorForeground })} + > + SAML + + + + Okta Workforce + Custom SAML Provider + + @@ -37,3 +73,53 @@ export const SelectProviderStep = (): JSX.Element => { ); }; + +type ProviderCardProps = PropsWithChildren<{ + isSelected?: boolean; + onClick?: () => void; +}>; + +const ProviderCard = ({ isSelected, onClick, children }: ProviderCardProps): JSX.Element => { + return ( + ({ + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: theme.space.$2, + height: theme.sizes.$32, + padding: theme.space.$1x5, + backgroundColor: theme.colors.$colorBackground, + ...(isSelected + ? { + boxShadow: `0 0 0 4px ${theme.colors.$colorRing}`, + } + : {}), + })} + > + {/* TODO: add provider icons */} + ({ + width: theme.sizes.$8, + height: theme.sizes.$8, + borderRadius: theme.radii.$md, + backgroundColor: theme.colors.$primary500, + })} + /> + + ({ color: theme.colors.$colorForeground })} + > + {children} + + + ); +}; From c39714273feae9d7784de95cc4b1fcf6cd9da369 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 15:50:20 -0300 Subject: [PATCH 02/26] feat(ui): wire provider selection state and real icons in ConfigureSSO step - Add local useState for the selected provider (okta | custom_saml) - Render real provider icons via iconImageUrl from img.clerk.com CDN - Gate Step.Footer.Continue on selection - Add provider lock-in warning Alert below the SAML group - Drop placeholder icon Box and unused background color --- .../ConfigureSSO/steps/SelectProviderStep.tsx | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index faaba8e356b..d5451f9a52f 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -1,12 +1,22 @@ -import type { PropsWithChildren } from 'react'; +import { iconImageUrl } from '@clerk/shared/constants'; +import React from 'react'; import { Box, Col, descriptors, Flow, Grid, SimpleButton, Text } from '@/customizables'; +import { Alert } from '@/ui/elements/Alert'; import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; +type ProviderType = 'okta' | 'custom_saml'; + +const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: string; iconId: string }> = [ + { id: 'okta', label: 'Okta Workforce', iconId: 'okta' }, + { id: 'custom_saml', label: 'Custom SAML Provider', iconId: 'saml' }, +]; + export const SelectProviderStep = (): JSX.Element => { const { goNext, goPrev, isFirstStep, isLastStep } = useWizard(); + const [selected, setSelected] = React.useState(null); return ( @@ -52,10 +62,21 @@ export const SelectProviderStep = (): JSX.Element => { gap={3} columns={2} > - Okta Workforce - Custom SAML Provider + {PROVIDER_OPTIONS.map(option => ( + setSelected(option.id)} + /> + ))} + + + Once a provider is selected you cannot change again until the configuration is over + @@ -66,7 +87,7 @@ export const SelectProviderStep = (): JSX.Element => { /> goNext()} - isDisabled={isLastStep} + isDisabled={isLastStep || !selected} /> @@ -74,12 +95,14 @@ export const SelectProviderStep = (): JSX.Element => { ); }; -type ProviderCardProps = PropsWithChildren<{ +type ProviderCardProps = { + iconId: string; + label: string; isSelected?: boolean; onClick?: () => void; -}>; +}; -const ProviderCard = ({ isSelected, onClick, children }: ProviderCardProps): JSX.Element => { +const ProviderCard = ({ iconId, label, isSelected, onClick }: ProviderCardProps): JSX.Element => { return ( - {/* TODO: add provider icons */} ({ width: theme.sizes.$8, height: theme.sizes.$8, - borderRadius: theme.radii.$md, - backgroundColor: theme.colors.$primary500, + backgroundImage: `url(${iconImageUrl(iconId)})`, + backgroundSize: 'contain', + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', })} /> @@ -118,7 +142,7 @@ const ProviderCard = ({ isSelected, onClick, children }: ProviderCardProps): JSX variant='body' sx={theme => ({ color: theme.colors.$colorForeground })} > - {children} + {label} ); From aea744a3009a9f78d9acefaa1e0efc68a638debd Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 15:53:12 -0300 Subject: [PATCH 03/26] test(ui): add unit tests for ConfigureSSO provider selection step Covers mount, tile labels and CDN icons, selection state via aria-pressed, Continue gating, and goNext invocation on Continue. --- .../__tests__/SelectProviderStep.test.tsx | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx new file mode 100644 index 00000000000..f492245ab24 --- /dev/null +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -0,0 +1,117 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { bindCreateFixtures } from '@/test/create-fixtures'; +import { render, screen } from '@/test/utils'; + +const goNext = vi.fn(); +const goPrev = vi.fn(); + +vi.mock('../../elements/Wizard', () => ({ + useWizard: () => ({ + activeSteps: [], + currentStep: undefined, + currentIndex: -1, + totalSteps: 0, + isFirstStep: true, + isLastStep: false, + isNested: false, + goNext, + goPrev, + goToStep: vi.fn(), + registerStep: vi.fn(), + unregisterStep: vi.fn(), + }), +})); + +import { SelectProviderStep } from '../SelectProviderStep'; + +const { createFixtures } = bindCreateFixtures('ConfigureSSO'); + +describe('SelectProviderStep', () => { + it('mounts and renders the step header', async () => { + const { wrapper } = await createFixtures(); + render(, { wrapper }); + + expect(screen.getByRole('heading', { name: 'Select provider' })).toBeInTheDocument(); + expect(screen.getByText('Select your identity provider')).toBeInTheDocument(); + }); + + it('renders both SAML provider tiles with their labels', async () => { + const { wrapper } = await createFixtures(); + render(, { wrapper }); + + expect(screen.getByRole('button', { name: 'Okta Workforce' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Custom SAML Provider' })).toBeInTheDocument(); + }); + + it('loads each tile icon from img.clerk.com', async () => { + const { wrapper } = await createFixtures(); + const { container } = render(, { wrapper }); + + // Emotion serializes sx into stylesheets, so we check both inline + the document's collected styles + const iconSpans = Array.from(container.querySelectorAll('button span[aria-hidden]')); + expect(iconSpans).toHaveLength(2); + + const collectedStyles = [ + ...Array.from(document.head.querySelectorAll('style')).map(s => s.textContent ?? ''), + ...iconSpans.map(el => (el as HTMLElement).style.backgroundImage ?? ''), + ].join('\n'); + + expect(collectedStyles).toMatch(/img\.clerk\.com\/static\/okta\.svg/); + expect(collectedStyles).toMatch(/img\.clerk\.com\/static\/saml\.svg/); + }); + + it('disables Continue when no provider is selected', async () => { + const { wrapper } = await createFixtures(); + render(, { wrapper }); + + expect(screen.getByRole('button', { name: /Continue/i })).toBeDisabled(); + }); + + it('marks the clicked tile as pressed and enables Continue', async () => { + const { wrapper } = await createFixtures(); + const { userEvent } = render(, { wrapper }); + + const oktaTile = screen.getByRole('button', { name: 'Okta Workforce' }); + expect(oktaTile).toHaveAttribute('aria-pressed', 'false'); + + await userEvent.click(oktaTile); + + expect(oktaTile).toHaveAttribute('aria-pressed', 'true'); + expect(screen.getByRole('button', { name: /Continue/i })).toBeEnabled(); + }); + + it('flips selection when a different tile is clicked', async () => { + const { wrapper } = await createFixtures(); + const { userEvent } = render(, { wrapper }); + + const oktaTile = screen.getByRole('button', { name: 'Okta Workforce' }); + const customSamlTile = screen.getByRole('button', { name: 'Custom SAML Provider' }); + + await userEvent.click(oktaTile); + expect(oktaTile).toHaveAttribute('aria-pressed', 'true'); + expect(customSamlTile).toHaveAttribute('aria-pressed', 'false'); + + await userEvent.click(customSamlTile); + expect(oktaTile).toHaveAttribute('aria-pressed', 'false'); + expect(customSamlTile).toHaveAttribute('aria-pressed', 'true'); + }); + + it('calls goNext when Continue is clicked after a selection', async () => { + goNext.mockClear(); + const { wrapper } = await createFixtures(); + const { userEvent } = render(, { wrapper }); + + await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); + await userEvent.click(screen.getByRole('button', { name: /Continue/i })); + + expect(goNext).toHaveBeenCalledTimes(1); + }); + + it('disables Previous on the first step', async () => { + const { wrapper } = await createFixtures(); + render(, { wrapper }); + + expect(screen.getByRole('button', { name: /Previous/i })).toBeDisabled(); + }); +}); From 62042f95400141bf576fe20ea575bffc3f00bf38 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 15:53:37 -0300 Subject: [PATCH 04/26] chore(ui): add changeset for ConfigureSSO provider selection step --- .changeset/configure-sso-select-provider-step.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/configure-sso-select-provider-step.md diff --git a/.changeset/configure-sso-select-provider-step.md b/.changeset/configure-sso-select-provider-step.md new file mode 100644 index 00000000000..1fa0149ccba --- /dev/null +++ b/.changeset/configure-sso-select-provider-step.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Implement the provider selection step of `<__experimental_ConfigureSSO />`. Renders the two SAML provider tiles (Okta Workforce and Custom SAML Provider) with real icons sourced from `img.clerk.com`, tracks the picked provider in local state, and gates `Step.Footer.Continue` on a selection. Includes a warning callout about provider lock-in and a minor `Step.Header` alignment tweak. From acf517041cca990ecf73cf77e47d5d41813dc142 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 16:19:54 -0300 Subject: [PATCH 05/26] refactor(ui): use Span instead of Box as='span' in ConfigureSSO provider card Replace `` with the dedicated `` primitive for the provider icon wrapper in `SelectProviderStep`, simplifying the markup and imports. --- .../src/components/ConfigureSSO/steps/SelectProviderStep.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index d5451f9a52f..70c2d48f52a 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -1,7 +1,7 @@ import { iconImageUrl } from '@clerk/shared/constants'; import React from 'react'; -import { Box, Col, descriptors, Flow, Grid, SimpleButton, Text } from '@/customizables'; +import { Col, descriptors, Flow, Grid, SimpleButton, Span, Text } from '@/customizables'; import { Alert } from '@/ui/elements/Alert'; import { Step } from '../elements/Step'; @@ -124,8 +124,7 @@ const ProviderCard = ({ iconId, label, isSelected, onClick }: ProviderCardProps) : {}), })} > - ({ width: theme.sizes.$8, From 3558d9c06547063f4bb6fc2d0dd648e6a6f96cc9 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 17:19:18 -0300 Subject: [PATCH 06/26] feat(ui,shared,localizations): localize Select Provider step in ConfigureSSO Wire every user-visible string in the Select Provider step through @clerk/localizations. Adds a selectProviderStep namespace under __experimental_configureSSO with title, subtitle, body title/description, SAML group label + provider labels, and the lock-in warning. Populates en-US and translates to all ~49 supported locales. --- .../configure-sso-select-provider-step.md | 5 +- packages/localizations/src/ar-SA.ts | 14 +++++ packages/localizations/src/be-BY.ts | 14 +++++ packages/localizations/src/bg-BG.ts | 14 +++++ packages/localizations/src/bn-IN.ts | 14 +++++ packages/localizations/src/ca-ES.ts | 14 +++++ packages/localizations/src/cs-CZ.ts | 14 +++++ packages/localizations/src/da-DK.ts | 14 +++++ packages/localizations/src/de-DE.ts | 15 +++++ packages/localizations/src/el-GR.ts | 14 +++++ packages/localizations/src/en-GB.ts | 14 +++++ packages/localizations/src/en-US.ts | 14 +++++ packages/localizations/src/es-CR.ts | 14 +++++ packages/localizations/src/es-ES.ts | 14 +++++ packages/localizations/src/es-MX.ts | 14 +++++ packages/localizations/src/es-UY.ts | 14 +++++ packages/localizations/src/fa-IR.ts | 14 +++++ packages/localizations/src/fi-FI.ts | 14 +++++ packages/localizations/src/fr-FR.ts | 15 +++++ packages/localizations/src/he-IL.ts | 14 +++++ packages/localizations/src/hi-IN.ts | 14 +++++ packages/localizations/src/hr-HR.ts | 14 +++++ packages/localizations/src/hu-HU.ts | 14 +++++ packages/localizations/src/id-ID.ts | 14 +++++ packages/localizations/src/is-IS.ts | 14 +++++ packages/localizations/src/it-IT.ts | 14 +++++ packages/localizations/src/ja-JP.ts | 14 +++++ packages/localizations/src/kk-KZ.ts | 14 +++++ packages/localizations/src/ko-KR.ts | 14 +++++ packages/localizations/src/mn-MN.ts | 14 +++++ packages/localizations/src/ms-MY.ts | 14 +++++ packages/localizations/src/nb-NO.ts | 14 +++++ packages/localizations/src/nl-BE.ts | 14 +++++ packages/localizations/src/nl-NL.ts | 14 +++++ packages/localizations/src/pl-PL.ts | 14 +++++ packages/localizations/src/pt-BR.ts | 15 +++++ packages/localizations/src/pt-PT.ts | 14 +++++ packages/localizations/src/ro-RO.ts | 14 +++++ packages/localizations/src/ru-RU.ts | 14 +++++ packages/localizations/src/sk-SK.ts | 14 +++++ packages/localizations/src/sr-RS.ts | 14 +++++ packages/localizations/src/sv-SE.ts | 14 +++++ packages/localizations/src/ta-IN.ts | 14 +++++ packages/localizations/src/te-IN.ts | 14 +++++ packages/localizations/src/th-TH.ts | 14 +++++ packages/localizations/src/tr-TR.ts | 14 +++++ packages/localizations/src/uk-UA.ts | 14 +++++ packages/localizations/src/vi-VN.ts | 14 +++++ packages/localizations/src/zh-CN.ts | 14 +++++ packages/localizations/src/zh-TW.ts | 14 +++++ packages/shared/src/types/localization.ts | 18 +++++- .../ConfigureSSO/steps/SelectProviderStep.tsx | 56 ++++++++++++------- 52 files changed, 745 insertions(+), 23 deletions(-) diff --git a/.changeset/configure-sso-select-provider-step.md b/.changeset/configure-sso-select-provider-step.md index 1fa0149ccba..086d23ec150 100644 --- a/.changeset/configure-sso-select-provider-step.md +++ b/.changeset/configure-sso-select-provider-step.md @@ -1,5 +1,8 @@ --- +'@clerk/localizations': patch +'@clerk/clerk-js': patch +'@clerk/shared': patch '@clerk/ui': patch --- -Implement the provider selection step of `<__experimental_ConfigureSSO />`. Renders the two SAML provider tiles (Okta Workforce and Custom SAML Provider) with real icons sourced from `img.clerk.com`, tracks the picked provider in local state, and gates `Step.Footer.Continue` on a selection. Includes a warning callout about provider lock-in and a minor `Step.Header` alignment tweak. +Implement the provider selection step of `<__experimental_ConfigureSSO />`. Renders the two SAML provider tiles (Okta Workforce and Custom SAML Provider) with real icons sourced from `img.clerk.com`, tracks the picked provider in local state, and gates `Step.Footer.Continue` on a selection. Includes a warning callout about provider lock-in and a minor `Step.Header` alignment tweak. All user-visible strings are wired through `@clerk/localizations`, with translations for every supported locale. diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 85082a9c074..6dcec6e4f13 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -186,6 +186,20 @@ export const arSA: LocalizationResource = { navbar: { title: 'تكوين تسجيل الدخول الموحد (SSO)', }, + selectProviderStep: { + title: 'اختر المزود', + subtitle: 'اختر المزود الذي ستقوم بإعداد تسجيل الدخول الموحد (SSO) له.', + body: { + title: 'اختر مزود الهوية الخاص بك', + description: 'سنرشدك خلال عملية الإعداد التفصيلية بعد ذلك.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'مزود SAML مخصص', + }, + warning: 'بمجرد اختيار المزود لا يمكنك التغيير مرة أخرى حتى انتهاء التكوين', + }, verifyEmailDomainStep: { title: 'التحقق من البريد الإلكتروني', subtitle: 'تحقق من عنوان البريد الإلكتروني الذي تريد تفعيل اتصال المؤسسة عليه.', diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index 653162b600e..efb52b8899b 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -187,6 +187,20 @@ export const beBY: LocalizationResource = { navbar: { title: 'Налада адзінага ўваходу (SSO)', }, + selectProviderStep: { + title: 'Выберыце правайдэра', + subtitle: 'Выберыце правайдэра, для якога вы будзеце наладжваць SSO.', + body: { + title: 'Выберыце вашага правайдэра ідэнтыфікацыі', + description: 'Мы правядзём вас праз падрабязны працэс налады далей.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Карыстальніцкі правайдэр SAML', + }, + warning: 'Пасля выбару правайдэра вы не зможаце змяніць яго, пакуль не скончыце канфігурацыю', + }, verifyEmailDomainStep: { title: 'Пацвердзіць адрас электроннай пошты', subtitle: 'Пацвердзіце адрас электроннай пошты, на якім вы хочаце ўключыць карпаратыўнае падключэнне.', diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index d03b0b6a317..1527387fc41 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -188,6 +188,20 @@ export const bgBG: LocalizationResource = { navbar: { title: 'Конфигуриране на единен вход (SSO)', }, + selectProviderStep: { + title: 'Изберете доставчик', + subtitle: 'Изберете доставчика, за който ще конфигурирате SSO.', + body: { + title: 'Изберете вашия доставчик на идентичност', + description: 'Ще ви преведем през подробния процес на настройка след това.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Персонализиран SAML доставчик', + }, + warning: 'След като изберете доставчик, не можете да го промените, докато конфигурацията не приключи', + }, verifyEmailDomainStep: { title: 'Потвърди имейл адреса', subtitle: 'Потвърдете имейл адреса, на който искате да активирате корпоративната връзка.', diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 2993b385f7c..b9edf8bda47 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -186,6 +186,20 @@ export const bnIN: LocalizationResource = { navbar: { title: 'একক সাইন-অন (SSO) কনফিগার করুন', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to setup SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'ইমেইল ঠিকানা যাচাই করুন', subtitle: 'যে ইমেইল ঠিকানায় আপনি এন্টারপ্রাইজ সংযোগ সক্রিয় করতে চান তা যাচাই করুন।', diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index b6808394db8..b23d8afccf0 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -194,6 +194,20 @@ export const caES: LocalizationResource = { navbar: { title: "Configura l'inici de sessió únic (SSO)", }, + selectProviderStep: { + title: 'Seleccioneu un proveïdor', + subtitle: 'Seleccioneu el proveïdor per al qual configurareu SSO.', + body: { + title: "Seleccioneu el vostre proveïdor d'identitat", + description: 'Us guiarem pel procés de configuració detallat a continuació.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Proveïdor SAML personalitzat', + }, + warning: 'Un cop seleccionat un proveïdor no podreu canviar-lo fins que la configuració hagi finalitzat', + }, verifyEmailDomainStep: { title: 'Verifica el correu electrònic', subtitle: "Verifica l'adreça de correu electrònic on vols habilitar la connexió empresarial.", diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index c640eaaa726..178631aedd9 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -190,6 +190,20 @@ export const csCZ: LocalizationResource = { navbar: { title: 'Nastavit jednotné přihlášení (SSO)', }, + selectProviderStep: { + title: 'Vyberte poskytovatele', + subtitle: 'Vyberte poskytovatele, pro kterého budete nastavovat SSO.', + body: { + title: 'Vyberte svého poskytovatele identity', + description: 'Provedeme vás dále podrobným procesem nastavení.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Vlastní poskytovatel SAML', + }, + warning: 'Jakmile vyberete poskytovatele, nelze ho změnit, dokud nebude konfigurace dokončena', + }, verifyEmailDomainStep: { title: 'Ověřit e-mailovou adresu', subtitle: 'Ověřte e-mailovou adresu, na které chcete povolit podnikové připojení.', diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index aacdf055c72..ce1fedbff78 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -187,6 +187,20 @@ export const daDK: LocalizationResource = { navbar: { title: 'Konfigurer single sign-on (SSO)', }, + selectProviderStep: { + title: 'Vælg udbyder', + subtitle: 'Vælg den udbyder, du vil opsætte SSO for.', + body: { + title: 'Vælg din identitetsudbyder', + description: 'Vi guider dig gennem den detaljerede opsætningsproces næste gang.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Brugerdefineret SAML-udbyder', + }, + warning: 'Når en udbyder er valgt, kan du ikke ændre den, før konfigurationen er færdig', + }, verifyEmailDomainStep: { title: 'Bekræft e-mailadresse', subtitle: 'Bekræft den e-mailadresse, du vil aktivere virksomhedsforbindelsen på.', diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index e81362695a8..ce71c1cfd2d 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -193,6 +193,21 @@ export const deDE: LocalizationResource = { navbar: { title: 'Single Sign-On (SSO) konfigurieren', }, + selectProviderStep: { + title: 'Anbieter auswählen', + subtitle: 'Wählen Sie den Anbieter, für den Sie SSO einrichten möchten.', + body: { + title: 'Wählen Sie Ihren Identitätsanbieter', + description: 'Wir führen Sie als nächstes durch den detaillierten Einrichtungsprozess.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Benutzerdefinierter SAML-Anbieter', + }, + warning: + 'Sobald ein Anbieter ausgewählt ist, können Sie ihn nicht mehr ändern, bis die Konfiguration abgeschlossen ist', + }, verifyEmailDomainStep: { title: 'E-Mail-Adresse verifizieren', subtitle: 'Verifizieren Sie die E-Mail-Adresse, für die Sie die Unternehmensverbindung aktivieren möchten.', diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 3cc94c52c47..ce9feccac77 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -187,6 +187,20 @@ export const elGR: LocalizationResource = { navbar: { title: 'Διαμόρφωση Ενιαίας Σύνδεσης (SSO)', }, + selectProviderStep: { + title: 'Επιλέξτε πάροχο', + subtitle: 'Επιλέξτε τον πάροχο για τον οποίο θα ρυθμίσετε SSO.', + body: { + title: 'Επιλέξτε τον πάροχο ταυτότητάς σας', + description: 'Θα σας καθοδηγήσουμε στη λεπτομερή διαδικασία ρύθμισης στη συνέχεια.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Προσαρμοσμένος πάροχος SAML', + }, + warning: 'Μόλις επιλεγεί ένας πάροχος δεν μπορείτε να τον αλλάξετε μέχρι να ολοκληρωθεί η ρύθμιση', + }, verifyEmailDomainStep: { title: 'Επαλήθευση διεύθυνσης email', subtitle: 'Επαληθεύστε τη διεύθυνση email στην οποία θέλετε να ενεργοποιήσετε τη σύνδεση επιχείρησης.', diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index 86357722efc..7b1bb444203 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -187,6 +187,20 @@ export const enGB: LocalizationResource = { navbar: { title: 'Configure Single Sign-On (SSO)', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to set up SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'Verify email address', subtitle: 'Verify the email address you want to enable the enterprise connection on.', diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 22a5e5f3012..4dc0dcd1bdc 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -209,6 +209,20 @@ export const enUS: LocalizationResource = { navbar: { title: 'Configure Single Sign-On (SSO)', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to setup SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'Verify email address', subtitle: 'Verify the email address you want to enable the enterprise connection on.', diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index ac66e72b102..e70fdb7d086 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -187,6 +187,20 @@ export const esCR: LocalizationResource = { navbar: { title: 'Configurar inicio de sesión único (SSO)', }, + selectProviderStep: { + title: 'Seleccionar proveedor', + subtitle: 'Selecciona el proveedor para el que vas a configurar SSO.', + body: { + title: 'Selecciona tu proveedor de identidad', + description: 'Te guiaremos a través del proceso de configuración detallado a continuación.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Proveedor SAML personalizado', + }, + warning: 'Una vez que se selecciona un proveedor no puedes cambiarlo hasta que termine la configuración', + }, verifyEmailDomainStep: { title: 'Verificar correo electrónico', subtitle: 'Verifica la dirección de correo electrónico en la que deseas habilitar la conexión empresarial.', diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index c5582b1e6bf..c22b5657186 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -193,6 +193,20 @@ export const esES: LocalizationResource = { navbar: { title: 'Configurar inicio de sesión único (SSO)', }, + selectProviderStep: { + title: 'Seleccionar proveedor', + subtitle: 'Selecciona el proveedor para el que vas a configurar SSO.', + body: { + title: 'Selecciona tu proveedor de identidad', + description: 'Te guiaremos a través del proceso de configuración detallado a continuación.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Proveedor SAML personalizado', + }, + warning: 'Una vez seleccionado un proveedor no podrás cambiarlo hasta que finalice la configuración', + }, verifyEmailDomainStep: { title: 'Verificar correo electrónico', subtitle: 'Verifica la dirección de correo electrónico en la que deseas habilitar la conexión empresarial.', diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index 4567eed9c7d..ffac231c140 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -188,6 +188,20 @@ export const esMX: LocalizationResource = { navbar: { title: 'Configurar inicio de sesión único (SSO)', }, + selectProviderStep: { + title: 'Seleccionar proveedor', + subtitle: 'Selecciona el proveedor para el que vas a configurar SSO.', + body: { + title: 'Selecciona tu proveedor de identidad', + description: 'Te guiaremos a través del proceso de configuración detallado a continuación.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Proveedor SAML personalizado', + }, + warning: 'Una vez que se selecciona un proveedor no puedes cambiarlo hasta que termine la configuración', + }, verifyEmailDomainStep: { title: 'Verificar correo electrónico', subtitle: 'Verifica la dirección de correo electrónico en la que deseas habilitar la conexión empresarial.', diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index c1ed2591d59..080f6b7db17 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -187,6 +187,20 @@ export const esUY: LocalizationResource = { navbar: { title: 'Configurar inicio de sesión único (SSO)', }, + selectProviderStep: { + title: 'Seleccionar proveedor', + subtitle: 'Seleccioná el proveedor para el que vas a configurar SSO.', + body: { + title: 'Seleccioná tu proveedor de identidad', + description: 'Te guiaremos a través del proceso de configuración detallado a continuación.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Proveedor SAML personalizado', + }, + warning: 'Una vez que se selecciona un proveedor no podés cambiarlo hasta que termine la configuración', + }, verifyEmailDomainStep: { title: 'Verificar correo electrónico', subtitle: 'Verificá la dirección de correo electrónico en la que querés habilitar la conexión empresarial.', diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 3966a021f7f..4b7b7f17638 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -191,6 +191,20 @@ export const faIR: LocalizationResource = { navbar: { title: 'پیکربندی ورود یکپارچه (SSO)', }, + selectProviderStep: { + title: 'ارائه‌دهنده را انتخاب کنید', + subtitle: 'ارائه‌دهنده‌ای را که می‌خواهید SSO را برای آن راه‌اندازی کنید، انتخاب کنید.', + body: { + title: 'ارائه‌دهنده هویت خود را انتخاب کنید', + description: 'در ادامه شما را در فرآیند راه‌اندازی دقیق راهنمایی خواهیم کرد.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'ارائه‌دهنده SAML سفارشی', + }, + warning: 'پس از انتخاب یک ارائه‌دهنده، نمی‌توانید آن را تا پایان پیکربندی تغییر دهید', + }, verifyEmailDomainStep: { title: 'تأیید آدرس ایمیل', subtitle: 'آدرس ایمیلی را که می‌خواهید اتصال سازمانی روی آن فعال شود، تأیید کنید.', diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index a8e0b80c12a..185a0a57e4e 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -214,6 +214,20 @@ export const fiFI: LocalizationResource = { navbar: { title: 'Määritä kertakirjautuminen (SSO)', }, + selectProviderStep: { + title: 'Valitse palveluntarjoaja', + subtitle: 'Valitse palveluntarjoaja, jolle määrität SSO:n.', + body: { + title: 'Valitse henkilöllisyyden tarjoaja', + description: 'Opastamme sinut yksityiskohtaisen määritysprosessin läpi seuraavaksi.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Mukautettu SAML-palveluntarjoaja', + }, + warning: 'Kun palveluntarjoaja on valittu, et voi vaihtaa sitä ennen kuin määritys on valmis', + }, verifyEmailDomainStep: { title: 'Vahvista sähköpostiosoite', subtitle: 'Vahvista sähköpostiosoite, jolle haluat ottaa yritysyhteyden käyttöön.', diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index f7b7ed79390..d0748f7cc2a 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -195,6 +195,21 @@ export const frFR: LocalizationResource = { navbar: { title: "Configurer l'authentification unique (SSO)", }, + selectProviderStep: { + title: 'Sélectionner un fournisseur', + subtitle: 'Sélectionnez le fournisseur pour lequel vous allez configurer le SSO.', + body: { + title: "Sélectionnez votre fournisseur d'identité", + description: 'Nous vous guiderons ensuite à travers le processus de configuration détaillé.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Fournisseur SAML personnalisé', + }, + warning: + "Une fois un fournisseur sélectionné, vous ne pourrez plus en changer jusqu'à la fin de la configuration", + }, verifyEmailDomainStep: { title: "Vérifier l'adresse e-mail", subtitle: "Vérifiez l'adresse e-mail sur laquelle vous souhaitez activer la connexion entreprise.", diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 8367699941b..789a1ffd158 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -186,6 +186,20 @@ export const heIL: LocalizationResource = { navbar: { title: 'הגדרת כניסה אחידה (SSO)', }, + selectProviderStep: { + title: 'בחר ספק', + subtitle: 'בחר את הספק שעבורו תגדיר SSO.', + body: { + title: 'בחר את ספק הזהות שלך', + description: 'נדריך אותך בתהליך ההגדרה המפורט בהמשך.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'ספק SAML מותאם אישית', + }, + warning: 'לאחר בחירת ספק לא ניתן לשנות אותו עד לסיום ההגדרה', + }, verifyEmailDomainStep: { title: 'אימות כתובת אימייל', subtitle: 'אמת את כתובת האימייל שעליה ברצונך להפעיל את חיבור הארגון.', diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 27e73fafa6a..c3aceb45dab 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -187,6 +187,20 @@ export const hiIN: LocalizationResource = { navbar: { title: 'सिंगल साइन-ऑन (SSO) कॉन्फ़िगर करें', }, + selectProviderStep: { + title: 'प्रदाता चुनें', + subtitle: 'उस प्रदाता का चयन करें जिसके लिए आप SSO सेट अप कर रहे हैं।', + body: { + title: 'अपना पहचान प्रदाता चुनें', + description: 'हम आपको आगे विस्तृत सेटअप प्रक्रिया के माध्यम से मार्गदर्शन करेंगे।', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'कस्टम SAML प्रदाता', + }, + warning: 'एक बार प्रदाता का चयन करने के बाद आप कॉन्फ़िगरेशन समाप्त होने तक इसे बदल नहीं सकते', + }, verifyEmailDomainStep: { title: 'ईमेल पता सत्यापित करें', subtitle: 'उस ईमेल पते को सत्यापित करें जिस पर आप एंटरप्राइज़ कनेक्शन सक्षम करना चाहते हैं।', diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index bf2b4c9d3b9..09431373581 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -216,6 +216,20 @@ export const hrHR: LocalizationResource = { navbar: { title: 'Konfiguriraj jedinstvenu prijavu (SSO)', }, + selectProviderStep: { + title: 'Odaberite pružatelja', + subtitle: 'Odaberite pružatelja za kojeg ćete postaviti SSO.', + body: { + title: 'Odaberite svog pružatelja identiteta', + description: 'Vodit ćemo vas kroz detaljan proces postavljanja u nastavku.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Prilagođeni SAML pružatelj', + }, + warning: 'Nakon odabira pružatelja ne možete ga ponovno mijenjati dok konfiguracija ne završi', + }, verifyEmailDomainStep: { title: 'Potvrdi e-mail adresu', subtitle: 'Potvrdite e-mail adresu na kojoj želite omogućiti poslovnu vezu.', diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index ba0d6eebe85..ba64af90d8f 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -216,6 +216,20 @@ export const huHU: LocalizationResource = { navbar: { title: 'Egyszeri bejelentkezés (SSO) beállítása', }, + selectProviderStep: { + title: 'Szolgáltató kiválasztása', + subtitle: 'Válassza ki azt a szolgáltatót, amelyhez beállítja az SSO-t.', + body: { + title: 'Válassza ki az identitásszolgáltatóját', + description: 'Ezután végigvezetjük a részletes beállítási folyamaton.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Egyéni SAML szolgáltató', + }, + warning: 'Miután kiválasztotta a szolgáltatót, nem módosíthatja, amíg a konfiguráció be nem fejeződik', + }, verifyEmailDomainStep: { title: 'E-mail-cím megerősítése', subtitle: 'Erősítse meg azt az e-mail-címet, amelyen engedélyezni szeretné a vállalati kapcsolatot.', diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 275c0ae0e3f..f3134584995 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -186,6 +186,20 @@ export const idID: LocalizationResource = { navbar: { title: 'Konfigurasi Single Sign-On (SSO)', }, + selectProviderStep: { + title: 'Pilih penyedia', + subtitle: 'Pilih penyedia yang akan Anda atur untuk SSO.', + body: { + title: 'Pilih penyedia identitas Anda', + description: 'Kami akan memandu Anda melalui proses penyiapan terperinci berikutnya.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Penyedia SAML Khusus', + }, + warning: 'Setelah penyedia dipilih, Anda tidak dapat mengubahnya lagi sampai konfigurasi selesai', + }, verifyEmailDomainStep: { title: 'Verifikasi alamat email', subtitle: 'Verifikasi alamat email yang ingin Anda aktifkan koneksi enterprise-nya.', diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 3b607d8b4c1..a480ec7538d 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -215,6 +215,20 @@ export const isIS: LocalizationResource = { navbar: { title: 'Stilla einnar innskráningar (SSO)', }, + selectProviderStep: { + title: 'Veldu þjónustuaðila', + subtitle: 'Veldu þjónustuaðilann sem þú ætlar að setja upp SSO fyrir.', + body: { + title: 'Veldu auðkennisþjónustuaðila þinn', + description: 'Við munum leiðbeina þér í gegnum ítarlegt uppsetningarferli næst.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Sérsniðinn SAML þjónustuaðili', + }, + warning: 'Þegar þjónustuaðili hefur verið valinn er ekki hægt að breyta aftur fyrr en stillingu er lokið', + }, verifyEmailDomainStep: { title: 'Staðfesta tölvupóstfang', subtitle: 'Staðfestu tölvupóstfangið sem þú vilt virkja fyrirtækjatenginguna á.', diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index 730f916f0a5..9e1b31419c4 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -193,6 +193,20 @@ export const itIT: LocalizationResource = { navbar: { title: 'Configura Single Sign-On (SSO)', }, + selectProviderStep: { + title: 'Seleziona provider', + subtitle: "Seleziona il provider per cui configurerai l'SSO.", + body: { + title: 'Seleziona il tuo provider di identità', + description: 'Ti guideremo nel processo di configurazione dettagliato successivamente.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Provider SAML personalizzato', + }, + warning: 'Una volta selezionato un provider non potrai cambiarlo fino al termine della configurazione', + }, verifyEmailDomainStep: { title: 'Verifica indirizzo email', subtitle: "Verifica l'indirizzo email su cui vuoi abilitare la connessione aziendale.", diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index ce0156d0756..7c2cffdf60d 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -197,6 +197,20 @@ export const jaJP: LocalizationResource = { navbar: { title: 'シングルサインオン(SSO)を設定', }, + selectProviderStep: { + title: 'プロバイダーを選択', + subtitle: 'SSOを設定するプロバイダーを選択してください。', + body: { + title: 'IDプロバイダーを選択してください', + description: '次に、詳細な設定プロセスをご案内します。', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'カスタムSAMLプロバイダー', + }, + warning: 'プロバイダーを選択すると、設定が完了するまで変更できません', + }, verifyEmailDomainStep: { title: 'メールアドレスを確認', subtitle: 'エンタープライズ接続を有効にしたいメールアドレスを確認します。', diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index 6ecf686962e..62ead50b941 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -186,6 +186,20 @@ export const kkKZ: LocalizationResource = { navbar: { title: 'Бірыңғай кіруді конфигурациялау (SSO)', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to setup SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'Электрондық пошта мекенжайын растау', subtitle: 'Кәсіпорын байланысын іске қосқыңыз келетін электрондық пошта мекенжайын растаңыз.', diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index f61a3517ee0..4685adf6c52 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -193,6 +193,20 @@ export const koKR: LocalizationResource = { navbar: { title: '싱글 사인온(SSO) 구성', }, + selectProviderStep: { + title: '공급자 선택', + subtitle: 'SSO를 설정할 공급자를 선택하세요.', + body: { + title: 'ID 공급자를 선택하세요', + description: '다음 단계에서 자세한 설정 프로세스를 안내해 드립니다.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: '사용자 지정 SAML 공급자', + }, + warning: '공급자를 선택하면 구성이 완료될 때까지 다시 변경할 수 없습니다', + }, verifyEmailDomainStep: { title: '이메일 주소 확인', subtitle: '엔터프라이즈 연결을 활성화하려는 이메일 주소를 확인하세요.', diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 8391fbc8d19..9e0f97dd220 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -186,6 +186,20 @@ export const mnMN: LocalizationResource = { navbar: { title: 'Нэгдсэн нэвтрэлт (SSO) тохируулах', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to setup SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'И-мэйл хаягийг баталгаажуулах', subtitle: 'Та байгууллагын холболтыг идэвхжүүлэхийг хүсэж буй и-мэйл хаягийг баталгаажуулна уу.', diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 4325ec06013..fab1a26e769 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -186,6 +186,20 @@ export const msMY: LocalizationResource = { navbar: { title: 'Konfigurasi Log Masuk Tunggal (SSO)', }, + selectProviderStep: { + title: 'Pilih pembekal', + subtitle: 'Pilih pembekal yang anda akan sediakan SSO untuknya.', + body: { + title: 'Pilih pembekal identiti anda', + description: 'Kami akan membimbing anda melalui proses persediaan terperinci seterusnya.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Pembekal SAML Tersuai', + }, + warning: 'Setelah pembekal dipilih anda tidak boleh menukar lagi sehingga konfigurasi selesai', + }, verifyEmailDomainStep: { title: 'Sahkan alamat e-mel', subtitle: 'Sahkan alamat e-mel yang anda ingin dayakan sambungan enterprise.', diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index d6d1b983b61..d1c4e530950 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -215,6 +215,20 @@ export const nbNO: LocalizationResource = { navbar: { title: 'Konfigurer enkeltpålogging (SSO)', }, + selectProviderStep: { + title: 'Velg leverandør', + subtitle: 'Velg leverandøren du skal konfigurere SSO for.', + body: { + title: 'Velg din identitetsleverandør', + description: 'Vi veileder deg gjennom den detaljerte konfigurasjonsprosessen neste gang.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Egendefinert SAML-leverandør', + }, + warning: 'Når en leverandør er valgt, kan du ikke endre igjen før konfigurasjonen er ferdig', + }, verifyEmailDomainStep: { title: 'Verifiser e-postadresse', subtitle: 'Verifiser e-postadressen du vil aktivere virksomhetstilkoblingen på.', diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 03d19bd0fc1..7968d1580a5 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -187,6 +187,20 @@ export const nlBE: LocalizationResource = { navbar: { title: 'Single sign-on (SSO) configureren', }, + selectProviderStep: { + title: 'Provider selecteren', + subtitle: 'Selecteer de provider waarvoor je SSO gaat instellen.', + body: { + title: 'Selecteer je identiteitsprovider', + description: 'We begeleiden je hierna door het gedetailleerde installatieproces.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Aangepaste SAML-provider', + }, + warning: 'Zodra een provider is geselecteerd, kun je deze niet meer wijzigen totdat de configuratie is voltooid', + }, verifyEmailDomainStep: { title: 'E-mailadres verifiëren', subtitle: 'Verifieer het e-mailadres waarop u de enterprise-verbinding wilt inschakelen.', diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 2826663eac4..ccdf24d00c2 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -187,6 +187,20 @@ export const nlNL: LocalizationResource = { navbar: { title: 'Single sign-on (SSO) configureren', }, + selectProviderStep: { + title: 'Provider selecteren', + subtitle: 'Selecteer de provider waarvoor je SSO gaat instellen.', + body: { + title: 'Selecteer je identiteitsprovider', + description: 'We begeleiden je hierna door het gedetailleerde installatieproces.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Aangepaste SAML-provider', + }, + warning: 'Zodra een provider is geselecteerd, kun je deze niet meer wijzigen totdat de configuratie is voltooid', + }, verifyEmailDomainStep: { title: 'E-mailadres verifiëren', subtitle: 'Verifieer het e-mailadres waarop je de enterprise-verbinding wilt inschakelen.', diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 23dbbe83887..3899bb9868b 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -187,6 +187,20 @@ export const plPL: LocalizationResource = { navbar: { title: 'Skonfiguruj logowanie jednokrotne (SSO)', }, + selectProviderStep: { + title: 'Wybierz dostawcę', + subtitle: 'Wybierz dostawcę, dla którego skonfigurujesz SSO.', + body: { + title: 'Wybierz swojego dostawcę tożsamości', + description: 'Następnie przeprowadzimy Cię przez szczegółowy proces konfiguracji.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Niestandardowy dostawca SAML', + }, + warning: 'Po wybraniu dostawcy nie można go ponownie zmienić aż do zakończenia konfiguracji', + }, verifyEmailDomainStep: { title: 'Zweryfikuj adres e-mail', subtitle: 'Zweryfikuj adres e-mail, na którym chcesz włączyć połączenie firmowe.', diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 45031976fe8..95be31e570e 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -193,6 +193,21 @@ export const ptBR: LocalizationResource = { navbar: { title: 'Configurar logon único (SSO)', }, + selectProviderStep: { + title: 'Selecionar provedor', + subtitle: 'Selecione o provedor para o qual você vai configurar o SSO.', + body: { + title: 'Selecione seu provedor de identidade', + description: 'Iremos guiá-lo pelo processo de configuração detalhado em seguida.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Provedor SAML personalizado', + }, + warning: + 'Depois que um provedor for selecionado, você não poderá alterá-lo até que a configuração seja concluída', + }, verifyEmailDomainStep: { title: 'Verificar endereço de e-mail', subtitle: 'Verifique o endereço de e-mail no qual deseja habilitar a conexão empresarial.', diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index be2a815585f..d3ae85a9936 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -194,6 +194,20 @@ export const ptPT: LocalizationResource = { navbar: { title: 'Configurar autenticação única (SSO)', }, + selectProviderStep: { + title: 'Selecionar fornecedor', + subtitle: 'Selecione o fornecedor para o qual vai configurar o SSO.', + body: { + title: 'Selecione o seu fornecedor de identidade', + description: 'Iremos guiá-lo pelo processo de configuração detalhado a seguir.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Fornecedor SAML personalizado', + }, + warning: 'Depois de um fornecedor ser selecionado não pode ser alterado até que a configuração esteja terminada', + }, verifyEmailDomainStep: { title: 'Verificar endereço de e-mail', subtitle: 'Verifique o endereço de e-mail no qual pretende ativar a ligação empresarial.', diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index ca4992050cf..8e391103126 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -193,6 +193,20 @@ export const roRO: LocalizationResource = { navbar: { title: 'Configurați autentificarea unică (SSO)', }, + selectProviderStep: { + title: 'Selectați furnizorul', + subtitle: 'Selectați furnizorul pentru care veți configura SSO.', + body: { + title: 'Selectați furnizorul de identitate', + description: 'Vă vom ghida în continuare prin procesul de configurare detaliat.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Furnizor SAML personalizat', + }, + warning: 'Odată ce un furnizor este selectat, nu îl puteți schimba până când configurația nu este finalizată', + }, verifyEmailDomainStep: { title: 'Verifică adresa de e-mail', subtitle: 'Verifică adresa de e-mail pe care dorești să activezi conexiunea enterprise.', diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 4436c4d2d83..67d5a703c76 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -187,6 +187,20 @@ export const ruRU: LocalizationResource = { navbar: { title: 'Настроить единый вход (SSO)', }, + selectProviderStep: { + title: 'Выберите поставщика', + subtitle: 'Выберите поставщика, для которого вы будете настраивать SSO.', + body: { + title: 'Выберите вашего поставщика идентификации', + description: 'Далее мы проведём вас через подробный процесс настройки.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Пользовательский поставщик SAML', + }, + warning: 'После выбора поставщика вы не сможете изменить его до завершения настройки', + }, verifyEmailDomainStep: { title: 'Подтвердить адрес электронной почты', subtitle: 'Подтвердите адрес электронной почты, для которого вы хотите включить корпоративное подключение.', diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 643190d6d97..0eacd3d4d30 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -187,6 +187,20 @@ export const skSK: LocalizationResource = { navbar: { title: 'Nastaviť jednotné prihlasovanie (SSO)', }, + selectProviderStep: { + title: 'Vyberte poskytovateľa', + subtitle: 'Vyberte poskytovateľa, pre ktorého budete nastavovať SSO.', + body: { + title: 'Vyberte si svojho poskytovateľa identity', + description: 'Následne vás prevedieme podrobným procesom nastavenia.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Vlastný poskytovateľ SAML', + }, + warning: 'Po výbere poskytovateľa ho nemôžete zmeniť, kým sa konfigurácia neukončí', + }, verifyEmailDomainStep: { title: 'Overiť e-mailovú adresu', subtitle: 'Overte e-mailovú adresu, na ktorej chcete povoliť podnikové pripojenie.', diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 17c67d348c2..4054cba9010 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -187,6 +187,20 @@ export const srRS: LocalizationResource = { navbar: { title: 'Konfiguriši jedinstvenu prijavu (SSO)', }, + selectProviderStep: { + title: 'Izaberite provajdera', + subtitle: 'Izaberite provajdera za koga ćete podesiti SSO.', + body: { + title: 'Izaberite svog provajdera identiteta', + description: 'Vodićemo vas kroz detaljan proces podešavanja u nastavku.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Prilagođeni SAML provajder', + }, + warning: 'Kada se provajder izabere, ne možete ga ponovo menjati dok se konfiguracija ne završi', + }, verifyEmailDomainStep: { title: 'Potvrdi adresu e-pošte', subtitle: 'Potvrdite adresu e-pošte na kojoj želite da omogućite enterprise konekciju.', diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 4e4947a0b3f..9e810473faf 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -186,6 +186,20 @@ export const svSE: LocalizationResource = { navbar: { title: 'Konfigurera enkel inloggning (SSO)', }, + selectProviderStep: { + title: 'Välj leverantör', + subtitle: 'Välj den leverantör du ska konfigurera SSO för.', + body: { + title: 'Välj din identitetsleverantör', + description: 'Vi guidar dig sedan genom den detaljerade konfigurationsprocessen.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Anpassad SAML-leverantör', + }, + warning: 'När en leverantör har valts kan du inte ändra igen förrän konfigurationen är klar', + }, verifyEmailDomainStep: { title: 'Verifiera e-postadress', subtitle: 'Verifiera e-postadressen som du vill aktivera företagsanslutningen för.', diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 81e4376254c..4db59aa9ec5 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -186,6 +186,20 @@ export const taIN: LocalizationResource = { navbar: { title: 'ஒற்றை உள்நுழைவை (SSO) உள்ளமை', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to setup SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'மின்னஞ்சல் முகவரியை சரிபார்க்கவும்', subtitle: 'நீங்கள் நிறுவன இணைப்பை இயக்க விரும்பும் மின்னஞ்சல் முகவரியை சரிபார்க்கவும்.', diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index 968b6cbee4b..d8cc6928bd9 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -186,6 +186,20 @@ export const teIN: LocalizationResource = { navbar: { title: 'సింగిల్ సైన్-ఆన్ (SSO) కాన్ఫిగర్ చేయండి', }, + selectProviderStep: { + title: 'Select provider', + subtitle: 'Select the provider you are going to setup SSO for.', + body: { + title: 'Select your identity provider', + description: "We'll guide you through the detailed setup process next.", + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Custom SAML Provider', + }, + warning: 'Once a provider is selected you cannot change again until the configuration is over', + }, verifyEmailDomainStep: { title: 'ఇమెయిల్ చిరునామా ధృవీకరించండి', subtitle: 'మీరు ఎంటర్‌ప్రైజ్ కనెక్షన్‌ను ప్రారంభించాలనుకుంటున్న ఇమెయిల్ చిరునామాను ధృవీకరించండి.', diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index 41672a54016..11109eeb68f 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -190,6 +190,20 @@ export const thTH: LocalizationResource = { navbar: { title: 'กำหนดค่าการลงชื่อเข้าใช้แบบครั้งเดียว (SSO)', }, + selectProviderStep: { + title: 'เลือกผู้ให้บริการ', + subtitle: 'เลือกผู้ให้บริการที่คุณจะตั้งค่า SSO ให้', + body: { + title: 'เลือกผู้ให้บริการข้อมูลประจำตัวของคุณ', + description: 'เราจะแนะนำคุณตลอดกระบวนการตั้งค่าโดยละเอียดในขั้นตอนถัดไป', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'ผู้ให้บริการ SAML แบบกำหนดเอง', + }, + warning: 'เมื่อเลือกผู้ให้บริการแล้วคุณไม่สามารถเปลี่ยนได้อีกจนกว่าการกำหนดค่าจะเสร็จสิ้น', + }, verifyEmailDomainStep: { title: 'ยืนยันที่อยู่อีเมล', subtitle: 'ยืนยันที่อยู่อีเมลที่คุณต้องการเปิดใช้งานการเชื่อมต่อองค์กร', diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index 6ee6c170a41..ce5b1d0e956 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -186,6 +186,20 @@ export const trTR: LocalizationResource = { navbar: { title: 'Tek Oturum Açmayı (SSO) Yapılandır', }, + selectProviderStep: { + title: 'Sağlayıcıyı seçin', + subtitle: "SSO'yu ayarlayacağınız sağlayıcıyı seçin.", + body: { + title: 'Kimlik sağlayıcınızı seçin', + description: 'Bir sonraki adımda sizi ayrıntılı kurulum sürecinde yönlendireceğiz.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Özel SAML Sağlayıcısı', + }, + warning: 'Bir sağlayıcı seçildikten sonra yapılandırma bitene kadar tekrar değiştiremezsiniz', + }, verifyEmailDomainStep: { title: 'E-posta adresini doğrula', subtitle: 'Kurumsal bağlantıyı etkinleştirmek istediğiniz e-posta adresini doğrulayın.', diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 68f0d8fd690..0b7cebcb375 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -187,6 +187,20 @@ export const ukUA: LocalizationResource = { navbar: { title: 'Налаштувати єдиний вхід (SSO)', }, + selectProviderStep: { + title: 'Виберіть постачальника', + subtitle: 'Виберіть постачальника, для якого ви налаштовуватимете SSO.', + body: { + title: 'Виберіть вашого постачальника ідентифікації', + description: 'Далі ми проведемо вас через детальний процес налаштування.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Користувацький постачальник SAML', + }, + warning: 'Після вибору постачальника ви не зможете змінити його, доки не буде завершено налаштування', + }, verifyEmailDomainStep: { title: 'Підтвердити адресу електронної пошти', subtitle: "Підтвердьте адресу електронної пошти, на якій ви хочете увімкнути корпоративне з'єднання.", diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 0a8e03c59c7..9aeb3a94c88 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -190,6 +190,20 @@ export const viVN: LocalizationResource = { navbar: { title: 'Cấu hình đăng nhập một lần (SSO)', }, + selectProviderStep: { + title: 'Chọn nhà cung cấp', + subtitle: 'Chọn nhà cung cấp mà bạn sẽ thiết lập SSO cho.', + body: { + title: 'Chọn nhà cung cấp danh tính của bạn', + description: 'Chúng tôi sẽ hướng dẫn bạn qua quy trình thiết lập chi tiết tiếp theo.', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: 'Nhà cung cấp SAML tùy chỉnh', + }, + warning: 'Khi đã chọn nhà cung cấp, bạn không thể thay đổi cho đến khi cấu hình hoàn tất', + }, verifyEmailDomainStep: { title: 'Xác minh địa chỉ email', subtitle: 'Xác minh địa chỉ email mà bạn muốn kích hoạt kết nối doanh nghiệp.', diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index c20a2b6466a..724014f9e6e 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -186,6 +186,20 @@ export const zhCN: LocalizationResource = { navbar: { title: '配置单点登录 (SSO)', }, + selectProviderStep: { + title: '选择提供商', + subtitle: '选择您要为其设置 SSO 的提供商。', + body: { + title: '选择您的身份提供商', + description: '接下来我们将引导您完成详细的设置过程。', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: '自定义 SAML 提供商', + }, + warning: '选择提供商后,在配置完成之前无法再次更改', + }, verifyEmailDomainStep: { title: '验证电子邮件地址', subtitle: '验证您想要启用企业连接的电子邮件地址。', diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 84307cbde86..537e1c54a2c 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -192,6 +192,20 @@ export const zhTW: LocalizationResource = { navbar: { title: '設定單一登入 (SSO)', }, + selectProviderStep: { + title: '選擇提供者', + subtitle: '選擇您要為其設定 SSO 的提供者。', + body: { + title: '選擇您的身分提供者', + description: '接下來我們將引導您完成詳細的設定流程。', + }, + saml: { + groupLabel: 'SAML', + okta: 'Okta Workforce', + customSaml: '自訂 SAML 提供者', + }, + warning: '選擇提供者後,在設定完成之前無法再次變更', + }, verifyEmailDomainStep: { title: '驗證電子郵件地址', subtitle: '驗證您想要啟用企業連線的電子郵件地址。', diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 1d7bc8d2d04..f50fee30714 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1293,13 +1293,27 @@ export type __internal_LocalizationResource = { }; }; configureSSO: { + missingManageEnterpriseConnectionsPermission: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; navbar: { title: LocalizationValue; }; - missingManageEnterpriseConnectionsPermission: { + selectProviderStep: { title: LocalizationValue; subtitle: LocalizationValue; - }; + body: { + title: LocalizationValue; + description: LocalizationValue; + }; + saml: { + groupLabel: LocalizationValue; + okta: LocalizationValue; + customSaml: LocalizationValue; + }; + warning: LocalizationValue; + }, verifyEmailDomainStep: { title: LocalizationValue; subtitle: LocalizationValue; diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 70c2d48f52a..60c1afe8533 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -1,7 +1,18 @@ import { iconImageUrl } from '@clerk/shared/constants'; import React from 'react'; -import { Col, descriptors, Flow, Grid, SimpleButton, Span, Text } from '@/customizables'; +import type { LocalizationKey } from '@/customizables'; +import { + Col, + descriptors, + Flow, + Grid, + localizationKeys, + SimpleButton, + Span, + Text, + useLocalizations, +} from '@/customizables'; import { Alert } from '@/ui/elements/Alert'; import { Step } from '../elements/Step'; @@ -9,9 +20,13 @@ import { useWizard } from '../elements/Wizard'; type ProviderType = 'okta' | 'custom_saml'; -const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: string; iconId: string }> = [ - { id: 'okta', label: 'Okta Workforce', iconId: 'okta' }, - { id: 'custom_saml', label: 'Custom SAML Provider', iconId: 'saml' }, +const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: LocalizationKey; iconId: string }> = [ + { id: 'okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' }, + { + id: 'custom_saml', + label: localizationKeys('configureSSO.selectProviderStep.saml.customSaml'), + iconId: 'saml', + }, ]; export const SelectProviderStep = (): JSX.Element => { @@ -25,8 +40,8 @@ export const SelectProviderStep = (): JSX.Element => { elementId={descriptors.configureSSOStep.setId('select-provider')} > @@ -36,17 +51,15 @@ export const SelectProviderStep = (): JSX.Element => { as='p' variant='subtitle' sx={theme => ({ color: theme.colors.$colorForeground })} - > - Select your identity provider - + localizationKey={localizationKeys('configureSSO.selectProviderStep.body.title')} + /> ({ color: theme.colors.$colorMutedForeground })} - > - We'll guide you through the detailed setup process next. - + localizationKey={localizationKeys('configureSSO.selectProviderStep.body.description')} + /> ({ gap: theme.space.$3 })}> @@ -54,9 +67,8 @@ export const SelectProviderStep = (): JSX.Element => { as='label' variant='subtitle' sx={theme => ({ color: theme.colors.$colorForeground })} - > - SAML - + localizationKey={localizationKeys('configureSSO.selectProviderStep.saml.groupLabel')} + /> { - - Once a provider is selected you cannot change again until the configuration is over - + @@ -97,12 +110,15 @@ export const SelectProviderStep = (): JSX.Element => { type ProviderCardProps = { iconId: string; - label: string; + label: LocalizationKey; isSelected?: boolean; onClick?: () => void; }; const ProviderCard = ({ iconId, label, isSelected, onClick }: ProviderCardProps): JSX.Element => { + const { t } = useLocalizations(); + const labelText = t(label); + return ( ({ color: theme.colors.$colorForeground })} > - {label} + {labelText} ); From 47fb5840f1ceafc854fec9a3e2b0609fcc5dc47a Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 17:26:40 -0300 Subject: [PATCH 07/26] feat(ui): extend ConfigureSSO flow provider with createConnection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds provider/setProvider/clearProvider/createConnection to the flow context so the Select Provider step can persist the chosen provider and create the enterprise connection on Continue. createConnection is wrapped in useReverification and is idempotent — it no-ops when an enterprise connection already exists. --- .../ConfigureSSO/ConfigureSSOContext.tsx | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx index 9513ca41a5e..c565c774398 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx @@ -1,6 +1,9 @@ +import { useReverification, useSession, useUser } from '@clerk/shared/react'; import type { EnterpriseConnectionResource } from '@clerk/shared/types'; import React, { type PropsWithChildren } from 'react'; +export type ProviderType = 'saml_okta' | 'saml_custom'; + /** * Shared form state for the ConfigureSSO wizard, persisted across steps */ @@ -9,10 +12,38 @@ export interface ConfigureSSOData { * The enterprise connection from the user's primary email address domain */ enterpriseConnection: EnterpriseConnectionResource | undefined; + /** + * Whether the underlying enterprise connection data is still loading + */ + isLoading: boolean; + /** + * The provider selected for this configuration. Reads from the existing + * enterprise connection when present, falling back to the local selection + * made on the Select Provider step. + */ + provider: ProviderType | undefined; + /** + * Sets the local provider selection used by Select Provider before a + * connection has been created. + */ + setProvider: (provider: ProviderType) => void; + /** + * Clears the local provider selection. Used by Reset on the Confirmation + * step (wired up by a follow-up PR). + */ + clearProvider: () => void; + /** + * Creates the enterprise connection from the current provider selection, + * the user's primary email domain, and the session's active organization. + * No-ops when an enterprise connection already exists so callers can + * safely re-trigger. + */ + createConnection: () => Promise; } interface ConfigureSSOFlowProviderProps { enterpriseConnection: EnterpriseConnectionResource | undefined; + isLoading?: boolean; } const ConfigureSSOFlowContext = React.createContext(null); @@ -20,13 +51,56 @@ ConfigureSSOFlowContext.displayName = 'ConfigureSSOFlowContext'; export const ConfigureSSOFlowProvider = ({ enterpriseConnection, + isLoading = false, children, }: PropsWithChildren): JSX.Element => { + const { user } = useUser(); + const { session } = useSession(); + const [localProvider, setLocalProvider] = React.useState(undefined); + + const provider = (enterpriseConnection?.provider as ProviderType | undefined) ?? localProvider; + + const createConnectionFetcher = React.useCallback(async () => { + if (enterpriseConnection) { + return; + } + if (!provider) { + throw new Error('Provider not selected'); + } + if (!user?.primaryEmailAddress) { + throw new Error('Primary email required'); + } + + const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; + const organizationId = session?.lastActiveOrganizationId ?? null; + + await user.createEnterpriseConnection({ + provider, + name: emailDomain, + organizationId, + }); + }, [enterpriseConnection, provider, user, session]); + + const createConnection = useReverification(createConnectionFetcher); + + const setProvider = React.useCallback((next: ProviderType) => { + setLocalProvider(next); + }, []); + + const clearProvider = React.useCallback(() => { + setLocalProvider(undefined); + }, []); + const value = React.useMemo( () => ({ enterpriseConnection, + isLoading, + provider, + setProvider, + clearProvider, + createConnection, }), - [enterpriseConnection], + [enterpriseConnection, isLoading, provider, setProvider, clearProvider, createConnection], ); return {children}; From 57651828cee1db887b14f1887a666c499056c632 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 17:27:11 -0300 Subject: [PATCH 08/26] feat(ui): add deriveInitialStep helper for ConfigureSSO Pure helper that returns the wizard step a user should mount on based on the state of their enterprise connection. No connection lands on Select Provider, an existing connection without SAML IdP metadata lands on Configure, and a fully configured connection lands on Confirmation. Covered by a table-driven unit test. --- .../__tests__/deriveInitialStep.test.ts | 88 +++++++++++++++++++ .../ConfigureSSO/deriveInitialStep.ts | 25 ++++++ 2 files changed, 113 insertions(+) create mode 100644 packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts create mode 100644 packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts diff --git a/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts b/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts new file mode 100644 index 00000000000..e8406482595 --- /dev/null +++ b/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts @@ -0,0 +1,88 @@ +import type { EnterpriseConnectionResource } from '@clerk/shared/types'; +import { describe, expect, it } from 'vitest'; + +import { deriveInitialStep, type WizardStepId } from '../deriveInitialStep'; + +const makeConnection = (overrides: Partial = {}): EnterpriseConnectionResource => + ({ + id: 'enc_1', + name: 'acme.com', + active: false, + provider: 'saml_okta', + logoPublicUrl: null, + domains: ['acme.com'], + organizationId: null, + syncUserAttributes: false, + disableAdditionalIdentifications: false, + allowOrganizationAccountLinking: false, + customAttributes: [], + oauthConfig: null, + samlConnection: null, + createdAt: null, + updatedAt: null, + __internal_toSnapshot: () => ({}) as any, + ...overrides, + }) as EnterpriseConnectionResource; + +describe('deriveInitialStep', () => { + const cases: Array<{ name: string; input: EnterpriseConnectionResource | undefined; expected: WizardStepId }> = [ + { + name: 'no connection → select-provider', + input: undefined, + expected: 'select-provider', + }, + { + name: 'connection without samlConnection → configure', + input: makeConnection({ samlConnection: null }), + expected: 'configure', + }, + { + name: 'connection with empty samlConnection.idpSsoUrl → configure', + input: makeConnection({ + samlConnection: { + id: 'saml_1', + name: 'acme.com', + active: false, + idpEntityId: '', + idpSsoUrl: '', + idpCertificate: '', + idpMetadataUrl: '', + idpMetadata: '', + acsUrl: 'https://clerk.example.com/acs', + spEntityId: 'https://clerk.example.com', + spMetadataUrl: 'https://clerk.example.com/sp-metadata', + allowSubdomains: false, + allowIdpInitiated: false, + forceAuthn: false, + }, + }), + expected: 'configure', + }, + { + name: 'connection with samlConnection.idpSsoUrl populated → confirmation', + input: makeConnection({ + samlConnection: { + id: 'saml_1', + name: 'acme.com', + active: true, + idpEntityId: 'https://idp.example.com/entity', + idpSsoUrl: 'https://idp.example.com/sso', + idpCertificate: 'CERT', + idpMetadataUrl: 'https://idp.example.com/metadata', + idpMetadata: '', + acsUrl: 'https://clerk.example.com/acs', + spEntityId: 'https://clerk.example.com', + spMetadataUrl: 'https://clerk.example.com/sp-metadata', + allowSubdomains: false, + allowIdpInitiated: false, + forceAuthn: false, + }, + }), + expected: 'confirmation', + }, + ]; + + it.each(cases)('$name', ({ input, expected }) => { + expect(deriveInitialStep(input)).toBe(expected); + }); +}); diff --git a/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts b/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts new file mode 100644 index 00000000000..b324cab39eb --- /dev/null +++ b/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts @@ -0,0 +1,25 @@ +import type { EnterpriseConnectionResource } from '@clerk/shared/types'; + +export type WizardStepId = 'select-provider' | 'verify-domain' | 'configure' | 'test' | 'confirmation'; + +/** + * Decides where the ConfigureSSO wizard should mount on (re)load based on + * the current state of the user's enterprise connection. + * + * No connection → `select-provider` + * Connection without SAML IdP metadata → `configure` + * Connection with SAML IdP metadata → `confirmation` + * + * The `test` step is intentionally absent — we can't derive a "last test + * passed" signal synchronously from the resource. Users can re-test from + * Confirmation. + */ +export const deriveInitialStep = (connection: EnterpriseConnectionResource | undefined): WizardStepId => { + if (!connection) { + return 'select-provider'; + } + if (!connection.samlConnection?.idpSsoUrl) { + return 'configure'; + } + return 'confirmation'; +}; From 61a9160d560bb30ee95c76bc689ec7f0b49cccee Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 17:28:07 -0300 Subject: [PATCH 09/26] feat(ui): create enterprise connection on Select Provider Continue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires Wizard.initialStepId via deriveInitialStep so the ConfigureSSO wizard re-mounts on the right step after reload. Renames the local ProviderType ids in Select Provider from 'okta' / 'custom_saml' to the backend values 'saml_okta' / 'saml_custom' so the Continue handler can forward them directly. Continue now sets the provider on the flow context, calls createConnection, and advances the wizard — wrapped in useCardState for loading and error display following the same pattern as Verify Domain. --- .../components/ConfigureSSO/ConfigureSSO.tsx | 79 +++++++++++-------- .../ConfigureSSO/steps/SelectProviderStep.tsx | 45 +++++++++-- 2 files changed, 85 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 4ab8dcccb3d..21ee3a68e14 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -10,10 +10,11 @@ import { ProfileCard } from '@/elements/ProfileCard'; import { ExclamationTriangle } from '@/icons'; import { Route, Switch } from '@/router'; -import { ConfigureSSOFlowProvider } from './ConfigureSSOContext'; +import { ConfigureSSOFlowProvider, useConfigureSSOFlow } from './ConfigureSSOContext'; import { ConfigureSSOHeader } from './ConfigureSSOHeader'; import { ConfigureSSONavbar } from './ConfigureSSONavbar'; import { ConfigureSSOSkeleton } from './ConfigureSSOSkeleton'; +import { deriveInitialStep } from './deriveInitialStep'; import { ProfileCardFooter, ProfileCardHeader } from './elements/ProfileCard'; import { Step } from './elements/Step'; import { Wizard } from './elements/Wizard'; @@ -74,43 +75,55 @@ const ConfigureSSOCardContent = () => { } return ( - - - + + + + ); +}; - - - +const ConfigureSSOSteps = () => { + const { enterpriseConnection } = useConfigureSSOFlow(); + const initialStepId = deriveInitialStep(enterpriseConnection); - - - + return ( + + - - - + + + - - - + + + - - - - - + + + + + + + + + + + + ); }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 60c1afe8533..bd833d097c8 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -13,17 +13,18 @@ import { Text, useLocalizations, } from '@/customizables'; +import { useCardState } from '@/elements/contexts'; import { Alert } from '@/ui/elements/Alert'; +import { handleError } from '@/utils/errorHandler'; +import { type ProviderType, useConfigureSSOFlow } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; -type ProviderType = 'okta' | 'custom_saml'; - const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: LocalizationKey; iconId: string }> = [ - { id: 'okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' }, + { id: 'saml_okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' }, { - id: 'custom_saml', + id: 'saml_custom', label: localizationKeys('configureSSO.selectProviderStep.saml.customSaml'), iconId: 'saml', }, @@ -31,8 +32,29 @@ const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: LocalizationKey export const SelectProviderStep = (): JSX.Element => { const { goNext, goPrev, isFirstStep, isLastStep } = useWizard(); + const { setProvider, createConnection } = useConfigureSSOFlow(); + const card = useCardState(); const [selected, setSelected] = React.useState(null); + const handleContinue = async () => { + if (!selected) { + return; + } + + setProvider(selected); + card.setError(undefined); + card.setLoading(); + + try { + await createConnection(); + void goNext(); + } catch (err) { + handleError(err as Error, [], card.setError); + } finally { + card.setIdle(); + } + }; + return ( { variant='warning' title={localizationKeys('configureSSO.selectProviderStep.warning')} /> + + {card.error ? ( + ({ color: theme.colors.$danger500, fontSize: theme.fontSizes.$sm })} + > + {card.error} + + ) : null} @@ -99,8 +131,9 @@ export const SelectProviderStep = (): JSX.Element => { isDisabled={isFirstStep} /> goNext()} - isDisabled={isLastStep || !selected} + onClick={handleContinue} + isLoading={card.isLoading} + isDisabled={isLastStep || !selected || card.isLoading} /> From de1f6e1f6dc1bb1f064af7f4ea84474951cac460 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 17:32:16 -0300 Subject: [PATCH 10/26] test(ui): refresh Select Provider tests for createConnection flow Mocks the ConfigureSSO flow context so the Continue handler can be verified end-to-end. Asserts setProvider is called with the backend provider id, followed by createConnection, then goNext, and adds coverage for the loading state and the rejection path where the wizard must not advance and the error must surface in the card. --- .../__tests__/SelectProviderStep.test.tsx | 154 ++++++++++++++++-- 1 file changed, 142 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index f492245ab24..42d1a7c3e5d 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -1,7 +1,10 @@ +import { ClerkRuntimeError } from '@clerk/shared/error'; +import type { ReactElement } from 'react'; import { describe, expect, it, vi } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; -import { render, screen } from '@/test/utils'; +import { render, screen, waitFor } from '@/test/utils'; +import { CardStateProvider } from '@/ui/elements/contexts'; const goNext = vi.fn(); const goPrev = vi.fn(); @@ -23,30 +26,64 @@ vi.mock('../../elements/Wizard', () => ({ }), })); +const setProvider = vi.fn(); +const clearProvider = vi.fn(); +const createConnection = vi.fn(); + +vi.mock('../../ConfigureSSOContext', () => ({ + useConfigureSSOFlow: () => ({ + enterpriseConnection: undefined, + isLoading: false, + provider: undefined, + setProvider, + clearProvider, + createConnection, + }), +})); + import { SelectProviderStep } from '../SelectProviderStep'; const { createFixtures } = bindCreateFixtures('ConfigureSSO'); +const renderStep = ( + wrapper: React.ComponentType<{ children?: React.ReactNode }>, + ui: ReactElement = , +) => { + return render({ui}, { wrapper }); +}; + +const resetMocks = () => { + goNext.mockReset(); + goPrev.mockReset(); + setProvider.mockReset(); + clearProvider.mockReset(); + createConnection.mockReset(); + createConnection.mockResolvedValue(undefined); +}; + describe('SelectProviderStep', () => { it('mounts and renders the step header', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - render(, { wrapper }); + renderStep(wrapper); expect(screen.getByRole('heading', { name: 'Select provider' })).toBeInTheDocument(); expect(screen.getByText('Select your identity provider')).toBeInTheDocument(); }); it('renders both SAML provider tiles with their labels', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - render(, { wrapper }); + renderStep(wrapper); expect(screen.getByRole('button', { name: 'Okta Workforce' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Custom SAML Provider' })).toBeInTheDocument(); }); it('loads each tile icon from img.clerk.com', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - const { container } = render(, { wrapper }); + const { container } = renderStep(wrapper); // Emotion serializes sx into stylesheets, so we check both inline + the document's collected styles const iconSpans = Array.from(container.querySelectorAll('button span[aria-hidden]')); @@ -62,15 +99,17 @@ describe('SelectProviderStep', () => { }); it('disables Continue when no provider is selected', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - render(, { wrapper }); + renderStep(wrapper); expect(screen.getByRole('button', { name: /Continue/i })).toBeDisabled(); }); it('marks the clicked tile as pressed and enables Continue', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - const { userEvent } = render(, { wrapper }); + const { userEvent } = renderStep(wrapper); const oktaTile = screen.getByRole('button', { name: 'Okta Workforce' }); expect(oktaTile).toHaveAttribute('aria-pressed', 'false'); @@ -82,8 +121,9 @@ describe('SelectProviderStep', () => { }); it('flips selection when a different tile is clicked', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - const { userEvent } = render(, { wrapper }); + const { userEvent } = renderStep(wrapper); const oktaTile = screen.getByRole('button', { name: 'Okta Workforce' }); const customSamlTile = screen.getByRole('button', { name: 'Custom SAML Provider' }); @@ -97,20 +137,110 @@ describe('SelectProviderStep', () => { expect(customSamlTile).toHaveAttribute('aria-pressed', 'true'); }); - it('calls goNext when Continue is clicked after a selection', async () => { - goNext.mockClear(); + it('calls setProvider, createConnection, then goNext when Continue is clicked', async () => { + resetMocks(); + const callOrder: string[] = []; + setProvider.mockImplementation(() => { + callOrder.push('setProvider'); + }); + createConnection.mockImplementation(() => { + callOrder.push('createConnection'); + return Promise.resolve(); + }); + goNext.mockImplementation(() => { + callOrder.push('goNext'); + }); + + const { wrapper } = await createFixtures(); + const { userEvent } = renderStep(wrapper); + + await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); + await userEvent.click(screen.getByRole('button', { name: /Continue/i })); + + await waitFor(() => { + expect(goNext).toHaveBeenCalledTimes(1); + }); + + expect(setProvider).toHaveBeenCalledWith('saml_okta'); + expect(createConnection).toHaveBeenCalledTimes(1); + expect(callOrder).toEqual(['setProvider', 'createConnection', 'goNext']); + }); + + it('forwards the Custom SAML backend provider id when selected', async () => { + resetMocks(); + const { wrapper } = await createFixtures(); + const { userEvent } = renderStep(wrapper); + + await userEvent.click(screen.getByRole('button', { name: 'Custom SAML Provider' })); + await userEvent.click(screen.getByRole('button', { name: /Continue/i })); + + await waitFor(() => { + expect(goNext).toHaveBeenCalledTimes(1); + }); + + expect(setProvider).toHaveBeenCalledWith('saml_custom'); + expect(createConnection).toHaveBeenCalledTimes(1); + }); + + it('shows loading state while createConnection is pending', async () => { + resetMocks(); + let resolveCreate: () => void = () => undefined; + createConnection.mockImplementation( + () => + new Promise(resolve => { + resolveCreate = resolve; + }), + ); + + const { wrapper } = await createFixtures(); + const { userEvent } = renderStep(wrapper); + + await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); + const continueButton = screen.getByRole('button', { name: /Continue/i }); + await userEvent.click(continueButton); + + // While create is pending, Continue stays disabled and goNext hasn't fired. + // The button's accessible name flips to the spinner's "Loading" label while pending. + await waitFor(() => { + expect(createConnection).toHaveBeenCalledTimes(1); + }); + expect(continueButton).toBeDisabled(); + expect(goNext).not.toHaveBeenCalled(); + + resolveCreate(); + + await waitFor(() => { + expect(goNext).toHaveBeenCalledTimes(1); + }); + }); + + it('does not advance and surfaces the error when createConnection rejects', async () => { + resetMocks(); + createConnection.mockRejectedValue(new ClerkRuntimeError('Backend unavailable', { code: 'create_failed' })); + const { wrapper } = await createFixtures(); - const { userEvent } = render(, { wrapper }); + const { userEvent, container } = renderStep(wrapper); await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); await userEvent.click(screen.getByRole('button', { name: /Continue/i })); - expect(goNext).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(createConnection).toHaveBeenCalledTimes(1); + }); + + expect(goNext).not.toHaveBeenCalled(); + expect(setProvider).toHaveBeenCalledWith('saml_okta'); + // Allow microtasks to flush so the rejection -> handleError -> setError chain settles + await waitFor(() => { + const text = container.textContent ?? ''; + expect(text).toContain('Backend unavailable'); + }); }); it('disables Previous on the first step', async () => { + resetMocks(); const { wrapper } = await createFixtures(); - render(, { wrapper }); + renderStep(wrapper); expect(screen.getByRole('button', { name: /Previous/i })).toBeDisabled(); }); From bf322cf64fa76b44f347ba31d11f0a7ead8cfedb Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Mon, 11 May 2026 17:34:45 -0300 Subject: [PATCH 11/26] chore(changeset): expand ConfigureSSO Select Provider entry for Phase 2 Notes the flow-context extensions, deriveInitialStep helper, and the createConnection-on-Continue behavior so the published changelog matches what the PR actually ships. --- .changeset/configure-sso-select-provider-step.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/configure-sso-select-provider-step.md b/.changeset/configure-sso-select-provider-step.md index 086d23ec150..947e8f08fb1 100644 --- a/.changeset/configure-sso-select-provider-step.md +++ b/.changeset/configure-sso-select-provider-step.md @@ -6,3 +6,5 @@ --- Implement the provider selection step of `<__experimental_ConfigureSSO />`. Renders the two SAML provider tiles (Okta Workforce and Custom SAML Provider) with real icons sourced from `img.clerk.com`, tracks the picked provider in local state, and gates `Step.Footer.Continue` on a selection. Includes a warning callout about provider lock-in and a minor `Step.Header` alignment tweak. All user-visible strings are wired through `@clerk/localizations`, with translations for every supported locale. + +Also extends the flow context with `provider`, `setProvider`, `clearProvider`, and an idempotent `createConnection`, adds the `deriveInitialStep` helper, and wires the wizard's `initialStepId` so the configure flow remounts on the right step after a reload. Continue on Select Provider now creates the enterprise connection and advances to the next step. From 31de875a2a28db02b67dbbd2592e3dee3329d9d9 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Tue, 12 May 2026 09:27:09 -0300 Subject: [PATCH 12/26] 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. --- .../components/ConfigureSSO/ConfigureSSO.tsx | 4 +- .../ConfigureSSO/ConfigureSSOContext.tsx | 41 ++++------ .../ConfigureSSO/deriveInitialStep.ts | 2 +- .../ConfigureSSO/steps/SelectProviderStep.tsx | 82 +++++++++++-------- .../ui/src/components/ConfigureSSO/types.ts | 3 + 5 files changed, 69 insertions(+), 63 deletions(-) create mode 100644 packages/ui/src/components/ConfigureSSO/types.ts diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 21ee3a68e14..ec494ccc91f 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -14,7 +14,6 @@ import { ConfigureSSOFlowProvider, useConfigureSSOFlow } from './ConfigureSSOCon import { ConfigureSSOHeader } from './ConfigureSSOHeader'; import { ConfigureSSONavbar } from './ConfigureSSONavbar'; import { ConfigureSSOSkeleton } from './ConfigureSSOSkeleton'; -import { deriveInitialStep } from './deriveInitialStep'; import { ProfileCardFooter, ProfileCardHeader } from './elements/ProfileCard'; import { Step } from './elements/Step'; import { Wizard } from './elements/Wizard'; @@ -85,8 +84,7 @@ const ConfigureSSOCardContent = () => { }; const ConfigureSSOSteps = () => { - const { enterpriseConnection } = useConfigureSSOFlow(); - const initialStepId = deriveInitialStep(enterpriseConnection); + const { initialStepId } = useConfigureSSOFlow(); return ( diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx index c565c774398..591c529288f 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx @@ -1,13 +1,15 @@ -import { useReverification, useSession, useUser } from '@clerk/shared/react'; +import { __internal_useUserEnterpriseConnections, useSession, useUser } from '@clerk/shared/react'; import type { EnterpriseConnectionResource } from '@clerk/shared/types'; import React, { type PropsWithChildren } from 'react'; -export type ProviderType = 'saml_okta' | 'saml_custom'; +import { deriveInitialStep } from './deriveInitialStep'; +import type { ProviderType, WizardStepId } from './types'; /** * Shared form state for the ConfigureSSO wizard, persisted across steps */ export interface ConfigureSSOData { + initialStepId: WizardStepId; /** * The enterprise connection from the user's primary email address domain */ @@ -27,11 +29,6 @@ export interface ConfigureSSOData { * connection has been created. */ setProvider: (provider: ProviderType) => void; - /** - * Clears the local provider selection. Used by Reset on the Confirmation - * step (wired up by a follow-up PR). - */ - clearProvider: () => void; /** * Creates the enterprise connection from the current provider selection, * the user's primary email domain, and the session's active organization. @@ -56,11 +53,15 @@ export const ConfigureSSOFlowProvider = ({ }: PropsWithChildren): JSX.Element => { const { user } = useUser(); const { session } = useSession(); - const [localProvider, setLocalProvider] = React.useState(undefined); + const [provider, setProvider] = React.useState( + enterpriseConnection?.provider as ProviderType, + ); + + const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections(); - const provider = (enterpriseConnection?.provider as ProviderType | undefined) ?? localProvider; + const initialStepId = deriveInitialStep(enterpriseConnection); - const createConnectionFetcher = React.useCallback(async () => { + const createConnection = React.useCallback(async () => { if (enterpriseConnection) { return; } @@ -74,33 +75,23 @@ export const ConfigureSSOFlowProvider = ({ const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; const organizationId = session?.lastActiveOrganizationId ?? null; - await user.createEnterpriseConnection({ - provider, + await createEnterpriseConnection({ + provider: 'saml_okta', name: emailDomain, organizationId, }); - }, [enterpriseConnection, provider, user, session]); - - const createConnection = useReverification(createConnectionFetcher); - - const setProvider = React.useCallback((next: ProviderType) => { - setLocalProvider(next); - }, []); - - const clearProvider = React.useCallback(() => { - setLocalProvider(undefined); - }, []); + }, [enterpriseConnection, provider, user, session, createEnterpriseConnection]); const value = React.useMemo( () => ({ + initialStepId, enterpriseConnection, isLoading, provider, setProvider, - clearProvider, createConnection, }), - [enterpriseConnection, isLoading, provider, setProvider, clearProvider, createConnection], + [initialStepId, enterpriseConnection, isLoading, provider, setProvider, createConnection], ); return {children}; diff --git a/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts b/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts index b324cab39eb..fb19fee3138 100644 --- a/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts +++ b/packages/ui/src/components/ConfigureSSO/deriveInitialStep.ts @@ -1,6 +1,6 @@ import type { EnterpriseConnectionResource } from '@clerk/shared/types'; -export type WizardStepId = 'select-provider' | 'verify-domain' | 'configure' | 'test' | 'confirmation'; +import type { WizardStepId } from './types'; /** * Decides where the ConfigureSSO wizard should mount on (re)load based on diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index bd833d097c8..180c379f8e8 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -17,23 +17,34 @@ import { useCardState } from '@/elements/contexts'; import { Alert } from '@/ui/elements/Alert'; import { handleError } from '@/utils/errorHandler'; -import { type ProviderType, useConfigureSSOFlow } from '../ConfigureSSOContext'; +import { useConfigureSSOFlow } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; +import type { ProviderType } from '../types'; -const PROVIDER_OPTIONS: ReadonlyArray<{ id: ProviderType; label: LocalizationKey; iconId: string }> = [ - { id: 'saml_okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' }, +const PROVIDER_GROUPS: ReadonlyArray<{ + id: 'saml'; + label: LocalizationKey; + options: ReadonlyArray<{ id: ProviderType; label: LocalizationKey; iconId: string }>; +}> = [ { - id: 'saml_custom', - label: localizationKeys('configureSSO.selectProviderStep.saml.customSaml'), - iconId: 'saml', + id: 'saml', + label: localizationKeys('configureSSO.selectProviderStep.saml.groupLabel'), + options: [ + { id: 'saml_okta', label: localizationKeys('configureSSO.selectProviderStep.saml.okta'), iconId: 'okta' }, + { + id: 'saml_custom', + label: localizationKeys('configureSSO.selectProviderStep.saml.customSaml'), + iconId: 'saml', + }, + ], }, ]; export const SelectProviderStep = (): JSX.Element => { - const { goNext, goPrev, isFirstStep, isLastStep } = useWizard(); - const { setProvider, createConnection } = useConfigureSSOFlow(); const card = useCardState(); + const { goNext, isLastStep } = useWizard(); + const { setProvider, createConnection } = useConfigureSSOFlow(); const [selected, setSelected] = React.useState(null); const handleContinue = async () => { @@ -84,29 +95,34 @@ export const SelectProviderStep = (): JSX.Element => { /> - ({ gap: theme.space.$3 })}> - ({ color: theme.colors.$colorForeground })} - localizationKey={localizationKeys('configureSSO.selectProviderStep.saml.groupLabel')} - /> - - ( + ({ gap: theme.space.$3 })} > - {PROVIDER_OPTIONS.map(option => ( - setSelected(option.id)} - /> - ))} - - + ({ color: theme.colors.$colorForeground })} + localizationKey={group.label} + /> + + + {group.options.map(option => ( + setSelected(option.id)} + /> + ))} + + + ))} { - goPrev()} - isDisabled={isFirstStep} - /> + + Date: Tue, 12 May 2026 10:08:20 -0300 Subject: [PATCH 13/26] fix(ui): pass selected provider to createConnection and restore reverification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - createConnection now accepts the provider as a parameter instead of reading it from React state (which was stale within the same tick as setProvider) - Restore the useReverification wrap on createConnection, matching the rest of @clerk/ui's user.* mutation pattern (createEmailAddress, delete, createTOTP, setPrimary, createWeb3Wallet, …) — handles backend session-reverification policy transparently when present, no-ops otherwise --- .../ConfigureSSO/ConfigureSSOContext.tsx | 51 ++++++++++--------- .../ConfigureSSO/steps/SelectProviderStep.tsx | 2 +- .../__tests__/SelectProviderStep.test.tsx | 2 + 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx index 591c529288f..94418d6f951 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx @@ -1,4 +1,4 @@ -import { __internal_useUserEnterpriseConnections, useSession, useUser } from '@clerk/shared/react'; +import { __internal_useUserEnterpriseConnections, useReverification, useSession, useUser } from '@clerk/shared/react'; import type { EnterpriseConnectionResource } from '@clerk/shared/types'; import React, { type PropsWithChildren } from 'react'; @@ -30,12 +30,11 @@ export interface ConfigureSSOData { */ setProvider: (provider: ProviderType) => void; /** - * Creates the enterprise connection from the current provider selection, - * the user's primary email domain, and the session's active organization. - * No-ops when an enterprise connection already exists so callers can - * safely re-trigger. + * Creates the enterprise connection for the supplied provider, the user's + * primary email domain, and the session's active organization. No-ops when + * an enterprise connection already exists so callers can safely re-trigger. */ - createConnection: () => Promise; + createConnection: (provider: ProviderType) => Promise; } interface ConfigureSSOFlowProviderProps { @@ -61,26 +60,28 @@ export const ConfigureSSOFlowProvider = ({ const initialStepId = deriveInitialStep(enterpriseConnection); - const createConnection = React.useCallback(async () => { - if (enterpriseConnection) { - return; - } - if (!provider) { - throw new Error('Provider not selected'); - } - if (!user?.primaryEmailAddress) { - throw new Error('Primary email required'); - } + const createConnectionFetcher = React.useCallback( + async (selectedProvider: ProviderType) => { + if (enterpriseConnection) { + return; + } + if (!user?.primaryEmailAddress) { + throw new Error('Primary email required'); + } - const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; - const organizationId = session?.lastActiveOrganizationId ?? null; + const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; + const organizationId = session?.lastActiveOrganizationId ?? null; - await createEnterpriseConnection({ - provider: 'saml_okta', - name: emailDomain, - organizationId, - }); - }, [enterpriseConnection, provider, user, session, createEnterpriseConnection]); + await createEnterpriseConnection({ + provider: selectedProvider, + name: emailDomain, + organizationId, + }); + }, + [enterpriseConnection, user, session, createEnterpriseConnection], + ); + + const createConnection = useReverification(createConnectionFetcher); const value = React.useMemo( () => ({ @@ -91,7 +92,7 @@ export const ConfigureSSOFlowProvider = ({ setProvider, createConnection, }), - [initialStepId, enterpriseConnection, isLoading, provider, setProvider, createConnection], + [initialStepId, enterpriseConnection, isLoading, provider, createConnection], ); return {children}; diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 180c379f8e8..7c0d8a2d12d 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -57,7 +57,7 @@ export const SelectProviderStep = (): JSX.Element => { card.setLoading(); try { - await createConnection(); + await createConnection(selected); void goNext(); } catch (err) { handleError(err as Error, [], card.setError); diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index 42d1a7c3e5d..2cb6fff3de9 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -163,6 +163,7 @@ describe('SelectProviderStep', () => { expect(setProvider).toHaveBeenCalledWith('saml_okta'); expect(createConnection).toHaveBeenCalledTimes(1); + expect(createConnection).toHaveBeenCalledWith('saml_okta'); expect(callOrder).toEqual(['setProvider', 'createConnection', 'goNext']); }); @@ -180,6 +181,7 @@ describe('SelectProviderStep', () => { expect(setProvider).toHaveBeenCalledWith('saml_custom'); expect(createConnection).toHaveBeenCalledTimes(1); + expect(createConnection).toHaveBeenCalledWith('saml_custom'); }); it('shows loading state while createConnection is pending', async () => { From e62ce600fdd52ce69a5ec2fb7c4efc51be92cd11 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Tue, 12 May 2026 10:10:51 -0300 Subject: [PATCH 14/26] refactor(ui): single-source the enterprise connection query in ConfigureSSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hoist the __internal_useUserEnterpriseConnections call to ConfigureSSOCardContent so the provider receives the data and the create mutation as props instead of calling the hook a second time - Drop the unused isLoading from the provider surface; the load gate upstream already consumes it - Rename ConfigureSSOFlowProvider → ConfigureSSOProvider, useConfigureSSOFlow → useConfigureSSO, drop the leftover "Flow" suffix --- .../components/ConfigureSSO/ConfigureSSO.tsx | 17 +++++---- .../ConfigureSSO/ConfigureSSOContext.tsx | 37 ++++++++----------- .../ConfigureSSO/steps/SelectProviderStep.tsx | 4 +- .../__tests__/SelectProviderStep.test.tsx | 3 +- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index ec494ccc91f..5dd9c15daab 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -10,7 +10,7 @@ import { ProfileCard } from '@/elements/ProfileCard'; import { ExclamationTriangle } from '@/icons'; import { Route, Switch } from '@/router'; -import { ConfigureSSOFlowProvider, useConfigureSSOFlow } from './ConfigureSSOContext'; +import { ConfigureSSOProvider, useConfigureSSO } from './ConfigureSSOContext'; import { ConfigureSSOHeader } from './ConfigureSSOHeader'; import { ConfigureSSONavbar } from './ConfigureSSONavbar'; import { ConfigureSSOSkeleton } from './ConfigureSSOSkeleton'; @@ -64,8 +64,11 @@ const AuthenticatedContent = withCoreUserGuard(() => { }); const ConfigureSSOCardContent = () => { - const { data: enterpriseConnections, isLoading } = __internal_useUserEnterpriseConnections({ enabled: true }); - + const { + data: enterpriseConnections, + isLoading, + createEnterpriseConnection, + } = __internal_useUserEnterpriseConnections({ enabled: true }); // Currently FAPI only supports one enterprise connection per user const enterpriseConnection = enterpriseConnections?.[0]; @@ -74,17 +77,17 @@ const ConfigureSSOCardContent = () => { } return ( - - + ); }; const ConfigureSSOSteps = () => { - const { initialStepId } = useConfigureSSOFlow(); + const { initialStepId } = useConfigureSSO(); return ( diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx index 94418d6f951..42a3b2f67c7 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx @@ -1,5 +1,5 @@ -import { __internal_useUserEnterpriseConnections, useReverification, useSession, useUser } from '@clerk/shared/react'; -import type { EnterpriseConnectionResource } from '@clerk/shared/types'; +import { useReverification, useSession, useUser } from '@clerk/shared/react'; +import type { CreateMeEnterpriseConnectionParams, EnterpriseConnectionResource } from '@clerk/shared/types'; import React, { type PropsWithChildren } from 'react'; import { deriveInitialStep } from './deriveInitialStep'; @@ -14,10 +14,6 @@ export interface ConfigureSSOData { * The enterprise connection from the user's primary email address domain */ enterpriseConnection: EnterpriseConnectionResource | undefined; - /** - * Whether the underlying enterprise connection data is still loading - */ - isLoading: boolean; /** * The provider selected for this configuration. Reads from the existing * enterprise connection when present, falling back to the local selection @@ -37,27 +33,27 @@ export interface ConfigureSSOData { createConnection: (provider: ProviderType) => Promise; } -interface ConfigureSSOFlowProviderProps { +interface ConfigureSSOProviderProps { enterpriseConnection: EnterpriseConnectionResource | undefined; - isLoading?: boolean; + createEnterpriseConnection: ( + params: CreateMeEnterpriseConnectionParams, + ) => Promise; } -const ConfigureSSOFlowContext = React.createContext(null); -ConfigureSSOFlowContext.displayName = 'ConfigureSSOFlowContext'; +const ConfigureSSOContext = React.createContext(null); +ConfigureSSOContext.displayName = 'ConfigureSSOContext'; -export const ConfigureSSOFlowProvider = ({ +export const ConfigureSSOProvider = ({ enterpriseConnection, - isLoading = false, + createEnterpriseConnection, children, -}: PropsWithChildren): JSX.Element => { +}: PropsWithChildren): JSX.Element => { const { user } = useUser(); const { session } = useSession(); const [provider, setProvider] = React.useState( enterpriseConnection?.provider as ProviderType, ); - const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections(); - const initialStepId = deriveInitialStep(enterpriseConnection); const createConnectionFetcher = React.useCallback( @@ -87,21 +83,20 @@ export const ConfigureSSOFlowProvider = ({ () => ({ initialStepId, enterpriseConnection, - isLoading, provider, setProvider, createConnection, }), - [initialStepId, enterpriseConnection, isLoading, provider, createConnection], + [initialStepId, enterpriseConnection, provider, createConnection], ); - return {children}; + return {children}; }; -export const useConfigureSSOFlow = (): ConfigureSSOData => { - const ctx = React.useContext(ConfigureSSOFlowContext); +export const useConfigureSSO = (): ConfigureSSOData => { + const ctx = React.useContext(ConfigureSSOContext); if (!ctx) { - throw new Error('useConfigureSSOFlow called outside .'); + throw new Error('useConfigureSSO called outside .'); } return ctx; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 7c0d8a2d12d..e012c48df89 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -17,7 +17,7 @@ import { useCardState } from '@/elements/contexts'; import { Alert } from '@/ui/elements/Alert'; import { handleError } from '@/utils/errorHandler'; -import { useConfigureSSOFlow } from '../ConfigureSSOContext'; +import { useConfigureSSO } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; import type { ProviderType } from '../types'; @@ -44,7 +44,7 @@ const PROVIDER_GROUPS: ReadonlyArray<{ export const SelectProviderStep = (): JSX.Element => { const card = useCardState(); const { goNext, isLastStep } = useWizard(); - const { setProvider, createConnection } = useConfigureSSOFlow(); + const { setProvider, createConnection } = useConfigureSSO(); const [selected, setSelected] = React.useState(null); const handleContinue = async () => { diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index 2cb6fff3de9..115c6b6efd3 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -31,9 +31,8 @@ const clearProvider = vi.fn(); const createConnection = vi.fn(); vi.mock('../../ConfigureSSOContext', () => ({ - useConfigureSSOFlow: () => ({ + useConfigureSSO: () => ({ enterpriseConnection: undefined, - isLoading: false, provider: undefined, setProvider, clearProvider, From 462dc41d41929ee676a861bfb422163db304d0d7 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Tue, 12 May 2026 10:25:33 -0300 Subject: [PATCH 15/26] feat(ui): surface SAML ACS URL in ConfigureSSO Configure step Pull the enterprise connection from ConfigureSSOContext inside ConfigureStep and render its SAML ACS URL as the single sign-on URL, replacing the placeholder text. Drop the now-unused isLastStep guard from the Select Provider continue button. --- packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx | 1 + .../src/components/ConfigureSSO/steps/ConfigureStep.tsx | 8 ++++---- .../components/ConfigureSSO/steps/SelectProviderStep.tsx | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 5dd9c15daab..9be9db0a9ea 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -69,6 +69,7 @@ const ConfigureSSOCardContent = () => { isLoading, createEnterpriseConnection, } = __internal_useUserEnterpriseConnections({ enabled: true }); + // Currently FAPI only supports one enterprise connection per user const enterpriseConnection = enterpriseConnections?.[0]; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 5b19dbb43c9..af420302c57 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -1,10 +1,12 @@ -import { descriptors, Flow, Text } from '@/customizables'; +import { descriptors, Flow } from '@/customizables'; +import { useConfigureSSO } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; export const ConfigureStep = (): JSX.Element => { const { goNext, goPrev, isFirstStep, isLastStep } = useWizard(); + const { enterpriseConnection } = useConfigureSSO(); return ( @@ -18,9 +20,7 @@ export const ConfigureStep = (): JSX.Element => { /> - - UI goes here - + Single sign-on URL: {enterpriseConnection?.samlConnection?.acsUrl} diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index e012c48df89..82e72b9b935 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -43,7 +43,7 @@ const PROVIDER_GROUPS: ReadonlyArray<{ export const SelectProviderStep = (): JSX.Element => { const card = useCardState(); - const { goNext, isLastStep } = useWizard(); + const { goNext } = useWizard(); const { setProvider, createConnection } = useConfigureSSO(); const [selected, setSelected] = React.useState(null); @@ -147,7 +147,7 @@ export const SelectProviderStep = (): JSX.Element => { From 7606b8ea3a6c8b4224802fd22ea24e084e21cb52 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Tue, 12 May 2026 11:25:00 -0300 Subject: [PATCH 16/26] fix(ui): import WizardStepId from types module in deriveInitialStep test --- .../ConfigureSSO/__tests__/deriveInitialStep.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts b/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts index e8406482595..2f67bbcc603 100644 --- a/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts +++ b/packages/ui/src/components/ConfigureSSO/__tests__/deriveInitialStep.test.ts @@ -1,7 +1,8 @@ import type { EnterpriseConnectionResource } from '@clerk/shared/types'; import { describe, expect, it } from 'vitest'; -import { deriveInitialStep, type WizardStepId } from '../deriveInitialStep'; +import { deriveInitialStep } from '../deriveInitialStep'; +import type { WizardStepId } from '../types'; const makeConnection = (overrides: Partial = {}): EnterpriseConnectionResource => ({ From 10f0e9c03352144a7206205e70e9c75eaffc0133 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Tue, 12 May 2026 23:02:08 -0300 Subject: [PATCH 17/26] feat(ui): stack Select Provider tiles single-column on mobile Below 30em the SAML provider grid drops to one card per row instead of the cramped two-column layout. Uses the existing mqu.sm breakpoint and swaps the Grid's columns variant for an sx-driven gridTemplateColumns so the responsive override can layer on top. --- .../components/ConfigureSSO/steps/SelectProviderStep.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 82e72b9b935..2d0bdb79e98 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -14,6 +14,7 @@ import { useLocalizations, } from '@/customizables'; import { useCardState } from '@/elements/contexts'; +import { mqu } from '@/styledSystem'; import { Alert } from '@/ui/elements/Alert'; import { handleError } from '@/utils/errorHandler'; @@ -109,7 +110,12 @@ export const SelectProviderStep = (): JSX.Element => { {group.options.map(option => ( Date: Wed, 13 May 2026 08:12:38 -0300 Subject: [PATCH 18/26] refactor(ui): shrink ConfigureSSO context to shared state only Drop createConnection from the context surface and the createEnterpriseConnection prop on ConfigureSSOProvider. The provider now only owns shared wizard state (enterpriseConnection, provider, setProvider, initialStepId). Create logic moves into the Select Provider step in a follow-up commit. --- .../components/ConfigureSSO/ConfigureSSO.tsx | 11 +---- .../ConfigureSSO/ConfigureSSOContext.tsx | 41 +------------------ 2 files changed, 4 insertions(+), 48 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 9be9db0a9ea..a286947c2e6 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -64,11 +64,7 @@ const AuthenticatedContent = withCoreUserGuard(() => { }); const ConfigureSSOCardContent = () => { - const { - data: enterpriseConnections, - isLoading, - createEnterpriseConnection, - } = __internal_useUserEnterpriseConnections({ enabled: true }); + const { data: enterpriseConnections, isLoading } = __internal_useUserEnterpriseConnections({ enabled: true }); // Currently FAPI only supports one enterprise connection per user const enterpriseConnection = enterpriseConnections?.[0]; @@ -78,10 +74,7 @@ const ConfigureSSOCardContent = () => { } return ( - + ); diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx index 42a3b2f67c7..1df2c315e6a 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx @@ -1,5 +1,4 @@ -import { useReverification, useSession, useUser } from '@clerk/shared/react'; -import type { CreateMeEnterpriseConnectionParams, EnterpriseConnectionResource } from '@clerk/shared/types'; +import type { EnterpriseConnectionResource } from '@clerk/shared/types'; import React, { type PropsWithChildren } from 'react'; import { deriveInitialStep } from './deriveInitialStep'; @@ -25,19 +24,10 @@ export interface ConfigureSSOData { * connection has been created. */ setProvider: (provider: ProviderType) => void; - /** - * Creates the enterprise connection for the supplied provider, the user's - * primary email domain, and the session's active organization. No-ops when - * an enterprise connection already exists so callers can safely re-trigger. - */ - createConnection: (provider: ProviderType) => Promise; } interface ConfigureSSOProviderProps { enterpriseConnection: EnterpriseConnectionResource | undefined; - createEnterpriseConnection: ( - params: CreateMeEnterpriseConnectionParams, - ) => Promise; } const ConfigureSSOContext = React.createContext(null); @@ -45,49 +35,22 @@ ConfigureSSOContext.displayName = 'ConfigureSSOContext'; export const ConfigureSSOProvider = ({ enterpriseConnection, - createEnterpriseConnection, children, }: PropsWithChildren): JSX.Element => { - const { user } = useUser(); - const { session } = useSession(); const [provider, setProvider] = React.useState( enterpriseConnection?.provider as ProviderType, ); const initialStepId = deriveInitialStep(enterpriseConnection); - const createConnectionFetcher = React.useCallback( - async (selectedProvider: ProviderType) => { - if (enterpriseConnection) { - return; - } - if (!user?.primaryEmailAddress) { - throw new Error('Primary email required'); - } - - const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; - const organizationId = session?.lastActiveOrganizationId ?? null; - - await createEnterpriseConnection({ - provider: selectedProvider, - name: emailDomain, - organizationId, - }); - }, - [enterpriseConnection, user, session, createEnterpriseConnection], - ); - - const createConnection = useReverification(createConnectionFetcher); - const value = React.useMemo( () => ({ initialStepId, enterpriseConnection, provider, setProvider, - createConnection, }), - [initialStepId, enterpriseConnection, provider, createConnection], + [initialStepId, enterpriseConnection, provider], ); return {children}; From 1da090a4a286e0c3f4f84d30391b5d60099c562b Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 08:17:23 -0300 Subject: [PATCH 19/26] refactor(ui): call __internal_useUserEnterpriseConnections directly in Select Provider step The Select Provider step now calls __internal_useUserEnterpriseConnections, useUser, and useSession directly, wrapping the create logic with useReverification + useCallback inline. handleContinue's behaviour is unchanged from the caller's perspective. Test mocks updated to stub the underlying hook instead of an injected createConnection. --- .../ConfigureSSO/steps/SelectProviderStep.tsx | 29 ++++++- .../__tests__/SelectProviderStep.test.tsx | 77 +++++++++++++------ 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 2d0bdb79e98..7db60166479 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -1,4 +1,5 @@ import { iconImageUrl } from '@clerk/shared/constants'; +import { __internal_useUserEnterpriseConnections, useReverification, useSession, useUser } from '@clerk/shared/react'; import React from 'react'; import type { LocalizationKey } from '@/customizables'; @@ -45,9 +46,35 @@ const PROVIDER_GROUPS: ReadonlyArray<{ export const SelectProviderStep = (): JSX.Element => { const card = useCardState(); const { goNext } = useWizard(); - const { setProvider, createConnection } = useConfigureSSO(); + const { setProvider, enterpriseConnection } = useConfigureSSO(); + const { user } = useUser(); + const { session } = useSession(); + const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections(); const [selected, setSelected] = React.useState(null); + const createConnection = useReverification( + React.useCallback( + async (selectedProvider: ProviderType) => { + if (enterpriseConnection) { + return; + } + if (!user?.primaryEmailAddress) { + throw new Error('Primary email required'); + } + + const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; + const organizationId = session?.lastActiveOrganizationId ?? null; + + await createEnterpriseConnection({ + provider: selectedProvider, + name: emailDomain, + organizationId, + }); + }, + [enterpriseConnection, user, session, createEnterpriseConnection], + ), + ); + const handleContinue = async () => { if (!selected) { return; diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index 115c6b6efd3..1937fe6b596 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -1,4 +1,5 @@ import { ClerkRuntimeError } from '@clerk/shared/error'; +import type * as ClerkSharedReact from '@clerk/shared/react'; import type { ReactElement } from 'react'; import { describe, expect, it, vi } from 'vitest'; @@ -27,19 +28,42 @@ vi.mock('../../elements/Wizard', () => ({ })); const setProvider = vi.fn(); -const clearProvider = vi.fn(); -const createConnection = vi.fn(); vi.mock('../../ConfigureSSOContext', () => ({ useConfigureSSO: () => ({ enterpriseConnection: undefined, provider: undefined, setProvider, - clearProvider, - createConnection, + initialStepId: 'select-provider', }), })); +const createEnterpriseConnection = vi.fn(); + +vi.mock('@clerk/shared/react', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + __internal_useUserEnterpriseConnections: () => ({ + data: undefined, + error: null, + isLoading: false, + isFetching: false, + createEnterpriseConnection, + updateEnterpriseConnection: vi.fn(), + deleteEnterpriseConnection: vi.fn(), + revalidate: vi.fn(), + }), + useReverification: (fn: T): T => fn, + useUser: () => ({ + user: { primaryEmailAddress: { emailAddress: 'iago@acme.com' } }, + }), + useSession: () => ({ + session: { lastActiveOrganizationId: 'org_test' }, + }), + }; +}); + import { SelectProviderStep } from '../SelectProviderStep'; const { createFixtures } = bindCreateFixtures('ConfigureSSO'); @@ -55,9 +79,8 @@ const resetMocks = () => { goNext.mockReset(); goPrev.mockReset(); setProvider.mockReset(); - clearProvider.mockReset(); - createConnection.mockReset(); - createConnection.mockResolvedValue(undefined); + createEnterpriseConnection.mockReset(); + createEnterpriseConnection.mockResolvedValue({}); }; describe('SelectProviderStep', () => { @@ -136,15 +159,15 @@ describe('SelectProviderStep', () => { expect(customSamlTile).toHaveAttribute('aria-pressed', 'true'); }); - it('calls setProvider, createConnection, then goNext when Continue is clicked', async () => { + it('calls setProvider, createEnterpriseConnection, then goNext when Continue is clicked', async () => { resetMocks(); const callOrder: string[] = []; setProvider.mockImplementation(() => { callOrder.push('setProvider'); }); - createConnection.mockImplementation(() => { - callOrder.push('createConnection'); - return Promise.resolve(); + createEnterpriseConnection.mockImplementation(() => { + callOrder.push('createEnterpriseConnection'); + return Promise.resolve({}); }); goNext.mockImplementation(() => { callOrder.push('goNext'); @@ -161,9 +184,13 @@ describe('SelectProviderStep', () => { }); expect(setProvider).toHaveBeenCalledWith('saml_okta'); - expect(createConnection).toHaveBeenCalledTimes(1); - expect(createConnection).toHaveBeenCalledWith('saml_okta'); - expect(callOrder).toEqual(['setProvider', 'createConnection', 'goNext']); + expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); + expect(createEnterpriseConnection).toHaveBeenCalledWith({ + provider: 'saml_okta', + name: 'acme.com', + organizationId: 'org_test', + }); + expect(callOrder).toEqual(['setProvider', 'createEnterpriseConnection', 'goNext']); }); it('forwards the Custom SAML backend provider id when selected', async () => { @@ -179,14 +206,18 @@ describe('SelectProviderStep', () => { }); expect(setProvider).toHaveBeenCalledWith('saml_custom'); - expect(createConnection).toHaveBeenCalledTimes(1); - expect(createConnection).toHaveBeenCalledWith('saml_custom'); + expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); + expect(createEnterpriseConnection).toHaveBeenCalledWith({ + provider: 'saml_custom', + name: 'acme.com', + organizationId: 'org_test', + }); }); - it('shows loading state while createConnection is pending', async () => { + it('shows loading state while createEnterpriseConnection is pending', async () => { resetMocks(); let resolveCreate: () => void = () => undefined; - createConnection.mockImplementation( + createEnterpriseConnection.mockImplementation( () => new Promise(resolve => { resolveCreate = resolve; @@ -203,7 +234,7 @@ describe('SelectProviderStep', () => { // While create is pending, Continue stays disabled and goNext hasn't fired. // The button's accessible name flips to the spinner's "Loading" label while pending. await waitFor(() => { - expect(createConnection).toHaveBeenCalledTimes(1); + expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); }); expect(continueButton).toBeDisabled(); expect(goNext).not.toHaveBeenCalled(); @@ -215,9 +246,11 @@ describe('SelectProviderStep', () => { }); }); - it('does not advance and surfaces the error when createConnection rejects', async () => { + it('does not advance and surfaces the error when createEnterpriseConnection rejects', async () => { resetMocks(); - createConnection.mockRejectedValue(new ClerkRuntimeError('Backend unavailable', { code: 'create_failed' })); + createEnterpriseConnection.mockRejectedValue( + new ClerkRuntimeError('Backend unavailable', { code: 'create_failed' }), + ); const { wrapper } = await createFixtures(); const { userEvent, container } = renderStep(wrapper); @@ -226,7 +259,7 @@ describe('SelectProviderStep', () => { await userEvent.click(screen.getByRole('button', { name: /Continue/i })); await waitFor(() => { - expect(createConnection).toHaveBeenCalledTimes(1); + expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); }); expect(goNext).not.toHaveBeenCalled(); From 847bbd2b23811af6310d3a56bdc36ad75230eb09 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 14:58:37 -0300 Subject: [PATCH 20/26] refactor(ui): move enterprise connection create call to Verify Domain step Previously Continue on Select Provider both staged the chosen provider and called `createEnterpriseConnection`. That meant the backend record was created before the user had actually verified ownership of the domain we were going to wire SSO into, which is why we needed an idempotency guard in the first place. Move the create call into Verify Domain's OTP `onResolve`, immediately after `setPrimaryEmailAddress`. By the time we hit that branch the user has verified the email and we've promoted it to primary, so the connection is only created against a confirmed-owned domain. The existing `enterpriseConnection` short-circuit inside `createConnection` keeps the operation idempotent for users who navigate back through the wizard. Select Provider becomes a pure selection step: track local state, call `setProvider`, advance. Trims the `__internal_useUserEnterpriseConnections`, `useReverification`, `useSession`, `useUser`, `useCardState`, and error routing it no longer needs. Tests are pared down to selection + navigation behavior; the previous create-related assertions move out of scope for this step. A follow-up could add a `VerifyDomainStep.test.tsx` covering the new create-on-resolve path. Skipped for this commit. --- .../configure-sso-select-provider-step.md | 2 +- .../ConfigureSSO/steps/SelectProviderStep.tsx | 60 +--------- .../ConfigureSSO/steps/VerifyDomainStep.tsx | 45 +++++++- .../__tests__/SelectProviderStep.test.tsx | 107 +----------------- 4 files changed, 49 insertions(+), 165 deletions(-) diff --git a/.changeset/configure-sso-select-provider-step.md b/.changeset/configure-sso-select-provider-step.md index 947e8f08fb1..d974b9f1169 100644 --- a/.changeset/configure-sso-select-provider-step.md +++ b/.changeset/configure-sso-select-provider-step.md @@ -7,4 +7,4 @@ Implement the provider selection step of `<__experimental_ConfigureSSO />`. Renders the two SAML provider tiles (Okta Workforce and Custom SAML Provider) with real icons sourced from `img.clerk.com`, tracks the picked provider in local state, and gates `Step.Footer.Continue` on a selection. Includes a warning callout about provider lock-in and a minor `Step.Header` alignment tweak. All user-visible strings are wired through `@clerk/localizations`, with translations for every supported locale. -Also extends the flow context with `provider`, `setProvider`, `clearProvider`, and an idempotent `createConnection`, adds the `deriveInitialStep` helper, and wires the wizard's `initialStepId` so the configure flow remounts on the right step after a reload. Continue on Select Provider now creates the enterprise connection and advances to the next step. +Also extends the flow context with `provider` and `setProvider`, adds the `deriveInitialStep` helper, and wires the wizard's `initialStepId` so the configure flow remounts on the right step after a reload. Continue on Select Provider stages the chosen provider and advances to the next step; the enterprise connection is created on Verify Domain once the user's email is verified and primary. diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 7db60166479..1cd57f13507 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -1,5 +1,4 @@ import { iconImageUrl } from '@clerk/shared/constants'; -import { __internal_useUserEnterpriseConnections, useReverification, useSession, useUser } from '@clerk/shared/react'; import React from 'react'; import type { LocalizationKey } from '@/customizables'; @@ -14,10 +13,8 @@ import { Text, useLocalizations, } from '@/customizables'; -import { useCardState } from '@/elements/contexts'; import { mqu } from '@/styledSystem'; import { Alert } from '@/ui/elements/Alert'; -import { handleError } from '@/utils/errorHandler'; import { useConfigureSSO } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; @@ -44,54 +41,16 @@ const PROVIDER_GROUPS: ReadonlyArray<{ ]; export const SelectProviderStep = (): JSX.Element => { - const card = useCardState(); const { goNext } = useWizard(); - const { setProvider, enterpriseConnection } = useConfigureSSO(); - const { user } = useUser(); - const { session } = useSession(); - const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections(); + const { setProvider } = useConfigureSSO(); const [selected, setSelected] = React.useState(null); - const createConnection = useReverification( - React.useCallback( - async (selectedProvider: ProviderType) => { - if (enterpriseConnection) { - return; - } - if (!user?.primaryEmailAddress) { - throw new Error('Primary email required'); - } - - const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; - const organizationId = session?.lastActiveOrganizationId ?? null; - - await createEnterpriseConnection({ - provider: selectedProvider, - name: emailDomain, - organizationId, - }); - }, - [enterpriseConnection, user, session, createEnterpriseConnection], - ), - ); - - const handleContinue = async () => { + const handleContinue = () => { if (!selected) { return; } - setProvider(selected); - card.setError(undefined); - card.setLoading(); - - try { - await createConnection(selected); - void goNext(); - } catch (err) { - handleError(err as Error, [], card.setError); - } finally { - card.setIdle(); - } + void goNext(); }; return ( @@ -161,16 +120,6 @@ export const SelectProviderStep = (): JSX.Element => { variant='warning' title={localizationKeys('configureSSO.selectProviderStep.warning')} /> - - {card.error ? ( - ({ color: theme.colors.$danger500, fontSize: theme.fontSizes.$sm })} - > - {card.error} - - ) : null} @@ -179,8 +128,7 @@ export const SelectProviderStep = (): JSX.Element => { diff --git a/packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx index 985f1779656..ba5a1079036 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx @@ -1,4 +1,4 @@ -import { useReverification, useSession, useUser } from '@clerk/shared/react'; +import { __internal_useUserEnterpriseConnections, useReverification, useSession, useUser } from '@clerk/shared/react'; import type { EmailAddressResource } from '@clerk/shared/types'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -19,14 +19,15 @@ import { Form } from '@/elements/Form'; import { DuotoneAtSymbol, ExclamationTriangle } from '@/icons'; import { handleError } from '@/utils/errorHandler'; -import { useConfigureSSOFlow } from '../ConfigureSSOContext'; +import { useConfigureSSO } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; import { useWizard, Wizard } from '../elements/Wizard'; +import type { ProviderType } from '../types'; export const VerifyDomainStep = (): JSX.Element => { const { user } = useUser(); const { session } = useSession(); - const { enterpriseConnection } = useConfigureSSOFlow(); + const { enterpriseConnection } = useConfigureSSO(); const { t } = useLocalizations(); const emailToVerify = @@ -240,6 +241,9 @@ export const EnterVerificationCodeStep = ({ emailToVerify?: EmailAddressResource; }): JSX.Element | null => { const { user } = useUser(); + const { session } = useSession(); + const { provider, enterpriseConnection } = useConfigureSSO(); + const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections(); const card = useCardState(); const codeSubmittedRef = useRef(false); const { goNext, goPrev, isFirstStep } = useWizard(); @@ -255,6 +259,28 @@ export const EnterVerificationCodeStep = ({ const setPrimaryEmailAddress = useReverification((emailAddressId: string) => user?.update({ primaryEmailAddressId: emailAddressId }), ); + const createConnection = useReverification( + useCallback( + async (selectedProvider: ProviderType) => { + if (enterpriseConnection) { + return; + } + if (!user?.primaryEmailAddress) { + throw new Error('Primary email required'); + } + + const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1]; + const organizationId = session?.lastActiveOrganizationId ?? null; + + await createEnterpriseConnection({ + provider: selectedProvider, + name: emailDomain, + organizationId, + }); + }, + [enterpriseConnection, user, session, createEnterpriseConnection], + ), + ); const prepare = useCallback( () => prepareEmailVerification()?.catch(err => handleError(err, [], card.setError)), [prepareEmailVerification, card], @@ -279,6 +305,19 @@ export const EnterVerificationCodeStep = ({ return; } } + + if (!provider) { + void goNext(); + return; + } + + try { + await createConnection(provider); + } catch (err) { + handleError(err as Error, [], card.setError); + return; + } + void goNext(); }, }); diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index 1937fe6b596..9450203e61d 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -1,5 +1,3 @@ -import { ClerkRuntimeError } from '@clerk/shared/error'; -import type * as ClerkSharedReact from '@clerk/shared/react'; import type { ReactElement } from 'react'; import { describe, expect, it, vi } from 'vitest'; @@ -38,32 +36,6 @@ vi.mock('../../ConfigureSSOContext', () => ({ }), })); -const createEnterpriseConnection = vi.fn(); - -vi.mock('@clerk/shared/react', async importOriginal => { - const actual = await importOriginal(); - return { - ...actual, - __internal_useUserEnterpriseConnections: () => ({ - data: undefined, - error: null, - isLoading: false, - isFetching: false, - createEnterpriseConnection, - updateEnterpriseConnection: vi.fn(), - deleteEnterpriseConnection: vi.fn(), - revalidate: vi.fn(), - }), - useReverification: (fn: T): T => fn, - useUser: () => ({ - user: { primaryEmailAddress: { emailAddress: 'iago@acme.com' } }, - }), - useSession: () => ({ - session: { lastActiveOrganizationId: 'org_test' }, - }), - }; -}); - import { SelectProviderStep } from '../SelectProviderStep'; const { createFixtures } = bindCreateFixtures('ConfigureSSO'); @@ -79,8 +51,6 @@ const resetMocks = () => { goNext.mockReset(); goPrev.mockReset(); setProvider.mockReset(); - createEnterpriseConnection.mockReset(); - createEnterpriseConnection.mockResolvedValue({}); }; describe('SelectProviderStep', () => { @@ -159,16 +129,12 @@ describe('SelectProviderStep', () => { expect(customSamlTile).toHaveAttribute('aria-pressed', 'true'); }); - it('calls setProvider, createEnterpriseConnection, then goNext when Continue is clicked', async () => { + it('records the provider and advances when Continue is clicked', async () => { resetMocks(); const callOrder: string[] = []; setProvider.mockImplementation(() => { callOrder.push('setProvider'); }); - createEnterpriseConnection.mockImplementation(() => { - callOrder.push('createEnterpriseConnection'); - return Promise.resolve({}); - }); goNext.mockImplementation(() => { callOrder.push('goNext'); }); @@ -184,13 +150,7 @@ describe('SelectProviderStep', () => { }); expect(setProvider).toHaveBeenCalledWith('saml_okta'); - expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); - expect(createEnterpriseConnection).toHaveBeenCalledWith({ - provider: 'saml_okta', - name: 'acme.com', - organizationId: 'org_test', - }); - expect(callOrder).toEqual(['setProvider', 'createEnterpriseConnection', 'goNext']); + expect(callOrder).toEqual(['setProvider', 'goNext']); }); it('forwards the Custom SAML backend provider id when selected', async () => { @@ -206,69 +166,6 @@ describe('SelectProviderStep', () => { }); expect(setProvider).toHaveBeenCalledWith('saml_custom'); - expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); - expect(createEnterpriseConnection).toHaveBeenCalledWith({ - provider: 'saml_custom', - name: 'acme.com', - organizationId: 'org_test', - }); - }); - - it('shows loading state while createEnterpriseConnection is pending', async () => { - resetMocks(); - let resolveCreate: () => void = () => undefined; - createEnterpriseConnection.mockImplementation( - () => - new Promise(resolve => { - resolveCreate = resolve; - }), - ); - - const { wrapper } = await createFixtures(); - const { userEvent } = renderStep(wrapper); - - await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); - const continueButton = screen.getByRole('button', { name: /Continue/i }); - await userEvent.click(continueButton); - - // While create is pending, Continue stays disabled and goNext hasn't fired. - // The button's accessible name flips to the spinner's "Loading" label while pending. - await waitFor(() => { - expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); - }); - expect(continueButton).toBeDisabled(); - expect(goNext).not.toHaveBeenCalled(); - - resolveCreate(); - - await waitFor(() => { - expect(goNext).toHaveBeenCalledTimes(1); - }); - }); - - it('does not advance and surfaces the error when createEnterpriseConnection rejects', async () => { - resetMocks(); - createEnterpriseConnection.mockRejectedValue( - new ClerkRuntimeError('Backend unavailable', { code: 'create_failed' }), - ); - - const { wrapper } = await createFixtures(); - const { userEvent, container } = renderStep(wrapper); - - await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); - await userEvent.click(screen.getByRole('button', { name: /Continue/i })); - - await waitFor(() => { - expect(createEnterpriseConnection).toHaveBeenCalledTimes(1); - }); - - expect(goNext).not.toHaveBeenCalled(); - expect(setProvider).toHaveBeenCalledWith('saml_okta'); - // Allow microtasks to flush so the rejection -> handleError -> setError chain settles - await waitFor(() => { - const text = container.textContent ?? ''; - expect(text).toContain('Backend unavailable'); - }); }); it('disables Previous on the first step', async () => { From 99bf849f1bde7ff44228b8b9c6fa76e92f0050cd Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 16:48:23 -0300 Subject: [PATCH 21/26] fix(ui): convert Select Provider cards to radio inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous SimpleButton + aria-pressed implementation had two problems: - Mutually-exclusive selection is semantically a radio group, not a set of independent toggle buttons. Native radio semantics give us screen-reader announcements ("radio, 1 of 2"), arrow-key navigation between options, and exclusive-selection guarantees from the browser — none of which the button pattern provided. - The selected-state boxShadow was rendered via React state, but SimpleButton's own :focus / :not(:focus) styles override boxShadow on every state change. Clicking the card briefly showed the ring (focused + checked), but clicking elsewhere cleared focus and the button's non-focus state took over, wiping the ring even though React state still said the card was selected. Refactor to a visually-hidden inside a - + ); }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index 9450203e61d..e25efd16fc2 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -63,13 +63,13 @@ describe('SelectProviderStep', () => { expect(screen.getByText('Select your identity provider')).toBeInTheDocument(); }); - it('renders both SAML provider tiles with their labels', async () => { + it('renders both SAML provider radios with their labels', async () => { resetMocks(); const { wrapper } = await createFixtures(); renderStep(wrapper); - expect(screen.getByRole('button', { name: 'Okta Workforce' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Custom SAML Provider' })).toBeInTheDocument(); + expect(screen.getByRole('radio', { name: 'Okta Workforce' })).toBeInTheDocument(); + expect(screen.getByRole('radio', { name: 'Custom SAML Provider' })).toBeInTheDocument(); }); it('loads each tile icon from img.clerk.com', async () => { @@ -78,7 +78,7 @@ describe('SelectProviderStep', () => { const { container } = renderStep(wrapper); // Emotion serializes sx into stylesheets, so we check both inline + the document's collected styles - const iconSpans = Array.from(container.querySelectorAll('button span[aria-hidden]')); + const iconSpans = Array.from(container.querySelectorAll('label span[aria-hidden]')); expect(iconSpans).toHaveLength(2); const collectedStyles = [ @@ -95,38 +95,40 @@ describe('SelectProviderStep', () => { const { wrapper } = await createFixtures(); renderStep(wrapper); + expect(screen.getByRole('radio', { name: 'Okta Workforce' })).not.toBeChecked(); + expect(screen.getByRole('radio', { name: 'Custom SAML Provider' })).not.toBeChecked(); expect(screen.getByRole('button', { name: /Continue/i })).toBeDisabled(); }); - it('marks the clicked tile as pressed and enables Continue', async () => { + it('marks the clicked radio as checked and enables Continue', async () => { resetMocks(); const { wrapper } = await createFixtures(); const { userEvent } = renderStep(wrapper); - const oktaTile = screen.getByRole('button', { name: 'Okta Workforce' }); - expect(oktaTile).toHaveAttribute('aria-pressed', 'false'); + const oktaRadio = screen.getByRole('radio', { name: 'Okta Workforce' }); + expect(oktaRadio).not.toBeChecked(); - await userEvent.click(oktaTile); + await userEvent.click(oktaRadio); - expect(oktaTile).toHaveAttribute('aria-pressed', 'true'); + expect(oktaRadio).toBeChecked(); expect(screen.getByRole('button', { name: /Continue/i })).toBeEnabled(); }); - it('flips selection when a different tile is clicked', async () => { + it('flips selection when a different radio is clicked', async () => { resetMocks(); const { wrapper } = await createFixtures(); const { userEvent } = renderStep(wrapper); - const oktaTile = screen.getByRole('button', { name: 'Okta Workforce' }); - const customSamlTile = screen.getByRole('button', { name: 'Custom SAML Provider' }); + const oktaRadio = screen.getByRole('radio', { name: 'Okta Workforce' }); + const customSamlRadio = screen.getByRole('radio', { name: 'Custom SAML Provider' }); - await userEvent.click(oktaTile); - expect(oktaTile).toHaveAttribute('aria-pressed', 'true'); - expect(customSamlTile).toHaveAttribute('aria-pressed', 'false'); + await userEvent.click(oktaRadio); + expect(oktaRadio).toBeChecked(); + expect(customSamlRadio).not.toBeChecked(); - await userEvent.click(customSamlTile); - expect(oktaTile).toHaveAttribute('aria-pressed', 'false'); - expect(customSamlTile).toHaveAttribute('aria-pressed', 'true'); + await userEvent.click(customSamlRadio); + expect(oktaRadio).not.toBeChecked(); + expect(customSamlRadio).toBeChecked(); }); it('records the provider and advances when Continue is clicked', async () => { @@ -142,7 +144,7 @@ describe('SelectProviderStep', () => { const { wrapper } = await createFixtures(); const { userEvent } = renderStep(wrapper); - await userEvent.click(screen.getByRole('button', { name: 'Okta Workforce' })); + await userEvent.click(screen.getByRole('radio', { name: 'Okta Workforce' })); await userEvent.click(screen.getByRole('button', { name: /Continue/i })); await waitFor(() => { @@ -158,7 +160,7 @@ describe('SelectProviderStep', () => { const { wrapper } = await createFixtures(); const { userEvent } = renderStep(wrapper); - await userEvent.click(screen.getByRole('button', { name: 'Custom SAML Provider' })); + await userEvent.click(screen.getByRole('radio', { name: 'Custom SAML Provider' })); await userEvent.click(screen.getByRole('button', { name: /Continue/i })); await waitFor(() => { From d03ff9ee2feb3e2a159f46de64bc5c0f54fcaadc Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 16:54:07 -0300 Subject: [PATCH 22/26] fix(ui): flip Okta provider icon in dark mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Okta SVG at img.clerk.com/static/okta.svg is monochromatic, so rendering it via background-image leaves it invisible against the dark foreground that ships when the appearance prop selects a dark theme. Switch monochromatic provider icons to mask-image + backgroundColor: $colorForeground so the icon picks up the theme's foreground color and inverts automatically. SAML stays on background-image — its SVG ships in full color and renders correctly on both light and dark backgrounds. Mirrors the existing pattern in common/ProviderIcon.tsx. --- .../ConfigureSSO/steps/SelectProviderStep.tsx | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 618e717b463..2aa33b1a199 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -11,6 +11,13 @@ import { Step } from '../elements/Step'; import { useWizard } from '../elements/Wizard'; import type { ProviderType } from '../types'; +/** + * Provider icons whose SVGs are monochromatic and should flip with the + * theme. Mirrors the SUPPORTS_MASK_IMAGE list in `common/ProviderIcon.tsx` + * — keep in sync if either grows. + */ +const MONOCHROMATIC_PROVIDER_ICONS: ReadonlySet = new Set(['okta']); + const PROVIDER_GROUPS: ReadonlyArray<{ id: 'saml'; label: LocalizationKey; @@ -194,14 +201,27 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide ({ - width: theme.sizes.$8, - height: theme.sizes.$8, - backgroundImage: `url(${iconImageUrl(iconId)})`, - backgroundSize: 'contain', - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', - })} + sx={theme => { + const isMonochromatic = MONOCHROMATIC_PROVIDER_ICONS.has(iconId); + const baseSize = { width: theme.sizes.$8, height: theme.sizes.$8 }; + if (isMonochromatic) { + return { + ...baseSize, + backgroundColor: theme.colors.$colorForeground, + maskImage: `url(${iconImageUrl(iconId)})`, + maskSize: 'contain', + maskPosition: 'center', + maskRepeat: 'no-repeat', + }; + } + return { + ...baseSize, + backgroundImage: `url(${iconImageUrl(iconId)})`, + backgroundSize: 'contain', + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + }; + }} /> Date: Wed, 13 May 2026 17:25:09 -0300 Subject: [PATCH 23/26] chore(shared): reformat localization types after locale rebase --- packages/shared/src/types/localization.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index f50fee30714..95b1879a80c 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1313,7 +1313,7 @@ export type __internal_LocalizationResource = { customSaml: LocalizationValue; }; warning: LocalizationValue; - }, + }; verifyEmailDomainStep: { title: LocalizationValue; subtitle: LocalizationValue; From fde8e0bc02f6498d9528e0d4232c57531985c487 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 17:25:11 -0300 Subject: [PATCH 24/26] style(ui): tighten Select Provider selected ring with primary color --- .../ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 2aa33b1a199..f3a864c373e 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -176,7 +176,7 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide }, // Selected ring — CSS-driven via :checked so it survives focus changes. '&:has(input:checked)': { - boxShadow: `0 0 0 4px ${theme.colors.$colorRing}`, + boxShadow: `0 0 0 2px ${theme.colors.$primary500}`, }, })} > From b4efe4f2dac64cf8019f161d1e7712043029e946 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 17:36:49 -0300 Subject: [PATCH 25/26] style(ui): align Select Provider card states with SimpleButton focus ring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the keyboard focus state and the selected state on the provider card now reuse `common.focusRingStyles` instead of bespoke outline / boxShadow rules. Same helper SimpleButton draws its own focus ring with, so the visual treatment matches the rest of @clerk/ui without re-deriving it here. Also drops the redundant `color: $colorForeground` overrides on the body Text components — Text defaults already render in the foreground color, with `colorScheme='secondary'` swapping in for the muted variant. --- .../ConfigureSSO/steps/SelectProviderStep.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index f3a864c373e..60e60f35217 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { LocalizationKey } from '@/customizables'; import { Box, Col, descriptors, Flow, Grid, localizationKeys, Span, Text, useLocalizations } from '@/customizables'; -import { mqu } from '@/styledSystem'; +import { common, mqu } from '@/styledSystem'; import { Alert } from '@/ui/elements/Alert'; import { useConfigureSSO } from '../ConfigureSSOContext'; @@ -66,15 +66,12 @@ export const SelectProviderStep = (): JSX.Element => { ({ gap: theme.space.$1x5 })}> ({ color: theme.colors.$colorForeground })} localizationKey={localizationKeys('configureSSO.selectProviderStep.body.title')} /> ({ color: theme.colors.$colorMutedForeground })} + colorScheme='secondary' localizationKey={localizationKeys('configureSSO.selectProviderStep.body.description')} /> @@ -87,7 +84,6 @@ export const SelectProviderStep = (): JSX.Element => { ({ color: theme.colors.$colorForeground })} localizationKey={group.label} /> @@ -171,12 +167,11 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide '&:hover': { backgroundColor: theme.colors.$neutralAlpha50 }, // Keyboard focus indication — fires when the inner input is focused. '&:has(input:focus-visible)': { - outline: `2px solid ${theme.colors.$colorRing}`, - outlineOffset: '2px', + ...common.focusRingStyles(theme), }, // Selected ring — CSS-driven via :checked so it survives focus changes. '&:has(input:checked)': { - boxShadow: `0 0 0 2px ${theme.colors.$primary500}`, + ...common.focusRingStyles(theme), }, })} > From 39c4fb94e34cd85483dc8d964ce287f6413bda0d Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 13 May 2026 17:39:47 -0300 Subject: [PATCH 26/26] style(ui): darken Select Provider card border on keyboard focus Match SimpleButton's outline variant when focused with hoverAsFocus: in addition to the focus ring, the card's own border darkens from $borderAlpha150 to $borderAlpha300 while the inner input has :focus-visible. The ring stays via common.focusRingStyles. Also restores the explicit variant='subtitle' on the body title Text so the visual hierarchy between sub-header and description holds. --- .../ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 60e60f35217..94613e6411d 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -66,6 +66,7 @@ export const SelectProviderStep = (): JSX.Element => { ({ gap: theme.space.$1x5 })}> @@ -168,6 +169,7 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide // Keyboard focus indication — fires when the inner input is focused. '&:has(input:focus-visible)': { ...common.focusRingStyles(theme), + borderColor: theme.colors.$borderAlpha300, }, // Selected ring — CSS-driven via :checked so it survives focus changes. '&:has(input:checked)': {