Skip to content

Commit aa97231

Browse files
authored
feat(clerk-js,types): Support switching to free plan (#5810)
1 parent 47470c0 commit aa97231

File tree

7 files changed

+161
-158
lines changed

7 files changed

+161
-158
lines changed

.changeset/eager-goats-vanish.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/types': patch
4+
---
5+
6+
Add support for switching to the free plan

packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export const CheckoutComplete = ({
147147
as='h2'
148148
textVariant='h2'
149149
localizationKey={
150-
checkout.subscription?.status === 'active'
150+
checkout.totals.totalDueNow.amount > 0
151151
? localizationKeys('__experimental_commerce.checkout.title__paymentSuccessful')
152152
: localizationKeys('__experimental_commerce.checkout.title__subscriptionSuccessful')
153153
}
@@ -201,7 +201,7 @@ export const CheckoutComplete = ({
201201
}),
202202
})}
203203
localizationKey={
204-
checkout.subscription?.status === 'active'
204+
checkout.totals.totalDueNow.amount > 0
205205
? localizationKeys('__experimental_commerce.checkout.description__paymentSuccessful')
206206
: localizationKeys('__experimental_commerce.checkout.description__subscriptionSuccessful')
207207
}
@@ -240,14 +240,14 @@ export const CheckoutComplete = ({
240240
<LineItems.Group variant='secondary'>
241241
<LineItems.Title
242242
title={
243-
checkout.subscription?.status === 'active'
243+
checkout.totals.totalDueNow.amount > 0
244244
? localizationKeys('__experimental_commerce.checkout.lineItems.title__paymentMethod')
245245
: localizationKeys('__experimental_commerce.checkout.lineItems.title__subscriptionBegins')
246246
}
247247
/>
248248
<LineItems.Description
249249
text={
250-
checkout.subscription?.status === 'active'
250+
checkout.totals.totalDueNow.amount > 0
251251
? checkout.paymentSource
252252
? `${capitalize(checkout.paymentSource.cardType)}${checkout.paymentSource.last4}`
253253
: '–'

packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx

Lines changed: 87 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ export const PlanDetails = (props: __experimental_PlanDetailsProps) => {
3131
return (
3232
<SubscriberTypeContext.Provider value={props.subscriberType || 'user'}>
3333
<PlansContextProvider>
34-
<_PlanDetails {...props} />
34+
<PlanDetailsInternal {...props} />
3535
</PlansContextProvider>
3636
</SubscriberTypeContext.Provider>
3737
);
3838
};
3939

40-
const _PlanDetails = ({
40+
const PlanDetailsInternal = ({
4141
plan,
4242
onSubscriptionCancel,
4343
portalRoot,
@@ -51,7 +51,8 @@ const _PlanDetails = ({
5151
const [planPeriod, setPlanPeriod] = useState<__experimental_CommerceSubscriptionPlanPeriod>(_planPeriod);
5252

5353
const { setIsOpen } = useDrawerContext();
54-
const { activeOrUpcomingSubscription, revalidate, buttonPropsForPlan } = usePlansContext();
54+
const { activeOrUpcomingSubscription, revalidate, buttonPropsForPlan, isDefaultPlanImplicitlyActiveOrUpcoming } =
55+
usePlansContext();
5556
const subscriberType = useSubscriberTypeContext();
5657

5758
if (!plan) {
@@ -202,7 +203,7 @@ const _PlanDetails = ({
202203
</Drawer.Body>
203204
) : null}
204205

205-
{plan.amount > 0 ? (
206+
{!plan.isDefault || !isDefaultPlanImplicitlyActiveOrUpcoming ? (
206207
<Drawer.Footer>
207208
{subscription ? (
208209
subscription.canceledAt ? (
@@ -315,7 +316,11 @@ interface HeaderProps {
315316
const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
316317
const { plan, subscription, closeSlot, planPeriod, setPlanPeriod } = props;
317318

318-
const { captionForSubscription } = usePlansContext();
319+
const { captionForSubscription, isDefaultPlanImplicitlyActiveOrUpcoming, subscriptions } = usePlansContext();
320+
321+
const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
322+
323+
const showBadge = !!subscription || isImplicitlyActiveOrUpcoming;
319324

320325
return (
321326
<Box
@@ -352,14 +357,14 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
352357
})}
353358
/>
354359
) : null}
355-
{subscription ? (
360+
{showBadge ? (
356361
<Box
357362
elementDescriptor={descriptors.planDetailBadgeContainer}
358363
sx={t => ({
359364
marginBlockEnd: t.space.$3,
360365
})}
361366
>
362-
{subscription.status === 'active' ? (
367+
{subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
363368
<Badge
364369
elementDescriptor={descriptors.planDetailBadge}
365370
localizationKey={localizationKeys('badge__currentPlan')}
@@ -398,88 +403,86 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
398403
) : null}
399404
</Box>
400405

401-
{plan.amount > 0 ? (
402-
<Flex
403-
elementDescriptor={descriptors.planDetailFeeContainer}
404-
align='center'
405-
wrap='wrap'
406-
sx={t => ({
407-
marginTop: t.space.$3,
408-
columnGap: t.space.$1x5,
409-
})}
410-
>
411-
<>
412-
<Text
413-
elementDescriptor={descriptors.planDetailFee}
414-
variant='h1'
415-
colorScheme='body'
406+
<Flex
407+
elementDescriptor={descriptors.planDetailFeeContainer}
408+
align='center'
409+
wrap='wrap'
410+
sx={t => ({
411+
marginTop: t.space.$3,
412+
columnGap: t.space.$1x5,
413+
})}
414+
>
415+
<>
416+
<Text
417+
elementDescriptor={descriptors.planDetailFee}
418+
variant='h1'
419+
colorScheme='body'
420+
>
421+
{plan.currencySymbol}
422+
{(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual'
423+
? plan.annualMonthlyAmountFormatted
424+
: plan.amountFormatted}
425+
</Text>
426+
<Text
427+
elementDescriptor={descriptors.planDetailFeePeriod}
428+
variant='caption'
429+
colorScheme='secondary'
430+
sx={t => ({
431+
textTransform: 'lowercase',
432+
':before': {
433+
content: '"/"',
434+
marginInlineEnd: t.space.$1,
435+
},
436+
})}
437+
localizationKey={localizationKeys('__experimental_commerce.month')}
438+
/>
439+
{plan.annualMonthlyAmount > 0 ? (
440+
<Box
441+
elementDescriptor={descriptors.planDetailFeePeriodNotice}
442+
sx={[
443+
_ => ({
444+
width: '100%',
445+
display: 'grid',
446+
gridTemplateRows:
447+
(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual' ? '1fr' : '0fr',
448+
}),
449+
]}
450+
// @ts-ignore - Needed until React 19 support
451+
inert={
452+
(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual' ? 'true' : undefined
453+
}
416454
>
417-
{plan.currencySymbol}
418-
{(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual'
419-
? plan.annualMonthlyAmountFormatted
420-
: plan.amountFormatted}
421-
</Text>
422-
<Text
423-
elementDescriptor={descriptors.planDetailFeePeriod}
424-
variant='caption'
425-
colorScheme='secondary'
426-
sx={t => ({
427-
textTransform: 'lowercase',
428-
':before': {
429-
content: '"/"',
430-
marginInlineEnd: t.space.$1,
431-
},
432-
})}
433-
localizationKey={localizationKeys('__experimental_commerce.month')}
434-
/>
435-
{plan.annualMonthlyAmount > 0 ? (
436455
<Box
437-
elementDescriptor={descriptors.planDetailFeePeriodNotice}
438-
sx={[
439-
_ => ({
440-
width: '100%',
441-
display: 'grid',
442-
gridTemplateRows:
443-
(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual' ? '1fr' : '0fr',
444-
}),
445-
]}
446-
// @ts-ignore - Needed until React 19 support
447-
inert={
448-
(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual' ? 'true' : undefined
449-
}
456+
elementDescriptor={descriptors.planDetailFeePeriodNoticeInner}
457+
sx={{
458+
overflow: 'hidden',
459+
minHeight: 0,
460+
}}
450461
>
451-
<Box
452-
elementDescriptor={descriptors.planDetailFeePeriodNoticeInner}
453-
sx={{
454-
overflow: 'hidden',
455-
minHeight: 0,
456-
}}
462+
<Text
463+
elementDescriptor={descriptors.planDetailFeePeriodNoticeLabel}
464+
variant='caption'
465+
colorScheme='secondary'
466+
sx={t => ({
467+
width: '100%',
468+
display: 'flex',
469+
alignItems: 'center',
470+
columnGap: t.space.$1,
471+
})}
457472
>
458-
<Text
459-
elementDescriptor={descriptors.planDetailFeePeriodNoticeLabel}
460-
variant='caption'
461-
colorScheme='secondary'
462-
sx={t => ({
463-
width: '100%',
464-
display: 'flex',
465-
alignItems: 'center',
466-
columnGap: t.space.$1,
467-
})}
468-
>
469-
<Icon
470-
icon={InformationCircle}
471-
colorScheme='neutral'
472-
size='sm'
473-
aria-hidden
474-
/>{' '}
475-
<Span localizationKey={localizationKeys('__experimental_commerce.billedAnnually')} />
476-
</Text>
477-
</Box>
473+
<Icon
474+
icon={InformationCircle}
475+
colorScheme='neutral'
476+
size='sm'
477+
aria-hidden
478+
/>{' '}
479+
<Span localizationKey={localizationKeys('__experimental_commerce.billedAnnually')} />
480+
</Text>
478481
</Box>
479-
) : null}
480-
</>
481-
</Flex>
482-
) : null}
482+
</Box>
483+
) : null}
484+
</>
485+
</Flex>
483486

484487
{!!subscription && (
485488
<Text

0 commit comments

Comments
 (0)