diff --git a/.changeset/thirty-candies-lick.md b/.changeset/thirty-candies-lick.md new file mode 100644 index 00000000000..250d28dd911 --- /dev/null +++ b/.changeset/thirty-candies-lick.md @@ -0,0 +1,12 @@ +--- +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/astro': minor +'@clerk/react': minor +'@clerk/nuxt': minor +'@clerk/vue': minor +'@clerk/ui': minor +--- + +Add experimental `` component. Not ready for usage yet. diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index 0f02bca09ff..facc4145374 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -31,3 +31,4 @@ export { default as Waitlist } from './interactive/Waitlist.astro'; export { default as OAuthConsent } from './interactive/OAuthConsent.astro'; export { default as PricingTable } from './interactive/PricingTable.astro'; export { default as APIKeys } from './interactive/APIKeys.astro'; +export { default as __experimental_ConfigureSSO } from './interactive/ConfigureSSO.astro'; diff --git a/packages/astro/src/astro-components/interactive/ConfigureSSO.astro b/packages/astro/src/astro-components/interactive/ConfigureSSO.astro new file mode 100644 index 00000000000..9fdb7bf37f0 --- /dev/null +++ b/packages/astro/src/astro-components/interactive/ConfigureSSO.astro @@ -0,0 +1,11 @@ +--- +import type { __experimental_ConfigureSSOProps } from '@clerk/shared/types'; +type Props = __experimental_ConfigureSSOProps; + +import InternalUIComponentRenderer from './InternalUIComponentRenderer.astro'; +--- + + diff --git a/packages/astro/src/internal/mount-clerk-astro-js-components.ts b/packages/astro/src/internal/mount-clerk-astro-js-components.ts index 97720d3de67..c4a6ac81ed8 100644 --- a/packages/astro/src/internal/mount-clerk-astro-js-components.ts +++ b/packages/astro/src/internal/mount-clerk-astro-js-components.ts @@ -21,7 +21,7 @@ const mountAllClerkAstroJSComponents = () => { waitlist: 'mountWaitlist', 'pricing-table': 'mountPricingTable', 'api-keys': 'mountAPIKeys', - 'oauth-consent': 'mountOAuthConsent', + 'configure-sso': '__experimental_mountConfigureSSO', } as const satisfies Record; Object.entries(mountFns).forEach(([category, mountFn]) => { diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 5807f6c3b3e..d2a2a89a22b 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -120,4 +120,4 @@ export type InternalUIComponentId = | 'waitlist' | 'pricing-table' | 'api-keys' - | 'oauth-consent'; + | 'configure-sso'; diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 3aef7fb1570..bcea517d7e1 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -2,8 +2,8 @@ "files": [ { "path": "./dist/clerk.js", "maxSize": "543KB" }, { "path": "./dist/clerk.browser.js", "maxSize": "70KB" }, - { "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" }, - { "path": "./dist/clerk.no-rhc.js", "maxSize": "309KB" }, + { "path": "./dist/clerk.legacy.browser.js", "maxSize": "112KB" }, + { "path": "./dist/clerk.no-rhc.js", "maxSize": "311KB" }, { "path": "./dist/clerk.native.js", "maxSize": "70KB" }, { "path": "./dist/vendors*.js", "maxSize": "7KB" }, { "path": "./dist/coinbase*.js", "maxSize": "36KB" }, diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 37b6433e226..8277c29a117 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -32,6 +32,7 @@ const AVAILABLE_COMPONENTS = [ 'waitlist', 'pricingTable', 'apiKeys', + 'configureSSO', 'oauthConsent', 'taskChooseOrganization', 'taskResetPassword', @@ -136,6 +137,7 @@ const componentControls: Record = { waitlist: buildComponentControls('waitlist'), pricingTable: buildComponentControls('pricingTable'), apiKeys: buildComponentControls('apiKeys'), + configureSSO: buildComponentControls('configureSSO'), oauthConsent: buildComponentControls('oauthConsent'), taskChooseOrganization: buildComponentControls('taskChooseOrganization'), taskResetPassword: buildComponentControls('taskResetPassword'), @@ -468,6 +470,9 @@ void (async () => { '/api-keys': () => { Clerk.mountAPIKeys(app, componentControls.apiKeys.getProps() ?? {}); }, + '/configure-sso': () => { + Clerk.__experimental_mountConfigureSSO(app, componentControls.configureSSO.getProps() ?? {}); + }, '/oauth-consent': () => { const searchParams = new URLSearchParams(window.location.search); const scopes = (searchParams.get('scope')?.split(',') ?? []).map(scope => ({ diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index 422e7496cb8..9591fe7e852 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -177,6 +177,14 @@ API Keys +
  • + + ConfigureSSO + +
  • ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; - public mountOAuthConsent = (node: HTMLDivElement, props?: OAuthConsentProps) => { + public mountOAuthConsent = (node: HTMLDivElement, props?: __internal_OAuthConsentProps) => { if (noUserExists(this)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderOAuthConsentComponentWhenUserDoesNotExist, { @@ -1450,6 +1455,59 @@ export class Clerk implements ClerkInterface { void this.#clerkUI?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; + /** + * Mount a configure SSO component at the target element. + * + * @experimental + * @param targetNode Target to mount the ConfigureSSO component. + * @param props Configuration parameters. + */ + public __experimental_mountConfigureSSO = (node: HTMLDivElement, props?: __experimental_ConfigureSSOProps) => { + if (disabledSelfServeSSOFeature(this, this.environment)) { + if (this.#instanceType === 'development') { + throw new ClerkRuntimeError(warnings.cannotRenderConfigureSSOComponentWhenDisabled, { + code: CANNOT_RENDER_SELF_SERVE_SSO_DISABLED_ERROR_CODE, + }); + } + return; + } + + if (disabledEmailAddressAttribute(this, this.environment)) { + if (this.#instanceType === 'development') { + throw new ClerkRuntimeError(warnings.cannotRenderConfigureSSOComponentWhenEmailAddressDisabled, { + code: CANNOT_RENDER_CONFIGURE_SSO_EMAIL_ADDRESS_DISABLED_ERROR_CODE, + }); + } + return; + } + + this.assertComponentsReady(this.#clerkUI); + const component = 'ConfigureSSO'; + void this.#clerkUI + .then(ui => ui.ensureMounted({ preloadHint: component })) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: '__experimental_configureSSO', + node, + props, + }), + ); + + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); + }; + + /** + * Unmount a configure SSO component from the target element. + * If there is no component mounted at the target node, results in a noop. + * + * @experimental + * @param targetNode Target node to unmount the ConfigureSSO component from. + */ + public __experimental_unmountConfigureSSO = (node: HTMLDivElement) => { + void this.#clerkUI?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); + }; + public mountTaskChooseOrganization = (node: HTMLDivElement, props?: TaskChooseOrganizationProps) => { const { isEnabled: isOrganizationsEnabled } = this.__internal_attemptToEnableEnvironmentSetting({ for: 'organizations', diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 48a8c85a426..aaabb6738b6 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -105,6 +105,7 @@ export class UserSettings extends BaseResource implements UserSettingsResource { }; enterpriseSSO: EnterpriseSSOSettings = { enabled: false, + self_serve_sso: false, }; passkeySettings: PasskeySettingsData = { allow_autofill: false, diff --git a/packages/clerk-js/src/test/fixture-helpers.ts b/packages/clerk-js/src/test/fixture-helpers.ts index b1564bcd841..f3498850197 100644 --- a/packages/clerk-js/src/test/fixture-helpers.ts +++ b/packages/clerk-js/src/test/fixture-helpers.ts @@ -536,7 +536,7 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { const withEnterpriseSso = () => { us.saml = { enabled: true }; - us.enterprise_sso = { enabled: true }; + us.enterprise_sso = { enabled: true, self_serve_sso: false }; }; const withBackupCode = (opts?: Partial) => { diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 79c8c63c469..e32c9bfc2b7 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -178,6 +178,11 @@ export const arSA: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'تكوين تسجيل الدخول الموحد (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'أنشاء منظمة', invitePage: { diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index c5b109d1dca..2b16a7ae936 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -178,6 +178,11 @@ export const beBY: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Налада адзінага ўваходу (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Стварыць арганізацыю', invitePage: { diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 49004e24135..96704ca286f 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -179,6 +179,11 @@ export const bgBG: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Конфигуриране на единен вход (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Създаване на организация', invitePage: { diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index b9a2463165b..765c3d7815a 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -178,6 +178,11 @@ export const bnIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'একক সাইন-অন (SSO) কনফিগার করুন', + }, + }, createOrganization: { formButtonSubmit: 'সংগঠন তৈরি করুন', invitePage: { diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index ab92189175a..fd021b11257 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -185,6 +185,11 @@ export const caES: LocalizationResource = { viewPayment: 'Veure pagament', year: 'Any', }, + configureSSO: { + navbar: { + title: "Configura l'inici de sessió únic (SSO)", + }, + }, createOrganization: { formButtonSubmit: 'Crea organització', invitePage: { diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 34da5326211..e23888e312f 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -182,6 +182,11 @@ export const csCZ: LocalizationResource = { viewPayment: undefined, year: 'Rok', }, + configureSSO: { + navbar: { + title: 'Nastavit jednotné přihlášení (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Vytvořit organizaci', invitePage: { diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 472f49f2ae6..5df63b312ed 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -178,6 +178,11 @@ export const daDK: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurer single sign-on (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Opret organisation', invitePage: { diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index f4fb1d2ba13..db24f051d2e 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -184,6 +184,11 @@ export const deDE: LocalizationResource = { viewPayment: 'Zahlung anzeigen', year: 'Jahr', }, + configureSSO: { + navbar: { + title: 'Single Sign-On (SSO) konfigurieren', + }, + }, createOrganization: { formButtonSubmit: 'Organisation erstellen', invitePage: { diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 4ba17bb6493..9147179a512 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -178,6 +178,11 @@ export const elGR: LocalizationResource = { viewPayment: 'Προβολή πληρωμής', year: 'έτος', }, + configureSSO: { + navbar: { + title: 'Διαμόρφωση Ενιαίας Σύνδεσης (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Δημιουργία οργανισμού', invitePage: { diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index b0aa4889f53..d529df0a938 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -178,6 +178,11 @@ export const enGB: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configure Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Create organisation', invitePage: { diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 3b5b3e1bc61..21f706f2883 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -197,6 +197,11 @@ export const enUS: LocalizationResource = { yearAbbreviation: 'yr', yearPerUnit: 'Year per {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Configure Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Create organization', invitePage: { diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index e305c0d0e6c..8b1028c51f1 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -178,6 +178,11 @@ export const esCR: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 45982e3c4be..6e0736e7078 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -184,6 +184,11 @@ export const esES: LocalizationResource = { viewPayment: 'Ver pago', year: 'Año', }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index cb340de4858..12a91d526e5 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -179,6 +179,11 @@ export const esMX: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index a4a387ef619..dc9a5f1fac2 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -178,6 +178,11 @@ export const esUY: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 04c9b1ef62b..1389120f971 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -183,6 +183,11 @@ export const faIR: LocalizationResource = { viewPayment: 'مشاهده پرداخت', year: 'سال', }, + configureSSO: { + navbar: { + title: 'پیکربندی ورود یکپارچه (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'ایجاد سازمان', invitePage: { diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index e4fa4e1a821..d835761ecbc 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -206,6 +206,11 @@ export const fiFI: LocalizationResource = { yearAbbreviation: 'v', yearPerUnit: 'Vuosi per {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Määritä kertakirjautuminen (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Luo organisaatio', invitePage: { diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index 5e4b3c05764..9f7fdfaee50 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -186,6 +186,11 @@ export const frFR: LocalizationResource = { viewPayment: 'Voir le paiement', year: 'An', }, + configureSSO: { + navbar: { + title: "Configurer l'authentification unique (SSO)", + }, + }, createOrganization: { formButtonSubmit: 'Créer l’organisation', invitePage: { diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 29913555481..6771d4daf90 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -178,6 +178,11 @@ export const heIL: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'הגדרת כניסה אחידה (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'צור ארגון', invitePage: { diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 94039ba7ee2..29651d6cbd3 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -178,6 +178,11 @@ export const hiIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'सिंगल साइन-ऑन (SSO) कॉन्फ़िगर करें', + }, + }, createOrganization: { formButtonSubmit: 'संगठन बनाएँ', invitePage: { diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index da8a1f39024..1af45a26c6e 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -207,6 +207,11 @@ export const hrHR: LocalizationResource = { yearAbbreviation: 'god', yearPerUnit: 'Godina po {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Konfiguriraj jedinstvenu prijavu (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'kreiraj organizaciju', invitePage: { diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index 9b634dc17de..8ba7b812c75 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -207,6 +207,11 @@ export const huHU: LocalizationResource = { yearAbbreviation: 'év', yearPerUnit: 'Év / {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Egyszeri bejelentkezés (SSO) beállítása', + }, + }, createOrganization: { formButtonSubmit: 'Szervezet létrehozása', invitePage: { diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 1297208c560..0b9880267ad 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -178,6 +178,11 @@ export const idID: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurasi Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Buat organisasi', invitePage: { diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 686dc893e31..e32720033ca 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -206,6 +206,11 @@ export const isIS: LocalizationResource = { yearAbbreviation: 'ár', yearPerUnit: 'Ár á {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Stilla einnar innskráningar (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Stofna samtök', invitePage: { diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index 7fd8e514f1c..93ee04f749c 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -184,6 +184,11 @@ export const itIT: LocalizationResource = { viewPayment: undefined, year: 'Anno', }, + configureSSO: { + navbar: { + title: 'Configura Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crea organizzazione', invitePage: { diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 053cc788f0d..a9f62ce514f 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -189,6 +189,11 @@ export const jaJP: LocalizationResource = { viewPayment: '支払いを表示', year: '年', }, + configureSSO: { + navbar: { + title: 'シングルサインオン(SSO)を設定', + }, + }, createOrganization: { formButtonSubmit: '組織を作成する', invitePage: { diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index d75f6034f2c..1c7c752c36a 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -178,6 +178,11 @@ export const kkKZ: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Бірыңғай кіруді конфигурациялау (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Ұйым құру', invitePage: { diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 28d9d3d309d..ddfec4d8212 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -182,6 +182,11 @@ export const koKR: LocalizationResource = { viewPayment: '결제 보기', year: '년', }, + configureSSO: { + navbar: { + title: '싱글 사인온(SSO) 구성', + }, + }, createOrganization: { formButtonSubmit: '조직 만들기', invitePage: { diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 66ac4813eb4..4ccdebd039d 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -178,6 +178,11 @@ export const mnMN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Нэгдсэн нэвтрэлт (SSO) тохируулах', + }, + }, createOrganization: { formButtonSubmit: 'Байгуулга үүсгэх', invitePage: { diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index ddd520f34f9..0e8da7e7832 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -178,6 +178,11 @@ export const msMY: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurasi Log Masuk Tunggal (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Cipta organisasi', invitePage: { diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index da2737185f0..b063903b209 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -207,6 +207,11 @@ export const nbNO: LocalizationResource = { yearAbbreviation: 'år', yearPerUnit: 'År per {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Konfigurer enkeltpålogging (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Opprett organisasjon', invitePage: { diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index cf172ff19cb..10df71a968a 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -178,6 +178,11 @@ export const nlBE: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Single sign-on (SSO) configureren', + }, + }, createOrganization: { formButtonSubmit: 'Creëer organisatie', invitePage: { diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 860e62983d6..128189d82db 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -178,6 +178,11 @@ export const nlNL: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Single sign-on (SSO) configureren', + }, + }, createOrganization: { formButtonSubmit: 'Creëer organisatie', invitePage: { diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 7a4e2d3d116..d87215cc89a 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -178,6 +178,11 @@ export const plPL: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Skonfiguruj logowanie jednokrotne (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Utwórz organizację', invitePage: { diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index c4c17027071..604fde40ec2 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -184,6 +184,11 @@ export const ptBR: LocalizationResource = { viewPayment: 'Ver pagamento', year: 'Ano', }, + configureSSO: { + navbar: { + title: 'Configurar logon único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Criar organização', invitePage: { diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 0dc1f68eaae..76519707741 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -186,6 +186,11 @@ export const ptPT: LocalizationResource = { viewPayment: 'Ver pagamento', year: 'Ano', }, + configureSSO: { + navbar: { + title: 'Configurar autenticação única (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Criar organização', invitePage: { diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index eadcabedded..c54f0159634 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -184,6 +184,11 @@ export const roRO: LocalizationResource = { viewPayment: 'Vezi plata', year: 'An', }, + configureSSO: { + navbar: { + title: 'Configurați autentificarea unică (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Creează organizație', invitePage: { diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 3dcfeb98df0..439dbabad3f 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -178,6 +178,11 @@ export const ruRU: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Настроить единый вход (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Создать организацию', invitePage: { diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 6fcd15b73e9..6d141338a80 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -178,6 +178,11 @@ export const skSK: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Nastaviť jednotné prihlasovanie (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Vytvoriť organizáciu', invitePage: { diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index fbcca7e1d6c..5edc8c961d6 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -178,6 +178,11 @@ export const srRS: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfiguriši jedinstvenu prijavu (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Kreiraj organizaciju', invitePage: { diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index a5e80ff65ed..fb3d7b2d18a 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -178,6 +178,11 @@ export const svSE: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurera enkel inloggning (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Skapa organisation', invitePage: { diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 5baa4de9e6c..77fa6116a45 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -178,6 +178,11 @@ export const taIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'ஒற்றை உள்நுழைவை (SSO) உள்ளமை', + }, + }, createOrganization: { formButtonSubmit: 'நிறுவனத்தை உருவாக்கு', invitePage: { diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index 4d63ab5b552..a019b92757a 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -178,6 +178,11 @@ export const teIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'సింగిల్ సైన్-ఆన్ (SSO) కాన్ఫిగర్ చేయండి', + }, + }, createOrganization: { formButtonSubmit: 'సంస్థను సృష్టించండి', invitePage: { diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index b074c63a6a9..d8c8264b878 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -182,6 +182,11 @@ export const thTH: LocalizationResource = { viewPayment: 'ดูการชำระเงิน', year: 'ปี', }, + configureSSO: { + navbar: { + title: 'กำหนดค่าการลงชื่อเข้าใช้แบบครั้งเดียว (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'สร้างองค์กร', invitePage: { diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index e5af54c494c..cfddf9e7a04 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -178,6 +178,11 @@ export const trTR: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Tek Oturum Açmayı (SSO) Yapılandır', + }, + }, createOrganization: { formButtonSubmit: 'Oluştur', invitePage: { diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 01495d07413..d1f62de46c8 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -178,6 +178,11 @@ export const ukUA: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Налаштувати єдиний вхід (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Створити організацію', invitePage: { diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 512e3b2672d..411ca53019b 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -182,6 +182,11 @@ export const viVN: LocalizationResource = { viewPayment: undefined, year: 'Năm', }, + configureSSO: { + navbar: { + title: 'Cấu hình đăng nhập một lần (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Tạo tổ chức', invitePage: { diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index f11b0913eda..8835fdfc716 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -178,6 +178,11 @@ export const zhCN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: '配置单点登录 (SSO)', + }, + }, createOrganization: { formButtonSubmit: '创建组织', invitePage: { diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index c37d6efd9ea..5bd804bfe98 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -184,6 +184,11 @@ export const zhTW: LocalizationResource = { viewPayment: '查看付款', year: '年', }, + configureSSO: { + navbar: { + title: '設定單一登入 (SSO)', + }, + }, createOrganization: { formButtonSubmit: '創建組織', invitePage: { diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 897b2ff9f03..ccabf817440 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -198,5 +198,22 @@ export default defineNuxtModule({ filePath: '@clerk/vue', }); }); + + /** + * Experimental components from `@clerk/vue/experimental`. + * @experimental These components and their prop types are unstable and may change in future releases. + */ + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const experimentalComponents: Array = [ + // SSO + 'ConfigureSSO', + ]; + experimentalComponents.forEach(component => { + void addComponent({ + name: component, + export: component, + filePath: '@clerk/vue/experimental', + }); + }); }, }); diff --git a/packages/react/src/components/uiComponents.tsx b/packages/react/src/components/uiComponents.tsx index a87b83af675..ba7c941618b 100644 --- a/packages/react/src/components/uiComponents.tsx +++ b/packages/react/src/components/uiComponents.tsx @@ -1,4 +1,5 @@ import type { + __experimental_ConfigureSSOProps, __internal_OAuthConsentProps, APIKeysProps, CreateOrganizationProps, @@ -644,6 +645,37 @@ export const APIKeys = withClerk( { component: 'ApiKeys', renderWhileLoading: true }, ); +/** + * @experimental This component is in early access and may change in future releases. + */ +export const ConfigureSSO = withClerk( + ({ clerk, component, fallback, ...props }: WithClerkProp<__experimental_ConfigureSSOProps & FallbackProp>) => { + const mountingStatus = useWaitForComponentMount(component); + const shouldShowFallback = mountingStatus === 'rendering' || !clerk.loaded; + + const rendererRootProps = { + ...(shouldShowFallback && fallback && { style: { display: 'none' } }), + }; + + return ( + <> + {shouldShowFallback && fallback} + {clerk.loaded && ( + + )} + + ); + }, + { component: 'ConfigureSSO', renderWhileLoading: true }, +); + export const OAuthConsent = withClerk( ({ clerk, component, fallback, ...props }: WithClerkProp<__internal_OAuthConsentProps & FallbackProp>) => { const mountingStatus = useWaitForComponentMount(component); diff --git a/packages/react/src/experimental.ts b/packages/react/src/experimental.ts index 001206478e7..c5deea83347 100644 --- a/packages/react/src/experimental.ts +++ b/packages/react/src/experimental.ts @@ -2,10 +2,21 @@ export { CheckoutButton } from './components/CheckoutButton'; export { PlanDetailsButton } from './components/PlanDetailsButton'; export { SubscriptionDetailsButton } from './components/SubscriptionDetailsButton'; +/** + * @experimental + * This component and its prop types are unstable and may change in future releases. + */ +export { ConfigureSSO } from './components/uiComponents'; + export type { __experimental_CheckoutButtonProps as CheckoutButtonProps, __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps, __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, + /** + * @experimental + * This type is unstable and may change in future releases. + */ + __experimental_ConfigureSSOProps as ConfigureSSOProps, } from '@clerk/shared/types'; export { diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b47acb36f15..513a79653fc 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -2,6 +2,7 @@ import { inBrowser } from '@clerk/shared/browser'; import { clerkEvents, createClerkEventBus } from '@clerk/shared/clerkEventBus'; import { loadClerkJSScript, loadClerkUIScript } from '@clerk/shared/loadClerkJsScript'; import type { + __experimental_ConfigureSSOProps, __internal_AttemptToEnableEnvironmentSettingParams, __internal_AttemptToEnableEnvironmentSettingResult, __internal_CheckoutProps, @@ -159,6 +160,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { private premountWaitlistNodes = new Map(); private premountPricingTableNodes = new Map(); private premountAPIKeysNodes = new Map(); + private premountConfigureSSONodes = new Map(); private premountOAuthConsentNodes = new Map(); private premountTaskChooseOrganizationNodes = new Map(); private premountTaskResetPasswordNodes = new Map(); @@ -747,6 +749,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { clerkjs.mountAPIKeys(node, props); }); + this.premountConfigureSSONodes.forEach((props, node) => { + clerkjs.__experimental_mountConfigureSSO(node, props); + }); + this.premountOAuthConsentNodes.forEach((props, node) => { clerkjs.__internal_mountOAuthConsent(node, props); }); @@ -1283,7 +1289,23 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; - __internal_mountOAuthConsent = (node: HTMLDivElement, props?: __internal_OAuthConsentProps) => { + __experimental_mountConfigureSSO = (node: HTMLDivElement, props?: __experimental_ConfigureSSOProps): void => { + if (this.clerkjs && this.loaded) { + this.clerkjs.__experimental_mountConfigureSSO(node, props); + } else { + this.premountConfigureSSONodes.set(node, props); + } + }; + + __experimental_unmountConfigureSSO = (node: HTMLDivElement): void => { + if (this.clerkjs && this.loaded) { + this.clerkjs.__experimental_unmountConfigureSSO(node); + } else { + this.premountConfigureSSONodes.delete(node); + } + }; + + __internal_mountOAuthConsent = (node: HTMLDivElement, props?: OAuthConsentProps) => { if (this.clerkjs && this.loaded) { this.clerkjs.__internal_mountOAuthConsent(node, props); } else { diff --git a/packages/shared/src/internal/clerk-js/componentGuards.ts b/packages/shared/src/internal/clerk-js/componentGuards.ts index 9b3fec8f82b..06f9ac443b9 100644 --- a/packages/shared/src/internal/clerk-js/componentGuards.ts +++ b/packages/shared/src/internal/clerk-js/componentGuards.ts @@ -45,3 +45,11 @@ export const disabledOrganizationAPIKeysFeature: ComponentGuard = (_, environmen export const disabledAllAPIKeysFeatures: ComponentGuard = (_, environment) => { return disabledUserAPIKeysFeature(_, environment) && disabledOrganizationAPIKeysFeature(_, environment); }; + +export const disabledSelfServeSSOFeature: ComponentGuard = (_, environment) => { + return !environment?.userSettings.enterpriseSSO.self_serve_sso; +}; + +export const disabledEmailAddressAttribute: ComponentGuard = (_, environment) => { + return !environment?.userSettings.attributes.email_address?.enabled; +}; diff --git a/packages/shared/src/internal/clerk-js/warnings.ts b/packages/shared/src/internal/clerk-js/warnings.ts index 2686713eaf7..94ba1e4e7e9 100644 --- a/packages/shared/src/internal/clerk-js/warnings.ts +++ b/packages/shared/src/internal/clerk-js/warnings.ts @@ -64,6 +64,10 @@ const warnings = { 'The component cannot be rendered when organization API keys are disabled. Since organization API keys are disabled, this is no-op.', cannotRenderOAuthConsentComponentWhenUserDoesNotExist: ' cannot render unless a user is signed in. Since no user is signed in, this is no-op.', + cannotRenderConfigureSSOComponentWhenDisabled: + 'The component cannot be rendered when self-serve SSO is disabled. Visit `https://dashboard.clerk.com` to enable the feature. Since self-serve SSO is disabled, this is no-op.', + cannotRenderConfigureSSOComponentWhenEmailAddressDisabled: + 'The component cannot be rendered when email addresses are disabled on the instance. Visit `https://dashboard.clerk.com` to enable email addresses. Since email addresses are disabled, this is no-op.', }; type SerializableWarnings = Serializable; diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index be87e83f76f..88613446bcb 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -661,6 +661,26 @@ export interface Clerk { */ unmountAPIKeys: (targetNode: HTMLDivElement) => void; + /** + * Mount a configure SSO component at the target element. + * + * @experimental This method is in early access and may change in future releases. + * + * @param targetNode - Target to mount the ConfigureSSO component. + * @param props - Configuration parameters. + */ + __experimental_mountConfigureSSO: (targetNode: HTMLDivElement, props?: __experimental_ConfigureSSOProps) => void; + + /** + * Unmount a configure SSO component from the target element. + * If there is no component mounted at the target node, results in a noop. + * + * @experimental This method is in early access and may change in future releases. + * + * @param targetNode - Target node to unmount the ConfigureSSO component from. + */ + __experimental_unmountConfigureSSO: (targetNode: HTMLDivElement) => void; + /** * Mounts a OAuth consent component at the target element. * @@ -2149,6 +2169,18 @@ export type APIKeysProps = { showDescription?: boolean; }; +/** + * @experimental This type is in early access and may change in future releases. + */ +export type __experimental_ConfigureSSOProps = { + /** + * Customisation options to fully match the Clerk components to your own brand. + * These options serve as overrides and will be merged with the global `appearance` + * prop of ClerkProvider (if one is provided) + */ + appearance?: ClerkAppearanceTheme; +}; + export type GetAPIKeysParams = ClerkPaginationParams<{ subject?: string; query?: string; diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 222509565bb..33f3d1d6e21 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1292,6 +1292,11 @@ export type __internal_LocalizationResource = { message: LocalizationValue; }; }; + configureSSO: { + navbar: { + title: LocalizationValue; + }; + }; apiKeys: { formTitle: LocalizationValue; formHint: LocalizationValue; diff --git a/packages/shared/src/types/userSettings.ts b/packages/shared/src/types/userSettings.ts index f8424d1eba6..dafa0190251 100644 --- a/packages/shared/src/types/userSettings.ts +++ b/packages/shared/src/types/userSettings.ts @@ -92,6 +92,7 @@ export type OAuthProviders = { }; export type EnterpriseSSOSettings = { enabled: boolean; + self_serve_sso: boolean; }; export type AttributesJSON = { diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx new file mode 100644 index 00000000000..4b744a8a8d0 --- /dev/null +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -0,0 +1,117 @@ +import { useOrganization } from '@clerk/shared/react/index'; +import type { __experimental_ConfigureSSOProps } from '@clerk/shared/types'; +import React from 'react'; + +import { useEnvironment, withCoreUserGuard } from '@/contexts'; +import { Box, Col, Flex, Flow, Icon, localizationKeys, Text, useAppearance } from '@/customizables'; +import { ApplicationLogo } from '@/elements/ApplicationLogo'; +import { withCardStateProvider } from '@/elements/contexts'; +import { NavBar, NavbarContextProvider } from '@/elements/Navbar'; +import { ProfileCard } from '@/elements/ProfileCard'; +import { BoxIcon } from '@/icons'; +import { Route, Switch } from '@/router'; + +const ConfigureSSOInternal = () => { + return ( + + + + + + + + + + ); +}; + +const AuthenticatedContent = withCoreUserGuard(() => { + const contentRef = React.useRef(null); + const { applicationName, logoImageUrl } = useEnvironment().displayConfig; + const { organizationSettings } = useEnvironment(); + const { parsedOptions } = useAppearance(); + const hasLogo = Boolean(parsedOptions.logoImageUrl || logoImageUrl); + + return ( + ({ display: 'grid', gridTemplateColumns: '1fr 3fr', height: t.sizes.$176, overflow: 'hidden' })} + > + + ({ + gap: t.space.$2, + padding: `${t.space.$none} ${t.space.$3}`, + maxWidth: '100%', + })} + > + {hasLogo ? ( + ({ width: t.space.$9, height: t.space.$9, borderRadius: t.radii.$md, overflow: 'hidden' })} + /> + ) : ( + ({ + width: t.space.$9, + height: t.space.$9, + flexShrink: 0, + borderRadius: t.radii.$md, + backgroundColor: t.colors.$primary500, + color: t.colors.$colorPrimaryForeground, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + })} + aria-hidden + > + ({ width: t.sizes.$4, height: t.sizes.$4 })} + /> + + )} + + + + {applicationName} + + {organizationSettings.enabled && } + + + } + titleSx={t => ({ fontSize: t.fontSizes.$lg })} + title={localizationKeys('configureSSO.navbar.title')} + routes={[]} + contentRef={contentRef} + /> + + + + ); +}); + +const OrganizationSidebarSubtitle = () => { + const { organization } = useOrganization(); + + if (!organization) { + return null; + } + + return ( + ({ color: t.colors.$colorMutedForeground })} + > + {organization?.name} + + ); +}; + +export const ConfigureSSO: React.ComponentType<__experimental_ConfigureSSOProps> = + withCardStateProvider(ConfigureSSOInternal); diff --git a/packages/ui/src/contexts/ClerkUIComponentsContext.tsx b/packages/ui/src/contexts/ClerkUIComponentsContext.tsx index 282160cf2af..24a3760c385 100644 --- a/packages/ui/src/contexts/ClerkUIComponentsContext.tsx +++ b/packages/ui/src/contexts/ClerkUIComponentsContext.tsx @@ -1,5 +1,6 @@ import type { OAuthConsentProps, + __experimental_ConfigureSSOProps, APIKeysProps, PricingTableProps, TaskChooseOrganizationProps, @@ -13,6 +14,7 @@ import type { ReactNode } from 'react'; import type { AvailableComponentName, AvailableComponentProps } from '../types'; import { APIKeysContext, + ConfigureSSOContext, CreateOrganizationContext, GoogleOneTapContext, OAuthConsentContext, @@ -114,6 +116,12 @@ export function ComponentContextProvider({ {children} ); + case 'ConfigureSSO': + return ( + + {children} + + ); case 'OAuthConsent': { // Translate capital-A `oAuth*` props from the accounts portal into // the lowercase `oauth*` context shape the component reads. diff --git a/packages/ui/src/contexts/components/ConfigureSSO.ts b/packages/ui/src/contexts/components/ConfigureSSO.ts new file mode 100644 index 00000000000..7ce177a6d85 --- /dev/null +++ b/packages/ui/src/contexts/components/ConfigureSSO.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +import type { ConfigureSSOCtx } from '../../types'; + +export const ConfigureSSOContext = createContext(null); + +export const useConfigureSSOContext = () => { + const context = useContext(ConfigureSSOContext); + + if (!context || context.componentName !== 'ConfigureSSO') { + throw new Error('Clerk: useConfigureSSOContext called outside ConfigureSSO.'); + } + + const { componentName, ...ctx } = context; + + return { + ...ctx, + componentName, + }; +}; diff --git a/packages/ui/src/contexts/components/index.ts b/packages/ui/src/contexts/components/index.ts index 4242c494ad6..25b15a20fab 100644 --- a/packages/ui/src/contexts/components/index.ts +++ b/packages/ui/src/contexts/components/index.ts @@ -1,5 +1,6 @@ export * from './APIKeys'; export * from './Checkout'; +export * from './ConfigureSSO'; export * from './CreateOrganization'; export * from './GoogleOneTap'; export * from './OAuthConsent'; diff --git a/packages/ui/src/elements/Navbar.tsx b/packages/ui/src/elements/Navbar.tsx index 8b2a577c792..1871c0c5825 100644 --- a/packages/ui/src/elements/Navbar.tsx +++ b/packages/ui/src/elements/Navbar.tsx @@ -7,7 +7,7 @@ import type { ElementDescriptor, ElementId } from '../customizables/elementDescr import { useNavigateToFlowStart, usePopover } from '../hooks'; import { Menu } from '../icons'; import { useRouter } from '../router'; -import type { PropsOfComponent } from '../styledSystem'; +import type { PropsOfComponent, ThemableCssProp } from '../styledSystem'; import { animations, common, mqu } from '../styledSystem'; import { colors } from '../utils/colors'; import { Card } from './Card'; @@ -44,14 +44,15 @@ export type NavbarRoute = { }; type NavBarProps = { title: LocalizationKey; - description: LocalizationKey; + titleSx?: ThemableCssProp; + description?: LocalizationKey; contentRef: React.RefObject; routes: NavbarRoute[]; header?: React.ReactNode; }; export const NavBar = (props: NavBarProps) => { - const { contentRef, title, description, routes, header } = props; + const { contentRef, title, titleSx, description, routes, header } = props; const { close } = useNavbarContext(); const { navigate } = useRouter(); const { navigateToFlowStart } = useNavigateToFlowStart(); @@ -126,6 +127,7 @@ export const NavBar = (props: NavBarProps) => { <> {header} @@ -140,9 +142,13 @@ export const NavBar = (props: NavBarProps) => { }; const NavbarContainer = ( - props: React.PropsWithChildren<{ title: LocalizationKey | string; description: LocalizationKey | string }>, + props: React.PropsWithChildren<{ + title: LocalizationKey | string; + titleSx?: ThemableCssProp; + description?: LocalizationKey | string; + }>, ) => { - const { title, description } = props; + const { title, titleSx, description } = props; return ( - + {description ? ( + + ) : null} {props.children} diff --git a/packages/ui/src/elements/contexts/index.tsx b/packages/ui/src/elements/contexts/index.tsx index cecccfe3d88..44eef53bf88 100644 --- a/packages/ui/src/elements/contexts/index.tsx +++ b/packages/ui/src/elements/contexts/index.tsx @@ -98,6 +98,7 @@ export type FlowMetadata = { | 'planDetails' | 'pricingTable' | 'apiKeys' + | 'configureSSO' | 'oauthConsent' | 'subscriptionDetails' | 'tasks' diff --git a/packages/ui/src/icons/box.svg b/packages/ui/src/icons/box.svg new file mode 100644 index 00000000000..b000531e8b0 --- /dev/null +++ b/packages/ui/src/icons/box.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 497b15f1abf..e282f04aae8 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -13,6 +13,7 @@ export { default as ArrowsUpDown } from './arrows-up-down.svg'; export { default as AuthApp } from './auth-app.svg'; export { default as Billing } from './billing.svg'; export { default as Block } from './block.svg'; +export { default as BoxIcon } from './box.svg'; export { default as Caret } from './caret.svg'; export { default as CaretLeft } from './caret-left.svg'; export { default as CaretRight } from './caret-right.svg'; diff --git a/packages/ui/src/internal/appearance.ts b/packages/ui/src/internal/appearance.ts index 71bc4815a94..cd0ce04dcfe 100644 --- a/packages/ui/src/internal/appearance.ts +++ b/packages/ui/src/internal/appearance.ts @@ -1014,6 +1014,7 @@ export type CheckoutTheme = Theme; export type PlanDetailTheme = Theme; export type SubscriptionDetailsTheme = Theme; export type APIKeysTheme = Theme; +export type __experimental_ConfigureSSOTheme = Theme; export type OAuthConsentTheme = Theme; export type TaskChooseOrganizationTheme = Theme; export type TaskResetPasswordTheme = Theme; @@ -1090,6 +1091,10 @@ export type Appearance = T & * Theme overrides that only apply to the `` component */ apiKeys?: T; + /** + * Theme overrides that only apply to the `` component + */ + __experimental_configureSSO?: T; /** * Theme overrides that only apply to the `` component */ diff --git a/packages/ui/src/lazyModules/components.ts b/packages/ui/src/lazyModules/components.ts index 0cc57f424ca..3c8e62f7e39 100644 --- a/packages/ui/src/lazyModules/components.ts +++ b/packages/ui/src/lazyModules/components.ts @@ -29,6 +29,7 @@ const componentImportPaths = { PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans/PlanDetails'), SubscriptionDetails: () => import(/* webpackChunkName: "subscriptionDetails" */ '../components/SubscriptionDetails'), APIKeys: () => import(/* webpackChunkName: "apiKeys" */ '../components/APIKeys/APIKeys'), + ConfigureSSO: () => import(/* webpackChunkName: "configureSSO" */ '../components/ConfigureSSO/ConfigureSSO'), OAuthConsent: () => import(/* webpackChunkName: "oauthConsent" */ '../components/OAuthConsent/OAuthConsent'), EnableOrganizationsPrompt: () => import(/* webpackChunkName: "enableOrganizationsPrompt" */ '../components/devPrompts/EnableOrganizationsPrompt'), @@ -120,6 +121,10 @@ export const PricingTable = lazy(() => export const APIKeys = lazy(() => componentImportPaths.APIKeys().then(module => ({ default: module.APIKeys }))); +export const ConfigureSSO = lazy(() => + componentImportPaths.ConfigureSSO().then(module => ({ default: module.ConfigureSSO })), +); + export const Checkout = lazy(() => componentImportPaths.Checkout().then(module => ({ default: module.Checkout }))); export const TaskChooseOrganization = lazy(() => @@ -180,6 +185,7 @@ export const ClerkComponents = { Checkout, PlanDetails, APIKeys, + ConfigureSSO, OAuthConsent, SubscriptionDetails, TaskChooseOrganization, diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 8bf8cbddd30..28b3d26c07a 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -1,4 +1,5 @@ import type { + __experimental_ConfigureSSOProps, __internal_CheckoutProps, OAuthConsentProps, __internal_PlanDetailsProps, @@ -64,6 +65,7 @@ export type AvailableComponentProps = | __internal_SubscriptionDetailsProps | __internal_PlanDetailsProps | APIKeysProps + | __experimental_ConfigureSSOProps | OAuthConsentProps | TaskChooseOrganizationProps | TaskResetPasswordProps @@ -145,6 +147,11 @@ export type APIKeysCtx = APIKeysProps & { mode?: ComponentMode; }; +export type ConfigureSSOCtx = __experimental_ConfigureSSOProps & { + componentName: 'ConfigureSSO'; + mode?: ComponentMode; +}; + export type CheckoutCtx = __internal_CheckoutProps & { componentName: 'Checkout'; } & NewSubscriptionRedirectUrl; @@ -249,6 +256,7 @@ export type AvailableComponentCtx = | PricingTableCtx | CheckoutCtx | APIKeysCtx + | ConfigureSSOCtx | OAuthConsentCtx | SubscriptionDetailsCtx | PlanDetailsCtx diff --git a/packages/vue/src/components/ui-components/ConfigureSSO.vue b/packages/vue/src/components/ui-components/ConfigureSSO.vue new file mode 100644 index 00000000000..bcdd2eea036 --- /dev/null +++ b/packages/vue/src/components/ui-components/ConfigureSSO.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/vue/src/experimental.ts b/packages/vue/src/experimental.ts index 63264348f21..285e05a3d09 100644 --- a/packages/vue/src/experimental.ts +++ b/packages/vue/src/experimental.ts @@ -19,6 +19,13 @@ export { default as CheckoutButton } from './components/CheckoutButton.vue'; */ export { default as PlanDetailsButton } from './components/PlanDetailsButton.vue'; +/** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's self-serve SSO feature which is not available yet. + * */ +export { default as ConfigureSSO } from './components/ui-components/ConfigureSSO.vue'; + export type { /** * @experimental @@ -31,11 +38,17 @@ export type { * These components and their prop types are unstable and may change in future releases. * They are part of Clerk's Billing feature which is available under public beta. */ - __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, + __experimental_ConfigureSSOProps as ConfigureSSOProps, /** * @experimental * These components and their prop types are unstable and may change in future releases. * They are part of Clerk's Billing feature which is available under public beta. */ + __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, + /** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's self-serve SSO feature which is not available yet. + */ __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps, } from '@clerk/shared/types';