diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 95d14949135..80403e9e330 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -161,6 +161,7 @@ export const enUS: LocalizationResource = {
},
},
reSubscribe: 'Resubscribe',
+ seats: 'Seats',
seeAllFeatures: 'See all features',
startFreeTrial: 'Start free trial',
startFreeTrial__days: 'Start {{days}}-day free trial',
diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts
index 4596b067216..0c471cf14ee 100644
--- a/packages/shared/src/types/localization.ts
+++ b/packages/shared/src/types/localization.ts
@@ -187,6 +187,7 @@ export type __internal_LocalizationResource = {
cancelSubscription: LocalizationValue;
keepSubscription: LocalizationValue;
reSubscribe: LocalizationValue;
+ seats: LocalizationValue;
subscribe: LocalizationValue;
startFreeTrial: LocalizationValue;
startFreeTrial__days: LocalizationValue<'days'>;
diff --git a/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx b/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx
index 43dc1b4a052..aa40bc8ced8 100644
--- a/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx
+++ b/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx
@@ -1,8 +1,9 @@
import type { BillingPlanResource, BillingSubscriptionItemResource } from '@clerk/shared/types';
-import { useMemo } from 'react';
+import { Fragment, useMemo } from 'react';
import { useProtect } from '@/ui/common/Gate';
import { ProfileSection } from '@/ui/elements/Section';
+import { common } from '@/ui/styledSystem';
import {
normalizeFormatted,
@@ -14,7 +15,7 @@ import {
} from '../../contexts';
import type { LocalizationKey } from '../../customizables';
import { Col, Flex, Icon, localizationKeys, Span, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables';
-import { ArrowsUpDown, CogFilled, Plans, Plus } from '../../icons';
+import { ArrowsUpDown, CogFilled, Plans, Plus, Users } from '../../icons';
import { useRouter } from '../../router';
import { SubscriptionBadge } from './badge';
@@ -50,7 +51,7 @@ export function SubscriptionsList({
const isManageButtonVisible = canManageBilling && !hasActiveFreePlan && subscriptionItems.length > 0;
- const sortedSubscriptions = useMemo(
+ const sortedSubscriptionItems = useMemo(
() =>
subscriptionItems.sort((a, b) => {
// always put active subscriptions first
@@ -78,7 +79,18 @@ export function SubscriptionsList({
})}
>
{subscriptionItems.length > 0 && (
-
+ ({
+ overflow: 'hidden',
+ 'tr > td': {
+ paddingTop: t.space.$3,
+ paddingBottom: t.space.$3,
+ paddingInlineStart: t.space.$3,
+ paddingInlineEnd: t.space.$3,
+ },
+ })}
+ tableHeadVisuallyHidden
+ >
|
|
- {sortedSubscriptions.map(subscription => (
- (
+
))}
@@ -152,78 +164,153 @@ export function SubscriptionsList({
);
}
-function SubscriptionRow({ subscription, length }: { subscription: BillingSubscriptionItemResource; length: number }) {
+function SubscriptionItemRow({
+ subscriptionItem,
+ length,
+}: {
+ subscriptionItem: BillingSubscriptionItemResource;
+ length: number;
+}) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const fee = subscription.planPeriod === 'annual' ? subscription.plan.annualFee! : subscription.plan.fee;
+ const fee = subscriptionItem.planPeriod === 'annual' ? subscriptionItem.plan.annualFee! : subscriptionItem.plan.fee!;
const { captionForSubscription } = usePlansContext();
const feeFormatted = useMemo(() => {
return normalizeFormatted(fee.amountFormatted);
}, [fee.amountFormatted]);
+
+ const subItemSeatsQty = subscriptionItem.seats?.quantity;
+
return (
-
-
-
-
+ {
+ if (subscriptionItem.status === 'upcoming') {
+ return {
+ background: common.mutedBackground(t),
+ };
+ }
+
+ return {};
+ }}
+ >
+ |
+
+
+ ({
+ width: t.sizes.$4,
+ height: t.sizes.$4,
+ opacity: t.opacity.$inactive,
+ color: t.colors.$colorMutedForeground,
+ })}
+ />
+ ({ marginInlineEnd: t.sizes.$1 })}
+ >
+ {subscriptionItem.plan.name}
+
+ {subscriptionItem.isFreeTrial || length > 1 || !!subscriptionItem.canceledAt ? (
+
+ ) : null}
+
+
+ {(!subscriptionItem.plan.isDefault || subscriptionItem.status === 'upcoming') && (
+ // here
+
+ )}
+
+ |
+ ({
+ textAlign: 'end',
+ })}
+ >
+
+ {fee.currencySymbol}
+ {feeFormatted}
+ {fee.amount > 0 && (
+ ({
+ color: t.colors.$colorMutedForeground,
+ textTransform: 'lowercase',
+ ':before': {
+ content: '"/"',
+ marginInline: t.space.$1,
+ },
+ })}
+ localizationKey={
+ subscriptionItem.planPeriod === 'annual'
+ ? localizationKeys('billing.year')
+ : localizationKeys('billing.month')
+ }
+ />
+ )}
+
+ |
+
+ {typeof subItemSeatsQty !== 'undefined' ? (
+ {
+ if (subscriptionItem.status === 'upcoming') {
+ return {
+ background: common.mutedBackground(t),
+ };
+ }
+
+ return {};
+ }}
+ >
+ |
+
+
+ ({
+ width: t.sizes.$4,
+ height: t.sizes.$4,
+ opacity: t.opacity.$inactive,
+ color: t.colors.$colorMutedForeground,
+ })}
+ />
+ ({ marginInlineEnd: t.sizes.$1 })}
+ localizationKey={localizationKeys('billing.seats')}
+ />
+
+
+ |
+ ({
+ textAlign: 'end',
+ })}
>
- ({
- width: t.sizes.$4,
- height: t.sizes.$4,
- opacity: t.opacity.$inactive,
- })}
- />
({ marginInlineEnd: t.sizes.$1 })}
- >
- {subscription.plan.name}
-
- {subscription.isFreeTrial || length > 1 || !!subscription.canceledAt ? (
-
- ) : null}
-
-
- {(!subscription.plan.isDefault || subscription.status === 'upcoming') && (
- // here
-
- )}
-
- |
- ({
- textAlign: 'end',
- })}
- >
-
- {fee.currencySymbol}
- {feeFormatted}
- {fee.amount > 0 && (
- ({
- color: t.colors.$colorMutedForeground,
- textTransform: 'lowercase',
- ':before': {
- content: '"/"',
- marginInline: t.space.$1,
- },
- })}
localizationKey={
- subscription.planPeriod === 'annual'
- ? localizationKeys('billing.year')
- : localizationKeys('billing.month')
+ subItemSeatsQty === null
+ ? localizationKeys('billing.pricingTable.seatCost.unlimitedSeats')
+ : localizationKeys('billing.pricingTable.seatCost.upToSeats', { endsAfterBlock: subItemSeatsQty })
}
/>
- )}
-
- |
-
+ |
+
+ ) : null}
+
);
}