Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tasty-animals-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

Fix "You must belong to an organization" screen showing when user has existing memberships, invitations or suggestions
Original file line number Diff line number Diff line change
Expand Up @@ -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 '..';

Expand Down Expand Up @@ -296,5 +299,56 @@ describe('TaskChooseOrganization', () => {
expect(queryByText(/you must belong to an organization/i)).toBeInTheDocument();
expect(queryByText(/contact your organization admin for an invitation/i)).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,
});
});
Comment on lines +303 to +310
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the test file to see the actual code around the flagged lines
head -320 packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx | tail -70

Repository: clerk/javascript

Length of output: 2669


🏁 Script executed:

# Search for withForceOrganizationSelection implementation in fixtures
fd -e ts -e tsx | xargs rg -l "withForceOrganizationSelection" | head -5

Repository: clerk/javascript

Length of output: 521


🏁 Script executed:

# Look at similar tests to compare task configuration
rg -n "withForceOrganizationSelection" packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx -B5 -A15

Repository: clerk/javascript

Length of output: 9572


🏁 Script executed:

# Search for the withForceOrganizationSelection implementation
rg -n "withForceOrganizationSelection\s*\(" packages/clerk-js/src/test/fixture-helpers.ts -A 20

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

# Also check if there's any task assignment related to organization selection in fixtures
rg -n "choose-organization" packages/clerk-js/src/test/fixture-helpers.ts -B 2 -A 2

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

# Let's find and examine the fixture-helpers file
find . -name "fixture-helpers.ts" -o -name "fixture*.ts" | head -10

Repository: clerk/javascript

Length of output: 148


🏁 Script executed:

# Search more broadly for withForceOrganizationSelection
rg "withForceOrganizationSelection" -A 15 -B 2

Repository: clerk/javascript

Length of output: 40420


🏁 Script executed:

# Let's examine the withForceOrganizationSelection implementation and surrounding context
cat -n packages/clerk-js/src/test/fixture-helpers.ts | grep -A 5 -B 5 "withForceOrganizationSelection"

Repository: clerk/javascript

Length of output: 782


🏁 Script executed:

# Let's also check the component to understand the task guard requirement
rg -n "withTaskGuard" packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ -A 2 -B 2

Repository: clerk/javascript

Length of output: 1237


🏁 Script executed:

# Let's find the withTaskGuard implementation in the shared file
rg -n "withTaskGuard" packages/ui/src/components/SessionTasks/tasks/shared -A 10 -B 2

Repository: clerk/javascript

Length of output: 2046


🏁 Script executed:

# Let's also check the test that was shown to not render without the task
sed -n '16,31p' packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx

Repository: clerk/javascript

Length of output: 737


🏁 Script executed:

# Let's examine the full withRedirect logic to understand what happens when task is missing
rg -n "withRedirect" packages/ui/src/components/SessionTasks/tasks/shared/withTaskGuard.ts -B 2 -A 10

Repository: clerk/javascript

Length of output: 1076


🏁 Script executed:

# Let's look at what the test expects to render at lines 349-351
sed -n '303,360p' packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx

Repository: clerk/javascript

Length of output: 1714


Add tasks array to the user fixture in this test.

The withForceOrganizationSelection() method only sets a configuration flag and does not automatically add the choose-organization task to the user. The component uses withTaskGuard (line 146 of index.tsx) which requires the task to be present in the session to render—without it, the component will redirect instead.

The test on lines 16-31 explicitly demonstrates this requirement by verifying the component does not render when the task is absent. The similar test on lines 286-301 (with the same configuration) includes tasks: [{ key: 'choose-organization' }]. Add this to the user fixture at lines 307-309 to ensure the component can render for the test assertions at lines 349-351 to execute.

🤖 Prompt for AI Agents
In
@packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx
around lines 303 - 310, The test uses withForceOrganizationSelection() but fails
to add the choose-organization task to the user fixture, so the component's
withTaskGuard prevents rendering; update the user fixture in this test (the
createFixtures callback that calls f.withUser(...)) to include tasks: [{ key:
'choose-organization' }], mirroring the other test, so the
TaskChooseOrganization component can render and the assertions at the end of the
test will run.


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: '[email protected]',
publicOrganizationData: {
name: 'OrgTwoSuggestion',
},
}),
],
total_count: 1,
}),
);

const { findByText, queryByRole } = render(<TaskChooseOrganization />, { wrapper });

expect(await findByText('Existing Org')).toBeInTheDocument();
expect(await findByText('Create new organization')).toBeInTheDocument();
expect(queryByRole('textbox', { name: /name/i })).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useClerk, useSession, useUser } from '@clerk/shared/react';
import { useState, type ComponentType } from 'react';
import { useState } from 'react';

import { useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts';
import { descriptors, Flex, Flow, localizationKeys, Spinner } from '@/ui/customizables';
Expand All @@ -14,24 +14,16 @@ import { ChooseOrganizationScreen } from './ChooseOrganizationScreen';
import { CreateOrganizationScreen } from './CreateOrganizationScreen';

const TaskChooseOrganizationInternal = () => {
const { signOut } = useClerk();
const { user } = useUser();
const { session } = useSession();
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 });
};
Comment on lines -21 to -30
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unused, somehow linter didn't caught it as well


const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
const hasExistingResources = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count);
const identifier = user?.primaryEmailAddress?.emailAddress ?? user?.username;
const isOrganizationCreationDisabled = !isLoading && !user?.createOrganizationEnabled && !hasExistingResources;

if (isOrganizationCreationDisabled) {
return <OrganizationCreationDisabledScreen />;
}

return (
<Flow.Root flow='taskChooseOrganization'>
Expand Down Expand Up @@ -127,18 +119,6 @@ const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrga
return <ChooseOrganizationScreen onCreateOrganizationClick={() => setCurrentFlow('create')} />;
});

export const withOrganizationCreationEnabledGuard = <T,>(Component: ComponentType<T>) => {
return (props: T) => {
const { user } = useUser();

if (!user?.createOrganizationEnabled) {
return <OrganizationCreationDisabledScreen />;
}

return <Component {...props} />;
};
};

function OrganizationCreationDisabledScreen() {
return (
<Flow.Root flow='taskChooseOrganization'>
Expand All @@ -163,8 +143,5 @@ function OrganizationCreationDisabledScreen() {
}

export const TaskChooseOrganization = withCoreSessionSwitchGuard(
withTaskGuard(
withCardStateProvider(withOrganizationCreationEnabledGuard(TaskChooseOrganizationInternal)),
'choose-organization',
),
withTaskGuard(withCardStateProvider(TaskChooseOrganizationInternal), 'choose-organization'),
);
Loading