Skip to content

feat(ui): Introduce dialog to reset enterprise connection#8706

Open
iagodahlem wants to merge 19 commits into
mainfrom
iago/orgs-1588-sdk-improve-confirmation-step-with-new-reset-connection
Open

feat(ui): Introduce dialog to reset enterprise connection#8706
iagodahlem wants to merge 19 commits into
mainfrom
iago/orgs-1588-sdk-improve-confirmation-step-with-new-reset-connection

Conversation

@iagodahlem
Copy link
Copy Markdown
Member

@iagodahlem iagodahlem commented May 29, 2026

Implements ORGS-1588: a dedicated reset connection dialog and a restyled confirmation step for <ConfigureSSO />.

Summary

  • <ResetConnectionDialog /> — new modal-based, type-to-confirm gated dialog that scopes its backdrop to the wizard's content container via portalRoot. Wraps the destructive flow behind useReverification(user.deleteEnterpriseConnection), clears the local provider selection on success, and asks the wizard to navigate back to the provider selection step.
  • Confirmation step restyle — unified Step.Header with an inline status badge, grouped Enable SSO + Domain rows, two-column configuration details rendered through ProfileSection.ItemList, outlined Configure again, destructive Reset connection, and a sticky info banner inside the step footer when the connection is inactive.
  • Step.Header badge prop — new badge?: ReactNode prop so a step can render an inline status pill next to its title without crowding the existing right-aligned children slot.
  • <OrganizationProfile /> content ref forwarding — propagates the shared ProfileCard content ref into <ConfigureSSO /> so the dialog portals into the wizard chrome (not the document body) when the component is embedded under <OrganizationProfile />.
  • Unit tests for <ResetConnectionDialog /> covering type-to-confirm gating, cancel, and the successful submit chain.

The wizard-wide reset entry (a Step.Footer.Reset compound part on Verify Domain / Configure / Test) ships in a follow-up PR — see ORGS-1550.

Screenshots

Drag-drop the latest captures from the running sandbox here — active + inactive confirmation, plus the reset dialog open.

Confirmation — active Confirmation — inactive Reset dialog
CleanShot 2026-06-01 at 10 05 25@2x CleanShot 2026-06-01 at 10 05 05@2x CleanShot 2026-06-01 at 10 05 48@2x

Test plan

  • Confirmation — inactive: title SSO Successfully configured + Inactive badge inline, grouped Enable SSO + Domain rows, two-column Configuration details with Sign on URL / Issuer / Certificate, outlined Configure again, destructive solid Reset connection, sticky (i) SSO is inactive and you need to enable it to authenticate banner in the footer.
  • Confirmation — active: same layout, Active badge, no inactive banner.
  • Dialog open: clicking Reset opens the dialog centered inside the wizard card; backdrop covers only the card, not the rest of the page; no close X in the corner.
  • Gating: empty input → Reset button disabled; wrong text → still disabled; the exact organization name → enables.
  • Cancel + dismiss: clicking Cancel closes the dialog without side effects.
  • Successful submit: type the org name and click Reset → reverification challenge → user.deleteEnterpriseConnection runs and the enterprise connections query is invalidated.
  • Embedded under <OrganizationProfile />: same behavior; the dialog portals into the wizard chrome, not the page body.
  • Unit tests: pnpm vitest run packages/ui/src/components/ConfigureSSO/__tests__/ResetConnectionDialog.test.tsx green.

Linear: ORGS-1588

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 65da4f8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@clerk/ui Patch
@clerk/chrome-extension Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 1, 2026 9:27pm

Request Review

Comment thread .changeset/sso-confirmation-restyle.md Outdated
Comment thread packages/ui/src/components/ConfigureSSO/__tests__/ResetConnectionDialog.test.tsx Outdated
Comment thread packages/ui/src/components/ConfigureSSO/__tests__/ResetConnectionDialog.test.tsx Outdated
Comment thread packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx Outdated
Comment thread packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx Outdated
const { enterpriseConnection, deleteEnterpriseConnection, setProvider } = useConfigureSSO();
const { goToStep } = useWizard();

const deleteConnection = useReverification((id: string) => deleteEnterpriseConnection(id));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need reverification here? I'm not sure if all FAPI mutations rely on it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping it — useReverification wraps every sensitive user.* mutation in @clerk/ui (delete account, delete passkey, etc.), and the pre-refactor inline ResetConnectionForm used it for the same destructive delete call. Skipping it here would diverge from the rest of the component surface.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we refactor those endpoints to the organization scoped instead of the user scope, then I think those won't be needed

@iagodahlem iagodahlem force-pushed the iago/orgs-1588-sdk-improve-confirmation-step-with-new-reset-connection branch from 929090b to b190354 Compare June 1, 2026 12:17
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 1, 2026

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8706

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8706

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8706

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8706

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8706

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8706

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8706

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8706

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8706

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8706

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8706

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8706

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8706

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8706

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8706

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8706

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8706

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8706

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8706

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8706

commit: 3b12c61

@iagodahlem iagodahlem marked this pull request as ready for review June 1, 2026 12:56
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

API Changes Report

Generated by snapi on 2026-06-01T13:15:28.891Z

Summary

Metric Count
Packages analyzed 6
Packages with changes 1
🔴 Breaking changes 1
🟡 Non-breaking changes 0
🟢 Additions 0

Warning
1 breaking change(s) detected - Major version bump required


@clerk/shared

Current version: 4.14.0
Recommended bump: MAJOR → 5.0.0

Subpath ./types

🔴 Breaking Changes (1)

Changed: __internal_LocalizationResource
Diff (before: 1680 lines, after: 1691 lines). Click to expand.
// ... 1208 unchanged lines elided ...
      navbar: {
        title: LocalizationValue;
      };
+     resetConnectionDialog: {
+       cancelButton: LocalizationValue;
+       confirmationFieldLabel: LocalizationValue<'name'>;
+       confirmationFieldPlaceholder: LocalizationValue<'name'>;
+       resetButton: LocalizationValue;
+       subtitle: LocalizationValue;
+       title: LocalizationValue;
+     };
      selectProviderStep: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        saml: {
          groupLabel: LocalizationValue;
          okta: LocalizationValue;
          customSaml: LocalizationValue;
        };
        warning: LocalizationValue;
      };
      verifyEmailDomainStep: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        addEmailAddress: {
          formTitle: LocalizationValue;
          formSubtitle: LocalizationValue;
          inputPlaceholder: LocalizationValue;
          inputLabel: LocalizationValue;
        };
        emailCode: {
          formTitle: LocalizationValue;
          formSubtitle: LocalizationValue<'identifier'>;
          resendButton: LocalizationValue;
          verified: {
            title: LocalizationValue;
            subtitle: LocalizationValue;
            inputLabel: LocalizationValue;
          };
        };
        domainTaken: {
          title: LocalizationValue<'domain'>;
          subtitle: LocalizationValue;
        };
      };
      testConfigurationStep: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        error__noSuccessfulTestRun: LocalizationValue;
        testUrl: {
          actionLabel__open: LocalizationValue;
        };
        testResults: {
          title: LocalizationValue;
          actionLabel__refresh: LocalizationValue;
          polling: LocalizationValue;
          status__success: LocalizationValue;
          status__failed: LocalizationValue;
          status__pending: LocalizationValue;
          empty: {
            title: LocalizationValue;
            subtitle: LocalizationValue;
          };
        };
        testRunDetails: {
          title: LocalizationValue;
          runDetails: {
            sectionTitle: LocalizationValue;
            timestamp: LocalizationValue;
            status: LocalizationValue;
            errorCode: LocalizationValue;
            fullMessage: LocalizationValue;
            actionLabel__copy: LocalizationValue;
            actionLabel__copied: LocalizationValue;
          };
          parsedUserInfo: {
            sectionTitle: LocalizationValue;
            email: LocalizationValue;
            firstName: LocalizationValue;
          };
          howToFix: {
            sectionTitle: LocalizationValue;
            actionLabel__viewDocumentation: LocalizationValue;
            saml_user_attribute_missing: {
              intro: LocalizationValue;
              step1: LocalizationValue;
              step2: LocalizationValue;
              step3: LocalizationValue;
            };
            saml_response_relaystate_missing: {
              description: LocalizationValue;
            };
            saml_email_address_domain_mismatch: {
              description: LocalizationValue;
            };
            oauth_access_denied: {
              description: LocalizationValue;
            };
            oauth_token_exchange_error: {
              description: LocalizationValue;
            };
            oauth_fetch_user_error: {
              intro: LocalizationValue;
              step1: LocalizationValue;
              step2: LocalizationValue;
            };
          };
        };
      };
      configureStep: {
        attributeMappingTable: {
          badges: {
            required: LocalizationValue;
            optional: LocalizationValue;
          };
        };
        samlOkta: {
          mainHeaderTitle: LocalizationValue;
          createAppStep: {
            headerSubtitle: LocalizationValue;
            createAppInstructions: {
              title: LocalizationValue;
              step1: LocalizationValue;
              step2: LocalizationValue;
              step3: LocalizationValue;
              step4: LocalizationValue;
              step5: LocalizationValue;
            };
            serviceProviderInstructions: {
              title: LocalizationValue;
              paragraph1: LocalizationValue;
              paragraph2: LocalizationValue;
              serviceProviderFields: {
                acsUrl: {
                  label: LocalizationValue;
                };
                spEntityId: {
                  label: LocalizationValue;
                };
              };
            };
            completeSamlIntegrationInstructions: {
              title: LocalizationValue;
              step1: LocalizationValue;
              step2: LocalizationValue;
            };
          };
          attributeMappingStep: {
            headerSubtitle: LocalizationValue;
            paragraph: LocalizationValue;
            step1: LocalizationValue;
            step2: LocalizationValue;
            attributeMappingTable: {
              columns: {
                name: LocalizationValue;
                expression: LocalizationValue;
              };
              rows: {
                email: {
                  name: LocalizationValue;
                  expression: LocalizationValue;
                };
                firstName: {
                  name: LocalizationValue;
                  expression: LocalizationValue;
                };
                lastName: {
                  name: LocalizationValue;
                  expression: LocalizationValue;
                };
              };
            };
          };
          assignUsersStep: {
            headerSubtitle: LocalizationValue;
            assignUsersInstructions: {
              title: LocalizationValue;
              paragraph: LocalizationValue;
              step1: LocalizationValue;
              step2: LocalizationValue;
              step3: LocalizationValue;
              step4: LocalizationValue;
              step5: LocalizationValue;
            };
          };
          identityProviderMetadataStep: {
            headerSubtitle: LocalizationValue;
            modes: {
              title: LocalizationValue;
              ariaLabel: LocalizationValue;
              metadataUrl: LocalizationValue;
              manual: LocalizationValue;
            };
            metadataUrl: {
              label: LocalizationValue;
              placeholder: LocalizationValue;
              description: LocalizationValue;
            };
            manual: {
              description: LocalizationValue;
              signOnUrl: {
                label: LocalizationValue;
                placeholder: LocalizationValue;
              };
              issuer: {
                label: LocalizationValue;
                placeholder: LocalizationValue;
              };
              signingCertificate: {
                label: LocalizationValue;
                uploadFile: LocalizationValue;
                replaceFile: LocalizationValue;
                removeFile: LocalizationValue;
                fileUploaded: LocalizationValue;
              };
            };
          };
        };
        samlCustom: {
          mainHeaderTitle: LocalizationValue;
          createAppStep: {
            headerSubtitle: LocalizationValue;
            createAppInstructions: {
              title: LocalizationValue;
              paragraph: LocalizationValue;
            };
            serviceProviderFields: {
              acsUrl: {
                label: LocalizationValue;
              };
              spEntityId: {
                label: LocalizationValue;
              };
            };
          };
          attributeMappingStep: {
            headerSubtitle: LocalizationValue;
            paragraph: LocalizationValue;
            attributeMappingTable: {
              title: LocalizationValue;
              columns: {
                userProfile: LocalizationValue;
                attributeName: LocalizationValue;
              };
              rows: {
                email: {
                  userProfile: LocalizationValue;
                  attributeName: LocalizationValue;
                };
                firstName: {
                  userProfile: LocalizationValue;
                  attributeName: LocalizationValue;
                };
                lastName: {
                  userProfile: LocalizationValue;
                  attributeName: LocalizationValue;
                };
              };
            };
          };
          assignUsersStep: {
            headerSubtitle: LocalizationValue;
            title: LocalizationValue;
            paragraph: LocalizationValue;
          };
          identityProviderMetadataStep: {
            headerSubtitle: LocalizationValue;
            modes: {
              title: LocalizationValue;
              ariaLabel: LocalizationValue;
              metadataUrl: LocalizationValue;
              manual: LocalizationValue;
            };
            metadataUrl: {
              label: LocalizationValue;
              placeholder: LocalizationValue;
              description: LocalizationValue;
            };
            manual: {
              description: LocalizationValue;
              signOnUrl: {
                label: LocalizationValue;
                placeholder: LocalizationValue;
              };
              issuer: {
                label: LocalizationValue;
                placeholder: LocalizationValue;
              };
              signingCertificate: {
                label: LocalizationValue;
                uploadFile: LocalizationValue;
                replaceFile: LocalizationValue;
                removeFile: LocalizationValue;
                fileUploaded: LocalizationValue;
              };
            };
          };
        };
      };
      confirmation: {
        statusSection: {
          title: LocalizationValue;
          activeBadge: LocalizationValue;
          inactiveBadge: LocalizationValue;
        };
        enableSection: {
          title: LocalizationValue;
        };
        domainSection: {
          title: LocalizationValue;
        };
        configurationSection: {
          title: LocalizationValue;
          ssoUrlLabel: LocalizationValue;
          issuerLabel: LocalizationValue;
          certificateLabel: LocalizationValue;
          configureAgainLink: LocalizationValue;
        };
        resetSection: {
          title: LocalizationValue;
          warning: LocalizationValue;
          confirmationFieldLabel: LocalizationValue<'name'>;
          submitButton: LocalizationValue;
+       };
+       inactiveBanner: {
+         title: LocalizationValue;
        };
      };
    };
// ... 154 unchanged lines elided ...

Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:LocalizationValue;roles:{[r:string]:LocalizationValue;};socialButtonsBlockButton:Localiz…{locale:string;maintenanceMode:LocalizationValue;roles:{[r:string]:LocalizationValue;};socialButtonsBlockButton:Localiz…


Report generated by snapi

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors the SSO wizard confirmation step from an inline reset confirmation card to a dedicated modal dialog. It adds localization and type keys for the reset dialog and inactive banner, updates Step.Header to accept an inline status badge, introduces ResetConnectionDialog with confirmation validation and an async delete flow that clears the selected provider and rewinds the wizard, restructures the confirmation step layout and styling, and threads a shared contentRef through OrganizationProfile so the dialog portals into the wizard chrome.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • clerk/javascript#8531: Refactors the same ConfirmationStep component's reset confirmation UX, which this PR builds upon with a modal-based approach.
  • clerk/javascript#8535: Also modifies Step.Header behavior/props used by the ConfigureSSO wizard; changes here intersect with that work.
  • clerk/javascript#8600: Related organization-profile self-serve SSO page integration that this PR extends by forwarding contentRef for dialog portaling.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering all major changes including the ResetConnectionDialog, confirmation step restyle, Step.Header badge prop, OrganizationProfile ref forwarding, and unit tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title accurately describes the main feature: introducing a dialog modal component for resetting enterprise connections in the SSO configuration flow.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx (1)

252-279: 💤 Low value

Consider disabling reset button when organization name is unavailable.

If organization?.name is falsy, confirmationValue becomes an empty string, making the dialog's submit button permanently disabled since canSubmit = Boolean(confirmationValue && ...) will be false. Users could open the dialog but would be unable to submit, only cancel.

Consider disabling the reset button or hiding the section when organization name is unavailable:

💡 Suggested improvement
 const ResetConnectionSection = (): JSX.Element => {
   const { organization } = useOrganization();
   const [isOpen, setIsOpen] = useState(false);
+  const confirmationValue = organization?.name ?? '';

   return (
     <ProfileSection.Root
       title={localizationKeys('configureSSO.confirmation.resetSection.title')}
       id='resetSso'
     >
       <Flex justify='start'>
         <Button
           elementDescriptor={descriptors.configureSSOConfirmationResetButton}
           variant='solid'
           colorScheme='danger'
           size='sm'
+          isDisabled={!confirmationValue}
           onClick={() => setIsOpen(true)}
           localizationKey={localizationKeys('configureSSO.confirmation.resetSection.title')}
         />
       </Flex>

       <ResetConnectionDialog
         isOpen={isOpen}
         onClose={() => setIsOpen(false)}
-        confirmationValue={organization?.name ?? ''}
+        confirmationValue={confirmationValue}
       />
     </ProfileSection.Root>
   );
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx` around
lines 252 - 279, The ResetConnectionSection opens ResetConnectionDialog with
confirmationValue set to organization?.name which can be empty and thus prevents
submission; update ResetConnectionSection to disable (or hide) the reset Button
when organization?.name is falsy so users cannot open an un-submittable dialog.
Specifically, check organization?.name before rendering or pass a disabled prop
to the Button rendered in ResetConnectionSection (the Button that calls
setIsOpen(true)) and ensure the UI conveys why it’s disabled; alternatively skip
rendering the entire ProfileSection.Root when organization?.name is missing to
avoid launching ResetConnectionDialog with an empty confirmationValue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx`:
- Around line 57-64: The placeholder for the form control is using the raw
confirmationValue instead of the localization key; update the useFormControl
call for confirmationField to pass
localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldPlaceholder',
{ name: confirmationValue }) as the placeholder so translators can control
formatting. Locate the confirmationField definition
(useFormControl('deleteConfirmation', ...)) and replace the current placeholder
argument with the localized key while keeping the label using
configureSSO.resetConnectionDialog.confirmationFieldLabel unchanged.

---

Nitpick comments:
In `@packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx`:
- Around line 252-279: The ResetConnectionSection opens ResetConnectionDialog
with confirmationValue set to organization?.name which can be empty and thus
prevents submission; update ResetConnectionSection to disable (or hide) the
reset Button when organization?.name is falsy so users cannot open an
un-submittable dialog. Specifically, check organization?.name before rendering
or pass a disabled prop to the Button rendered in ResetConnectionSection (the
Button that calls setIsOpen(true)) and ensure the UI conveys why it’s disabled;
alternatively skip rendering the entire ProfileSection.Root when
organization?.name is missing to avoid launching ResetConnectionDialog with an
empty confirmationValue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: ae810c5c-535a-41cc-8ed2-11c203be67c1

📥 Commits

Reviewing files that changed from the base of the PR and between 35020c3 and 354109b.

📒 Files selected for processing (11)
  • .changeset/cool-boats-burn.md
  • packages/localizations/src/en-US.ts
  • packages/shared/src/types/localization.ts
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
  • packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx
  • packages/ui/src/components/ConfigureSSO/__tests__/ResetConnectionDialog.test.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Step.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx
  • packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx
  • packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSSOPage.tsx
  • packages/ui/src/components/OrganizationProfile/index.tsx

Comment on lines +57 to +64
const confirmationField = useFormControl('deleteConfirmation', '', {
type: 'text',
label: localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldLabel', {
name: confirmationValue,
}),
isRequired: true,
placeholder: confirmationValue,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the localized placeholder key instead of raw organization text.

Line 63 bypasses configureSSO.resetConnectionDialog.confirmationFieldPlaceholder, so translators cannot control placeholder formatting and the new key is effectively unused.

Proposed fix
   const confirmationField = useFormControl('deleteConfirmation', '', {
     type: 'text',
     label: localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldLabel', {
       name: confirmationValue,
     }),
     isRequired: true,
-    placeholder: confirmationValue,
+    placeholder: localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldPlaceholder', {
+      name: confirmationValue,
+    }),
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const confirmationField = useFormControl('deleteConfirmation', '', {
type: 'text',
label: localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldLabel', {
name: confirmationValue,
}),
isRequired: true,
placeholder: confirmationValue,
});
const confirmationField = useFormControl('deleteConfirmation', '', {
type: 'text',
label: localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldLabel', {
name: confirmationValue,
}),
isRequired: true,
placeholder: localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldPlaceholder', {
name: confirmationValue,
}),
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx` around
lines 57 - 64, The placeholder for the form control is using the raw
confirmationValue instead of the localization key; update the useFormControl
call for confirmationField to pass
localizationKeys('configureSSO.resetConnectionDialog.confirmationFieldPlaceholder',
{ name: confirmationValue }) as the placeholder so translators can control
formatting. Locate the confirmationField definition
(useFormControl('deleteConfirmation', ...)) and replace the current placeholder
argument with the localized key while keeping the label using
configureSSO.resetConnectionDialog.confirmationFieldLabel unchanged.

Comment thread packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/ui/src/elements/Form.tsx (1)

184-191: ⚡ Quick win

Add an explicit return type to PlainInput.

PlainInput is part of the exported Form API surface and should declare its return type explicitly.

✍️ Proposed fix
-const PlainInput = (props: CommonInputProps & { elementDescriptor?: ElementDescriptor }) => {
+const PlainInput = (props: CommonInputProps & { elementDescriptor?: ElementDescriptor }): JSX.Element => {
   const { elementDescriptor, ...rest } = props;
   return (
     <CommonInputWrapper {...rest}>
       <Field.Input elementDescriptor={elementDescriptor} />
     </CommonInputWrapper>
   );
 };

As per coding guidelines, "Always define explicit return types for functions, especially public APIs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/elements/Form.tsx` around lines 184 - 191, PlainInput
currently lacks an explicit return type; update its signature to include one
(e.g., annotate PlainInput as returning React.ReactElement or React.ReactElement
| null) so the exported Form API has an explicit type. Modify the declaration
for PlainInput (the function that accepts CommonInputProps & {
elementDescriptor?: ElementDescriptor } and renders CommonInputWrapper and
Field.Input) to add the return type (for example: const PlainInput = (props:
...) : React.ReactElement => { ... }), and ensure React is imported if needed
for the type.
packages/ui/src/elements/FieldControl.tsx (1)

258-270: ⚡ Quick win

Keep elementDescriptor and elementId overrides consistent.

Line 269 allows descriptor override, but Line 270 always derives elementId from descriptors.formFieldInput. That can produce mismatched selector classes when a custom descriptor is used.

♻️ Proposed fix
 type InputElementProps = {
   elementDescriptor?: ElementDescriptor;
+  elementId?: ElementId;
 };
 
-const InputElement = forwardRef<HTMLInputElement, InputElementProps>(({ elementDescriptor }, ref) => {
+const InputElement = forwardRef<HTMLInputElement, InputElementProps>(({ elementDescriptor, elementId }, ref) => {
   const { t } = useLocalizations();
   const formField = useFormField();
   const { placeholder, ...inputProps } = sanitizeInputProps(formField);
+  const resolvedElementDescriptor = elementDescriptor || descriptors.formFieldInput;
   return (
     <Input
       ref={ref}
-      elementDescriptor={elementDescriptor || descriptors.formFieldInput}
-      elementId={descriptors.formFieldInput.setId(formField.fieldId)}
+      elementDescriptor={resolvedElementDescriptor}
+      elementId={elementId || descriptors.formFieldInput.setId(formField.fieldId)}
       {...inputProps}
       placeholder={t(placeholder)}
     />
   );
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/elements/FieldControl.tsx` around lines 258 - 270, The
InputElement component allows overriding the elementDescriptor via the
elementDescriptor prop but always derives elementId from
descriptors.formFieldInput, which can mismatch custom descriptors; update
InputElement so elementId is computed from the same descriptor you pass into the
Input component (use the resolvedDescriptor variable or the elementDescriptor
prop fallback used for elementDescriptor) — e.g., resolve const
resolvedDescriptor = elementDescriptor || descriptors.formFieldInput and pass
resolvedDescriptor.setId(formField.fieldId) as elementId to Input (keep
sanitizeInputProps and ref usage unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/ui/src/elements/FieldControl.tsx`:
- Around line 258-270: The InputElement component allows overriding the
elementDescriptor via the elementDescriptor prop but always derives elementId
from descriptors.formFieldInput, which can mismatch custom descriptors; update
InputElement so elementId is computed from the same descriptor you pass into the
Input component (use the resolvedDescriptor variable or the elementDescriptor
prop fallback used for elementDescriptor) — e.g., resolve const
resolvedDescriptor = elementDescriptor || descriptors.formFieldInput and pass
resolvedDescriptor.setId(formField.fieldId) as elementId to Input (keep
sanitizeInputProps and ref usage unchanged).

In `@packages/ui/src/elements/Form.tsx`:
- Around line 184-191: PlainInput currently lacks an explicit return type;
update its signature to include one (e.g., annotate PlainInput as returning
React.ReactElement or React.ReactElement | null) so the exported Form API has an
explicit type. Modify the declaration for PlainInput (the function that accepts
CommonInputProps & { elementDescriptor?: ElementDescriptor } and renders
CommonInputWrapper and Field.Input) to add the return type (for example: const
PlainInput = (props: ...) : React.ReactElement => { ... }), and ensure React is
imported if needed for the type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 213854e4-af71-424a-9336-c3236809da8c

📥 Commits

Reviewing files that changed from the base of the PR and between c54190c and 3b12c61.

📒 Files selected for processing (6)
  • packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx
  • packages/ui/src/customizables/elementDescriptors.ts
  • packages/ui/src/elements/FieldControl.tsx
  • packages/ui/src/elements/Form.tsx
  • packages/ui/src/internal/appearance.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx
  • packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx

Standalone modal dialog with type-to-confirm gating. Wraps the destructive
delete flow (useReverification + deleteEnterpriseConnection + provider clear +
wizard rewind to select-provider). Controlled via isOpen/onClose props with
the confirmation value injected by the caller.

Widens ConfigureSSOContext.setProvider to accept undefined so the dialog can
clear the local provider selection after deleting the connection, and adds
the configureSSO.resetConnectionDialog localization keys (and matching type
entries in @clerk/shared) for the dialog copy.
Replaces the inline reset confirmation card (Action.Root / Action.Card pattern)
with the new ResetConnectionDialog modal. The destructive button now opens the
dialog instead of expanding an inline form; the dialog handles type-to-confirm,
reverification, deletion, and wizard rewind internally.
…tainer

Portals the dialog into the wizard's content container instead of the document
body and switches the backdrop to absolute positioning so it stays inside the
ConfigureSSO card. Disables the modal-context toggle so the auto-rendered
close button is suppressed — the Cancel button is the explicit dismiss for the
destructive flow.
iagodahlem added 16 commits June 1, 2026 18:26
Backdrop now uses a transparent fill plus backdrop-filter blur so the wizard
chrome shows through without an opaque tint. Card switches to a tighter border
radius, start-aligned text, and reduced padding to match the design spec for
the inline reset confirmation.
The OrganizationProfile self-serve SSO page was creating its own contentRef
that diverged from the ProfileCard content ref. ConfigureSSO needs the same
container ref to portal child modals (ResetConnectionDialog) into the wizard
chrome instead of the document body, so the SSO page now receives the shared
ref from the surrounding routes and forwards it down.
Unifies the SSO Successfully configured title with the status badge, lays out
configuration details in a two-column grid, swaps the configure-again and
reset connection actions to dedicated outlined and destructive buttons, and
adds a sticky inactive banner inside the step footer when the connection is
disabled.
Covers the type-to-confirm gating, cancel and reset interactions, and the
successful submit chain (delete + provider clear + wizard rewind + onClose).
Lets a step render an inline badge next to the title without crowding the
right-aligned children slot. The SSO confirmation step now passes its active
status badge through this prop instead of layering its own header component
inside the body.
The hand-rolled grid layered onto the confirmation step recreated what
ProfileSection.Root already provides for every other profile section. Each
detail row now lives inside its own ProfileSection.Root, restoring the
profile-section descriptors and consistent responsive collapse while keeping
the outlined Configure again button, destructive Reset connection button,
status header badge, and inactive footer banner from the recent restyle.
Drops the top border and redundant top padding from the Domain section so it
visually pairs with the Enable SSO row above it. Matches the design intent of
keeping the connection-state controls (toggle + domain) in a single block,
separated from Configuration details and Reset connection by the existing
section dividers.
Wraps the confirmation body in Step.Section so it picks up the canonical
section padding instead of hand-rolling padding on Step.Body. Drops the top
border and redundant top padding from the Enable SSO section so it visually
joins the header above. Aligns the Configuration details section title to the
top via centered={false} and folds the Configure again action into the same
grid as the detail rows so it shares their column alignment.
…ion details

Drops the hand-rolled grid that paired each detail label with its value and
rebuilds the rows on top of ProfileSection.ItemList + ProfileSection.Item.
The Configure again action moves below the row list and reuses the section's
inset padding so it lines up with the row labels above.
Flips the detail rows from space-between to start justification and gives the
label a fixed-width column matching the previous grid minimum so labels stop
wrapping when values are long and all three values line up at the same x.
Values keep truncating with ellipsis when they overflow.
Widens the Sign on URL label to fit the longest copy without wrapping and
sets an explicit gap on the item list so the detail rows space consistently
without inheriting the default tight gap from ProfileSection.ItemList.
Folds the per-commit changeset stubs accumulated during this PR into a single
generated changeset that describes the whole change end to end.
Wraps the configuration item list and the Configure again action in a Col
with a shared gap, lets the items inherit the default space-between layout
with a fixed-width label column, and aligns the Issuer label width to the
Sign on URL row so labels sit on a consistent column.
Updates the dialog test name to read as a behavioral assertion and removes
the verbose confirmation value comment now that the prop name carries the
intent on its own.
The Reset connection button only appears on the confirmation step, which
itself only renders when the enterprise connection exists, so the missing
connection branch is not reachable in practice. Removes the matching test
and the stale provider setter JSDoc that no longer describes the flow.
…active banner

Adds element descriptors so the reset dialog (card, confirmation input,
destructive submit, cancel) and the inactive footer banner on the confirmation
step can be targeted by appearance customizations. Surface-level only; no
behavior changes.
@LauraBeatris LauraBeatris force-pushed the iago/orgs-1588-sdk-improve-confirmation-step-with-new-reset-connection branch from 3b12c61 to 65da4f8 Compare June 1, 2026 21:26
@LauraBeatris LauraBeatris changed the title feat(ui): ResetConnectionDialog + ConfigureSSO confirmation restyle feat(ui): Introduce dialog to reset enterprise connection Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants