Skip to content

Commit 30bac73

Browse files
authored
chore(clerk-js): Tidy up layout and logic of PlanDetails drawer (#5928)
1 parent ea622ba commit 30bac73

File tree

7 files changed

+115
-79
lines changed

7 files changed

+115
-79
lines changed

.changeset/metal-tips-joke.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
---
4+
5+
Clean up layout and logic of the `PlanDetails` drawer

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

Lines changed: 99 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
CommerceSubscriptionResource,
99
} from '@clerk/types';
1010
import * as React from 'react';
11-
import { useState } from 'react';
11+
import { useMemo, useState } from 'react';
1212

1313
import { useProtect } from '../../common';
1414
import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
@@ -19,7 +19,9 @@ import { handleError } from '../../utils';
1919
export const PlanDetails = (props: __internal_PlanDetailsProps) => {
2020
return (
2121
<SubscriberTypeContext.Provider value={props.subscriberType || 'user'}>
22-
<PlanDetailsInternal {...props} />
22+
<Drawer.Content>
23+
<PlanDetailsInternal {...props} />
24+
</Drawer.Content>
2325
</SubscriberTypeContext.Provider>
2426
);
2527
};
@@ -28,18 +30,22 @@ const PlanDetailsInternal = ({
2830
plan,
2931
onSubscriptionCancel,
3032
portalRoot,
31-
planPeriod: _planPeriod = 'month',
33+
initialPlanPeriod = 'month',
3234
}: __internal_PlanDetailsProps) => {
3335
const clerk = useClerk();
3436
const { organization } = useOrganization();
3537
const [showConfirmation, setShowConfirmation] = useState(false);
3638
const [isSubmitting, setIsSubmitting] = useState(false);
3739
const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
38-
const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(_planPeriod);
40+
const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
3941

4042
const { setIsOpen } = useDrawerContext();
41-
const { activeOrUpcomingSubscription, revalidateAll, buttonPropsForPlan, isDefaultPlanImplicitlyActiveOrUpcoming } =
42-
usePlansContext();
43+
const {
44+
activeOrUpcomingSubscriptionBasedOnPlanPeriod,
45+
revalidateAll,
46+
buttonPropsForPlan,
47+
isDefaultPlanImplicitlyActiveOrUpcoming,
48+
} = usePlansContext();
4349
const subscriberType = useSubscriberTypeContext();
4450
const canManageBilling = useProtect(
4551
has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
@@ -49,7 +55,7 @@ const PlanDetailsInternal = ({
4955
return null;
5056
}
5157

52-
const subscription = activeOrUpcomingSubscription(plan);
58+
const subscription = activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod);
5359

5460
const handleClose = () => {
5561
if (setIsOpen) {
@@ -105,7 +111,7 @@ const PlanDetailsInternal = ({
105111
};
106112

107113
return (
108-
<Drawer.Content>
114+
<>
109115
<Drawer.Header
110116
sx={t =>
111117
!hasFeatures
@@ -209,7 +215,10 @@ const PlanDetailsInternal = ({
209215
/>
210216
) : (
211217
<Col gap={4}>
212-
{!!subscription && subscription.planPeriod === 'month' && plan.annualMonthlyAmount > 0 ? (
218+
{!!subscription &&
219+
subscription.planPeriod === 'month' &&
220+
plan.annualMonthlyAmount > 0 &&
221+
planPeriod === 'annual' ? (
213222
<Button
214223
block
215224
variant='bordered'
@@ -220,7 +229,7 @@ const PlanDetailsInternal = ({
220229
localizationKey={localizationKeys('commerce.switchToAnnual')}
221230
/>
222231
) : null}
223-
{!!subscription && subscription.planPeriod === 'annual' ? (
232+
{!!subscription && subscription.planPeriod === 'annual' && planPeriod === 'month' ? (
224233
<Button
225234
block
226235
variant='bordered'
@@ -314,7 +323,7 @@ const PlanDetailsInternal = ({
314323
)}
315324
</Drawer.Confirmation>
316325
) : null}
317-
</Drawer.Content>
326+
</>
318327
);
319328
};
320329

@@ -338,7 +347,14 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
338347

339348
const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
340349

341-
const showBadge = !!subscription || isImplicitlyActiveOrUpcoming;
350+
const showBadge = !!subscription;
351+
352+
const getPlanFee = useMemo(() => {
353+
if (plan.annualMonthlyAmount <= 0) {
354+
return plan.amountFormatted;
355+
}
356+
return planPeriod === 'annual' ? plan.annualMonthlyAmountFormatted : plan.amountFormatted;
357+
}, [plan, planPeriod]);
342358

343359
return (
344360
<Box
@@ -362,27 +378,58 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
362378
</Box>
363379
) : null}
364380

365-
{plan.avatarUrl ? (
366-
<Avatar
367-
boxElementDescriptor={descriptors.planDetailAvatar}
368-
size={_ => 40}
369-
title={plan.name}
370-
initials={plan.name[0]}
371-
rounded={false}
372-
imageUrl={plan.avatarUrl}
373-
sx={t => ({
374-
marginBlockEnd: t.space.$3,
375-
})}
376-
/>
377-
) : null}
378-
<Box
379-
sx={t => ({
380-
paddingInlineEnd: t.space.$10,
381-
})}
381+
<Col
382+
gap={3}
383+
elementDescriptor={descriptors.planDetailBadgeAvatarTitleDescriptionContainer}
382384
>
383-
<Flex
384-
gap={2}
385-
align='center'
385+
{showBadge ? (
386+
<Flex
387+
align='center'
388+
gap={3}
389+
elementDescriptor={descriptors.planDetailBadgeContainer}
390+
sx={t => ({
391+
paddingInlineEnd: t.space.$10,
392+
})}
393+
>
394+
{subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
395+
<Badge
396+
elementDescriptor={descriptors.planDetailBadge}
397+
localizationKey={localizationKeys('badge__activePlan')}
398+
colorScheme={'secondary'}
399+
/>
400+
) : (
401+
<Badge
402+
elementDescriptor={descriptors.planDetailBadge}
403+
localizationKey={localizationKeys('badge__upcomingPlan')}
404+
colorScheme={'primary'}
405+
/>
406+
)}
407+
{!!subscription && (
408+
<Text
409+
elementDescriptor={descriptors.planDetailCaption}
410+
variant={'caption'}
411+
localizationKey={captionForSubscription(subscription)}
412+
colorScheme='secondary'
413+
/>
414+
)}
415+
</Flex>
416+
) : null}
417+
{plan.avatarUrl ? (
418+
<Avatar
419+
boxElementDescriptor={descriptors.planDetailAvatar}
420+
size={_ => 40}
421+
title={plan.name}
422+
initials={plan.name[0]}
423+
rounded={false}
424+
imageUrl={plan.avatarUrl}
425+
sx={t => ({
426+
marginBlockEnd: t.space.$3,
427+
})}
428+
/>
429+
) : null}
430+
<Col
431+
gap={1}
432+
elementDescriptor={descriptors.planDetailTitleDescriptionContainer}
386433
>
387434
<Heading
388435
elementDescriptor={descriptors.planDetailTitle}
@@ -391,37 +438,17 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
391438
>
392439
{plan.name}
393440
</Heading>
394-
{showBadge ? (
395-
<Flex elementDescriptor={descriptors.planDetailBadgeContainer}>
396-
{subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
397-
<Badge
398-
elementDescriptor={descriptors.planDetailBadge}
399-
localizationKey={localizationKeys('badge__activePlan')}
400-
colorScheme={'secondary'}
401-
/>
402-
) : (
403-
<Badge
404-
elementDescriptor={descriptors.planDetailBadge}
405-
localizationKey={localizationKeys('badge__upcomingPlan')}
406-
colorScheme={'primary'}
407-
/>
408-
)}
409-
</Flex>
441+
{plan.description ? (
442+
<Text
443+
elementDescriptor={descriptors.planDetailDescription}
444+
variant='subtitle'
445+
colorScheme='secondary'
446+
>
447+
{plan.description}
448+
</Text>
410449
) : null}
411-
</Flex>
412-
{plan.description ? (
413-
<Text
414-
elementDescriptor={descriptors.planDetailDescription}
415-
variant='subtitle'
416-
colorScheme='secondary'
417-
sx={t => ({
418-
marginTop: t.space.$1,
419-
})}
420-
>
421-
{plan.description}
422-
</Text>
423-
) : null}
424-
</Box>
450+
</Col>
451+
</Col>
425452

426453
<Flex
427454
elementDescriptor={descriptors.planDetailFeeContainer}
@@ -439,9 +466,7 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
439466
colorScheme='body'
440467
>
441468
{plan.currencySymbol}
442-
{(subscription && subscription.planPeriod === 'annual') || planPeriod === 'annual'
443-
? plan.annualMonthlyAmountFormatted
444-
: plan.amountFormatted}
469+
{getPlanFee}
445470
</Text>
446471
<Text
447472
elementDescriptor={descriptors.planDetailFeePeriod}
@@ -459,7 +484,7 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
459484
</>
460485
</Flex>
461486

462-
{!subscription || (subscription.planPeriod === 'month' && plan.annualMonthlyAmount > 0) ? (
487+
{plan.annualMonthlyAmount > 0 ? (
463488
<Box
464489
elementDescriptor={descriptors.planDetailPeriodToggle}
465490
sx={t => ({
@@ -473,15 +498,17 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
473498
label={localizationKeys('commerce.billedAnnually')}
474499
/>
475500
</Box>
476-
) : null}
477-
478-
{!!subscription && (
501+
) : (
479502
<Text
480-
elementDescriptor={descriptors.planDetailCaption}
481-
variant={'caption'}
482-
localizationKey={captionForSubscription(subscription)}
503+
elementDescriptor={descriptors.pricingTableCardFeePeriodNotice}
504+
variant='caption'
483505
colorScheme='secondary'
506+
localizationKey={
507+
plan.isDefault ? localizationKeys('commerce.alwaysFree') : localizationKeys('commerce.billedMonthlyOnly')
508+
}
484509
sx={t => ({
510+
justifySelf: 'flex-start',
511+
alignSelf: 'center',
485512
marginTop: t.space.$3,
486513
})}
487514
/>

packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ function Card(props: CardProps) {
117117
clerk.__internal_openPlanDetails({
118118
plan,
119119
subscriberType,
120-
planPeriod,
120+
initialPlanPeriod: planPeriod,
121121
portalRoot,
122122
});
123123
};
@@ -414,10 +414,11 @@ const CardHeader = React.forwardRef<HTMLDivElement, CardHeaderProps>((props, ref
414414
localizationKey={
415415
plan.isDefault ? localizationKeys('commerce.alwaysFree') : localizationKeys('commerce.billedMonthlyOnly')
416416
}
417-
sx={{
417+
sx={t => ({
418418
justifySelf: 'flex-start',
419419
alignSelf: 'center',
420-
}}
420+
marginTop: t.space.$1,
421+
})}
421422
/>
422423
)}
423424
</Box>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,13 +339,14 @@ export const usePlansContext = () => {
339339
appearance,
340340
newSubscriptionRedirectUrl,
341341
}: HandleSelectPlanProps) => {
342-
const subscription = activeOrUpcomingSubscription(plan);
342+
const subscription = activeOrUpcomingSubscriptionWithPlanPeriod(plan, planPeriod);
343343

344344
const portalRoot = getClosestProfileScrollBox(mode, event);
345345

346346
if (subscription && subscription.planPeriod === planPeriod && !subscription.canceledAt) {
347347
clerk.__internal_openPlanDetails({
348348
plan,
349+
initialPlanPeriod: planPeriod,
349350
subscriberType,
350351
onSubscriptionCancel: () => {
351352
revalidateAll();

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,12 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
288288
'pricingTableMatrixFooter',
289289

290290
'planDetailHeader',
291-
'planDetailAvatarBadgeContainer',
292291
'planDetailAvatar',
292+
'planDetailBadgeAvatarTitleDescriptionContainer',
293293
'planDetailBadgeContainer',
294294
'planDetailBadge',
295295
'planDetailTitle',
296+
'planDetailTitleDescriptionContainer',
296297
'planDetailDescription',
297298
'planDetailAction',
298299
'planDetailFeeContainer',

packages/types/src/appearance.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,12 @@ export type ElementsConfig = {
421421
pricingTableMatrixFooter: WithOptions;
422422

423423
planDetailHeader: WithOptions;
424-
planDetailAvatarBadgeContainer: WithOptions;
425424
planDetailAvatar: WithOptions;
425+
planDetailBadgeAvatarTitleDescriptionContainer: WithOptions;
426426
planDetailBadgeContainer: WithOptions;
427427
planDetailBadge: WithOptions;
428428
planDetailTitle: WithOptions;
429+
planDetailTitleDescriptionContainer: WithOptions;
429430
planDetailDescription: WithOptions;
430431
planDetailAction: WithOptions;
431432
planDetailFeeContainer: WithOptions;

packages/types/src/clerk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1641,7 +1641,7 @@ export type __internal_PlanDetailsProps = {
16411641
appearance?: PlanDetailTheme;
16421642
plan?: CommercePlanResource;
16431643
subscriberType?: CommerceSubscriberType;
1644-
planPeriod?: CommerceSubscriptionPlanPeriod;
1644+
initialPlanPeriod?: CommerceSubscriptionPlanPeriod;
16451645
onSubscriptionCancel?: () => void;
16461646
portalId?: string;
16471647
portalRoot?: PortalRoot;

0 commit comments

Comments
 (0)