Skip to content

Commit d330732

Browse files
committed
refactor(ui): delete the connection directly from the reset dialog
1 parent c76ce3e commit d330732

5 files changed

Lines changed: 31 additions & 55 deletions

File tree

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

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,6 @@ export interface ConfigureSSOData {
4545
* derive the connection name without each calling `useUser` itself.
4646
*/
4747
primaryEmailAddress: EmailAddressResource | undefined;
48-
/**
49-
* Deletes the current connection. Reset does NOT navigate: once the delete
50-
* revalidates the source and drops `hasConnection`, the active step's entry
51-
* guard breaks and the wizard self-corrects, re-seating to the
52-
* furthest-reachable step (the same `initialState` derivation it uses on
53-
* mount). Reads the reverification-wrapped `deleteConnection` mutation off the
54-
* bundle. A no-op when there is no connection. Reset affordances call this
55-
* instead of `useWizard().goToStep`, so they work from ANY footer (including
56-
* nested SAML configure footers) without binding to a nested wizard.
57-
*/
58-
resetConnection: () => Promise<void>;
5948
}
6049

6150
interface ConfigureSSOProviderProps {
@@ -96,21 +85,6 @@ export const ConfigureSSOProvider = ({
9685
primaryEmailAddress,
9786
children,
9887
}: PropsWithChildren<ConfigureSSOProviderProps>): JSX.Element => {
99-
const { deleteConnection } = mutations;
100-
const connectionId = enterpriseConnection?.id;
101-
102-
// Reset is pure deletion — no navigation. Deleting revalidates the source and
103-
// drops `hasConnection`, which breaks the active step's entry guard; the
104-
// wizard self-corrects in its render phase, re-seating to the
105-
// furthest-reachable step (verified email → select-provider; unverified →
106-
// verify-domain). Never a hardcoded step, never a remount.
107-
const resetConnection = React.useCallback(async () => {
108-
if (!connectionId) {
109-
return;
110-
}
111-
await deleteConnection(connectionId);
112-
}, [connectionId, deleteConnection]);
113-
11488
const value = React.useMemo<ConfigureSSOData>(
11589
() => ({
11690
contentRef,
@@ -119,9 +93,8 @@ export const ConfigureSSOProvider = ({
11993
testRuns,
12094
mutations,
12195
primaryEmailAddress,
122-
resetConnection,
12396
}),
124-
[contentRef, enterpriseConnection, mutations, connectionState, testRuns, primaryEmailAddress, resetConnection],
97+
[contentRef, enterpriseConnection, mutations, connectionState, testRuns, primaryEmailAddress],
12598
);
12699

127100
return <ConfigureSSOContext.Provider value={value}>{children}</ConfigureSSOContext.Provider>;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useWizard } from './elements/Wizard';
1414
* the guard-driven `isReachable` flag (the same predicate `goToStep` checks), so
1515
* a disabled breadcrumb item and a blocked jump always agree. Completion stays
1616
* positional. The reset affordance now lives in the step footers
17-
* (`Step.Footer.Reset`), which drive it off the context `resetConnection()`
17+
* (`Step.Footer.Reset`), which delete the connection via the context mutation
1818
* rather than a wizard binding.
1919
*/
2020
export const ConfigureSSOHeader = (): JSX.Element => {

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const ResetConnectionDialog = (props: ResetConnectionDialogProps): JSX.El
4646
const ResetConnectionDialogContent = withCardStateProvider((props: ResetConnectionDialogProps) => {
4747
const { onClose, confirmationValue } = props;
4848
const card = useCardState();
49-
const { enterpriseConnection, resetConnection } = useConfigureSSO();
49+
const { enterpriseConnection, mutations } = useConfigureSSO();
5050

5151
const confirmationField = useFormControl('deleteConfirmation', '', {
5252
type: 'text',
@@ -65,12 +65,13 @@ const ResetConnectionDialogContent = withCardStateProvider((props: ResetConnecti
6565
}
6666

6767
try {
68-
// `resetConnection` deletes the connection — a pure delete, no navigation.
69-
// Dropping `hasConnection` breaks the active step's entry guard, so the
70-
// wizard self-corrects to the furthest-reachable step. No `useWizard()`
71-
// here — that lets this dialog be triggered from ANY footer (including the
72-
// nested SAML configure footers) without binding to a nested wizard.
73-
await resetConnection();
68+
// Reset is a pure delete — no navigation. Dropping `hasConnection` breaks
69+
// the active step's entry guard, so the wizard self-corrects to the
70+
// furthest-reachable step. The mutation is already reverification-wrapped.
71+
// No `useWizard()` here — that lets this dialog be triggered from ANY
72+
// footer (including the nested SAML configure footers) without binding to
73+
// a nested wizard.
74+
await mutations.deleteConnection(enterpriseConnection.id);
7475
onClose();
7576
} catch (err) {
7677
handleError(err as Error, [confirmationField], card.setError);

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import { bindCreateFixtures } from '@/test/create-fixtures';
55
import { render, screen, waitFor } from '@/test/utils';
66
import { CardStateProvider } from '@/ui/elements/contexts';
77

8-
// The dialog no longer touches the wizard. On confirm it calls the context
9-
// `resetConnection()` — a pure delete, no navigation — and the wizard
10-
// self-corrects to the furthest-reachable step once the active step's guard
11-
// breaks. That lets the dialog be triggered from ANY footer (including nested
12-
// SAML configure footers) without binding to a nested wizard.
13-
const resetConnection = vi.fn();
8+
// The dialog no longer touches the wizard. On confirm it calls the
9+
// reverification-wrapped `mutations.deleteConnection(id)` directly — a pure
10+
// delete, no navigation — and the wizard self-corrects to the
11+
// furthest-reachable step once the active step's guard breaks. That lets the
12+
// dialog be triggered from ANY footer (including nested SAML configure footers)
13+
// without binding to a nested wizard.
14+
const deleteConnection = vi.fn();
1415

1516
const connectionMockState = vi.hoisted(() => ({
1617
current: { id: 'idn_connection_1' } as Partial<EnterpriseConnectionResource> | null,
@@ -20,9 +21,9 @@ vi.mock('../ConfigureSSOContext', () => ({
2021
useConfigureSSO: () => ({
2122
enterpriseConnection: connectionMockState.current,
2223
contentRef: { current: null },
23-
// The dialog's confirm calls the context `resetConnection`, which owns the
24-
// reverification-wrapped delete. No navigation — the wizard self-corrects.
25-
resetConnection,
24+
// The dialog's confirm calls the reverification-wrapped `deleteConnection`
25+
// mutation directly. No navigation — the wizard self-corrects.
26+
mutations: { deleteConnection },
2627
}),
2728
}));
2829

@@ -49,8 +50,8 @@ const renderDialog = (
4950
};
5051

5152
const resetMocks = () => {
52-
resetConnection.mockReset();
53-
resetConnection.mockResolvedValue(undefined);
53+
deleteConnection.mockReset();
54+
deleteConnection.mockResolvedValue(undefined);
5455
connectionMockState.current = { id: 'idn_connection_1' };
5556
};
5657

@@ -116,7 +117,7 @@ describe('ResetConnectionDialog', () => {
116117

117118
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
118119
expect(onClose).toHaveBeenCalledTimes(1);
119-
expect(resetConnection).not.toHaveBeenCalled();
120+
expect(deleteConnection).not.toHaveBeenCalled();
120121
});
121122

122123
it('resets the connection (delete + re-derive) and closes on a successful submit', async () => {
@@ -129,8 +130,9 @@ describe('ResetConnectionDialog', () => {
129130
await userEvent.click(screen.getByRole('button', { name: 'Reset connection' }));
130131

131132
await waitFor(() => {
132-
expect(resetConnection).toHaveBeenCalledTimes(1);
133+
expect(deleteConnection).toHaveBeenCalledTimes(1);
133134
});
135+
expect(deleteConnection).toHaveBeenCalledWith('idn_connection_1');
134136
await waitFor(() => {
135137
expect(onClose).toHaveBeenCalledTimes(1);
136138
});

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,12 @@ FooterContinue.displayName = 'Step.Footer.Continue';
197197
* there is no connection (so it only shows on configure / test / confirmation,
198198
* never on verify-domain / select-provider).
199199
*
200-
* It deliberately does NOT call `useWizard()`. The confirm path calls the
201-
* context `resetConnection()` (a pure delete; the wizard then self-corrects to
202-
* the furthest-reachable step when the active step's guard breaks), so this
203-
* works from ANY footer — including the nested SAML configure footers, which
204-
* have their own (linear) wizard. That is what kills the old per-step
205-
* nested-binding trap.
200+
* It deliberately does NOT call `useWizard()`. The confirm path deletes the
201+
* connection directly via the context mutation (a pure delete; the wizard then
202+
* self-corrects to the furthest-reachable step when the active step's guard
203+
* breaks), so this works from ANY footer — including the nested SAML configure
204+
* footers, which have their own (linear) wizard. That is what kills the old
205+
* per-step nested-binding trap.
206206
*
207207
* `marginInlineStart: 'auto'` pushes it to the far-left of the `justify='end'`
208208
* footer row, matching the prior destructive affordance.

0 commit comments

Comments
 (0)