Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(clerk-js): Use Gate in OrganizationSwitcher #1851

Merged
merged 2 commits into from
Oct 13, 2023
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/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('[email protected]')).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: ['[email protected]'] });
});
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: ['[email protected]'] });
});
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: ['[email protected]'] });
});
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: ['[email protected]'], 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: ['[email protected]'], 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: ['[email protected]'],
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: ['[email protected]'],
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