Skip to content

Commit

Permalink
feat(clerk-js): Use Gate in OrganizationSwitcher (#1851)
Browse files Browse the repository at this point in the history
* feat(clerk-js): Use Gate in OrganizationSwitcher

* fix(clerk-js): Update to new permission names
  • Loading branch information
panteliselef authored Oct 13, 2023
1 parent e1e5d37 commit 29485eb
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-seahorses-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Replace role based check with permission based checks inside the OrganizationSwitcher component.
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,8 @@ describe('OrganizationMembers', () => {
total_count: 2,
}),
);
const { queryByText, getByRole } = render(<OrganizationMembers />, { wrapper });
await waitFor(async () => {
await userEvent.click(getByRole('tab', { name: 'Invitations' }));
});
const { queryByText, findByRole } = render(<OrganizationMembers />, { 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -175,15 +175,20 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
},
);

const NotificationCountBadgeManageButton = () => {
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 <NotificationCountBadge notificationCount={membershipRequests?.count || 0} />;
};
const { membershipRequests } = useCoreOrganization({
membershipRequests: isDomainsEnabled || undefined,
});

return <NotificationCountBadge notificationCount={membershipRequests?.count || 0} />;
},
{
// if the user is not able to accept a request we should not notify them
permission: 'org:sys_memberships:manage',
},
);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef } from 'react';

import { NotificationCountBadge } from '../../common';
import { NotificationCountBadge, withGate } from '../../common';
import {
useCoreOrganization,
useCoreOrganizationList,
Expand Down Expand Up @@ -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 (
<NotificationCountBadge
containerSx={t => ({
marginLeft: `${t.space.$2}`,
})}
notificationCount={notificationCount}
/>
);
};
return (
<NotificationCountBadge
containerSx={t => ({
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',
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -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(<OrganizationSwitcher />, { 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(<OrganizationSwitcher />, { 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(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button'));
Expand Down Expand Up @@ -63,6 +66,8 @@ describe('OrganizationSwitcher', () => {
}),
);

fixtures.clerk.session?.isAuthorized.mockResolvedValue(true);

await runFakeTimers(async () => {
const { getByText } = render(<OrganizationSwitcher />, { wrapper });

Expand Down Expand Up @@ -103,6 +108,8 @@ describe('OrganizationSwitcher', () => {
}),
);

fixtures.clerk.session?.isAuthorized.mockResolvedValue(true);

await runFakeTimers(async () => {
const { getByText } = render(<OrganizationSwitcher />, { wrapper });

Expand All @@ -115,21 +122,23 @@ 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(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button'));
expect(getByText('Create Organization')).toBeDefined();
});

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(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button'));
Expand All @@ -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(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button'));
Expand All @@ -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(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button'));
Expand All @@ -181,6 +192,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: true,
});
});
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
Expand All @@ -189,14 +201,15 @@ 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'],
organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
create_organization_enabled: false,
});
});
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
props.setProps({ hidePersonal: true });
const { queryByRole } = await act(() => render(<OrganizationSwitcher />, { wrapper }));
expect(queryByRole('button', { name: 'Create Organization' })).not.toBeInTheDocument();
Expand All @@ -211,6 +224,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: false,
});
});
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
fixtures.clerk.user?.getOrganizationInvitations.mockReturnValueOnce(
Promise.resolve({
data: [
Expand Down Expand Up @@ -254,6 +268,7 @@ describe('OrganizationSwitcher', () => {
create_organization_enabled: false,
});
});
fixtures.clerk.session?.isAuthorized.mockResolvedValue(false);
fixtures.clerk.user?.getOrganizationSuggestions.mockReturnValueOnce(
Promise.resolve({
data: [
Expand Down Expand Up @@ -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(<OrganizationSwitcher />, { wrapper });
Expand Down Expand Up @@ -330,6 +346,7 @@ describe('OrganizationSwitcher', () => {
});
});

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

0 comments on commit 29485eb

Please sign in to comment.