-
Notifications
You must be signed in to change notification settings - Fork 408
feat(clerk-js,clerk-react): Introduce development modal to enable organizations #7159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
414db9f
e9ea329
c01e05c
da0ef4a
18d151f
6d2d46c
66e9f7d
820f9db
65b8905
aa64e19
7cf82a8
aba82d7
5a48d71
c26c4d5
189c3b7
8a1f192
bd81f0b
17891ea
4fdbcc9
f6362dd
22f576a
a988488
43bae8d
5e42490
56fbc71
5c1571c
f349e35
ad77be6
4ff0e54
864643f
938457e
f3b01ea
3f94b70
7c06473
cf9a27c
fe2d1ed
8ec969c
b3b3cee
4dfdabc
e1b1273
1ad837b
e67acbc
21aed04
04ceaa5
728ab6e
517f532
3e14e69
08a4cbd
4974e2e
a7cdb01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| '@clerk/clerk-js': minor | ||
| '@clerk/shared': minor | ||
| '@clerk/clerk-react': minor | ||
| '@clerk/vue': minor | ||
| --- | ||
|
|
||
| Introduce in-app development prompt to enable the Organizations feature | ||
|
|
||
| In development instances, when using organization components or hooks for the first time, developers will see a prompt to enable the Organizations feature directly in their app, eliminating the need to visit the Clerk Dashboard. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,9 @@ import { | |
| import type { | ||
| __experimental_CheckoutInstance, | ||
| __experimental_CheckoutOptions, | ||
| __internal_AttemptToEnableEnvironmentSettingParams, | ||
| __internal_CheckoutProps, | ||
| __internal_EnableOrganizationsPromptProps, | ||
| __internal_OAuthConsentProps, | ||
| __internal_PlanDetailsProps, | ||
| __internal_SubscriptionDetailsProps, | ||
|
|
@@ -745,6 +747,58 @@ export class Clerk implements ClerkInterface { | |
| void this.#componentControls.ensureMounted().then(controls => controls.closeModal('userVerification')); | ||
| }; | ||
|
|
||
| public __internal_attemptToEnableEnvironmentSetting = ( | ||
LauraBeatris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| params: __internal_AttemptToEnableEnvironmentSettingParams, | ||
| ): { status: 'enabled' | 'prompt-shown' } => { | ||
| const { for: setting, caller } = params; | ||
|
|
||
| if (!this.user) { | ||
| console.warn( | ||
| `Clerk: "${caller}" requires an active user session. Ensure a user is signed in before executing ${caller}.`, | ||
| ); | ||
| } | ||
|
|
||
| if ( | ||
| // If not in development instance, return enabled status in order to not open the prompt | ||
| this.#instanceType !== 'development' | ||
| ) { | ||
| return { status: 'enabled' }; | ||
| } | ||
|
|
||
| switch (setting) { | ||
| case 'organizations': | ||
| if (!disabledOrganizationsFeature(this, this.environment)) { | ||
| return { status: 'enabled' }; | ||
| } | ||
|
|
||
| this.__internal_openEnableOrganizationsPrompt({ | ||
| caller, | ||
| // Reload current window to all invalidate all resources | ||
| // related to organizations, eg: roles | ||
| onSuccess: () => window.location.reload(), | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's still some delay in which the window is reloading and the screen kepts showing a blank state without having the component mounted due to the stale environment data. I'm thinking of applying an optimistic update, at least on the "enabled" status, to have the components mounted meanwhile, or not closing the modal and introduce a loading status until the window gets reloaded. |
||
| onClose: params.onClose, | ||
| } as __internal_EnableOrganizationsPromptProps); | ||
|
|
||
| return { status: 'prompt-shown' }; | ||
| default: | ||
| return { status: 'enabled' }; | ||
| } | ||
| }; | ||
LauraBeatris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public __internal_openEnableOrganizationsPrompt = (props: __internal_EnableOrganizationsPromptProps): void => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| void this.#componentControls | ||
| .ensureMounted({ preloadHint: 'EnableOrganizationsPrompt' }) | ||
| .then(controls => controls.openModal('enableOrganizationsPrompt', props || {})); | ||
|
|
||
| this.telemetry?.record(eventPrebuiltComponentMounted('EnableOrganizationsPrompt', props)); | ||
| }; | ||
|
|
||
| public __internal_closeEnableOrganizationsPrompt = (): void => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| void this.#componentControls.ensureMounted().then(controls => controls.closeModal('enableOrganizationsPrompt')); | ||
| }; | ||
|
|
||
| public __internal_openBlankCaptchaModal = (): Promise<unknown> => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| return this.#componentControls | ||
|
|
@@ -816,14 +870,21 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| public openOrganizationProfile = (props?: OrganizationProfileProps): void => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'OrganizationProfile', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
| if (noOrganizationExists(this)) { | ||
| if (this.#instanceType === 'development') { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderComponentWhenOrgDoesNotExist, { | ||
|
|
@@ -846,14 +907,21 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| public openCreateOrganization = (props?: CreateOrganizationProps): void => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'CreateOrganization', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
| void this.#componentControls | ||
| .ensureMounted({ preloadHint: 'CreateOrganization' }) | ||
| .then(controls => controls.openModal('createOrganization', props || {})); | ||
|
|
@@ -988,14 +1056,21 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| public mountOrganizationProfile = (node: HTMLDivElement, props?: OrganizationProfileProps) => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'OrganizationProfile', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
| const userExists = !noUserExists(this); | ||
| if (noOrganizationExists(this) && userExists) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
@@ -1028,14 +1103,21 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| public mountCreateOrganization = (node: HTMLDivElement, props?: CreateOrganizationProps) => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'CreateOrganization', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
| void this.#componentControls?.ensureMounted({ preloadHint: 'CreateOrganization' }).then(controls => | ||
| controls.mountComponent({ | ||
| name: 'CreateOrganization', | ||
|
|
@@ -1059,14 +1141,21 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| public mountOrganizationSwitcher = (node: HTMLDivElement, props?: OrganizationSwitcherProps) => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'OrganizationSwitcher', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationSwitcher'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
| void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationSwitcher' }).then(controls => | ||
| controls.mountComponent({ | ||
| name: 'OrganizationSwitcher', | ||
|
|
@@ -1098,14 +1187,21 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| public mountOrganizationList = (node: HTMLDivElement, props?: OrganizationListProps) => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
|
|
||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'OrganizationList', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationList'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
| void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationList' }).then(controls => | ||
| controls.mountComponent({ | ||
| name: 'OrganizationList', | ||
|
|
@@ -1294,12 +1390,17 @@ export class Clerk implements ClerkInterface { | |
| public mountTaskChooseOrganization = (node: HTMLDivElement, props?: TaskChooseOrganizationProps) => { | ||
| this.assertComponentsReady(this.#componentControls); | ||
|
|
||
| if (disabledOrganizationsFeature(this, this.environment)) { | ||
| if (this.#instanceType === 'development') { | ||
| const { status } = this.__internal_attemptToEnableEnvironmentSetting({ | ||
| for: 'organizations', | ||
| caller: 'TaskChooseOrganization', | ||
| onClose: () => { | ||
| throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('TaskChooseOrganization'), { | ||
| code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| if (status === 'prompt-shown') { | ||
| return; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import type { ClerkResourceJSON, DevToolsResource, EnableEnvironmentSettingParams } from '@clerk/shared/types'; | ||
|
|
||
| import { BaseResource } from './Base'; | ||
|
|
||
| /** | ||
| * @internal | ||
| */ | ||
| export class DevTools extends BaseResource implements DevToolsResource { | ||
| pathRoot = '/dev_tools'; | ||
|
|
||
| protected fromJSON(_data: ClerkResourceJSON | null): this { | ||
| return this; | ||
| } | ||
|
|
||
| async __internal_enableEnvironmentSetting(params: EnableEnvironmentSettingParams) { | ||
| await this._basePatch({ | ||
| path: `${this.pathRoot}/enable_environment_setting`, | ||
| body: params, | ||
| }); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.