Skip to content

Commit c4dbcf7

Browse files
authored
fix(clerk-js): Hide "must belong to org" screen when org resources exist (#7557)
1 parent 6b26afc commit c4dbcf7

File tree

6 files changed

+98
-19
lines changed

6 files changed

+98
-19
lines changed

.changeset/fuzzy-hotels-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
---
4+
5+
Fix "You must belong to an organization" screen showing when user has existing memberships, invitations or suggestions

packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type ChooseOrganizationScreenProps = {
3333

3434
export const ChooseOrganizationScreen = (props: ChooseOrganizationScreenProps) => {
3535
const card = useCardState();
36+
const { user } = useUser();
3637
const { ref, userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
3738

3839
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
@@ -50,7 +51,13 @@ export const ChooseOrganizationScreen = (props: ChooseOrganizationScreenProps) =
5051
sx={t => ({ padding: `${t.space.$none} ${t.space.$8}` })}
5152
>
5253
<Header.Title localizationKey={localizationKeys('taskChooseOrganization.chooseOrganization.title')} />
53-
<Header.Subtitle localizationKey={localizationKeys('taskChooseOrganization.chooseOrganization.subtitle')} />
54+
<Header.Subtitle
55+
localizationKey={
56+
user?.createOrganizationEnabled
57+
? localizationKeys('taskChooseOrganization.chooseOrganization.subtitle')
58+
: localizationKeys('taskChooseOrganization.chooseOrganization.subtitle__createOrganizationDisabled')
59+
}
60+
/>
5461
</Header.Root>
5562
<Card.Alert sx={t => ({ margin: `${t.space.$none} ${t.space.$8}` })}>{card.error}</Card.Alert>
5663
<Col elementDescriptor={descriptors.main}>

packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { describe, expect, it } from 'vitest';
33

44
import { bindCreateFixtures } from '@/test/create-fixtures';
55
import { render } from '@/test/utils';
6-
import { createFakeUserOrganizationMembership } from '@/ui/components/OrganizationSwitcher/__tests__/test-utils';
6+
import {
7+
createFakeUserOrganizationMembership,
8+
createFakeUserOrganizationSuggestion,
9+
} from '@/ui/components/OrganizationSwitcher/__tests__/test-utils';
710

811
import { TaskChooseOrganization } from '..';
912

@@ -240,4 +243,75 @@ describe('TaskChooseOrganization', () => {
240243
expect(queryByLabelText(/Slug/i)).toBeInTheDocument();
241244
});
242245
});
246+
247+
describe('when users are not allowed to create organizations', () => {
248+
it('does not display create organization screen', async () => {
249+
const { wrapper } = await createFixtures(f => {
250+
f.withOrganizations();
251+
f.withForceOrganizationSelection();
252+
f.withUser({
253+
create_organization_enabled: false,
254+
tasks: [{ key: 'choose-organization' }],
255+
});
256+
});
257+
258+
const { findByText, queryByText } = render(<TaskChooseOrganization />, { wrapper });
259+
260+
expect(await findByText(/you must belong to an organization/i)).toBeInTheDocument();
261+
expect(await findByText(/contact your organization admin for an invitation/i)).toBeInTheDocument();
262+
expect(queryByText(/create new organization/i)).not.toBeInTheDocument();
263+
});
264+
265+
it('with existing memberships or suggestions, displays create organization screen', async () => {
266+
const { wrapper, fixtures } = await createFixtures(f => {
267+
f.withOrganizations();
268+
f.withForceOrganizationSelection();
269+
f.withUser({
270+
create_organization_enabled: false,
271+
tasks: [{ key: 'choose-organization' }],
272+
});
273+
});
274+
275+
fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
276+
Promise.resolve({
277+
data: [
278+
createFakeUserOrganizationMembership({
279+
id: '1',
280+
organization: {
281+
id: '1',
282+
name: 'Existing Org',
283+
slug: 'org1',
284+
membersCount: 1,
285+
adminDeleteEnabled: false,
286+
maxAllowedMemberships: 1,
287+
pendingInvitationsCount: 1,
288+
},
289+
}),
290+
],
291+
total_count: 1,
292+
}),
293+
);
294+
295+
fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
296+
Promise.resolve({
297+
data: [
298+
createFakeUserOrganizationSuggestion({
299+
id: '2',
300+
emailAddress: '[email protected]',
301+
publicOrganizationData: {
302+
name: 'OrgTwoSuggestion',
303+
},
304+
}),
305+
],
306+
total_count: 1,
307+
}),
308+
);
309+
310+
const { findByText, queryByText } = render(<TaskChooseOrganization />, { wrapper });
311+
312+
expect(await findByText('Join an existing organization')).toBeInTheDocument();
313+
expect(await queryByText('Create new organization')).not.toBeInTheDocument();
314+
expect(await findByText('Existing Org')).toBeInTheDocument();
315+
});
316+
});
243317
});

packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useClerk, useSession, useUser } from '@clerk/shared/react';
2-
import { type ComponentType, useState } from 'react';
2+
import { useState } from 'react';
33

44
import { useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts';
55
import { descriptors, Flex, Flow, localizationKeys, Spinner } from '@/ui/customizables';
@@ -14,10 +14,16 @@ import { ChooseOrganizationScreen } from './ChooseOrganizationScreen';
1414
import { CreateOrganizationScreen } from './CreateOrganizationScreen';
1515

1616
const TaskChooseOrganizationInternal = () => {
17+
const { user } = useUser();
1718
const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
1819

1920
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
2021
const hasExistingResources = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count);
22+
const isOrganizationCreationDisabled = !isLoading && !user?.createOrganizationEnabled && !hasExistingResources;
23+
24+
if (isOrganizationCreationDisabled) {
25+
return <OrganizationCreationDisabledScreen />;
26+
}
2127

2228
return (
2329
<Flow.Root flow='taskChooseOrganization'>
@@ -113,18 +119,6 @@ const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrga
113119
return <ChooseOrganizationScreen onCreateOrganizationClick={() => setCurrentFlow('create')} />;
114120
});
115121

116-
export const withOrganizationCreationEnabledGuard = <T extends object>(Component: ComponentType<T>) => {
117-
return (props: T) => {
118-
const { user } = useUser();
119-
120-
if (!user?.createOrganizationEnabled) {
121-
return <OrganizationCreationDisabledScreen />;
122-
}
123-
124-
return <Component {...props} />;
125-
};
126-
};
127-
128122
function OrganizationCreationDisabledScreen() {
129123
return (
130124
<Flow.Root flow='taskChooseOrganization'>
@@ -149,8 +143,5 @@ function OrganizationCreationDisabledScreen() {
149143
}
150144

151145
export const TaskChooseOrganization = withCoreSessionSwitchGuard(
152-
withTaskGuard(
153-
withCardStateProvider(withOrganizationCreationEnabledGuard(TaskChooseOrganizationInternal)),
154-
'choose-organization',
155-
),
146+
withTaskGuard(withCardStateProvider(TaskChooseOrganizationInternal), 'choose-organization'),
156147
);

packages/localizations/src/en-US.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,7 @@ export const enUS: LocalizationResource = {
862862
action__invitationAccept: 'Join',
863863
action__suggestionsAccept: 'Request to join',
864864
subtitle: 'Join an existing organization or create a new one',
865+
subtitle__createOrganizationDisabled: 'Join an existing organization',
865866
suggestionsAcceptedLabel: 'Pending approval',
866867
title: 'Choose an organization',
867868
},

packages/shared/src/types/localization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,7 @@ export type __internal_LocalizationResource = {
13041304
chooseOrganization: {
13051305
title: LocalizationValue;
13061306
subtitle: LocalizationValue;
1307+
subtitle__createOrganizationDisabled: LocalizationValue;
13071308
suggestionsAcceptedLabel: LocalizationValue;
13081309
action__suggestionsAccept: LocalizationValue;
13091310
action__createOrganization: LocalizationValue;

0 commit comments

Comments
 (0)