Skip to content

Commit 441b254

Browse files
committed
feat(clerk-js): Use Gate in OrganizationSwitcher
1 parent 5a3995b commit 441b254

4 files changed

Lines changed: 77 additions & 46 deletions

File tree

.changeset/shy-seahorses-begin.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+
Replace role based check with permission based checks inside the OrganizationSwitcher component.

packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { OrganizationResource } from '@clerk/types';
22
import React from 'react';
33

44
import { runIfFunctionOrReturn } from '../../../utils';
5-
import { NotificationCountBadge } from '../../common';
5+
import { NotificationCountBadge, withGate } from '../../common';
66
import {
77
useCoreClerk,
88
useCoreOrganization,
@@ -173,15 +173,20 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
173173
},
174174
);
175175

176-
const NotificationCountBadgeManageButton = () => {
177-
const { membership } = useCoreOrganization();
178-
const { organizationSettings } = useEnvironment();
179-
const isAdmin = membership?.role === 'admin';
180-
const allowRequests = organizationSettings?.domains?.enabled && isAdmin;
176+
const NotificationCountBadgeManageButton = withGate(
177+
() => {
178+
const { organizationSettings } = useEnvironment();
181179

182-
const { membershipRequests } = useCoreOrganization({
183-
membershipRequests: allowRequests || undefined,
184-
});
180+
const isDomainsEnabled = organizationSettings?.domains?.enabled;
185181

186-
return <NotificationCountBadge notificationCount={membershipRequests?.count || 0} />;
187-
};
182+
const { membershipRequests } = useCoreOrganization({
183+
membershipRequests: isDomainsEnabled || undefined,
184+
});
185+
186+
return <NotificationCountBadge notificationCount={membershipRequests?.count || 0} />;
187+
},
188+
{
189+
// if the user is not able to accept a request we should not notify them
190+
permission: 'org:memberships:manage',
191+
},
192+
);

packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { forwardRef } from 'react';
22

3-
import { NotificationCountBadge } from '../../common';
3+
import { NotificationCountBadge, withGate } from '../../common';
44
import {
55
useCoreOrganization,
66
useCoreOrganizationList,
@@ -69,28 +69,32 @@ export const OrganizationSwitcherTrigger = withAvatarShimmer(
6969
);
7070
}),
7171
);
72-
const NotificationCountBadgeSwitcherTrigger = () => {
73-
/**
74-
* Prefetch user invitations and suggestions
75-
*/
76-
const { userInvitations, userSuggestions } = useCoreOrganizationList(organizationListParams);
77-
const { membership } = useCoreOrganization();
78-
const { organizationSettings } = useEnvironment();
79-
const isAdmin = membership?.role === 'admin';
80-
const allowRequests = organizationSettings?.domains?.enabled && isAdmin;
81-
const { membershipRequests } = useCoreOrganization({
82-
membershipRequests: allowRequests || undefined,
83-
});
72+
const NotificationCountBadgeSwitcherTrigger = withGate(
73+
() => {
74+
/**
75+
* Prefetch user invitations and suggestions
76+
*/
77+
const { userInvitations, userSuggestions } = useCoreOrganizationList(organizationListParams);
78+
const { organizationSettings } = useEnvironment();
79+
const isDomainsEnabled = organizationSettings?.domains?.enabled;
80+
const { membershipRequests } = useCoreOrganization({
81+
membershipRequests: isDomainsEnabled || undefined,
82+
});
8483

85-
const notificationCount =
86-
(userInvitations.count || 0) + (userSuggestions.count || 0) + (membershipRequests?.count || 0);
84+
const notificationCount =
85+
(userInvitations.count || 0) + (userSuggestions.count || 0) + (membershipRequests?.count || 0);
8786

88-
return (
89-
<NotificationCountBadge
90-
containerSx={t => ({
91-
marginLeft: `${t.space.$2}`,
92-
})}
93-
notificationCount={notificationCount}
94-
/>
95-
);
96-
};
87+
return (
88+
<NotificationCountBadge
89+
containerSx={t => ({
90+
marginLeft: `${t.space.$2}`,
91+
})}
92+
notificationCount={notificationCount}
93+
/>
94+
);
95+
},
96+
{
97+
// if the user is not able to accept a request we should not notify them
98+
permission: 'org:memberships:manage',
99+
},
100+
);

packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { MembershipRole } from '@clerk/types';
22
import { describe } from '@jest/globals';
33

4-
import { render, runFakeTimers, waitFor } from '../../../../testUtils';
4+
import { act, render, runFakeTimers, waitFor } from '../../../../testUtils';
55
import { bindCreateFixtures } from '../../../utils/test/createFixtures';
66
import { OrganizationSwitcher } from '../OrganizationSwitcher';
77
import { createFakeUserOrganizationInvitation, createFakeUserOrganizationSuggestion } from './utlis';
@@ -10,30 +10,33 @@ const { createFixtures } = bindCreateFixtures('OrganizationSwitcher');
1010

1111
describe('OrganizationSwitcher', () => {
1212
it('renders component', async () => {
13-
const { wrapper } = await createFixtures(f => {
13+
const { wrapper, fixtures } = await createFixtures(f => {
1414
f.withOrganizations();
1515
f.withUser({ email_addresses: ['test@clerk.dev'] });
1616
});
17-
const { queryByRole } = render(<OrganizationSwitcher />, { wrapper });
17+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
18+
const { queryByRole } = await act(async () => render(<OrganizationSwitcher />, { wrapper }));
1819
expect(queryByRole('button')).toBeDefined();
1920
});
2021

2122
describe('Personal Workspace', () => {
2223
it('shows the personal workspace when enabled', async () => {
23-
const { wrapper, props } = await createFixtures(f => {
24+
const { wrapper, props, fixtures } = await createFixtures(f => {
2425
f.withOrganizations();
2526
f.withUser({ email_addresses: ['test@clerk.dev'] });
2627
});
28+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
2729
props.setProps({ hidePersonal: false });
28-
const { getByText } = render(<OrganizationSwitcher />, { wrapper });
30+
const { getByText } = await act(async () => render(<OrganizationSwitcher />, { wrapper }));
2931
expect(getByText('Personal account')).toBeDefined();
3032
});
3133

3234
it('does not show the personal workspace when disabled', async () => {
33-
const { wrapper, props } = await createFixtures(f => {
35+
const { wrapper, props, fixtures } = await createFixtures(f => {
3436
f.withOrganizations();
3537
f.withUser({ email_addresses: ['test@clerk.dev'] });
3638
});
39+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
3740
props.setProps({ hidePersonal: true });
3841
const { queryByText, getByRole, userEvent, getByText } = render(<OrganizationSwitcher />, { wrapper });
3942
await userEvent.click(getByRole('button'));
@@ -63,6 +66,8 @@ describe('OrganizationSwitcher', () => {
6366
}),
6467
);
6568

69+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(true);
70+
6671
await runFakeTimers(async () => {
6772
const { getByText } = render(<OrganizationSwitcher />, { wrapper });
6873

@@ -103,6 +108,8 @@ describe('OrganizationSwitcher', () => {
103108
}),
104109
);
105110

111+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(true);
112+
106113
await runFakeTimers(async () => {
107114
const { getByText } = render(<OrganizationSwitcher />, { wrapper });
108115

@@ -115,21 +122,23 @@ describe('OrganizationSwitcher', () => {
115122

116123
describe('OrganizationSwitcherPopover', () => {
117124
it('opens the organization switcher popover when clicked', async () => {
118-
const { wrapper, props } = await createFixtures(f => {
125+
const { wrapper, props, fixtures } = await createFixtures(f => {
119126
f.withOrganizations();
120127
f.withUser({ email_addresses: ['test@clerk.dev'], create_organization_enabled: true });
121128
});
129+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
122130
props.setProps({ hidePersonal: true });
123131
const { getByText, getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
124132
await userEvent.click(getByRole('button'));
125133
expect(getByText('Create Organization')).toBeDefined();
126134
});
127135

128136
it('lists all organizations the user belongs to', async () => {
129-
const { wrapper, props } = await createFixtures(f => {
137+
const { wrapper, props, fixtures } = await createFixtures(f => {
130138
f.withOrganizations();
131139
f.withUser({ email_addresses: ['test@clerk.dev'], organization_memberships: ['Org1', 'Org2'] });
132140
});
141+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
133142
props.setProps({ hidePersonal: false });
134143
const { getAllByText, getByText, getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
135144
await userEvent.click(getByRole('button'));
@@ -143,13 +152,14 @@ describe('OrganizationSwitcher', () => {
143152
['Member', 'basic_member'],
144153
['Guest', 'guest_member'],
145154
])('shows the text "%s" for the %s role in the active organization', async (text, role) => {
146-
const { wrapper, props } = await createFixtures(f => {
155+
const { wrapper, props, fixtures } = await createFixtures(f => {
147156
f.withOrganizations();
148157
f.withUser({
149158
email_addresses: ['test@clerk.dev'],
150159
organization_memberships: [{ name: 'Org1', role: role as MembershipRole }],
151160
});
152161
});
162+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
153163
props.setProps({ hidePersonal: true });
154164
const { getAllByText, getByText, getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
155165
await userEvent.click(getByRole('button'));
@@ -165,6 +175,7 @@ describe('OrganizationSwitcher', () => {
165175
organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
166176
});
167177
});
178+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
168179
props.setProps({ hidePersonal: true });
169180
const { getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
170181
await userEvent.click(getByRole('button'));
@@ -181,6 +192,7 @@ describe('OrganizationSwitcher', () => {
181192
create_organization_enabled: true,
182193
});
183194
});
195+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
184196
props.setProps({ hidePersonal: true });
185197
const { getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
186198
await userEvent.click(getByRole('button'));
@@ -189,16 +201,17 @@ describe('OrganizationSwitcher', () => {
189201
});
190202

191203
it('does not display create organization button if permissions not present', async () => {
192-
const { wrapper, props } = await createFixtures(f => {
204+
const { wrapper, props, fixtures } = await createFixtures(f => {
193205
f.withOrganizations();
194206
f.withUser({
195207
email_addresses: ['test@clerk.dev'],
196208
organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
197209
create_organization_enabled: false,
198210
});
199211
});
212+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
200213
props.setProps({ hidePersonal: true });
201-
const { queryByRole } = render(<OrganizationSwitcher />, { wrapper });
214+
const { queryByRole } = await act(async () => render(<OrganizationSwitcher />, { wrapper }));
202215
expect(queryByRole('button', { name: 'Create Organization' })).not.toBeInTheDocument();
203216
});
204217

@@ -211,6 +224,7 @@ describe('OrganizationSwitcher', () => {
211224
create_organization_enabled: false,
212225
});
213226
});
227+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
214228
fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
215229
Promise.resolve({
216230
data: [
@@ -254,6 +268,7 @@ describe('OrganizationSwitcher', () => {
254268
create_organization_enabled: false,
255269
});
256270
});
271+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
257272
fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
258273
Promise.resolve({
259274
data: [
@@ -303,6 +318,7 @@ describe('OrganizationSwitcher', () => {
303318
});
304319
});
305320
fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
321+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
306322

307323
props.setProps({ hidePersonal: true });
308324
const { getByRole, getByText, userEvent } = render(<OrganizationSwitcher />, { wrapper });
@@ -330,6 +346,7 @@ describe('OrganizationSwitcher', () => {
330346
});
331347
});
332348

349+
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
333350
fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
334351
const { getByRole, getByText, userEvent } = render(<OrganizationSwitcher />, { wrapper });
335352
await userEvent.click(getByRole('button'));

0 commit comments

Comments
 (0)