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/sweet-queens-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Show counter of members/invitations/requests even if it is 0.
19 changes: 5 additions & 14 deletions packages/clerk-js/src/ui/common/NotificationCountBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,27 @@
import { formatToCompactNumber } from '../../ui/utils/intl';
import { Flex, localizationKeys, NotificationBadge, useLocalizations } from '../customizables';
import { useDelayedVisibility, usePrefersReducedMotion } from '../hooks';
import { usePrefersReducedMotion } from '../hooks';
import type { PropsOfComponent, ThemableCssProp } from '../styledSystem';
import { animations } from '../styledSystem';

type NotificationCountBadgeProps = PropsOfComponent<typeof NotificationBadge> & {
notificationCount: number;
shouldDelayVisibility?: boolean;
containerSx?: ThemableCssProp;
};

export const NotificationCountBadge = (props: NotificationCountBadgeProps) => {
const { notificationCount, containerSx, shouldDelayVisibility, ...restProps } = props;
const { notificationCount, containerSx, ...restProps } = props;
const prefersReducedMotion = usePrefersReducedMotion();
const delayVisibility = shouldDelayVisibility || notificationCount > 0;
const showNotification = useDelayedVisibility(delayVisibility, 350) || false;
const { t } = useLocalizations();
const localeKey = t(localizationKeys('locale'));
const formatedNotificationCount = formatToCompactNumber(notificationCount, localeKey);
const formattedNotificationCount = formatToCompactNumber(notificationCount, localeKey);

const enterExitAnimation: ThemableCssProp = t => ({
animation: prefersReducedMotion
? 'none'
: `${showNotification ? animations.notificationAnimation : animations.outAnimation} ${
t.transitionDuration.$textField
} ${t.transitionTiming.$slowBezier} 0s 1 normal forwards`,
: `${animations.notificationAnimation} ${t.transitionDuration.$textField} ${t.transitionTiming.$slowBezier} 0s 1 normal forwards`,
});

if (!showNotification) {
return null;
}

return (
<Flex
justify='center'
Expand All @@ -46,7 +37,7 @@ export const NotificationCountBadge = (props: NotificationCountBadgeProps) => {
sx={enterExitAnimation}
{...restProps}
>
{formatedNotificationCount}
{formattedNotificationCount}
</NotificationBadge>
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,32 +71,37 @@ export const OrganizationMembers = withCardStateProvider(() => {
</Animated>
<Card.Alert>{card.error}</Card.Alert>
<Tabs>
<TabsList sx={t => ({ gap: t.space.$4 })}>
<TabsList sx={t => ({ gap: t.space.$2 })}>
{canReadMemberships && (
<Tab localizationKey={localizationKeys('organizationProfile.membersPage.start.headerTitle__members')}>
<NotificationCountBadge
notificationCount={memberships?.count || 0}
shouldDelayVisibility
colorScheme='outline'
/>
{memberships?.data && !memberships.isLoading && (
<NotificationCountBadge
notificationCount={memberships.count}
colorScheme='outline'
/>
)}
</Tab>
)}
{canManageMemberships && (
<Tab
localizationKey={localizationKeys('organizationProfile.membersPage.start.headerTitle__invitations')}
>
<NotificationCountBadge
notificationCount={invitations?.count || 0}
colorScheme='outline'
/>
{invitations?.data && !invitations.isLoading && (
<NotificationCountBadge
notificationCount={invitations.count}
colorScheme='outline'
/>
)}
</Tab>
)}
{canManageMemberships && isDomainsEnabled && (
<Tab localizationKey={localizationKeys('organizationProfile.membersPage.start.headerTitle__requests')}>
<NotificationCountBadge
notificationCount={membershipRequests?.count || 0}
colorScheme='outline'
/>
{membershipRequests?.data && !membershipRequests.isLoading && (
<NotificationCountBadge
notificationCount={membershipRequests.count}
colorScheme='outline'
/>
)}
</Tab>
)}
</TabsList>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ describe('OrganizationMembers', () => {
expect(getByRole('heading', { name: /members/i })).toBeInTheDocument();

// Tabs
expect(getByRole('tab', { name: 'Members' })).toBeInTheDocument();
expect(getByRole('tab', { name: 'Invitations' })).toBeInTheDocument();
expect(getByRole('tab', { name: /Members/i })).toBeInTheDocument();
expect(getByRole('tab', { name: /Invitations/i })).toBeInTheDocument();
});

it('shows requests if domains is turned on', async () => {
Expand All @@ -54,7 +54,7 @@ describe('OrganizationMembers', () => {

await waitForLoadingCompleted(container);

expect(getByRole('tab', { name: 'Requests' })).toBeInTheDocument();
expect(getByRole('tab', { name: /Requests/i })).toBeInTheDocument();
});

it('shows an invite button inside invitations tab if the current user is an admin', async () => {
Expand All @@ -67,7 +67,7 @@ describe('OrganizationMembers', () => {

const { getByRole, findByText } = render(<OrganizationMembers />, { wrapper });

await userEvent.click(getByRole('tab', { name: 'Invitations' }));
await userEvent.click(getByRole('tab', { name: /Invitations/i }));
expect(await findByText('Invited')).toBeInTheDocument();
expect(getByRole('button', { name: 'Invite' })).toBeInTheDocument();
});
Expand All @@ -87,9 +87,9 @@ describe('OrganizationMembers', () => {

await waitForLoadingCompleted(container);

expect(queryByRole('tab', { name: 'Members' })).toBeInTheDocument();
expect(queryByRole('tab', { name: 'Invitations' })).not.toBeInTheDocument();
expect(queryByRole('tab', { name: 'Requests' })).not.toBeInTheDocument();
expect(queryByRole('tab', { name: /Members/i })).toBeInTheDocument();
expect(queryByRole('tab', { name: /Invitations/i })).not.toBeInTheDocument();
expect(queryByRole('tab', { name: /Requests/i })).not.toBeInTheDocument();
});

it('does not show members tab or navbar route if user is lacking permissions', async () => {
Expand Down Expand Up @@ -378,7 +378,7 @@ describe('OrganizationMembers', () => {
const { container, getByRole, getByText, findByText } = render(<OrganizationMembers />, { wrapper });

await waitForLoadingCompleted(container);
await userEvent.click(getByRole('tab', { name: 'Invitations' }));
await userEvent.click(getByRole('tab', { name: /Invitations/i }));

expect(await findByText('admin1@clerk.com')).toBeInTheDocument();
expect(getByText('Admin')).toBeInTheDocument();
Expand Down Expand Up @@ -432,7 +432,7 @@ describe('OrganizationMembers', () => {
const { queryByText, getByRole } = render(<OrganizationMembers />, { wrapper });

await waitFor(async () => {
await userEvent.click(getByRole('tab', { name: 'Requests' }));
await userEvent.click(getByRole('tab', { name: /Requests/i }));
});

expect(fixtures.clerk.organization?.getMembershipRequests).toHaveBeenCalledWith({
Expand Down