Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1f15755
refactor(ui): centralize ConfigureSSO data fetching into a single hook
iagodahlem May 30, 2026
2e4a5dd
refactor(ui): read ConfigureSSO step state from the centralized data …
iagodahlem May 30, 2026
9bec32d
refactor(ui): centralize ConfigureSSO connection mutations with rever…
iagodahlem May 30, 2026
99752e6
feat(ui): add pure state machine for ConfigureSSO wizard
iagodahlem May 30, 2026
d40e91b
refactor(ui): reorder ConfigureSSO steps + align machine initial-step…
iagodahlem Jun 1, 2026
b988da0
feat(ui): add ConfigureSSO machine hook, submit runner, footer submit…
iagodahlem Jun 1, 2026
5b15420
refactor(ui): drive ConfigureSSO navigation from the state machine
iagodahlem Jun 1, 2026
0cf8e00
chore(ui): remove legacy deriveInitialStep and step-change error effect
iagodahlem Jun 1, 2026
0513cf8
chore(ui): add changeset for ConfigureSSO state machine refactor
iagodahlem Jun 1, 2026
11be8a3
test(ui): align ResetConnectionDialog test with machine-driven reset
iagodahlem Jun 2, 2026
e1af208
refactor(ui): make Wizard a generic, declarative, synchronously-deriv…
iagodahlem Jun 2, 2026
a3a483c
refactor(ui): declare the ConfigureSSO step graph in JSX
iagodahlem Jun 2, 2026
fa8afe4
refactor(ui): extract pure enterprise connection domain predicates
iagodahlem Jun 2, 2026
a3a16c5
refactor(ui): rename connection mutations hook to useEnterpriseConnec…
iagodahlem Jun 2, 2026
4d4c3fb
refactor(ui): rename test-runs hook to useEnterpriseConnectionTestRuns
iagodahlem Jun 2, 2026
704787b
refactor(ui): add useOrganizationEnterpriseConnection umbrella hook
iagodahlem Jun 2, 2026
171ed9a
refactor(ui): unify ConfigureSSO test-run fetching to a single source
iagodahlem Jun 2, 2026
a869c57
refactor(ui): drop wizard onComplete prop for parent fall-through
iagodahlem Jun 2, 2026
8a8bc5c
refactor(ui): replace submit runner with step-local continue handlers
iagodahlem Jun 2, 2026
13fc484
fix(ui): bubble nested wizard navigation outside the state updater
iagodahlem Jun 3, 2026
470a776
refactor(ui): export ConfigureSSOSteps directly instead of via alias
iagodahlem Jun 3, 2026
b419bf9
refactor(ui): introduce OrganizationEnterpriseConnection domain aggre…
iagodahlem Jun 3, 2026
4aae1c6
refactor(ui): consolidate ConfigureSSO data hook onto the domain aggr…
iagodahlem Jun 3, 2026
b0c0987
refactor(ui): drive Wizard primitive from a steps config array
iagodahlem Jun 3, 2026
bbd9d68
refactor(ui): convert ConfigureSSO wizards to the steps config array
iagodahlem Jun 3, 2026
ccf4e24
refactor(ui): drop dead domain-taken logic from ConfigureSSO
iagodahlem Jun 3, 2026
2318ef7
refactor(ui): trim ConfigureSSO comments + restore ConfigureAgain but…
iagodahlem Jun 3, 2026
b00a9d4
refactor(ui): split Wizard steps config from declarative rendering
iagodahlem Jun 4, 2026
ecb5954
refactor(ui): source ConfigureSSO provider from the connection aggregate
iagodahlem Jun 4, 2026
d5791ef
refactor(ui): inline step headers in the remaining SAML configure steps
iagodahlem Jun 4, 2026
9556574
chore(ui): drop dead commented-out provider state and nav TODO comments
iagodahlem Jun 4, 2026
d47b039
refactor(shared): optimistically update enterprise-connection cache o…
iagodahlem Jun 4, 2026
d4a9924
refactor(ui): drop redundant top-level refreshTestRuns from ConfigureSSO
iagodahlem Jun 4, 2026
e3d1ae0
fix(ui): defer ConfigureSSO test-runs query on the fresh-start path
iagodahlem Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/coral-rivers-bloom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

Refactor `<ConfigureSSO />` to drive its wizard navigation through an internal state machine. Step routing now lives in a single pure reducer instead of inside individual steps, data is fetched once through a single hook, and all connection mutations are centralized and wrapped with reverification. No changes to the public API.
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,45 @@ function useOrganizationEnterpriseConnections(
const createEnterpriseConnection = useCallback(
async (createParams: CreateOrganizationEnterpriseConnectionParams) => {
const created = await organization?.createEnterpriseConnection(createParams);
await revalidate();

queryClient.setQueryData(queryKey, (prev: EnterpriseConnectionResource[]) => {
return [...prev, created];
});

return created;
},
[organization, revalidate],
[organization, queryClient, queryKey],
);

const updateEnterpriseConnection = useCallback(
async (enterpriseConnectionId: string, updateParams: UpdateOrganizationEnterpriseConnectionParams) => {
const updated = await organization?.updateEnterpriseConnection(enterpriseConnectionId, updateParams);
await revalidate();

queryClient.setQueryData(queryKey, (prev: EnterpriseConnectionResource[]) => {
return prev.map(connection => {
if (connection.id === enterpriseConnectionId) {
return updated;
}
return connection;
});
});

return updated;
},
[organization, revalidate],
[organization, queryClient, queryKey],
);

const deleteEnterpriseConnection = useCallback(
async (enterpriseConnectionId: string) => {
const deleted = await organization?.deleteEnterpriseConnection(enterpriseConnectionId);
await revalidate();

queryClient.setQueryData(queryKey, (prev: EnterpriseConnectionResource[]) => {
return prev.filter(connection => connection.id !== enterpriseConnectionId);
});

return deleted;
},
[organization, revalidate],
[organization, queryClient, queryKey],
);

return {
Expand Down
140 changes: 21 additions & 119 deletions packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import {
__internal_useOrganizationEnterpriseConnections,
__internal_useOrganizationEnterpriseConnectionTestRuns,
useSession,
} from '@clerk/shared/react';
import type { ConfigureSSOProps, EnterpriseConnectionResource } from '@clerk/shared/types';
import { useSession } from '@clerk/shared/react';
import type { ConfigureSSOProps } from '@clerk/shared/types';
import React from 'react';

import { useProtect } from '@/common';
import { withCoreUserGuard } from '@/contexts';
import { Col, Flex, Flow, Heading, Icon, localizationKeys, Text } from '@/customizables';
import { useCardState, withCardStateProvider } from '@/elements/contexts';
import { withCardStateProvider } from '@/elements/contexts';
import { ProfileCard } from '@/elements/ProfileCard';
import { ExclamationTriangle } from '@/icons';
import { Route, Switch } from '@/router';

import { ConfigureSSOProvider, useConfigureSSO } from './ConfigureSSOContext';
import { ConfigureSSOHeader } from './ConfigureSSOHeader';
import { ConfigureSSOProvider } from './ConfigureSSOContext';
import { ConfigureSSONavbar } from './ConfigureSSONavbar';
import { ConfigureSSOSkeleton } from './ConfigureSSOSkeleton';
import { ConfigureSSOSteps } from './ConfigureSSOSteps';
import { ProfileCardFooter, ProfileCardHeader } from './elements/ProfileCard';
import { Step } from './elements/Step';
import { useWizard, Wizard } from './elements/Wizard';
import { ConfigureStep, ConfirmationStep, SelectProviderStep, TestConfigurationStep, VerifyDomainStep } from './steps';
import { useOrganizationEnterpriseConnection } from './hooks/useOrganizationEnterpriseConnection';

const ConfigureSSOInternal = () => {
return (
Expand Down Expand Up @@ -51,90 +46,37 @@ const AuthenticatedContent = withCoreUserGuard(() => {

export const ConfigureSSOContent = ({ contentRef }: { contentRef: React.RefObject<HTMLDivElement> }) => {
const {
data: enterpriseConnections,
isLoading: isLoadingEnterpriseConnections,
createEnterpriseConnection,
updateEnterpriseConnection,
deleteEnterpriseConnection,
} = __internal_useOrganizationEnterpriseConnections({ enabled: true });
// Currently the self-serve SSO UI flow only supports one enterprise connection per organization
const enterpriseConnection = enterpriseConnections?.[0];

const { hasSuccessfulTestRun, isLoading: isLoadingTestRuns } = useHasSuccessfulTestRun(enterpriseConnection);

const isLoading = isLoadingEnterpriseConnections || isLoadingTestRuns;
isLoading,
enterpriseConnection,
organizationEnterpriseConnection,
testRuns,
mutations,
primaryEmailAddress,
} = useOrganizationEnterpriseConnection();

// Gate loading one level above the provider so the context never observes a
// loading state. The single test-run source is part of this initial fetch, so
// a cold landing on the test step is covered by the full skeleton here.
if (isLoading) {
return <ConfigureSSOSkeleton />;
}

return (
<ConfigureSSOProtect>
<ConfigureSSOProvider
hasSuccessfulTestRun={hasSuccessfulTestRun}
organizationEnterpriseConnection={organizationEnterpriseConnection}
testRuns={testRuns}
enterpriseConnection={enterpriseConnection}
contentRef={contentRef}
createEnterpriseConnection={createEnterpriseConnection}
updateEnterpriseConnection={updateEnterpriseConnection}
deleteEnterpriseConnection={deleteEnterpriseConnection}
mutations={mutations}
primaryEmailAddress={primaryEmailAddress}
>
<ConfigureSSOSteps />
</ConfigureSSOProvider>
</ConfigureSSOProtect>
);
};

const ConfigureSSOSteps = () => {
const { initialStepId, enterpriseConnection } = useConfigureSSO();

return (
<Wizard initialStepId={initialStepId}>
<ResetCardErrorOnStepChange />
<ConfigureSSOHeader />

{/*
* `select-provider` is only a wizard step while there's no enterprise
* connection yet — creating one unregisters this step, which:
* 1. Hides it from the breadcrumb (no need for a manual filter), and
* 2. Prevents `goPrev` from any later step (e.g. configure's first
* substep) from ever bubbling back into provider selection.
*/}
{!enterpriseConnection && (
<Wizard.Step id='select-provider'>
<SelectProviderStep />
</Wizard.Step>
)}

<Wizard.Step
id='verify-domain'
label='Verify domain'
>
<VerifyDomainStep />
</Wizard.Step>

<Wizard.Step
id='configure'
label='Configure'
>
<ConfigureStep />
</Wizard.Step>

<Wizard.Step
id='test'
label='Test'
>
<TestConfigurationStep />
</Wizard.Step>

<Wizard.Step
id='confirmation'
label='Confirmation'
>
<ConfirmationStep />
</Wizard.Step>
</Wizard>
);
};

const ConfigureSSOProtect = ({ children }: { children: React.ReactNode }) => {
const { session } = useSession();
const isPersonalWorkspace = !session?.lastActiveOrganizationId;
Expand Down Expand Up @@ -193,44 +135,4 @@ const MissingManageEnterpriseConnectionsPermission = () => (
</>
);

/**
* Sentinel component rendered inside `<Wizard>`
*
* Clears any card-level error whenever the active step transitions, so a stale failure from one step
* doesn't leak into the next
*/
const ResetCardErrorOnStepChange = (): null => {
const { currentStep } = useWizard();
const card = useCardState();
const previousStepIdRef = React.useRef(currentStep?.id);

React.useEffect(() => {
if (previousStepIdRef.current === currentStep?.id) {
return;
}

previousStepIdRef.current = currentStep?.id;
card.setError(undefined);
}, [currentStep?.id, card]);

return null;
};

/**
* Fetches a single successful test run for the given connection on mount
*/
const useHasSuccessfulTestRun = (
enterpriseConnection: EnterpriseConnectionResource | undefined,
): { hasSuccessfulTestRun: boolean; isLoading: boolean } => {
const { data: successfulTestRuns, isLoading } = __internal_useOrganizationEnterpriseConnectionTestRuns({
enterpriseConnectionId: enterpriseConnection?.id ?? null,
params: { initialPage: 1, pageSize: 1, status: ['success'] },
});

return {
hasSuccessfulTestRun: (successfulTestRuns?.length ?? 0) > 0,
isLoading,
};
};

export const ConfigureSSO: React.ComponentType<ConfigureSSOProps> = withCardStateProvider(ConfigureSSOInternal);
Loading
Loading