Skip to content

Commit c0052f1

Browse files
authored
feat(clerk-js,localizations,msw,shared,ui): Add display for member limits (#7920)
1 parent 0e036d2 commit c0052f1

6 files changed

Lines changed: 125 additions & 4 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { UserButtonSignedIn } from './user-button-signed-in';
22
export { CheckoutAccountCredit } from './checkout-account-credit';
3+
export { OrgProfileSeatLimit } from './org-profile-seat-limit';
34
export { PricingTableSBB } from './pricing-table-sbb';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
BillingService,
3+
clerkHandlers,
4+
EnvironmentService,
5+
SessionService,
6+
setClerkState,
7+
type MockScenario,
8+
UserService,
9+
OrganizationService,
10+
} from '@clerk/msw';
11+
12+
export function OrgProfileSeatLimit(): MockScenario {
13+
const organization = OrganizationService.create({ maxAllowedMemberships: 10 });
14+
const user = UserService.create();
15+
user.organizationMemberships = [
16+
{
17+
object: 'organization_membership',
18+
id: 'orgmem_3004mVaZrB4yD63C9KuwTMWNKbj',
19+
public_metadata: {},
20+
role: 'org:owner',
21+
role_name: 'Owner',
22+
permissions: [
23+
'org:applications:create',
24+
'org:applications:manage',
25+
'org:applications:delete',
26+
'org:billing:read',
27+
'org:billing:manage',
28+
'org:config:read',
29+
'org:config:manage',
30+
'org:global:read',
31+
'org:global:manage',
32+
'org:instances:create',
33+
'org:instances:manage',
34+
'org:instances:delete',
35+
'org:restrictions:read',
36+
'org:restrictions:manage',
37+
'org:secrets:manage',
38+
'org:users:imp',
39+
'org:sys_profile:manage',
40+
'org:sys_profile:delete',
41+
'org:sys_billing:read',
42+
'org:sys_billing:manage',
43+
'org:sys_domains:read',
44+
'org:sys_domains:manage',
45+
'org:sys_memberships:read',
46+
'org:sys_memberships:manage',
47+
],
48+
created_at: 1752751315275,
49+
updated_at: 1752751315275,
50+
organization,
51+
},
52+
];
53+
const session = SessionService.create(user);
54+
const plans = BillingService.createDefaultPlans();
55+
const subscription = BillingService.createSubscription(plans[1]);
56+
57+
setClerkState({
58+
environment: EnvironmentService.MULTI_SESSION,
59+
session,
60+
user,
61+
organization,
62+
billing: {
63+
plans,
64+
subscription,
65+
},
66+
});
67+
68+
return {
69+
description: 'OrganizationProfile with a seat limit',
70+
handlers: clerkHandlers,
71+
initialState: { session, user, organization },
72+
name: 'org-profile-seat-limit',
73+
};
74+
}

packages/localizations/src/en-US.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ export const enUS: LocalizationResource = {
474474
start: {
475475
headerTitle__general: 'General',
476476
headerTitle__members: 'Members',
477+
membershipSeatUsageLabel: '{{count}} of {{limit}} seats used',
477478
profileSection: {
478479
primaryButton: 'Update profile',
479480
title: 'Organization Profile',

packages/msw/request-handlers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,8 +1114,10 @@ export const clerkHandlers = [
11141114
const membership = (currentUser as any).organizationMemberships.find((m: any) => m.organization?.id === orgId);
11151115
if (membership) {
11161116
return createNoStoreResponse({
1117-
data: [SessionService.serialize(membership)],
1118-
total_count: 1,
1117+
response: {
1118+
data: [SessionService.serialize(membership)],
1119+
total_count: 1,
1120+
},
11191121
});
11201122
}
11211123
}

packages/shared/src/types/localization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,7 @@ export type __internal_LocalizationResource = {
10221022
badge__manualInvitation: LocalizationValue;
10231023
start: {
10241024
headerTitle__members: LocalizationValue;
1025+
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
10251026
headerTitle__general: LocalizationValue;
10261027
profileSection: {
10271028
title: LocalizationValue;

packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useOrganization } from '@clerk/shared/react';
22
import { useState } from 'react';
33

44
import { useFetchRoles } from '@/hooks/useFetchRoles';
5+
import { Users } from '@/icons';
56
import { Alert } from '@/ui/elements/Alert';
67
import { Animated } from '@/ui/elements/Animated';
78
import { Card } from '@/ui/elements/Card';
@@ -11,7 +12,7 @@ import { Tab, TabPanel, TabPanels, Tabs, TabsList } from '@/ui/elements/Tabs';
1112

1213
import { NotificationCountBadge, useProtect } from '../../common';
1314
import { useEnvironment } from '../../contexts';
14-
import { Col, descriptors, Flex, localizationKeys } from '../../customizables';
15+
import { Box, Col, descriptors, Flex, Icon, localizationKeys, Text } from '../../customizables';
1516
import { Action } from '../../elements/Action';
1617
import { mqu } from '../../styledSystem';
1718
import { ActiveMembersList } from './ActiveMembersList';
@@ -33,7 +34,7 @@ export const OrganizationMembers = withCardStateProvider(() => {
3334
const [query, setQuery] = useState('');
3435
const [search, setSearch] = useState('');
3536

36-
const { membershipRequests, memberships, invitations } = useOrganization({
37+
const { membershipRequests, memberships, invitations, organization } = useOrganization({
3738
membershipRequests: isDomainsEnabled || undefined,
3839
invitations: canManageMemberships || undefined,
3940
memberships: canReadMemberships
@@ -57,6 +58,7 @@ export const OrganizationMembers = withCardStateProvider(() => {
5758
elementDescriptor={descriptors.profilePage}
5859
elementId={descriptors.profilePage.setId('organizationMembers')}
5960
gap={4}
61+
sx={theme => ({ paddingBottom: theme.space.$13 })}
6062
>
6163
<Action.Root animate={false}>
6264
<Animated asChild>
@@ -173,6 +175,46 @@ export const OrganizationMembers = withCardStateProvider(() => {
173175
</Tabs>
174176
</Action.Root>
175177
</Col>
178+
179+
{canReadMemberships && !!memberships?.count && organization && organization.maxAllowedMemberships > 0 ? (
180+
<Box
181+
sx={theme => ({
182+
position: 'absolute',
183+
bottom: 0,
184+
left: 0,
185+
right: 0,
186+
backgroundColor: theme.colors.$colorBackground,
187+
borderTop: `1px solid ${theme.colors.$borderAlpha100}`,
188+
paddingInline: theme.space.$4,
189+
height: theme.space.$13,
190+
display: 'flex',
191+
alignItems: 'center',
192+
justifyContent: 'center',
193+
})}
194+
>
195+
<Text
196+
sx={t => ({
197+
display: 'inline-flex',
198+
alignItems: 'center',
199+
gap: t.space.$2,
200+
})}
201+
>
202+
<Icon
203+
icon={Users}
204+
size='md'
205+
colorScheme='neutral'
206+
/>
207+
<Text
208+
as='span'
209+
colorScheme='inherit'
210+
localizationKey={localizationKeys('organizationProfile.start.membershipSeatUsageLabel', {
211+
count: memberships.count + (invitations?.count ?? 0),
212+
limit: organization.maxAllowedMemberships,
213+
})}
214+
/>
215+
</Text>
216+
</Box>
217+
) : null}
176218
</Col>
177219
);
178220
});

0 commit comments

Comments
 (0)