Skip to content

Commit 23edb7d

Browse files
committed
feat(clerk-js,shared): make it easier to cancel a subscription
1 parent 5966383 commit 23edb7d

File tree

4 files changed

+88
-76
lines changed

4 files changed

+88
-76
lines changed

packages/clerk-js/sandbox/template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@
358358
<script
359359
type="text/javascript"
360360
src="/<%= htmlRspackPlugin.files.js[0] %>"
361-
data-clerk-publishable-key="pk_test_dG91Y2hlZC1sYWR5YmlyZC0yMy5jbGVyay5hY2NvdW50cy5kZXYk"
361+
data-clerk-publishable-key="pk_test_ZmFpdGhmdWwtZG9yeS04Ni5jbGVyay5hY2NvdW50cy5sY2xjbGVyay5jb20k"
362362
></script>
363363
<script
364364
type="text/javascript"

packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx

Lines changed: 83 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { CardAlert } from '@/ui/elements/Card/CardAlert';
1818
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
1919
import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
2020
import { LineItems } from '@/ui/elements/LineItems';
21-
import { ThreeDotsMenu } from '@/ui/elements/ThreeDotsMenu';
2221
import { handleError } from '@/ui/utils/errorHandler';
2322
import { formatDate } from '@/ui/utils/formatDate';
2423

@@ -398,83 +397,93 @@ const SubscriptionCardActions = ({ subscription }: { subscription: BillingSubscr
398397
[__internal_openCheckout, revalidateAll, portalRoot, setIsOpen],
399398
);
400399

401-
const actions = React.useMemo(() => {
402-
if (!canManageBilling) {
403-
return [];
404-
}
400+
const handleSwitchPlan = useCallback(() => {
401+
openCheckout({
402+
planId: subscription.plan.id,
403+
planPeriod: subscription.planPeriod === 'month' ? 'annual' : 'month',
404+
for: subscriberType,
405+
});
406+
}, [openCheckout, subscription.plan.id, subscription.planPeriod, subscriberType]);
407+
408+
const handleCancelSubscription = useCallback(() => {
409+
setSubscription(subscription);
410+
setConfirmationOpen(true);
411+
}, [subscription, setSubscription, setConfirmationOpen]);
412+
413+
const handleResubscribe = useCallback(() => {
414+
openCheckout({
415+
planId: subscription.plan.id,
416+
planPeriod: subscription.planPeriod,
417+
for: subscriberType,
418+
});
419+
}, [openCheckout, subscription.plan.id, subscription.planPeriod, subscriberType]);
420+
421+
if (!canManageBilling) {
422+
return null;
423+
}
405424

406-
return [
407-
isSwitchable
408-
? {
409-
label:
410-
subscription.planPeriod === 'month'
411-
? localizationKeys('billing.switchToAnnualWithAnnualPrice', {
412-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
413-
price: normalizeFormatted(subscription.plan.annualFee!.amountFormatted),
414-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
415-
currency: subscription.plan.annualFee!.currencySymbol,
416-
})
417-
: localizationKeys('billing.switchToMonthlyWithPrice', {
418-
price: normalizeFormatted(subscription.plan.fee.amountFormatted),
419-
currency: subscription.plan.fee.currencySymbol,
420-
}),
421-
onClick: () => {
422-
openCheckout({
423-
planId: subscription.plan.id,
424-
planPeriod: subscription.planPeriod === 'month' ? 'annual' : 'month',
425-
for: subscriberType,
426-
});
427-
},
428-
}
429-
: null,
430-
isCancellable
431-
? {
432-
isDestructive: true,
433-
label: subscription.isFreeTrial
434-
? localizationKeys('billing.cancelFreeTrial', {
435-
plan: subscription.plan.name,
436-
})
437-
: localizationKeys('billing.cancelSubscription'),
438-
onClick: () => {
439-
setSubscription(subscription);
440-
setConfirmationOpen(true);
441-
},
442-
}
443-
: null,
444-
isReSubscribable
445-
? {
446-
label: localizationKeys('billing.reSubscribe'),
447-
onClick: () => {
448-
openCheckout({
449-
planId: subscription.plan.id,
450-
planPeriod: subscription.planPeriod,
451-
for: subscriberType,
452-
});
453-
},
454-
}
455-
: null,
456-
].filter(a => a !== null);
457-
}, [
458-
isSwitchable,
459-
subscription,
460-
isCancellable,
461-
openCheckout,
462-
subscriberType,
463-
setSubscription,
464-
canManageBilling,
465-
isReSubscribable,
466-
setConfirmationOpen,
467-
]);
425+
const showSwitchButton = isSwitchable;
426+
const showCancelButton = isCancellable;
427+
const showResubscribeButton = isReSubscribable;
468428

469-
if (actions.length === 0) {
429+
if (!showSwitchButton && !showCancelButton && !showResubscribeButton) {
470430
return null;
471431
}
472432

473433
return (
474-
<ThreeDotsMenu
475-
variant='bordered'
476-
actions={actions}
477-
/>
434+
<Flex
435+
elementDescriptor={descriptors.subscriptionDetailsCardActions}
436+
gap={2}
437+
sx={t => ({
438+
paddingInline: t.space.$3,
439+
paddingBlock: t.space.$3,
440+
borderBlockStartWidth: t.borderWidths.$normal,
441+
borderBlockStartStyle: t.borderStyles.$solid,
442+
borderBlockStartColor: t.colors.$borderAlpha100,
443+
})}
444+
>
445+
{showSwitchButton && (
446+
<Button
447+
elementDescriptor={descriptors.subscriptionDetailsActionButton}
448+
variant='outline'
449+
size='xs'
450+
textVariant='buttonSmall'
451+
onClick={handleSwitchPlan}
452+
localizationKey={
453+
subscription.planPeriod === 'month'
454+
? localizationKeys('billing.switchToAnnual')
455+
: localizationKeys('billing.switchToMonthly')
456+
}
457+
/>
458+
)}
459+
{showResubscribeButton && (
460+
<Button
461+
elementDescriptor={descriptors.subscriptionDetailsActionButton}
462+
variant='outline'
463+
size='xs'
464+
textVariant='buttonSmall'
465+
onClick={handleResubscribe}
466+
localizationKey={localizationKeys('billing.reSubscribe')}
467+
/>
468+
)}
469+
{showCancelButton && (
470+
<Button
471+
elementDescriptor={descriptors.subscriptionDetailsCancelButton}
472+
variant='ghost'
473+
size='xs'
474+
textVariant='buttonSmall'
475+
colorScheme='danger'
476+
onClick={handleCancelSubscription}
477+
localizationKey={
478+
subscription.isFreeTrial
479+
? localizationKeys('billing.cancelFreeTrial', {
480+
plan: subscription.plan.name,
481+
})
482+
: localizationKeys('billing.cancelSubscription')
483+
}
484+
/>
485+
)}
486+
</Flex>
478487
);
479488
};
480489

@@ -539,7 +548,6 @@ const SubscriptionCard = ({ subscription }: { subscription: BillingSubscriptionI
539548

540549
{/* Pricing details */}
541550
<Flex
542-
elementDescriptor={descriptors.subscriptionDetailsCardActions}
543551
justify='between'
544552
align='center'
545553
>
@@ -555,8 +563,6 @@ const SubscriptionCard = ({ subscription }: { subscription: BillingSubscriptionI
555563
{fee.amountFormatted} /{' '}
556564
{t(localizationKeys(`billing.${subscription.planPeriod === 'month' ? 'month' : 'year'}`))}
557565
</Text>
558-
559-
<SubscriptionCardActions subscription={subscription} />
560566
</Flex>
561567
</Col>
562568

@@ -599,6 +605,8 @@ const SubscriptionCard = ({ subscription }: { subscription: BillingSubscriptionI
599605
value={formatDate(subscription.periodStart)}
600606
/>
601607
) : null}
608+
609+
<SubscriptionCardActions subscription={subscription} />
602610
</Col>
603611
);
604612
};

packages/clerk-js/src/ui/customizables/elementDescriptors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
506506
'subscriptionDetailsCardBody',
507507
'subscriptionDetailsCardFooter',
508508
'subscriptionDetailsCardActions',
509+
'subscriptionDetailsActionButton',
510+
'subscriptionDetailsCancelButton',
509511
'subscriptionDetailsDetailRow',
510512
'subscriptionDetailsDetailRowLabel',
511513
'subscriptionDetailsDetailRowValue',

packages/shared/src/types/appearance.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,8 @@ export type ElementsConfig = {
641641
subscriptionDetailsCardBody: WithOptions;
642642
subscriptionDetailsCardFooter: WithOptions;
643643
subscriptionDetailsCardActions: WithOptions;
644+
subscriptionDetailsActionButton: WithOptions;
645+
subscriptionDetailsCancelButton: WithOptions;
644646
subscriptionDetailsDetailRow: WithOptions;
645647
subscriptionDetailsDetailRowLabel: WithOptions;
646648
subscriptionDetailsDetailRowValue: WithOptions;

0 commit comments

Comments
 (0)