From 05b92507c1ea1d85cb754b477a9aaf19338b2dd7 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:58:59 -0300 Subject: [PATCH 01/10] Add `OrganizationCreationDefaults` resource --- .../resources/OrganizationCreationDefaults.ts | 42 +++++++++++++++++++ packages/shared/src/types/index.ts | 3 +- .../src/types/organizationCreationDefaults.ts | 20 +++++++++ packages/shared/src/types/snapshots.ts | 3 ++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts create mode 100644 packages/shared/src/types/organizationCreationDefaults.ts diff --git a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts new file mode 100644 index 00000000000..83b927b68dd --- /dev/null +++ b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts @@ -0,0 +1,42 @@ +import type { + OrganizationCreationDefaultsJSON, + OrganizationCreationDefaultsJSONSnapshot, + OrganizationCreationDefaultsResource, +} from '@clerk/shared/types'; + +import { BaseResource } from './internal'; + +export class OrganizationCreationDefaults extends BaseResource implements OrganizationCreationDefaultsResource { + creationAdvisory: { + type: 'existing_org_with_domain'; + severity: 'warning'; + } | null = null; + + public constructor(data: OrganizationCreationDefaultsJSON | OrganizationCreationDefaultsJSONSnapshot | null = null) { + super(); + this.fromJSON(data); + } + + protected fromJSON(data: OrganizationCreationDefaultsJSON | OrganizationCreationDefaultsJSONSnapshot | null): this { + if (!data) { + return this; + } + + if (data.creation_advisory) { + this.creationAdvisory = this.withDefault(data.creation_advisory, this.creationAdvisory ?? null); + } + + return this; + } + + public __internal_toSnapshot(): OrganizationCreationDefaultsJSONSnapshot { + return { + creation_advisory: this.creationAdvisory + ? { + type: this.creationAdvisory.type, + severity: this.creationAdvisory.severity, + } + : null, + } as unknown as OrganizationCreationDefaultsJSONSnapshot; + } +} diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 85cecc41a27..5ef821571b1 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -13,6 +13,7 @@ export type * from './customPages'; export type * from './deletedObject'; export type * from './devtools'; export type * from './displayConfig'; +export type * from './elementIds'; export type * from './emailAddress'; export type * from './enterpriseAccount'; export type * from './environment'; @@ -32,6 +33,7 @@ export type * from './localization'; export type * from './multiDomain'; export type * from './oauth'; export type * from './organization'; +export type * from './organizationCreationDefaults'; export type * from './organizationDomain'; export type * from './organizationInvitation'; export type * from './organizationMembership'; @@ -49,7 +51,6 @@ export type * from './protectConfig'; export type * from './redirects'; export type * from './resource'; export type * from './role'; -export type * from './elementIds'; export type * from './router'; /** * TODO @revamp-hooks: Drop this in the next major release. diff --git a/packages/shared/src/types/organizationCreationDefaults.ts b/packages/shared/src/types/organizationCreationDefaults.ts new file mode 100644 index 00000000000..e510af241f8 --- /dev/null +++ b/packages/shared/src/types/organizationCreationDefaults.ts @@ -0,0 +1,20 @@ +import type { ClerkResourceJSON } from './json'; +import type { ClerkResource } from './resource'; + +export type OrganizationCreationAdvisoryType = 'existing_org_with_domain'; + +export type OrganizationCreationAdvisorySeverity = 'warning'; + +export interface OrganizationCreationDefaultsJSON extends ClerkResourceJSON { + creation_advisory: { + type: OrganizationCreationAdvisoryType; + severity: OrganizationCreationAdvisorySeverity; + } | null; +} + +export interface OrganizationCreationDefaultsResource extends ClerkResource { + creationAdvisory: { + type: OrganizationCreationAdvisoryType; + severity: OrganizationCreationAdvisorySeverity; + } | null; +} diff --git a/packages/shared/src/types/snapshots.ts b/packages/shared/src/types/snapshots.ts index 6db74374c44..a1d239c329f 100644 --- a/packages/shared/src/types/snapshots.ts +++ b/packages/shared/src/types/snapshots.ts @@ -28,6 +28,7 @@ import type { VerificationJSON, Web3WalletJSON, } from './json'; +import type { OrganizationCreationDefaultsJSON } from './organizationCreationDefaults'; import type { OrganizationSettingsJSON } from './organizationSettings'; import type { ProtectConfigJSON } from './protectConfig'; import type { SignInJSON } from './signIn'; @@ -143,6 +144,8 @@ export type OrganizationMembershipJSONSnapshot = OrganizationMembershipJSON; export type OrganizationSettingsJSONSnapshot = OrganizationSettingsJSON; +export type OrganizationCreationDefaultsJSONSnapshot = OrganizationCreationDefaultsJSON; + export type PasskeyJSONSnapshot = Override; export type PhoneNumberJSONSnapshot = Override< From 7ca2f6cd356a795cbe7a7ba05dbfbef31379242d Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Tue, 16 Dec 2025 22:02:02 -0300 Subject: [PATCH 02/10] Add TODOs for UI tests --- .../__tests__/TaskChooseOrganization.test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx index 28952bd96c9..caefb108e76 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx @@ -297,4 +297,10 @@ describe('TaskChooseOrganization', () => { expect(queryByText(/contact your organization admin for an invitation/i)).toBeInTheDocument(); }); }); + + describe('with organization creation defaults', () => { + it.todo('displays warning when organization already exists for user email domain'); + + it.todo('prefills create organization form with defaults'); + }); }); From 945f86ce229bb0f76d422330523b92461a547a18 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:04:34 -0300 Subject: [PATCH 03/10] Add alert for creation advisory --- .../resources/OrganizationCreationDefaults.ts | 10 ++++++++ packages/localizations/src/en-US.ts | 4 +++ packages/shared/src/types/localization.ts | 3 +++ .../CreateOrganizationScreen.tsx | 14 +++++++++++ .../OrganizationCreationDefaultsAlert.tsx | 25 +++++++++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx diff --git a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts index 83b927b68dd..16ac507e142 100644 --- a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts +++ b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts @@ -29,6 +29,16 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi return this; } + static async retrieve(): Promise { + return await BaseResource._fetch({ + path: '/me/organization_creation_defaults', + method: 'GET', + }).then(res => { + const data = res?.response as unknown as OrganizationCreationDefaultsJSON; + return new OrganizationCreationDefaults(data); + }); + } + public __internal_toSnapshot(): OrganizationCreationDefaultsJSONSnapshot { return { creation_advisory: this.creationAdvisory diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 5edd6618118..bea87be2cc8 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -877,6 +877,10 @@ export const enUS: LocalizationResource = { actionLink: 'Sign out', actionText: 'Signed in as {{identifier}}', }, + alerts: { + existingOrgWithDomain: + 'An organization already exists for the detected company name and email domain. Join by invitation.', + }, }, taskResetPassword: { formButtonPrimary: 'Reset Password', diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index a8f3f980653..70717140c87 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1307,6 +1307,9 @@ export type __internal_LocalizationResource = { title: LocalizationValue; subtitle: LocalizationValue; }; + alerts: { + existingOrgWithDomain: LocalizationValue; + }; }; taskResetPassword: { title: LocalizationValue; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx index 2cf177d11a4..75d7c0baa07 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx @@ -4,6 +4,7 @@ import type { CreateOrganizationParams } from '@clerk/shared/types'; import { useEnvironment } from '@/ui/contexts'; import { useSessionTasksContext, useTaskChooseOrganizationContext } from '@/ui/contexts/components/SessionTasks'; import { localizationKeys } from '@/ui/customizables'; +// or from '@/ui/elements' import { useCardState } from '@/ui/elements/contexts'; import { Form } from '@/ui/elements/Form'; import { FormButtonContainer } from '@/ui/elements/FormButtons'; @@ -14,6 +15,17 @@ import { handleError } from '@/ui/utils/errorHandler'; import { useFormControl } from '@/ui/utils/useFormControl'; import { organizationListParams } from '../../../OrganizationSwitcher/utils'; +import { OrganizationCreationDefaultsAlert } from './OrganizationCreationDefaultsAlert'; + +// TODO: Replace with actual API call to OrganizationCreationDefaults.retrieve() +const organizationCreationDefaults = { + creationAdvisory: { + type: 'existing_org_with_domain' as const, + severity: 'warning' as const, + }, + pathRoot: '', + reload: () => Promise.resolve({} as any), +}; type CreateOrganizationScreenProps = { onCancel?: () => void; @@ -88,7 +100,9 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = + ({ padding: `${t.space.$none} ${t.space.$10} ${t.space.$8}` })}> + + ); +} + +const advisoryTypeToLocalizationKey: Record = { + existing_org_with_domain: localizationKeys('taskChooseOrganization.alerts.existingOrgWithDomain'), +}; From 986368f50f9449b7de7ded34447a2fc95d0ae824 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:23:03 -0300 Subject: [PATCH 04/10] Update entity to include form defaults --- .../resources/OrganizationCreationDefaults.ts | 30 ++++++++++++++----- .../src/types/organizationCreationDefaults.ts | 12 ++++++-- .../CreateOrganizationForm.tsx | 1 + .../CreateOrganizationScreen.tsx | 3 +- .../OrganizationCreationDefaultsAlert.tsx | 6 ++-- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts index 16ac507e142..f872602acb5 100644 --- a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts +++ b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts @@ -1,4 +1,6 @@ import type { + OrganizationCreationAdvisorySeverity, + OrganizationCreationAdvisoryType, OrganizationCreationDefaultsJSON, OrganizationCreationDefaultsJSONSnapshot, OrganizationCreationDefaultsResource, @@ -7,10 +9,17 @@ import type { import { BaseResource } from './internal'; export class OrganizationCreationDefaults extends BaseResource implements OrganizationCreationDefaultsResource { - creationAdvisory: { - type: 'existing_org_with_domain'; - severity: 'warning'; + advisory: { + type: OrganizationCreationAdvisoryType; + severity: OrganizationCreationAdvisorySeverity; } | null = null; + form: { + name: string; + slug: string; + } = { + name: '', + slug: '', + }; public constructor(data: OrganizationCreationDefaultsJSON | OrganizationCreationDefaultsJSONSnapshot | null = null) { super(); @@ -22,8 +31,13 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi return this; } - if (data.creation_advisory) { - this.creationAdvisory = this.withDefault(data.creation_advisory, this.creationAdvisory ?? null); + if (data.advisory) { + this.advisory = this.withDefault(data.advisory, this.advisory ?? null); + } + + if (data.form) { + this.form.name = this.withDefault(data.form.name, this.form.name); + this.form.slug = this.withDefault(data.form.slug, this.form.slug); } return this; @@ -41,10 +55,10 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi public __internal_toSnapshot(): OrganizationCreationDefaultsJSONSnapshot { return { - creation_advisory: this.creationAdvisory + advisory: this.advisory ? { - type: this.creationAdvisory.type, - severity: this.creationAdvisory.severity, + type: this.advisory.type, + severity: this.advisory.severity, } : null, } as unknown as OrganizationCreationDefaultsJSONSnapshot; diff --git a/packages/shared/src/types/organizationCreationDefaults.ts b/packages/shared/src/types/organizationCreationDefaults.ts index e510af241f8..badc42917d0 100644 --- a/packages/shared/src/types/organizationCreationDefaults.ts +++ b/packages/shared/src/types/organizationCreationDefaults.ts @@ -6,15 +6,23 @@ export type OrganizationCreationAdvisoryType = 'existing_org_with_domain'; export type OrganizationCreationAdvisorySeverity = 'warning'; export interface OrganizationCreationDefaultsJSON extends ClerkResourceJSON { - creation_advisory: { + advisory: { type: OrganizationCreationAdvisoryType; severity: OrganizationCreationAdvisorySeverity; } | null; + form: { + name: string; + slug: string; + }; } export interface OrganizationCreationDefaultsResource extends ClerkResource { - creationAdvisory: { + advisory: { type: OrganizationCreationAdvisoryType; severity: OrganizationCreationAdvisorySeverity; } | null; + form: { + name: string; + slug: string; + }; } diff --git a/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx b/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx index 0bc7f29a13d..f206a91f31d 100644 --- a/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx +++ b/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx @@ -36,6 +36,7 @@ type CreateOrganizationFormProps = { }; }; +// TODO -> Prefill form with organization creation defaults export const CreateOrganizationForm = withCardStateProvider((props: CreateOrganizationFormProps) => { const card = useCardState(); const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx index 75d7c0baa07..35ba1a75a56 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx @@ -4,7 +4,6 @@ import type { CreateOrganizationParams } from '@clerk/shared/types'; import { useEnvironment } from '@/ui/contexts'; import { useSessionTasksContext, useTaskChooseOrganizationContext } from '@/ui/contexts/components/SessionTasks'; import { localizationKeys } from '@/ui/customizables'; -// or from '@/ui/elements' import { useCardState } from '@/ui/elements/contexts'; import { Form } from '@/ui/elements/Form'; import { FormButtonContainer } from '@/ui/elements/FormButtons'; @@ -19,7 +18,7 @@ import { OrganizationCreationDefaultsAlert } from './OrganizationCreationDefault // TODO: Replace with actual API call to OrganizationCreationDefaults.retrieve() const organizationCreationDefaults = { - creationAdvisory: { + advisory: { type: 'existing_org_with_domain' as const, severity: 'warning' as const, }, diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx index 067f8c1b28d..eb88d8ffcb0 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx @@ -1,21 +1,21 @@ import type { OrganizationCreationAdvisoryType, OrganizationCreationDefaultsResource } from '@clerk/shared/types'; import { type LocalizationKey, localizationKeys } from '@/localization'; -import { Alert } from '@/ui/elements/Alert'; // or from '@/ui/elements' +import { Alert } from '@/ui/elements/Alert'; export function OrganizationCreationDefaultsAlert({ organizationCreationDefaults, }: { organizationCreationDefaults: OrganizationCreationDefaultsResource; }) { - if (!organizationCreationDefaults.creationAdvisory) { + if (!organizationCreationDefaults.advisory) { return null; } return ( ); } From ea2299172397cef2e8f784efdf5a0204e41dfa6e Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:41:05 -0300 Subject: [PATCH 05/10] Render logo field --- .../resources/OrganizationCreationDefaults.ts | 3 + .../src/types/organizationCreationDefaults.ts | 2 + .../CreateOrganizationScreen.tsx | 60 ++++++++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts index f872602acb5..460b63751ed 100644 --- a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts +++ b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts @@ -16,9 +16,11 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi form: { name: string; slug: string; + logo: string | null; } = { name: '', slug: '', + logo: null, }; public constructor(data: OrganizationCreationDefaultsJSON | OrganizationCreationDefaultsJSONSnapshot | null = null) { @@ -38,6 +40,7 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi if (data.form) { this.form.name = this.withDefault(data.form.name, this.form.name); this.form.slug = this.withDefault(data.form.slug, this.form.slug); + this.form.logo = this.withDefault(data.form.logo, this.form.logo); } return this; diff --git a/packages/shared/src/types/organizationCreationDefaults.ts b/packages/shared/src/types/organizationCreationDefaults.ts index badc42917d0..4b3cd280f19 100644 --- a/packages/shared/src/types/organizationCreationDefaults.ts +++ b/packages/shared/src/types/organizationCreationDefaults.ts @@ -13,6 +13,7 @@ export interface OrganizationCreationDefaultsJSON extends ClerkResourceJSON { form: { name: string; slug: string; + logo: string | null; }; } @@ -24,5 +25,6 @@ export interface OrganizationCreationDefaultsResource extends ClerkResource { form: { name: string; slug: string; + logo: string | null; }; } diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx index 35ba1a75a56..1c0d19fc826 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx @@ -1,27 +1,37 @@ import { useOrganizationList } from '@clerk/shared/react'; import type { CreateOrganizationParams } from '@clerk/shared/types'; +import React from 'react'; import { useEnvironment } from '@/ui/contexts'; import { useSessionTasksContext, useTaskChooseOrganizationContext } from '@/ui/contexts/components/SessionTasks'; -import { localizationKeys } from '@/ui/customizables'; +import { Icon, localizationKeys } from '@/ui/customizables'; import { useCardState } from '@/ui/elements/contexts'; import { Form } from '@/ui/elements/Form'; import { FormButtonContainer } from '@/ui/elements/FormButtons'; import { FormContainer } from '@/ui/elements/FormContainer'; import { Header } from '@/ui/elements/Header'; +import { IconButton } from '@/ui/elements/IconButton'; +import { Upload } from '@/ui/icons'; import { createSlug } from '@/ui/utils/createSlug'; import { handleError } from '@/ui/utils/errorHandler'; import { useFormControl } from '@/ui/utils/useFormControl'; +import { OrganizationProfileAvatarUploader } from '../../../OrganizationProfile/OrganizationProfileAvatarUploader'; import { organizationListParams } from '../../../OrganizationSwitcher/utils'; import { OrganizationCreationDefaultsAlert } from './OrganizationCreationDefaultsAlert'; // TODO: Replace with actual API call to OrganizationCreationDefaults.retrieve() +// TODO - Only replace if .organization_settings.organization_creation_defaults.enabled const organizationCreationDefaults = { advisory: { type: 'existing_org_with_domain' as const, severity: 'warning' as const, }, + form: { + name: '', + slug: '', + logo: null, + }, pathRoot: '', reload: () => Promise.resolve({} as any), }; @@ -38,6 +48,7 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = userMemberships: organizationListParams.userMemberships, }); const { organizationSettings } = useEnvironment(); + const [file, setFile] = React.useState(); const nameField = useFormControl('name', '', { type: 'text', @@ -68,6 +79,10 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = const organization = await createOrganization(createOrgParams); + if (file) { + await organization.setLogo({ file }); + } + await setActive({ organization, navigate: async ({ session }) => { @@ -88,6 +103,11 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = slugField.setValue(val); }; + const onAvatarRemove = () => { + card.setIdle(); + return setFile(null); + }; + const isSubmitButtonDisabled = !nameField.value || !isLoaded; return ( @@ -101,8 +121,44 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = ({ padding: `${t.space.$none} ${t.space.$10} ${t.space.$8}` })}> - + + await setFile(file)} + onAvatarRemove={file ? onAvatarRemove : null} + avatarPreviewPlaceholder={ + ({ + color: t.colors.$colorMutedForeground, + transitionDuration: t.transitionDuration.$controls, + })} + /> + } + sx={t => ({ + width: t.sizes.$16, + height: t.sizes.$16, + borderRadius: t.radii.$md, + borderWidth: t.borderWidths.$normal, + borderStyle: t.borderStyles.$dashed, + borderColor: t.colors.$borderAlpha200, + backgroundColor: t.colors.$neutralAlpha50, + ':hover': { + backgroundColor: t.colors.$neutralAlpha50, + svg: { + transform: 'scale(1.2)', + }, + }, + })} + /> + } + /> Date: Wed, 17 Dec 2025 14:51:54 -0300 Subject: [PATCH 06/10] Add `organizationCreationDefaults` to environment resource --- .../src/core/resources/OrganizationSettings.ts | 12 ++++++++++++ packages/shared/src/types/organizationSettings.ts | 6 ++++++ .../CreateOrganizationScreen.tsx | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/OrganizationSettings.ts b/packages/clerk-js/src/core/resources/OrganizationSettings.ts index 8960b347d62..a9fa5873d49 100644 --- a/packages/clerk-js/src/core/resources/OrganizationSettings.ts +++ b/packages/clerk-js/src/core/resources/OrganizationSettings.ts @@ -23,6 +23,11 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe } = { disabled: false, }; + organizationCreationDefaults: { + enabled: boolean; + } = { + enabled: false, + }; enabled: boolean = false; maxAllowedMemberships: number = 1; forceOrganizationSelection!: boolean; @@ -51,6 +56,13 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe this.slug.disabled = this.withDefault(data.slug.disabled, this.slug.disabled); } + if (data.organization_creation_defaults) { + this.organizationCreationDefaults.enabled = this.withDefault( + data.organization_creation_defaults.enabled, + this.organizationCreationDefaults.enabled, + ); + } + this.enabled = this.withDefault(data.enabled, this.enabled); this.maxAllowedMemberships = this.withDefault(data.max_allowed_memberships, this.maxAllowedMemberships); this.forceOrganizationSelection = this.withDefault( diff --git a/packages/shared/src/types/organizationSettings.ts b/packages/shared/src/types/organizationSettings.ts index ab9e0704e1e..e9a24b8e0f0 100644 --- a/packages/shared/src/types/organizationSettings.ts +++ b/packages/shared/src/types/organizationSettings.ts @@ -20,6 +20,9 @@ export interface OrganizationSettingsJSON extends ClerkResourceJSON { slug: { disabled: boolean; }; + organization_creation_defaults: { + enabled: boolean; + }; } export interface OrganizationSettingsResource extends ClerkResource { @@ -37,5 +40,8 @@ export interface OrganizationSettingsResource extends ClerkResource { slug: { disabled: boolean; }; + organizationCreationDefaults: { + enabled: boolean; + }; __internal_toSnapshot: () => OrganizationSettingsJSONSnapshot; } diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx index 1c0d19fc826..c5e7b1a1175 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx @@ -1,6 +1,6 @@ import { useOrganizationList } from '@clerk/shared/react'; import type { CreateOrganizationParams } from '@clerk/shared/types'; -import React from 'react'; +import { useState } from 'react'; import { useEnvironment } from '@/ui/contexts'; import { useSessionTasksContext, useTaskChooseOrganizationContext } from '@/ui/contexts/components/SessionTasks'; @@ -48,7 +48,7 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = userMemberships: organizationListParams.userMemberships, }); const { organizationSettings } = useEnvironment(); - const [file, setFile] = React.useState(); + const [file, setFile] = useState(); const nameField = useFormControl('name', '', { type: 'text', From c5fcb3968ada894c2db0c8f3e2b59b3eac019f89 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 18 Dec 2025 17:22:15 -0300 Subject: [PATCH 07/10] Prefill with org defaults --- .../src/core/resources/Environment.ts | 2 + packages/clerk-js/src/core/resources/User.ts | 3 ++ packages/shared/src/types/user.ts | 2 + .../CreateOrganizationScreen.tsx | 35 ++++++---------- .../OrganizationCreationDefaultsAlert.tsx | 19 +++++---- .../__tests__/TaskChooseOrganization.test.tsx | 10 ++++- .../tasks/TaskChooseOrganization/index.tsx | 40 +++++++++++-------- packages/ui/src/elements/AvatarUploader.tsx | 3 +- 8 files changed, 65 insertions(+), 49 deletions(-) diff --git a/packages/clerk-js/src/core/resources/Environment.ts b/packages/clerk-js/src/core/resources/Environment.ts index 5e961e3f354..53a3404bc6f 100644 --- a/packages/clerk-js/src/core/resources/Environment.ts +++ b/packages/clerk-js/src/core/resources/Environment.ts @@ -16,6 +16,8 @@ import { APIKeySettings } from './APIKeySettings'; import { AuthConfig, BaseResource, CommerceSettings, DisplayConfig, ProtectConfig, UserSettings } from './internal'; import { OrganizationSettings } from './OrganizationSettings'; +// TODO -> Update with new flag for default orgs +// Use it to conditionally trigger query export class Environment extends BaseResource implements EnvironmentResource { private static instance: Environment; diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index ae7ef203c63..079b7ae2f75 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -53,6 +53,7 @@ import { UserOrganizationInvitation, Web3Wallet, } from './internal'; +import { OrganizationCreationDefaults } from './OrganizationCreationDefaults'; export class User extends BaseResource implements UserResource { pathRoot = '/me'; @@ -275,6 +276,8 @@ export class User extends BaseResource implements UserResource { getOrganizationMemberships: GetOrganizationMemberships = retrieveMembership => OrganizationMembership.retrieve(retrieveMembership); + getOrganizationCreationDefaults = () => OrganizationCreationDefaults.retrieve(); + leaveOrganization = async (organizationId: string): Promise => { const json = ( await BaseResource._fetch({ diff --git a/packages/shared/src/types/user.ts b/packages/shared/src/types/user.ts index 93d7a9ef4a4..ac1c40b2fbd 100644 --- a/packages/shared/src/types/user.ts +++ b/packages/shared/src/types/user.ts @@ -7,6 +7,7 @@ import type { ExternalAccountResource } from './externalAccount'; import type { ImageResource } from './image'; import type { UserJSON } from './json'; import type { OAuthScope } from './oauth'; +import type { OrganizationCreationDefaultsResource } from './organizationCreationDefaults'; import type { OrganizationInvitationStatus } from './organizationInvitation'; import type { OrganizationMembershipResource } from './organizationMembership'; import type { OrganizationSuggestionResource, OrganizationSuggestionStatus } from './organizationSuggestion'; @@ -115,6 +116,7 @@ export interface UserResource extends ClerkResource, BillingPayerMethods { getOrganizationSuggestions: ( params?: GetUserOrganizationSuggestionsParams, ) => Promise>; + getOrganizationCreationDefaults: () => Promise; leaveOrganization: (organizationId: string) => Promise; createTOTP: () => Promise; verifyTOTP: (params: VerifyTOTPParams) => Promise; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx index c5e7b1a1175..e0f3ee05eed 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx @@ -1,5 +1,5 @@ import { useOrganizationList } from '@clerk/shared/react'; -import type { CreateOrganizationParams } from '@clerk/shared/types'; +import type { CreateOrganizationParams, OrganizationCreationDefaultsResource } from '@clerk/shared/types'; import { useState } from 'react'; import { useEnvironment } from '@/ui/contexts'; @@ -20,24 +20,9 @@ import { OrganizationProfileAvatarUploader } from '../../../OrganizationProfile/ import { organizationListParams } from '../../../OrganizationSwitcher/utils'; import { OrganizationCreationDefaultsAlert } from './OrganizationCreationDefaultsAlert'; -// TODO: Replace with actual API call to OrganizationCreationDefaults.retrieve() -// TODO - Only replace if .organization_settings.organization_creation_defaults.enabled -const organizationCreationDefaults = { - advisory: { - type: 'existing_org_with_domain' as const, - severity: 'warning' as const, - }, - form: { - name: '', - slug: '', - logo: null, - }, - pathRoot: '', - reload: () => Promise.resolve({} as any), -}; - type CreateOrganizationScreenProps = { onCancel?: () => void; + organizationCreationDefaults?: OrganizationCreationDefaultsResource; }; export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) => { @@ -50,12 +35,12 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = const { organizationSettings } = useEnvironment(); const [file, setFile] = useState(); - const nameField = useFormControl('name', '', { + const nameField = useFormControl('name', props.organizationCreationDefaults?.form.name ?? '', { type: 'text', label: localizationKeys('taskChooseOrganization.createOrganization.formFieldLabel__name'), placeholder: localizationKeys('taskChooseOrganization.createOrganization.formFieldInputPlaceholder__name'), }); - const slugField = useFormControl('slug', '', { + const slugField = useFormControl('slug', props.organizationCreationDefaults?.form.slug ?? '', { type: 'text', label: localizationKeys('taskChooseOrganization.createOrganization.formFieldLabel__slug'), placeholder: localizationKeys('taskChooseOrganization.createOrganization.formFieldInputPlaceholder__slug'), @@ -81,6 +66,11 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = if (file) { await organization.setLogo({ file }); + } else if (defaultLogoUrl) { + const response = await fetch(defaultLogoUrl); + const blob = await response.blob(); + const logoFile = new File([blob], 'logo', { type: blob.type }); + await organization.setLogo({ file: logoFile }); } await setActive({ @@ -109,6 +99,7 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = }; const isSubmitButtonDisabled = !nameField.value || !isLoaded; + const defaultLogoUrl = file === undefined ? props.organizationCreationDefaults?.form.logo : undefined; return ( <> @@ -122,11 +113,11 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = ({ padding: `${t.space.$none} ${t.space.$10} ${t.space.$8}` })}> - + await setFile(file)} - onAvatarRemove={file ? onAvatarRemove : null} + onAvatarRemove={file || defaultLogoUrl ? onAvatarRemove : null} avatarPreviewPlaceholder={ + + + ); } +// TODO -> Update with latest advisory where meta is returned +// TODO -> Include email domain in message, eg: {{ meta }} const advisoryTypeToLocalizationKey: Record = { existing_org_with_domain: localizationKeys('taskChooseOrganization.alerts.existingOrgWithDomain'), }; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx index caefb108e76..87bd5c6fcf3 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx @@ -299,8 +299,14 @@ describe('TaskChooseOrganization', () => { }); describe('with organization creation defaults', () => { - it.todo('displays warning when organization already exists for user email domain'); + describe('when enabled on environment', () => { + it.todo('displays warning when organization already exists for user email domain'); - it.todo('prefills create organization form with defaults'); + it.todo('prefills create organization form with defaults'); + }); + + describe('when disabled on environment', () => { + it.todo('does not fetch for creation defaults'); + }); }); }); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx index 3275a184707..668fb3ae220 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx @@ -1,7 +1,9 @@ import { useClerk, useSession, useUser } from '@clerk/shared/react'; -import { useState, type ComponentType } from 'react'; +import type { OrganizationCreationDefaultsResource } from '@clerk/shared/types'; +import { type ComponentType, useState } from 'react'; -import { useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts'; +import { useFetch } from '@/hooks'; +import { useEnvironment, useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts'; import { descriptors, Flex, Flow, localizationKeys, Spinner } from '@/ui/customizables'; import { Card } from '@/ui/elements/Card'; import { withCardStateProvider } from '@/ui/elements/contexts'; @@ -14,24 +16,23 @@ import { ChooseOrganizationScreen } from './ChooseOrganizationScreen'; import { CreateOrganizationScreen } from './CreateOrganizationScreen'; const TaskChooseOrganizationInternal = () => { - const { signOut } = useClerk(); const { user } = useUser(); - const { session } = useSession(); + const { environment } = useEnvironment(); const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView(); - const { otherSessions } = useMultipleSessions({ user }); - const { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl } = useSignOutContext(); - - const handleSignOut = () => { - if (otherSessions.length === 0) { - return signOut(navigateAfterSignOut); - } - - return signOut(navigateAfterMultiSessionSingleSignOutUrl, { sessionId: session?.id }); - }; + const organizationCreationDefaults = useFetch( + environment.organizationSettings.organizationCreationDefaults.enabled + ? user?.getOrganizationCreationDefaults + : undefined, + 'organization-creation-defaults', + { staleTime: Infinity }, + ); - const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading; + const isLoading = + userMemberships?.isLoading || + userInvitations?.isLoading || + userSuggestions?.isLoading || + organizationCreationDefaults.isLoading; const hasExistingResources = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count); - const identifier = user?.primaryEmailAddress?.emailAddress ?? user?.username; return ( @@ -55,7 +56,10 @@ const TaskChooseOrganizationInternal = () => { /> ) : ( - + )} @@ -111,6 +115,7 @@ const TaskChooseOrganizationCardFooter = () => { type TaskChooseOrganizationFlowsProps = { initialFlow: 'create' | 'choose'; + organizationCreationDefaults?: OrganizationCreationDefaultsResource; }; const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrganizationFlowsProps) => { @@ -120,6 +125,7 @@ const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrga return ( setCurrentFlow('choose') : undefined} + organizationCreationDefaults={props.organizationCreationDefaults} /> ); } diff --git a/packages/ui/src/elements/AvatarUploader.tsx b/packages/ui/src/elements/AvatarUploader.tsx index f1ae367f2c2..8a7f99b7a0d 100644 --- a/packages/ui/src/elements/AvatarUploader.tsx +++ b/packages/ui/src/elements/AvatarUploader.tsx @@ -90,9 +90,10 @@ export const AvatarUploader = (props: AvatarUploaderProps) => { await handleFileDrop(f); }; + const hasExistingImage = !!(avatarPreview.props as { imageUrl?: string })?.imageUrl; const previewElement = objectUrl ? React.cloneElement(avatarPreview, { imageUrl: objectUrl }) - : avatarPreviewPlaceholder + : avatarPreviewPlaceholder && !hasExistingImage ? React.cloneElement(avatarPreviewPlaceholder, { onClick: openDialog }) : avatarPreview; From 41e096f5d9c8980b11183dbfa31bdd0b84855cda Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:03:41 -0300 Subject: [PATCH 08/10] Conditionally fetch for defaults --- packages/clerk-js/src/core/resources/Environment.ts | 2 -- .../CreateOrganization/CreateOrganizationForm.tsx | 1 - .../SessionTasks/tasks/TaskChooseOrganization/index.tsx | 6 ++---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/clerk-js/src/core/resources/Environment.ts b/packages/clerk-js/src/core/resources/Environment.ts index 53a3404bc6f..5e961e3f354 100644 --- a/packages/clerk-js/src/core/resources/Environment.ts +++ b/packages/clerk-js/src/core/resources/Environment.ts @@ -16,8 +16,6 @@ import { APIKeySettings } from './APIKeySettings'; import { AuthConfig, BaseResource, CommerceSettings, DisplayConfig, ProtectConfig, UserSettings } from './internal'; import { OrganizationSettings } from './OrganizationSettings'; -// TODO -> Update with new flag for default orgs -// Use it to conditionally trigger query export class Environment extends BaseResource implements EnvironmentResource { private static instance: Environment; diff --git a/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx b/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx index f206a91f31d..0bc7f29a13d 100644 --- a/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx +++ b/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx @@ -36,7 +36,6 @@ type CreateOrganizationFormProps = { }; }; -// TODO -> Prefill form with organization creation defaults export const CreateOrganizationForm = withCardStateProvider((props: CreateOrganizationFormProps) => { const card = useCardState(); const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx index 668fb3ae220..0b6873c4e72 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx @@ -17,12 +17,10 @@ import { CreateOrganizationScreen } from './CreateOrganizationScreen'; const TaskChooseOrganizationInternal = () => { const { user } = useUser(); - const { environment } = useEnvironment(); + const { organizationSettings } = useEnvironment(); const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView(); const organizationCreationDefaults = useFetch( - environment.organizationSettings.organizationCreationDefaults.enabled - ? user?.getOrganizationCreationDefaults - : undefined, + organizationSettings.organizationCreationDefaults?.enabled ? user?.getOrganizationCreationDefaults : undefined, 'organization-creation-defaults', { staleTime: Infinity }, ); From 33bc7bab6c4a9b43c2f1eae1e5d34538818b10e4 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:12:19 -0300 Subject: [PATCH 09/10] Include meta into advisory message --- .../resources/OrganizationCreationDefaults.ts | 6 +++-- packages/localizations/src/en-US.ts | 2 +- packages/shared/src/types/localization.ts | 2 +- .../src/types/organizationCreationDefaults.ts | 6 +++-- .../OrganizationCreationDefaultsAlert.tsx | 26 +++++++++++++------ .../tasks/TaskChooseOrganization/index.tsx | 2 +- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts index 460b63751ed..865d9085f3a 100644 --- a/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts +++ b/packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts @@ -10,8 +10,9 @@ import { BaseResource } from './internal'; export class OrganizationCreationDefaults extends BaseResource implements OrganizationCreationDefaultsResource { advisory: { - type: OrganizationCreationAdvisoryType; + code: OrganizationCreationAdvisoryType; severity: OrganizationCreationAdvisorySeverity; + meta: Record; } | null = null; form: { name: string; @@ -60,7 +61,8 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi return { advisory: this.advisory ? { - type: this.advisory.type, + code: this.advisory.code, + meta: this.advisory.meta, severity: this.advisory.severity, } : null, diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index bea87be2cc8..cca24bd22ef 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -879,7 +879,7 @@ export const enUS: LocalizationResource = { }, alerts: { existingOrgWithDomain: - 'An organization already exists for the detected company name and email domain. Join by invitation.', + 'An organization already exists for the detected company name and {{email}}. Join by invitation.', }, }, taskResetPassword: { diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 70717140c87..e3322a40d4a 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1308,7 +1308,7 @@ export type __internal_LocalizationResource = { subtitle: LocalizationValue; }; alerts: { - existingOrgWithDomain: LocalizationValue; + existingOrgWithDomain: LocalizationValue<'email'>; }; }; taskResetPassword: { diff --git a/packages/shared/src/types/organizationCreationDefaults.ts b/packages/shared/src/types/organizationCreationDefaults.ts index 4b3cd280f19..5c3fdb2a24d 100644 --- a/packages/shared/src/types/organizationCreationDefaults.ts +++ b/packages/shared/src/types/organizationCreationDefaults.ts @@ -7,8 +7,9 @@ export type OrganizationCreationAdvisorySeverity = 'warning'; export interface OrganizationCreationDefaultsJSON extends ClerkResourceJSON { advisory: { - type: OrganizationCreationAdvisoryType; + code: OrganizationCreationAdvisoryType; severity: OrganizationCreationAdvisorySeverity; + meta: Record; } | null; form: { name: string; @@ -19,8 +20,9 @@ export interface OrganizationCreationDefaultsJSON extends ClerkResourceJSON { export interface OrganizationCreationDefaultsResource extends ClerkResource { advisory: { - type: OrganizationCreationAdvisoryType; + code: OrganizationCreationAdvisoryType; severity: OrganizationCreationAdvisorySeverity; + meta: Record; } | null; form: { name: string; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx index 9e15365aaf0..7cca5d332ba 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx @@ -1,14 +1,15 @@ -import type { OrganizationCreationAdvisoryType, OrganizationCreationDefaultsResource } from '@clerk/shared/types'; +import type { OrganizationCreationDefaultsResource } from '@clerk/shared/types'; import { Alert, Text } from '@/customizables'; -import { type LocalizationKey, localizationKeys } from '@/localization'; +import { localizationKeys } from '@/localization'; export function OrganizationCreationDefaultsAlert({ organizationCreationDefaults, }: { organizationCreationDefaults?: OrganizationCreationDefaultsResource; }) { - if (!organizationCreationDefaults?.advisory) { + const localizationKey = advisoryToLocalizationKey(organizationCreationDefaults?.advisory); + if (!localizationKey) { return null; } @@ -16,15 +17,24 @@ export function OrganizationCreationDefaultsAlert({ ); } -// TODO -> Update with latest advisory where meta is returned -// TODO -> Include email domain in message, eg: {{ meta }} -const advisoryTypeToLocalizationKey: Record = { - existing_org_with_domain: localizationKeys('taskChooseOrganization.alerts.existingOrgWithDomain'), +const advisoryToLocalizationKey = (advisory?: OrganizationCreationDefaultsResource['advisory']) => { + if (!advisory) { + return null; + } + + switch (advisory.code) { + case 'existing_org_with_domain': + return localizationKeys('taskChooseOrganization.alerts.existingOrgWithDomain', { + email: advisory.meta.email, + }); + default: + return null; + } }; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx index 0b6873c4e72..cbe216914d1 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx @@ -113,7 +113,7 @@ const TaskChooseOrganizationCardFooter = () => { type TaskChooseOrganizationFlowsProps = { initialFlow: 'create' | 'choose'; - organizationCreationDefaults?: OrganizationCreationDefaultsResource; + organizationCreationDefaults?: OrganizationCreationDefaultsResource | null; }; const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrganizationFlowsProps) => { From 22edba6ce46e5da67e9c6b5171f3042e6ed25459 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:25:24 -0300 Subject: [PATCH 10/10] Implement tests --- packages/clerk-js/src/test/fixture-helpers.ts | 4 + .../__tests__/TaskChooseOrganization.test.tsx | 74 ++++++++++++++++++- .../tasks/TaskChooseOrganization/index.tsx | 1 - 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/packages/clerk-js/src/test/fixture-helpers.ts b/packages/clerk-js/src/test/fixture-helpers.ts index 86547dae2c0..b1564bcd841 100644 --- a/packages/clerk-js/src/test/fixture-helpers.ts +++ b/packages/clerk-js/src/test/fixture-helpers.ts @@ -344,6 +344,9 @@ const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) const withOrganizationSlug = (enabled = false) => { os.slug.disabled = !enabled; }; + const withOrganizationCreationDefaults = (enabled = false) => { + os.organization_creation_defaults.enabled = enabled; + }; const withOrganizationDomains = (modes?: OrganizationEnrollmentMode[], defaultRole?: string) => { os.domains.enabled = true; @@ -356,6 +359,7 @@ const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) withOrganizationDomains, withForceOrganizationSelection, withOrganizationSlug, + withOrganizationCreationDefaults, }; }; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx index 87bd5c6fcf3..93f5d7a30dc 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx @@ -300,13 +300,81 @@ describe('TaskChooseOrganization', () => { describe('with organization creation defaults', () => { describe('when enabled on environment', () => { - it.todo('displays warning when organization already exists for user email domain'); + it('displays warning when organization already exists for user email domain', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withForceOrganizationSelection(); + f.withOrganizationCreationDefaults(true); + f.withUser({ + email_addresses: ['test@clerk.com'], + create_organization_enabled: true, + tasks: [{ key: 'choose-organization' }], + }); + }); + + fixtures.clerk.user?.getOrganizationCreationDefaults.mockReturnValueOnce( + Promise.resolve({ + advisory: { + code: 'existing_org_with_domain', + severity: 'warning', + meta: { email: 'test@clerk.com' }, + }, + }), + ); + + const { findByText } = render(, { wrapper }); + + expect( + await findByText(/an organization already exists for the detected company name and test@clerk\.com/i), + ).toBeInTheDocument(); + }); + + it('prefills create organization form with defaults', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withForceOrganizationSelection(); + f.withOrganizationCreationDefaults(true); + f.withUser({ + email_addresses: ['test@clerk.com'], + create_organization_enabled: true, + tasks: [{ key: 'choose-organization' }], + }); + }); - it.todo('prefills create organization form with defaults'); + fixtures.clerk.user?.getOrganizationCreationDefaults.mockReturnValueOnce( + Promise.resolve({ + form: { + name: 'Test Org', + slug: 'test-org', + logo: null, + }, + }), + ); + + const { findByText } = render(, { wrapper }); + + expect(await findByText('Test Org')).toBeInTheDocument(); + expect(await findByText('test-org')).toBeInTheDocument(); + }); }); describe('when disabled on environment', () => { - it.todo('does not fetch for creation defaults'); + it('does not fetch for creation defaults', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withForceOrganizationSelection(); + f.withOrganizationCreationDefaults(false); + f.withUser({ + email_addresses: ['test@clerk.com'], + create_organization_enabled: true, + tasks: [{ key: 'choose-organization' }], + }); + }); + + render(, { wrapper }); + + expect(fixtures.clerk.user?.getOrganizationCreationDefaults).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx index cbe216914d1..4ce89f856f2 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx @@ -22,7 +22,6 @@ const TaskChooseOrganizationInternal = () => { const organizationCreationDefaults = useFetch( organizationSettings.organizationCreationDefaults?.enabled ? user?.getOrganizationCreationDefaults : undefined, 'organization-creation-defaults', - { staleTime: Infinity }, ); const isLoading =