diff --git a/.changeset/fuzzy-hotels-decide.md b/.changeset/fuzzy-hotels-decide.md
new file mode 100644
index 00000000000..26e45da6afa
--- /dev/null
+++ b/.changeset/fuzzy-hotels-decide.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Fix "You must belong to an organization" screen showing when user has existing memberships, invitations or suggestions
diff --git a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx
index 6942ae08806..c0ac1acc82a 100644
--- a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx
+++ b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx
@@ -33,6 +33,7 @@ type ChooseOrganizationScreenProps = {
export const ChooseOrganizationScreen = (props: ChooseOrganizationScreenProps) => {
const card = useCardState();
+ const { user } = useUser();
const { ref, userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
@@ -50,7 +51,13 @@ export const ChooseOrganizationScreen = (props: ChooseOrganizationScreenProps) =
sx={t => ({ padding: `${t.space.$none} ${t.space.$8}` })}
>
-
+
({ margin: `${t.space.$none} ${t.space.$8}` })}>{card.error}
diff --git a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx
index b0604a57582..f6daf9e1dcd 100644
--- a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx
+++ b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx
@@ -3,7 +3,10 @@ import { describe, expect, it } from 'vitest';
import { bindCreateFixtures } from '@/test/create-fixtures';
import { render } from '@/test/utils';
-import { createFakeUserOrganizationMembership } from '@/ui/components/OrganizationSwitcher/__tests__/test-utils';
+import {
+ createFakeUserOrganizationMembership,
+ createFakeUserOrganizationSuggestion,
+} from '@/ui/components/OrganizationSwitcher/__tests__/test-utils';
import { TaskChooseOrganization } from '..';
@@ -240,4 +243,75 @@ describe('TaskChooseOrganization', () => {
expect(queryByLabelText(/Slug/i)).toBeInTheDocument();
});
});
+
+ describe('when users are not allowed to create organizations', () => {
+ it('does not display create organization screen', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withOrganizations();
+ f.withForceOrganizationSelection();
+ f.withUser({
+ create_organization_enabled: false,
+ tasks: [{ key: 'choose-organization' }],
+ });
+ });
+
+ const { findByText, queryByText } = render(, { wrapper });
+
+ expect(await findByText(/you must belong to an organization/i)).toBeInTheDocument();
+ expect(await findByText(/contact your organization admin for an invitation/i)).toBeInTheDocument();
+ expect(queryByText(/create new organization/i)).not.toBeInTheDocument();
+ });
+
+ it('with existing memberships or suggestions, displays create organization screen', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.withOrganizations();
+ f.withForceOrganizationSelection();
+ f.withUser({
+ create_organization_enabled: false,
+ tasks: [{ key: 'choose-organization' }],
+ });
+ });
+
+ fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
+ Promise.resolve({
+ data: [
+ createFakeUserOrganizationMembership({
+ id: '1',
+ organization: {
+ id: '1',
+ name: 'Existing Org',
+ slug: 'org1',
+ membersCount: 1,
+ adminDeleteEnabled: false,
+ maxAllowedMemberships: 1,
+ pendingInvitationsCount: 1,
+ },
+ }),
+ ],
+ total_count: 1,
+ }),
+ );
+
+ fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
+ Promise.resolve({
+ data: [
+ createFakeUserOrganizationSuggestion({
+ id: '2',
+ emailAddress: 'two@clerk.com',
+ publicOrganizationData: {
+ name: 'OrgTwoSuggestion',
+ },
+ }),
+ ],
+ total_count: 1,
+ }),
+ );
+
+ const { findByText, queryByText } = render(, { wrapper });
+
+ expect(await findByText('Join an existing organization')).toBeInTheDocument();
+ expect(await queryByText('Create new organization')).not.toBeInTheDocument();
+ expect(await findByText('Existing Org')).toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx
index c42c6f6ef91..dc3375e0d0b 100644
--- a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx
+++ b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx
@@ -1,5 +1,5 @@
import { useClerk, useSession, useUser } from '@clerk/shared/react';
-import { type ComponentType, useState } from 'react';
+import { useState } from 'react';
import { useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts';
import { descriptors, Flex, Flow, localizationKeys, Spinner } from '@/ui/customizables';
@@ -14,10 +14,16 @@ import { ChooseOrganizationScreen } from './ChooseOrganizationScreen';
import { CreateOrganizationScreen } from './CreateOrganizationScreen';
const TaskChooseOrganizationInternal = () => {
+ const { user } = useUser();
const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
const hasExistingResources = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count);
+ const isOrganizationCreationDisabled = !isLoading && !user?.createOrganizationEnabled && !hasExistingResources;
+
+ if (isOrganizationCreationDisabled) {
+ return ;
+ }
return (
@@ -113,18 +119,6 @@ const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrga
return setCurrentFlow('create')} />;
});
-export const withOrganizationCreationEnabledGuard = (Component: ComponentType) => {
- return (props: T) => {
- const { user } = useUser();
-
- if (!user?.createOrganizationEnabled) {
- return ;
- }
-
- return ;
- };
-};
-
function OrganizationCreationDisabledScreen() {
return (
@@ -149,8 +143,5 @@ function OrganizationCreationDisabledScreen() {
}
export const TaskChooseOrganization = withCoreSessionSwitchGuard(
- withTaskGuard(
- withCardStateProvider(withOrganizationCreationEnabledGuard(TaskChooseOrganizationInternal)),
- 'choose-organization',
- ),
+ withTaskGuard(withCardStateProvider(TaskChooseOrganizationInternal), 'choose-organization'),
);
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index d2890bbdbb1..8e9c64c7b33 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -862,6 +862,7 @@ export const enUS: LocalizationResource = {
action__invitationAccept: 'Join',
action__suggestionsAccept: 'Request to join',
subtitle: 'Join an existing organization or create a new one',
+ subtitle__createOrganizationDisabled: 'Join an existing organization',
suggestionsAcceptedLabel: 'Pending approval',
title: 'Choose an organization',
},
diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts
index 9e789d9c8fa..806826143d8 100644
--- a/packages/shared/src/types/localization.ts
+++ b/packages/shared/src/types/localization.ts
@@ -1304,6 +1304,7 @@ export type __internal_LocalizationResource = {
chooseOrganization: {
title: LocalizationValue;
subtitle: LocalizationValue;
+ subtitle__createOrganizationDisabled: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__createOrganization: LocalizationValue;