From acf00ff2ca537fbbc95f2469d04a305fbe708aa7 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 10:27:16 -0300 Subject: [PATCH 01/13] feat(ui): add segmented control to ConfigureSSO submit-config sub-step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps the SAML config submission body in a two-mode segmented control inside <__experimental_ConfigureSSO />'s Configure step. Users pick between Add via metadata URL (default) and Configure manually. The metadata URL form keeps its existing behavior; the manual entry form lands in a follow-up commit and is intentionally an empty body for now with the Continue button disabled. Mode is local component state in SubmitSamlConfigSubStep — internal flag, not wizard navigation — so the segmented control swap stays inside the sub-step. Locale keys added under configureSSO.configureStep.samlOkta.modes in en-US. --- .../configure-sso-segmented-control-shell.md | 7 + packages/localizations/src/en-US.ts | 5 + packages/shared/src/types/localization.ts | 5 + .../ConfigureSSO/steps/ConfigureStep.tsx | 365 +++++++++--------- 4 files changed, 210 insertions(+), 172 deletions(-) create mode 100644 .changeset/configure-sso-segmented-control-shell.md diff --git a/.changeset/configure-sso-segmented-control-shell.md b/.changeset/configure-sso-segmented-control-shell.md new file mode 100644 index 00000000000..5c971716b77 --- /dev/null +++ b/.changeset/configure-sso-segmented-control-shell.md @@ -0,0 +1,7 @@ +--- +'@clerk/localizations': patch +'@clerk/shared': patch +'@clerk/ui': patch +--- + +Add a two-mode segmented control to the SAML config submission sub-step in `<__experimental_ConfigureSSO />`. Users pick between **Add via metadata URL** (default) and **Configure manually**. The metadata URL form is unchanged; the manual entry form ships in a follow-up commit. Locale keys added under `configureSSO.configureStep.samlOkta.modes` in `en-US`. diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 92cc7bd8af8..da812838967 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -339,6 +339,11 @@ export const enUS: LocalizationResource = { placeholder: 'Paste URL here...', description: 'In your Okta SAML app, go to the Sign On tab and retrieve the metadata URL. Paste it below.', }, + modes: { + ariaLabel: 'Configuration mode', + metadataUrl: 'Add via metadata URL', + manual: 'Configure manually', + }, }, }, }, diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 7b23db716c7..ce71235f3c8 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1427,6 +1427,11 @@ export type __internal_LocalizationResource = { placeholder: LocalizationValue; description: LocalizationValue; }; + modes: { + ariaLabel: LocalizationValue; + metadataUrl: LocalizationValue; + manual: LocalizationValue; + }; }; }; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 48c92857b49..b070816b46d 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -17,13 +17,15 @@ import { Th, Thead, Tr, + useLocalizations, } from '@/customizables'; import { ClipboardInput } from '@/elements/ClipboardInput'; import { useCardState } from '@/elements/contexts'; import { Form } from '@/elements/Form'; +import { SegmentedControl } from '@/elements/SegmentedControl'; import { Check, ClipboardOutline } from '@/icons'; -import { handleError } from '@/utils/errorHandler'; import { useFormControl } from '@/ui/utils/useFormControl'; +import { handleError } from '@/utils/errorHandler'; import { useConfigureSSO } from '../ConfigureSSOContext'; import { Step } from '../elements/Step'; @@ -270,139 +272,137 @@ export const ConfigureAttributesSubStep = (): JSX.Element => { <> ({ gap: theme.space.$3 })}> - ({ gap: theme.space.$3 })}> - + - ({ - 'tr > th:first-of-type': { - paddingInlineStart: theme.space.$4, - }, - })} - > - - - +
- ({ fontSize: theme.fontSizes.$xs })} - localizationKey={localizationKeys( - 'configureSSO.configureStep.attributeMapping.columns.attribute', - )} - /> -
({ + 'tr > th:first-of-type': { + paddingInlineStart: theme.space.$4, + }, + })} + > + + + + + + + + + + {ATTRIBUTE_ROWS.map(row => ( + + - - - - - {ATTRIBUTE_ROWS.map(row => ( - - - - - - ))} - -
+ ({ fontSize: theme.fontSizes.$xs })} + localizationKey={localizationKeys('configureSSO.configureStep.attributeMapping.columns.attribute')} + /> + + ({ fontSize: theme.fontSizes.$xs })} + localizationKey={localizationKeys('configureSSO.configureStep.attributeMapping.columns.claimName')} + /> +
+ ({ gap: theme.space.$2 })} + > + + + + + + ({ fontSize: theme.fontSizes.$xs })} - localizationKey={localizationKeys( - 'configureSSO.configureStep.attributeMapping.columns.claimName', - )} + as='span' + sx={{ fontFamily: 'monospace' }} + localizationKey={row.claim} /> - +
- ({ gap: theme.space.$2 })} - > - - - - - - -
- + ))} + + + + - ({ gap: theme.space.$3 })}> + ({ + gap: theme.space.$1x5, + margin: 0, + paddingInlineStart: theme.space.$5, + listStyleType: 'decimal', + })} + > - - ({ - gap: theme.space.$1x5, - margin: 0, - paddingInlineStart: theme.space.$5, - listStyleType: 'decimal', - })} + - ({ + gap: theme.space.$1x5, + margin: 0, + marginTop: theme.space.$1x5, + paddingInlineStart: theme.space.$5, + listStyleType: '"- "', + })} > - - ({ - gap: theme.space.$1x5, - margin: 0, - marginTop: theme.space.$1x5, - paddingInlineStart: theme.space.$5, - listStyleType: '"- "', - })} - > - {ATTRIBUTE_PAIRS.map(pair => ( - - + {ATTRIBUTE_PAIRS.map(pair => ( + + - + - - - ))} - - - + + + ))} + +
@@ -427,54 +427,52 @@ export const AssignUsersSubStep = (): JSX.Element => { return ( <> - ({ gap: theme.space.$5 })}> - ({ gap: theme.space.$3 })}> - ({ gap: theme.space.$3 })}> + + + + ({ + gap: theme.space.$1x5, + margin: 0, + paddingInlineStart: theme.space.$5, + listStyleType: 'decimal', + })} + > + + + + - - ({ - gap: theme.space.$1x5, - margin: 0, - paddingInlineStart: theme.space.$5, - listStyleType: 'decimal', - })} - > - - - - - - @@ -495,10 +493,13 @@ export const AssignUsersSubStep = (): JSX.Element => { export const SubmitSamlConfigSubStep = (): JSX.Element => { const card = useCardState(); + const { t } = useLocalizations(); const { goNext, goPrev, isFirstStep } = useWizard(); const { enterpriseConnection } = useConfigureSSO(); const { updateEnterpriseConnection } = __internal_useUserEnterpriseConnections(); + const [mode, setMode] = React.useState<'metadataUrl' | 'manual'>('metadataUrl'); + const updateConnection = useReverification( React.useCallback( async (params: UpdateMeEnterpriseConnectionParams) => { @@ -520,7 +521,7 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { }); const trimmedMetadataUrl = metadataUrlField.value.trim(); - const canSubmit = trimmedMetadataUrl.length > 0 && !card.isLoading; + const canSubmit = mode === 'metadataUrl' && trimmedMetadataUrl.length > 0 && !card.isLoading; const handleContinue = async () => { if (!enterpriseConnection || !canSubmit) { @@ -547,14 +548,34 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { fill sx={theme => ({ gap: theme.space.$5 })} > - - - - + setMode(value as 'metadataUrl' | 'manual')} + fullWidth + > + + + + + {mode === 'metadataUrl' && ( + <> + + + + + + )} From 31364b8405fc60e2e21dca0ab4ba086e148e9f4f Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 10:35:56 -0300 Subject: [PATCH 02/13] fix(ui): align ConfigureSSO submit-config sub-step copy with Figma Two copy fixes against the Configure step's SAML submission sub-step: - The metadata segmented-control tab now reads "Add via metadata" instead of "Add via metadata URL". Figma's tab label is the shorter variant; the description below the field still spells out "metadata URL". - Add a section heading "Fill in your Okta SAML application details" above the segmented control, matching the per-sub-step subtitle pattern used in ConfigureAttributes and AssignUsers. Wired through a new configureSSO.configureStep.samlOkta.submitSamlConfig.title locale key. --- packages/localizations/src/en-US.ts | 5 ++++- packages/shared/src/types/localization.ts | 3 +++ .../ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index da812838967..23628d0915e 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -341,9 +341,12 @@ export const enUS: LocalizationResource = { }, modes: { ariaLabel: 'Configuration mode', - metadataUrl: 'Add via metadata URL', + metadataUrl: 'Add via metadata', manual: 'Configure manually', }, + submitSamlConfig: { + title: 'Fill in your Okta SAML application details', + }, }, }, }, diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index ce71235f3c8..c31e79f0905 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1432,6 +1432,9 @@ export type __internal_LocalizationResource = { metadataUrl: LocalizationValue; manual: LocalizationValue; }; + submitSamlConfig: { + title: LocalizationValue; + }; }; }; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index b070816b46d..5549ae765de 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -548,6 +548,11 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { fill sx={theme => ({ gap: theme.space.$5 })} > + Date: Thu, 14 May 2026 11:14:46 -0300 Subject: [PATCH 03/13] feat(ui): add manual entry fields to ConfigureSSO submit-config sub-step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renders the Configure manually tab body in SubmitSamlConfigSubStep with the per-mode description and three fields: - Sign on URL — text input wired with useFormControl('idpSsoUrl') - Issuer — text input wired with useFormControl('idpEntityId') - Signing certificate — label plus a stub outline button with the Upload icon. The button is intentionally inert; the file picker and filename chip land in a follow-up commit. Adds 'idpEntityId' and 'idpSsoUrl' to the FieldId union and a new configureSSO.configureStep.samlOkta.manual locale block with the description, per-field labels and placeholders, and the cert label plus upload-file button copy. The Continue button stays disabled in manual mode for now — its payload handler ({ saml: { idpSsoUrl, idpEntityId, idpCertificate } }) ships in the next commit. --- packages/localizations/src/en-US.ts | 15 ++++ packages/shared/src/types/elementIds.ts | 2 + packages/shared/src/types/localization.ts | 15 ++++ .../ConfigureSSO/steps/ConfigureStep.tsx | 69 ++++++++++++++++++- 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 23628d0915e..ae3c37986f5 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -347,6 +347,21 @@ export const enUS: LocalizationResource = { submitSamlConfig: { title: 'Fill in your Okta SAML application details', }, + manual: { + description: 'In your Okta SAML app, go to the Sign On tab and retrieve these values.', + signOnUrl: { + label: 'Sign on URL', + placeholder: 'Paste URL here...', + }, + issuer: { + label: 'Issuer', + placeholder: 'Paste URL here...', + }, + signingCertificate: { + label: 'Signing certificate', + uploadFile: 'Upload file', + }, + }, }, }, }, diff --git a/packages/shared/src/types/elementIds.ts b/packages/shared/src/types/elementIds.ts index 4d2d9ab72f2..bc2f2c0e6ad 100644 --- a/packages/shared/src/types/elementIds.ts +++ b/packages/shared/src/types/elementIds.ts @@ -26,7 +26,9 @@ export type FieldId = | 'apiKeyExpirationDate' | 'apiKeyRevokeConfirmation' | 'apiKeySecret' + | 'idpEntityId' | 'idpMetadataUrl' + | 'idpSsoUrl' | 'acsUrl' | 'spEntityId' | 'web3WalletName'; diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index c31e79f0905..96432350d16 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1435,6 +1435,21 @@ export type __internal_LocalizationResource = { submitSamlConfig: { title: LocalizationValue; }; + manual: { + description: LocalizationValue; + signOnUrl: { + label: LocalizationValue; + placeholder: LocalizationValue; + }; + issuer: { + label: LocalizationValue; + placeholder: LocalizationValue; + }; + signingCertificate: { + label: LocalizationValue; + uploadFile: LocalizationValue; + }; + }; }; }; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 5549ae765de..7cc347732bd 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -4,11 +4,14 @@ import React from 'react'; import { Badge, + Button, Col, descriptors, Flex, Flow, + FormLabel, Heading, + Icon, localizationKeys, Table, Tbody, @@ -23,7 +26,7 @@ import { ClipboardInput } from '@/elements/ClipboardInput'; import { useCardState } from '@/elements/contexts'; import { Form } from '@/elements/Form'; import { SegmentedControl } from '@/elements/SegmentedControl'; -import { Check, ClipboardOutline } from '@/icons'; +import { Check, ClipboardOutline, Upload } from '@/icons'; import { useFormControl } from '@/ui/utils/useFormControl'; import { handleError } from '@/utils/errorHandler'; @@ -520,6 +523,20 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { isRequired: true, }); + const signOnUrlField = useFormControl('idpSsoUrl', '', { + type: 'text', + label: localizationKeys('configureSSO.configureStep.samlOkta.manual.signOnUrl.label'), + placeholder: localizationKeys('configureSSO.configureStep.samlOkta.manual.signOnUrl.placeholder'), + isRequired: true, + }); + + const issuerField = useFormControl('idpEntityId', '', { + type: 'text', + label: localizationKeys('configureSSO.configureStep.samlOkta.manual.issuer.label'), + placeholder: localizationKeys('configureSSO.configureStep.samlOkta.manual.issuer.placeholder'), + isRequired: true, + }); + const trimmedMetadataUrl = metadataUrlField.value.trim(); const canSubmit = mode === 'metadataUrl' && trimmedMetadataUrl.length > 0 && !card.isLoading; @@ -581,6 +598,56 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { )} + + {mode === 'manual' && ( + <> + + + + + + + + + + + + + + + + + + + )} From 45d5de5525753cb2cd2146a7ee98a72275ed1cf3 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 11:21:35 -0300 Subject: [PATCH 04/13] feat(ui): wire cert file picker in ConfigureSSO manual mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the stub Upload file button in SubmitSamlConfigSubStep with a working file picker. A hidden file input captures the selected cert (accepts .pem/.crt/.cer/.txt); when present, the cert row swaps to a filename label plus a small remove button that clears state and resets the input. Cert state lives in component-local React state — no useFormControl, no FieldId — since the Continue handler that consumes it ships in a follow-up. Adds a configureSSO.configureStep.samlOkta.manual.signingCertificate. removeFile locale key for the remove button's aria-label. --- packages/localizations/src/en-US.ts | 1 + packages/shared/src/types/localization.ts | 1 + .../ConfigureSSO/steps/ConfigureStep.tsx | 83 +++++++++++++++---- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index ae3c37986f5..1d9046bdc09 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -360,6 +360,7 @@ export const enUS: LocalizationResource = { signingCertificate: { label: 'Signing certificate', uploadFile: 'Upload file', + removeFile: 'Remove file', }, }, }, diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 96432350d16..b1bcbccff6c 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1448,6 +1448,7 @@ export type __internal_LocalizationResource = { signingCertificate: { label: LocalizationValue; uploadFile: LocalizationValue; + removeFile: LocalizationValue; }; }; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 7cc347732bd..37016425c71 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -26,7 +26,7 @@ import { ClipboardInput } from '@/elements/ClipboardInput'; import { useCardState } from '@/elements/contexts'; import { Form } from '@/elements/Form'; import { SegmentedControl } from '@/elements/SegmentedControl'; -import { Check, ClipboardOutline, Upload } from '@/icons'; +import { Check, ClipboardOutline, Close, Upload } from '@/icons'; import { useFormControl } from '@/ui/utils/useFormControl'; import { handleError } from '@/utils/errorHandler'; @@ -502,6 +502,8 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { const { updateEnterpriseConnection } = __internal_useUserEnterpriseConnections(); const [mode, setMode] = React.useState<'metadataUrl' | 'manual'>('metadataUrl'); + const [certFile, setCertFile] = React.useState(null); + const certInputRef = React.useRef(null); const updateConnection = useReverification( React.useCallback( @@ -626,25 +628,70 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { /> - + + + ) : ( + + ({ fontSize: t.fontSizes.$sm })} + > + {certFile.name} + + + + + )} )} From 1cedfd521bc5673ece81f3b19ea2a5e31124dc2f Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 13:55:01 -0300 Subject: [PATCH 05/13] feat(ui): wire ConfigureSSO manual-mode submit Enables Continue in Configure manually mode of SubmitSamlConfigSubStep when Sign on URL, Issuer, and a cert file are all present. handleContinue now branches on the mode: metadata URL keeps its existing single-field payload; manual reads the selected cert via File.text() and posts { saml: { idpSsoUrl, idpEntityId, idpCertificate } } through the same user.updateEnterpriseConnection mutation. Backend rejections route through handleError into the relevant text fields per mode (metadataUrlField for URL mode, signOnUrlField and issuerField for manual). Cert-specific errors fall through to the card- level error surface since the cert UI has no inline error slot. --- .../ConfigureSSO/steps/ConfigureStep.tsx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 37016425c71..af93d121d24 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -540,7 +540,13 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { }); const trimmedMetadataUrl = metadataUrlField.value.trim(); - const canSubmit = mode === 'metadataUrl' && trimmedMetadataUrl.length > 0 && !card.isLoading; + const trimmedSignOnUrl = signOnUrlField.value.trim(); + const trimmedIssuer = issuerField.value.trim(); + + const canSubmit = + !card.isLoading && + ((mode === 'metadataUrl' && trimmedMetadataUrl.length > 0) || + (mode === 'manual' && trimmedSignOnUrl.length > 0 && trimmedIssuer.length > 0 && certFile !== null)); const handleContinue = async () => { if (!enterpriseConnection || !canSubmit) { @@ -551,10 +557,25 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { card.setLoading(); try { - await updateConnection({ saml: { idpMetadataUrl: trimmedMetadataUrl } }); + if (mode === 'metadataUrl') { + await updateConnection({ saml: { idpMetadataUrl: trimmedMetadataUrl } }); + } else { + const idpCertificate = await certFile!.text(); + await updateConnection({ + saml: { + idpSsoUrl: trimmedSignOnUrl, + idpEntityId: trimmedIssuer, + idpCertificate, + }, + }); + } void goNext(); } catch (err) { - handleError(err as Error, [metadataUrlField], card.setError); + if (mode === 'metadataUrl') { + handleError(err as Error, [metadataUrlField], card.setError); + } else { + handleError(err as Error, [signOnUrlField, issuerField], card.setError); + } } finally { card.setIdle(); } From 3aa10b0a7b4c2cf1d22f523babb66a6fe5968f32 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 14:50:58 -0300 Subject: [PATCH 06/13] refactor(ui): split ConfigureSSO submit-config body into per-mode panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the metadata URL and manual-entry bodies of SubmitSamlConfigSubStep into MetadataUrlPanel and ManualEntryPanel components. State stays lifted in the parent — useFormControl hooks, certFile, mode, card, updateConnection, handleContinue, canSubmit, and Step.Footer all live in SubmitSamlConfigSubStep. The panels are pure render components that receive their fields and cert state as props. Field state is preserved across mode switches as a consequence — the lifted state outlives the panel that renders it. Acceptable trade-off given the "no useEffects, single hook upstream + prop-injection" rule. Also aligns the cert file input's accept list with the Clerk Dashboard's SAML connection form: .pem, .key, .crt, .cer, .cert. Drops .txt, adds .key and .cert. --- .../ConfigureSSO/steps/ConfigureStep.tsx | 250 ++++++++++-------- 1 file changed, 142 insertions(+), 108 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index af93d121d24..9414561edaf 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -1,5 +1,5 @@ import { __internal_useUserEnterpriseConnections, useReverification } from '@clerk/shared/react'; -import type { UpdateMeEnterpriseConnectionParams } from '@clerk/shared/types'; +import type { FieldId, UpdateMeEnterpriseConnectionParams } from '@clerk/shared/types'; import React from 'react'; import { @@ -27,6 +27,7 @@ import { useCardState } from '@/elements/contexts'; import { Form } from '@/elements/Form'; import { SegmentedControl } from '@/elements/SegmentedControl'; import { Check, ClipboardOutline, Close, Upload } from '@/icons'; +import type { FormControlState } from '@/ui/utils/useFormControl'; import { useFormControl } from '@/ui/utils/useFormControl'; import { handleError } from '@/utils/errorHandler'; @@ -494,6 +495,137 @@ export const AssignUsersSubStep = (): JSX.Element => { ); }; +type FormControl = FormControlState; + +type MetadataUrlPanelProps = { + field: FormControl; +}; + +type ManualEntryPanelProps = { + signOnUrlField: FormControl; + issuerField: FormControl; + certFile: File | null; + setCertFile: React.Dispatch>; +}; + +const MetadataUrlPanel = ({ field }: MetadataUrlPanelProps): JSX.Element => { + return ( + <> + + + + + + ); +}; + +const ManualEntryPanel = ({ + signOnUrlField, + issuerField, + certFile, + setCertFile, +}: ManualEntryPanelProps): JSX.Element => { + const { t } = useLocalizations(); + const certInputRef = React.useRef(null); + + return ( + <> + + + + + + + + + + + + + + + + setCertFile(e.target.files?.[0] ?? null)} + /> + + {certFile === null ? ( + + ) : ( + + ({ fontSize: t.fontSizes.$sm })} + > + {certFile.name} + + + + + )} + + + ); +}; + export const SubmitSamlConfigSubStep = (): JSX.Element => { const card = useCardState(); const { t } = useLocalizations(); @@ -503,7 +635,6 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { const [mode, setMode] = React.useState<'metadataUrl' | 'manual'>('metadataUrl'); const [certFile, setCertFile] = React.useState(null); - const certInputRef = React.useRef(null); const updateConnection = useReverification( React.useCallback( @@ -609,112 +740,15 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { /> - {mode === 'metadataUrl' && ( - <> - - - - - - )} - - {mode === 'manual' && ( - <> - - - - - - - - - - - - - - - - setCertFile(e.target.files?.[0] ?? null)} - /> - - {certFile === null ? ( - - ) : ( - - ({ fontSize: t.fontSizes.$sm })} - > - {certFile.name} - - - - - )} - - + {mode === 'metadataUrl' ? ( + + ) : ( + )} From 4c2b6b5fe6dc88405f769e5a83b12b0972691bc8 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 15:35:29 -0300 Subject: [PATCH 07/13] refactor(ui): tighten ConfigureSSO submit-config panel layout Reorganizes SubmitSamlConfigSubStep so the orchestrator stays at the top of the file with the per-mode panels declared below it, mirroring the convention used elsewhere in the codebase. Also narrows certFile in handleContinue via an early return guard instead of a non-null assertion, and swaps the Step.Section sx callback for the gap shorthand prop. --- .../ConfigureSSO/steps/ConfigureStep.tsx | 269 +++++++++--------- 1 file changed, 135 insertions(+), 134 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 9414561edaf..92f0829b428 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -495,137 +495,6 @@ export const AssignUsersSubStep = (): JSX.Element => { ); }; -type FormControl = FormControlState; - -type MetadataUrlPanelProps = { - field: FormControl; -}; - -type ManualEntryPanelProps = { - signOnUrlField: FormControl; - issuerField: FormControl; - certFile: File | null; - setCertFile: React.Dispatch>; -}; - -const MetadataUrlPanel = ({ field }: MetadataUrlPanelProps): JSX.Element => { - return ( - <> - - - - - - ); -}; - -const ManualEntryPanel = ({ - signOnUrlField, - issuerField, - certFile, - setCertFile, -}: ManualEntryPanelProps): JSX.Element => { - const { t } = useLocalizations(); - const certInputRef = React.useRef(null); - - return ( - <> - - - - - - - - - - - - - - - - setCertFile(e.target.files?.[0] ?? null)} - /> - - {certFile === null ? ( - - ) : ( - - ({ fontSize: t.fontSizes.$sm })} - > - {certFile.name} - - - - - )} - - - ); -}; - export const SubmitSamlConfigSubStep = (): JSX.Element => { const card = useCardState(); const { t } = useLocalizations(); @@ -680,7 +549,7 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { (mode === 'manual' && trimmedSignOnUrl.length > 0 && trimmedIssuer.length > 0 && certFile !== null)); const handleContinue = async () => { - if (!enterpriseConnection || !canSubmit) { + if (!certFile || !enterpriseConnection || !canSubmit) { return; } @@ -691,7 +560,7 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { if (mode === 'metadataUrl') { await updateConnection({ saml: { idpMetadataUrl: trimmedMetadataUrl } }); } else { - const idpCertificate = await certFile!.text(); + const idpCertificate = await certFile.text(); await updateConnection({ saml: { idpSsoUrl: trimmedSignOnUrl, @@ -717,7 +586,7 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { ({ gap: theme.space.$5 })} + gap={5} > { ); }; + +type FormControl = FormControlState; + +type MetadataUrlPanelProps = { + field: FormControl; +}; + +type ManualEntryPanelProps = { + signOnUrlField: FormControl; + issuerField: FormControl; + certFile: File | null; + setCertFile: React.Dispatch>; +}; + +const MetadataUrlPanel = ({ field }: MetadataUrlPanelProps): JSX.Element => { + return ( + <> + + + + + + ); +}; + +const ManualEntryPanel = ({ + signOnUrlField, + issuerField, + certFile, + setCertFile, +}: ManualEntryPanelProps): JSX.Element => { + const { t } = useLocalizations(); + const certInputRef = React.useRef(null); + + return ( + <> + + + + + + + + + + + + + + + + setCertFile(e.target.files?.[0] ?? null)} + /> + + {certFile === null ? ( + + ) : ( + ({ paddingTop: theme.space.$1, paddingBottom: theme.space.$1 })} + > + + {certFile.name} + + + + + )} + + + ); +}; From e20a0317f1a7971c35315a7e00b3f211268d453d Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 16:10:12 -0300 Subject: [PATCH 08/13] feat(ui): pre-fill ConfigureSSO submit-config and wire cert error feedback Two changes for the Configure manually flow: - Pre-fills the metadata URL, Sign on URL, and Issuer text inputs from the existing enterpriseConnection.samlConnection on mount and defaults to the manual segmented-control tab when prior config exists, so the Configure-again entry from Confirmation lands users on an editable form pre-populated with their current values. Fresh setups still default to the metadata-URL tab. - Promotes the cert picker to a real useFormControl-backed field with a new idpCertificate FieldId, so the cert label and inline error feedback render through Field.Root / Field.Label / Field.Feedback in the same chrome as the surrounding text inputs. Backend errors keyed idp_certificate now route inline below the cert UI via handleError. When the existing connection already has a cert on file, the picker shows a Replace file affordance and Continue stays enabled without requiring a fresh upload; the manual PATCH omits idpCertificate from the payload when no new file was selected. --- packages/localizations/src/en-US.ts | 1 + packages/shared/src/types/elementIds.ts | 1 + packages/shared/src/types/localization.ts | 1 + .../ConfigureSSO/steps/ConfigureStep.tsx | 82 +++++++++++++------ 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 1d9046bdc09..da6671e0351 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -360,6 +360,7 @@ export const enUS: LocalizationResource = { signingCertificate: { label: 'Signing certificate', uploadFile: 'Upload file', + replaceFile: 'Replace file', removeFile: 'Remove file', }, }, diff --git a/packages/shared/src/types/elementIds.ts b/packages/shared/src/types/elementIds.ts index bc2f2c0e6ad..f16db90125a 100644 --- a/packages/shared/src/types/elementIds.ts +++ b/packages/shared/src/types/elementIds.ts @@ -26,6 +26,7 @@ export type FieldId = | 'apiKeyExpirationDate' | 'apiKeyRevokeConfirmation' | 'apiKeySecret' + | 'idpCertificate' | 'idpEntityId' | 'idpMetadataUrl' | 'idpSsoUrl' diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index b1bcbccff6c..a22e268bb7e 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1448,6 +1448,7 @@ export type __internal_LocalizationResource = { signingCertificate: { label: LocalizationValue; uploadFile: LocalizationValue; + replaceFile: LocalizationValue; removeFile: LocalizationValue; }; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 92f0829b428..6fb52d7b9e8 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -9,7 +9,6 @@ import { descriptors, Flex, Flow, - FormLabel, Heading, Icon, localizationKeys, @@ -24,6 +23,7 @@ import { } from '@/customizables'; import { ClipboardInput } from '@/elements/ClipboardInput'; import { useCardState } from '@/elements/contexts'; +import { Field } from '@/elements/FieldControl'; import { Form } from '@/elements/Form'; import { SegmentedControl } from '@/elements/SegmentedControl'; import { Check, ClipboardOutline, Close, Upload } from '@/icons'; @@ -502,7 +502,16 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { const { enterpriseConnection } = useConfigureSSO(); const { updateEnterpriseConnection } = __internal_useUserEnterpriseConnections(); - const [mode, setMode] = React.useState<'metadataUrl' | 'manual'>('metadataUrl'); + const samlConnection = enterpriseConnection?.samlConnection; + const hasExistingConfig = Boolean( + samlConnection?.idpSsoUrl || + samlConnection?.idpEntityId || + samlConnection?.idpCertificate || + samlConnection?.idpMetadataUrl, + ); + const existingCertPresent = Boolean(samlConnection?.idpCertificate); + + const [mode, setMode] = React.useState<'metadataUrl' | 'manual'>(hasExistingConfig ? 'manual' : 'metadataUrl'); const [certFile, setCertFile] = React.useState(null); const updateConnection = useReverification( @@ -518,38 +527,45 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { ), ); - const metadataUrlField = useFormControl('idpMetadataUrl', '', { + const metadataUrlField = useFormControl('idpMetadataUrl', samlConnection?.idpMetadataUrl ?? '', { type: 'text', label: localizationKeys('configureSSO.configureStep.samlOkta.metadataUrl.label'), placeholder: localizationKeys('configureSSO.configureStep.samlOkta.metadataUrl.placeholder'), isRequired: true, }); - const signOnUrlField = useFormControl('idpSsoUrl', '', { + const signOnUrlField = useFormControl('idpSsoUrl', samlConnection?.idpSsoUrl ?? '', { type: 'text', label: localizationKeys('configureSSO.configureStep.samlOkta.manual.signOnUrl.label'), placeholder: localizationKeys('configureSSO.configureStep.samlOkta.manual.signOnUrl.placeholder'), isRequired: true, }); - const issuerField = useFormControl('idpEntityId', '', { + const issuerField = useFormControl('idpEntityId', samlConnection?.idpEntityId ?? '', { type: 'text', label: localizationKeys('configureSSO.configureStep.samlOkta.manual.issuer.label'), placeholder: localizationKeys('configureSSO.configureStep.samlOkta.manual.issuer.placeholder'), isRequired: true, }); + const certFileField = useFormControl('idpCertificate', '', { + type: 'text', + label: localizationKeys('configureSSO.configureStep.samlOkta.manual.signingCertificate.label'), + isRequired: true, + }); + const trimmedMetadataUrl = metadataUrlField.value.trim(); const trimmedSignOnUrl = signOnUrlField.value.trim(); const trimmedIssuer = issuerField.value.trim(); + const hasCert = certFile !== null || existingCertPresent; const canSubmit = !card.isLoading && ((mode === 'metadataUrl' && trimmedMetadataUrl.length > 0) || - (mode === 'manual' && trimmedSignOnUrl.length > 0 && trimmedIssuer.length > 0 && certFile !== null)); + (mode === 'manual' && trimmedSignOnUrl.length > 0 && trimmedIssuer.length > 0 && hasCert)); const handleContinue = async () => { - if (!certFile || !enterpriseConnection || !canSubmit) { + if (!enterpriseConnection || !canSubmit) { return; } @@ -560,21 +576,23 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { if (mode === 'metadataUrl') { await updateConnection({ saml: { idpMetadataUrl: trimmedMetadataUrl } }); } else { - const idpCertificate = await certFile.text(); - await updateConnection({ - saml: { - idpSsoUrl: trimmedSignOnUrl, - idpEntityId: trimmedIssuer, - idpCertificate, - }, - }); + const samlPayload: NonNullable = { + idpSsoUrl: trimmedSignOnUrl, + idpEntityId: trimmedIssuer, + }; + + if (certFile !== null) { + samlPayload.idpCertificate = await certFile.text(); + } + + await updateConnection({ saml: samlPayload }); } void goNext(); } catch (err) { if (mode === 'metadataUrl') { handleError(err as Error, [metadataUrlField], card.setError); } else { - handleError(err as Error, [signOnUrlField, issuerField], card.setError); + handleError(err as Error, [signOnUrlField, issuerField, certFileField], card.setError); } } finally { card.setIdle(); @@ -615,8 +633,10 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { )} @@ -646,8 +666,10 @@ type MetadataUrlPanelProps = { type ManualEntryPanelProps = { signOnUrlField: FormControl; issuerField: FormControl; + certFileField: FormControl; certFile: File | null; setCertFile: React.Dispatch>; + existingCertPresent: boolean; }; const MetadataUrlPanel = ({ field }: MetadataUrlPanelProps): JSX.Element => { @@ -668,8 +690,10 @@ const MetadataUrlPanel = ({ field }: MetadataUrlPanelProps): JSX.Element => { const ManualEntryPanel = ({ signOnUrlField, issuerField, + certFileField, certFile, setCertFile, + existingCertPresent, }: ManualEntryPanelProps): JSX.Element => { const { t } = useLocalizations(); const certInputRef = React.useRef(null); @@ -690,14 +714,10 @@ const ManualEntryPanel = ({ - - - - + + + + setCertFile(e.target.files?.[0] ?? null)} + onChange={e => { + setCertFile(e.target.files?.[0] ?? null); + certFileField.clearFeedback(); + }} /> {certFile === null ? ( @@ -725,7 +748,9 @@ const ManualEntryPanel = ({ @@ -751,6 +776,7 @@ const ManualEntryPanel = ({ )} onClick={() => { setCertFile(null); + certFileField.clearFeedback(); if (certInputRef.current) { certInputRef.current.value = ''; } @@ -764,7 +790,9 @@ const ManualEntryPanel = ({ )} - + + + ); }; From 07a0fa620ebc555d605a6022fa7e45707809a33f Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 16:32:38 -0300 Subject: [PATCH 09/13] refactor(ui): tighten cert field spacing in ConfigureSSO manual entry Wraps the cert field section in a Box and groups Field.Label, the file input, and the upload/filename row inside a Col gap-2 so the label-to- control spacing stays consistent with the surrounding inputs. Field.Feedback sits outside the Col so the error row keeps its default spacing below the control. Also swaps the filename Text from an inline fontSize sx to the buttonSmall variant and adds vertical padding to the filename chip so it shares the upload button's baseline height. --- .../ConfigureSSO/steps/ConfigureStep.tsx | 156 +++++++++--------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 6fb52d7b9e8..8a0436422be 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { Badge, + Box, Button, Col, descriptors, @@ -714,85 +715,88 @@ const ManualEntryPanel = ({ - - - - - - { - setCertFile(e.target.files?.[0] ?? null); - certFileField.clearFeedback(); - }} - /> - - {certFile === null ? ( - - ) : ( - ({ paddingTop: theme.space.$1, paddingBottom: theme.space.$1 })} - > - - {certFile.name} - - - - - )} + /> - - + {certFile === null ? ( + + ) : ( + ({ paddingTop: theme.space.$1, paddingBottom: theme.space.$1 })} + > + + {certFile.name} + + + + + )} + + + + ); }; From 66b4d191379ee2b85f07089127d096cb7d869ed9 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Thu, 14 May 2026 16:33:34 -0300 Subject: [PATCH 10/13] refactor(ui): don't pre-fill metadata URL on ConfigureSSO Configure-again Reverts the metadata URL field to always initialize as an empty string, matching the Clerk Dashboard's SAML connection form behavior. The metadata URL flow is a one-shot operation; pre-filling it implies the user is editing the URL rather than re-submitting fresh metadata, which is the opposite of how the metadata mode works on every other surface. Sign on URL, Issuer, and the cert state are still pre-filled / preserved for the manual mode's edit flow. --- packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 8a0436422be..80ddaa732a3 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -528,7 +528,7 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { ), ); - const metadataUrlField = useFormControl('idpMetadataUrl', samlConnection?.idpMetadataUrl ?? '', { + const metadataUrlField = useFormControl('idpMetadataUrl', '', { type: 'text', label: localizationKeys('configureSSO.configureStep.samlOkta.metadataUrl.label'), placeholder: localizationKeys('configureSSO.configureStep.samlOkta.metadataUrl.placeholder'), From 50a23ca871ad1fc8cc3bca7595778e48e34cfc25 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Fri, 15 May 2026 13:48:46 -0300 Subject: [PATCH 11/13] Update translations to have bold nodes --- packages/localizations/src/en-US.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index da6671e0351..5c515b49967 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -288,11 +288,11 @@ export const enUS: LocalizationResource = { subtitle: 'Create a new enterprise application in your Okta Dashboard', createApp: { title: 'Create a new enterprise application in Okta', - step1: 'Sign in to Okta and go to Admin → Applications.', - step2: 'Click Create App Integration.', - step3: 'Select SAML 2.0.', + step1: 'Sign in to Okta and go to Admin → Applications.', + step2: 'Click Create App Integration.', + step3: 'Select SAML 2.0.', step4: 'Fill in the General Settings (App name is required).', - step5: 'Click Next to complete creating the application.', + step5: 'Click Next to complete creating the application.', }, serviceProvider: { title: 'Configure service provider', @@ -303,12 +303,13 @@ export const enUS: LocalizationResource = { }, completeSamlIntegration: { title: 'Complete SAML integration', - step1: 'Select This is an internal app that we have created from the options menu.', - step2: 'Complete the form with any comments and select "Finish".', + step1: 'Select This is an internal app that we have created from the options menu.', + step2: 'Complete the form with any comments and select Finish.', }, configureAttributes: { - step1: 'In the Okta dashboard, find the Attribute Statements section.', - step2: 'Select Add Expression for each attribute, and enter the following name and expression pairs:', + step1: 'In the Okta dashboard, find the Attribute Statements section.', + step2: + 'Select Add Expression for each attribute, and enter the following name and expression pairs:', pairs: { conjunction: ' and ', email: { @@ -328,11 +329,12 @@ export const enUS: LocalizationResource = { assignUsers: { title: 'Assign selected user or group in Okta', paragraph: 'You need to assign users or groups to your enterprise app before they can use it to sign in.', - step1: 'In the Okta dashboard, select the Assignments tab.', - step2: 'Select the Assign dropdown. You can either select Assign to people or Assign to groups.', + step1: 'In the Okta dashboard, select the Assignments tab.', + step2: + 'Select the Assign dropdown. You can either select Assign to people or Assign to groups.', step3: 'In the search field, enter the user or group of users that you want to assign to the application.', - step4: 'Select the Assign button next to the user or group that you want to assign.', - step5: 'Select the Done button to complete the assignment.', + step4: 'Select the Assign button next to the user or group that you want to assign.', + step5: 'Select the Done button to complete the assignment.', }, metadataUrl: { label: 'Metadata URL', From b4efb060de40432b5c0596e28651002df533603a Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Fri, 15 May 2026 13:50:24 -0300 Subject: [PATCH 12/13] Add default value for IdP metadata URL --- packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 80ddaa732a3..8a0436422be 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -528,7 +528,7 @@ export const SubmitSamlConfigSubStep = (): JSX.Element => { ), ); - const metadataUrlField = useFormControl('idpMetadataUrl', '', { + const metadataUrlField = useFormControl('idpMetadataUrl', samlConnection?.idpMetadataUrl ?? '', { type: 'text', label: localizationKeys('configureSSO.configureStep.samlOkta.metadataUrl.label'), placeholder: localizationKeys('configureSSO.configureStep.samlOkta.metadataUrl.placeholder'), From 44277efb6810ed60c840d32c8efb4892e2ffb734 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Fri, 15 May 2026 14:06:05 -0300 Subject: [PATCH 13/13] Add state when certificate is uploaded --- packages/localizations/src/en-US.ts | 1 + packages/shared/src/types/localization.ts | 1 + .../ConfigureSSO/steps/ConfigureStep.tsx | 50 ++++++++++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 5c515b49967..918f475e494 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -364,6 +364,7 @@ export const enUS: LocalizationResource = { uploadFile: 'Upload file', replaceFile: 'Replace file', removeFile: 'Remove file', + fileUploaded: 'File uploaded', }, }, }, diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index a22e268bb7e..054aa19d9f2 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1450,6 +1450,7 @@ export type __internal_LocalizationResource = { uploadFile: LocalizationValue; replaceFile: LocalizationValue; removeFile: LocalizationValue; + fileUploaded: LocalizationValue; }; }; }; diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx index 8a0436422be..c9c93571978 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx @@ -735,28 +735,40 @@ const ManualEntryPanel = ({ /> {certFile === null ? ( - + + + ) : (