diff --git a/.changeset/tender-glasses-post.md b/.changeset/tender-glasses-post.md new file mode 100644 index 00000000000..3b2e5e42e60 --- /dev/null +++ b/.changeset/tender-glasses-post.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Sort subscriptions and add free plan to subsciption list if it's upcoming diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 346b4b026ee..c6bfe6f055e 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -23,8 +23,8 @@ { "path": "./dist/pricingTable*.js", "maxSize": "4.02KB" }, { "path": "./dist/checkout*.js", "maxSize": "5.79KB" }, { "path": "./dist/paymentSources*.js", "maxSize": "9.06KB" }, - { "path": "./dist/up-billing-page*.js", "maxSize": "2.5KB" }, - { "path": "./dist/op-billing-page*.js", "maxSize": "2.5KB" }, + { "path": "./dist/up-billing-page*.js", "maxSize": "3.0KB" }, + { "path": "./dist/op-billing-page*.js", "maxSize": "3.0KB" }, { "path": "./dist/sessionTasks*.js", "maxSize": "1KB" } ] } diff --git a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx index 156a48d9f73..2d8ce1f86cd 100644 --- a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx +++ b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx @@ -1,4 +1,4 @@ -import type { CommerceSubscriptionResource } from '@clerk/types'; +import type { CommercePlanResource, CommerceSubscriptionResource } from '@clerk/types'; import { useProtect } from '../../common'; import { usePlansContext, useSubscriberTypeContext } from '../../contexts'; @@ -20,13 +20,32 @@ import { } from '../../customizables'; import { CogFilled, Plans } from '../../icons'; +// TODO(commerce): This will probably need to handled by FAPI +function includeFreeToSubscriptionsLint(subscriptions: CommerceSubscriptionResource[], plans: CommercePlanResource[]) { + const cancelledSubscription = subscriptions.find(sub => sub.canceledAt && sub.status === 'active'); + const hasUpcomingSubscription = subscriptions.some(sub => sub.status === 'upcoming'); + const freePlan = plans?.find(plan => plan.hasBaseFee === false && plan.amount === 0); + + if (cancelledSubscription && !hasUpcomingSubscription && freePlan) { + return [ + ...subscriptions, + { + plan: freePlan, + periodStart: cancelledSubscription.periodEnd, + status: 'upcoming', + } as CommerceSubscriptionResource, + ]; + } + + return subscriptions; +} + export function SubscriptionsList() { - const { subscriptions, handleSelectPlan, captionForSubscription, canManageSubscription } = usePlansContext(); + const { subscriptions, plans, handleSelectPlan, captionForSubscription, canManageSubscription } = usePlansContext(); const subscriberType = useSubscriberTypeContext(); const canManageBilling = useProtect( has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user', ); - const handleSelectSubscription = ( subscription: CommerceSubscriptionResource, event?: React.MouseEvent, @@ -39,6 +58,21 @@ export function SubscriptionsList() { }); }; + const subscriptionsWithUpcomingFreePlan = includeFreeToSubscriptionsLint(subscriptions, plans); + + const sortedSubscriptions = subscriptionsWithUpcomingFreePlan.sort((a, b) => { + // alway put active subscriptions first + if (a.status === 'active' && b.status !== 'active') { + return -1; + } + + if (b.status === 'active' && a.status !== 'active') { + return 1; + } + + return 1; + }); + return ( @@ -49,7 +83,7 @@ export function SubscriptionsList() { - {subscriptions.map(subscription => ( + {sortedSubscriptions.map(subscription => ( @@ -71,12 +105,12 @@ export function SubscriptionsList() { > {subscription.plan.name} - {subscriptions.length > 1 || !!subscription.canceledAt ? ( + {sortedSubscriptions.length > 1 || !!subscription.canceledAt ? ( @@ -99,17 +133,19 @@ export function SubscriptionsList() { {subscription.planPeriod === 'annual' ? subscription.plan.annualMonthlyAmountFormatted : subscription.plan.amountFormatted} - ({ - color: t.colors.$colorTextSecondary, - textTransform: 'lowercase', - ':before': { - content: '"/"', - marginInline: t.space.$1, - }, - })} - localizationKey={localizationKeys('commerce.month')} - /> + {subscription.plan.amount > 0 && ( + ({ + color: t.colors.$colorTextSecondary, + textTransform: 'lowercase', + ':before': { + content: '"/"', + marginInline: t.space.$1, + }, + })} + localizationKey={localizationKeys('commerce.month')} + /> + )}
- {canManageSubscription({ subscription }) && ( + {canManageSubscription({ subscription }) && subscription.id && (