diff --git a/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx b/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx index 43dc1b4a052..0269aadc774 100644 --- a/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx +++ b/packages/ui/src/components/Subscriptions/SubscriptionsList.tsx @@ -1,5 +1,5 @@ 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'; @@ -14,7 +14,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 +50,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 @@ -94,11 +94,11 @@ export function SubscriptionsList({ - {sortedSubscriptions.map(subscription => ( - ( + ))} @@ -152,78 +152,132 @@ 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 endsAfterBlock = subscriptionItem.seats?.quantity; + return ( - - - - + + + + + ({ + width: t.sizes.$4, + height: t.sizes.$4, + opacity: t.opacity.$inactive, + })} + /> + ({ 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 endsAfterBlock !== 'undefined' ? ( + + + + + ({ + width: t.sizes.$4, + height: t.sizes.$4, + opacity: t.opacity.$inactive, + })} + /> + ({ marginInlineEnd: t.sizes.$1 })} + > + 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') + endsAfterBlock === null + ? localizationKeys('billing.pricingTable.seatCost.unlimitedSeats') + : localizationKeys('billing.pricingTable.seatCost.upToSeats', { endsAfterBlock }) } /> - )} - - - + + + ) : null} + ); }