diff --git a/.changeset/shy-seahorses-begin.md b/.changeset/shy-seahorses-begin.md
new file mode 100644
index 00000000000..1961e77037c
--- /dev/null
+++ b/.changeset/shy-seahorses-begin.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Replace role based check with permission based checks inside the OrganizationSwitcher component.
diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx
index 6e9d2aaaa5a..3eeb70f6d5b 100644
--- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx
@@ -285,10 +285,8 @@ describe('OrganizationMembers', () => {
total_count: 2,
}),
);
- const { queryByText, getByRole } = render(, { wrapper });
- await waitFor(async () => {
- await userEvent.click(getByRole('tab', { name: 'Invitations' }));
- });
+ const { queryByText, findByRole } = render(, { wrapper });
+ await userEvent.click(await findByRole('tab', { name: 'Invitations' }));
expect(fixtures.clerk.organization?.getInvitations).toHaveBeenCalled();
expect(queryByText('admin1@clerk.dev')).toBeInTheDocument();
expect(queryByText('Admin')).toBeInTheDocument();
diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
index 728ba9371cd..f13dd845c9f 100644
--- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
@@ -2,7 +2,7 @@ import type { OrganizationResource } from '@clerk/types';
import React from 'react';
import { runIfFunctionOrReturn } from '../../../utils';
-import { NotificationCountBadge } from '../../common';
+import { NotificationCountBadge, withGate } from '../../common';
import {
useCoreClerk,
useCoreOrganization,
@@ -175,15 +175,20 @@ export const OrganizationSwitcherPopover = React.forwardRef {
- const { membership } = useCoreOrganization();
- const { organizationSettings } = useEnvironment();
- const isAdmin = membership?.role === 'admin';
- const allowRequests = organizationSettings?.domains?.enabled && isAdmin;
+const NotificationCountBadgeManageButton = withGate(
+ () => {
+ const { organizationSettings } = useEnvironment();
- const { membershipRequests } = useCoreOrganization({
- membershipRequests: allowRequests || undefined,
- });
+ const isDomainsEnabled = organizationSettings?.domains?.enabled;
- return ;
-};
+ const { membershipRequests } = useCoreOrganization({
+ membershipRequests: isDomainsEnabled || undefined,
+ });
+
+ return ;
+ },
+ {
+ // if the user is not able to accept a request we should not notify them
+ permission: 'org:sys_memberships:manage',
+ },
+);
diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
index 8575795b24e..4d7e5f90f45 100644
--- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
@@ -1,6 +1,6 @@
import { forwardRef } from 'react';
-import { NotificationCountBadge } from '../../common';
+import { NotificationCountBadge, withGate } from '../../common';
import {
useCoreOrganization,
useCoreOrganizationList,
@@ -72,28 +72,32 @@ export const OrganizationSwitcherTrigger = withAvatarShimmer(
);
}),
);
-const NotificationCountBadgeSwitcherTrigger = () => {
- /**
- * Prefetch user invitations and suggestions
- */
- const { userInvitations, userSuggestions } = useCoreOrganizationList(organizationListParams);
- const { membership } = useCoreOrganization();
- const { organizationSettings } = useEnvironment();
- const isAdmin = membership?.role === 'admin';
- const allowRequests = organizationSettings?.domains?.enabled && isAdmin;
- const { membershipRequests } = useCoreOrganization({
- membershipRequests: allowRequests || undefined,
- });
+const NotificationCountBadgeSwitcherTrigger = withGate(
+ () => {
+ /**
+ * Prefetch user invitations and suggestions
+ */
+ const { userInvitations, userSuggestions } = useCoreOrganizationList(organizationListParams);
+ const { organizationSettings } = useEnvironment();
+ const isDomainsEnabled = organizationSettings?.domains?.enabled;
+ const { membershipRequests } = useCoreOrganization({
+ membershipRequests: isDomainsEnabled || undefined,
+ });
- const notificationCount =
- (userInvitations.count || 0) + (userSuggestions.count || 0) + (membershipRequests?.count || 0);
+ const notificationCount =
+ (userInvitations.count || 0) + (userSuggestions.count || 0) + (membershipRequests?.count || 0);
- return (
- ({
- marginLeft: `${t.space.$2}`,
- })}
- notificationCount={notificationCount}
- />
- );
-};
+ return (
+ ({
+ marginLeft: `${t.space.$2}`,
+ })}
+ notificationCount={notificationCount}
+ />
+ );
+ },
+ {
+ // if the user is not able to accept a request we should not notify them
+ permission: 'org:sys_memberships:manage',
+ },
+);
diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
index b1bf8f1a4a2..51e4a8cba92 100644
--- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
@@ -10,30 +10,33 @@ const { createFixtures } = bindCreateFixtures('OrganizationSwitcher');
describe('OrganizationSwitcher', () => {
it('renders component', async () => {
- const { wrapper } = await createFixtures(f => {
+ const { wrapper, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({ email_addresses: ['test@clerk.dev'] });
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
const { queryByRole } = await act(() => render(, { wrapper }));
expect(queryByRole('button')).toBeDefined();
});
describe('Personal Workspace', () => {
it('shows the personal workspace when enabled', async () => {
- const { wrapper, props } = await createFixtures(f => {
+ const { wrapper, props, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({ email_addresses: ['test@clerk.dev'] });
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: false });
const { getByText } = await act(() => render(, { wrapper }));
expect(getByText('Personal account')).toBeDefined();
});
it('does not show the personal workspace when disabled', async () => {
- const { wrapper, props } = await createFixtures(f => {
+ const { wrapper, props, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({ email_addresses: ['test@clerk.dev'] });
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { queryByText, getByRole, userEvent, getByText } = render(, { wrapper });
await userEvent.click(getByRole('button'));
@@ -63,6 +66,8 @@ describe('OrganizationSwitcher', () => {
}),
);
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(true);
+
await runFakeTimers(async () => {
const { getByText } = render(, { wrapper });
@@ -103,6 +108,8 @@ describe('OrganizationSwitcher', () => {
}),
);
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(true);
+
await runFakeTimers(async () => {
const { getByText } = render(, { wrapper });
@@ -115,10 +122,11 @@ describe('OrganizationSwitcher', () => {
describe('OrganizationSwitcherPopover', () => {
it('opens the organization switcher popover when clicked', async () => {
- const { wrapper, props } = await createFixtures(f => {
+ const { wrapper, props, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({ email_addresses: ['test@clerk.dev'], create_organization_enabled: true });
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { getByText, getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button'));
@@ -126,10 +134,11 @@ describe('OrganizationSwitcher', () => {
});
it('lists all organizations the user belongs to', async () => {
- const { wrapper, props } = await createFixtures(f => {
+ const { wrapper, props, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({ email_addresses: ['test@clerk.dev'], organization_memberships: ['Org1', 'Org2'] });
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: false });
const { getAllByText, getByText, getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button'));
@@ -143,13 +152,14 @@ describe('OrganizationSwitcher', () => {
['Member', 'basic_member'],
['Guest', 'guest_member'],
])('shows the text "%s" for the %s role in the active organization', async (text, role) => {
- const { wrapper, props } = await createFixtures(f => {
+ const { wrapper, props, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['test@clerk.dev'],
organization_memberships: [{ name: 'Org1', role: role as MembershipRole }],
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { getAllByText, getByText, getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button'));
@@ -165,6 +175,7 @@ describe('OrganizationSwitcher', () => {
organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button'));
@@ -181,6 +192,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: true,
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
@@ -189,7 +201,7 @@ describe('OrganizationSwitcher', () => {
});
it('does not display create organization button if permissions not present', async () => {
- const { wrapper, props } = await createFixtures(f => {
+ const { wrapper, props, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['test@clerk.dev'],
@@ -197,6 +209,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: false,
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { queryByRole } = await act(() => render(, { wrapper }));
expect(queryByRole('button', { name: 'Create Organization' })).not.toBeInTheDocument();
@@ -211,6 +224,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: false,
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
Promise.resolve({
data: [
@@ -254,6 +268,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: false,
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
Promise.resolve({
data: [
@@ -303,6 +318,7 @@ describe('OrganizationSwitcher', () => {
});
});
fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { getByRole, getByText, userEvent } = render(, { wrapper });
@@ -330,6 +346,7 @@ describe('OrganizationSwitcher', () => {
});
});
+ fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
const { getByRole, getByText, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button'));