Skip to content

Commit 06067d2

Browse files
committed
fix(clerk-js): Improve flows for switching between plans
1 parent 1df4b8a commit 06067d2

File tree

3 files changed

+54
-11
lines changed

3 files changed

+54
-11
lines changed

packages/clerk-js/sandbox/template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@
437437
<script
438438
type="text/javascript"
439439
src="/<%= htmlRspackPlugin.files.js[0] %>"
440-
data-clerk-publishable-key="pk_test_dG91Y2hlZC1sYWR5YmlyZC0yMy5jbGVyay5hY2NvdW50cy5kZXYk"
440+
data-clerk-publishable-key="pk_test_ZGV2b3RlZC15YWstMjkuY2xlcmsuYWNjb3VudHNzdGFnZS5kZXYk"
441441
></script>
442442
<script
443443
type="text/javascript"

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ function Card(props: CardProps) {
104104
const collapseFeatures = pricingTableProps.collapseFeatures || false;
105105
const { id, slug } = plan;
106106

107-
const { buttonPropsForPlan, upcomingSubscriptionsExist, activeOrUpcomingSubscription } = usePlansContext();
107+
const { buttonPropsForPlan, upcomingSubscriptionsExist, activeOrUpcomingSubscriptionBasedOnPlanPeriod } =
108+
usePlansContext();
108109

109110
const showPlanDetails = (event?: React.MouseEvent<HTMLElement>) => {
110111
const portalRoot = getClosestProfileScrollBox(mode, event);
@@ -117,9 +118,12 @@ function Card(props: CardProps) {
117118
});
118119
};
119120

120-
const subscription = activeOrUpcomingSubscription(plan);
121-
const hasFeatures = plan.features.length > 0;
121+
const subscription = React.useMemo(
122+
() => activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod),
123+
[plan, planPeriod, activeOrUpcomingSubscriptionBasedOnPlanPeriod],
124+
);
122125
const isPlanActive = subscription?.status === 'active';
126+
const hasFeatures = plan.features.length > 0;
123127
const showStatusRow = !!subscription;
124128
const isEligibleForSwitch = planPeriod !== subscription?.planPeriod && !plan.isDefault;
125129
const isEligibleForSwitchToAnnual = isEligibleForSwitch && plan.annualMonthlyAmount > 0 && planPeriod === 'annual';

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

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,42 @@ export const usePlansContext = () => {
174174
[ctx.subscriptions],
175175
);
176176

177+
// returns all subscriptions for a plan that are active or upcoming
178+
const activeAndUpcomingSubscriptions = useCallback(
179+
(plan: CommercePlanResource) => {
180+
return ctx.subscriptions
181+
.filter(subscription => subscription.plan.id === plan.id)
182+
.filter(subscription => subscription.status === 'active' || subscription.status === 'upcoming');
183+
},
184+
[ctx.subscriptions],
185+
);
186+
187+
// return the active or upcoming subscription for a plan based on the plan period, if there is no subscription for the plan period, return the first subscription
188+
const activeOrUpcomingSubscriptionWithPlanPeriod = useCallback(
189+
(plan: CommercePlanResource, planPeriod: CommerceSubscriptionPlanPeriod = 'month') => {
190+
const plansSubscriptions = activeAndUpcomingSubscriptions(plan);
191+
// Handle multiple subscriptions for the same plan
192+
if (plansSubscriptions.length > 1) {
193+
const subscriptionBaseOnPanPeriod = plansSubscriptions.find(subscription => {
194+
return subscription.planPeriod === planPeriod;
195+
});
196+
197+
if (subscriptionBaseOnPanPeriod) {
198+
return subscriptionBaseOnPanPeriod;
199+
}
200+
201+
return plansSubscriptions[0];
202+
}
203+
204+
if (plansSubscriptions.length === 1) {
205+
return plansSubscriptions[0];
206+
}
207+
208+
return undefined;
209+
},
210+
[activeAndUpcomingSubscriptions],
211+
);
212+
177213
const canManageSubscription = useCallback(
178214
({ plan, subscription: sub }: { plan?: CommercePlanResource; subscription?: CommerceSubscriptionResource }) => {
179215
const subscription = sub ?? (plan ? activeOrUpcomingSubscription(plan) : undefined);
@@ -209,7 +245,8 @@ export const usePlansContext = () => {
209245
colorScheme: 'secondary' | 'primary';
210246
isDisabled: boolean;
211247
} => {
212-
const subscription = sub ?? (plan ? activeOrUpcomingSubscription(plan) : undefined);
248+
const subscription =
249+
sub ?? (plan ? activeOrUpcomingSubscriptionWithPlanPeriod(plan, selectedPlanPeriod) : undefined);
213250
let _selectedPlanPeriod = selectedPlanPeriod;
214251
if (_selectedPlanPeriod === 'annual' && sub?.plan.annualMonthlyAmount === 0) {
215252
_selectedPlanPeriod = 'month';
@@ -220,8 +257,8 @@ export const usePlansContext = () => {
220257
const getLocalizationKey = () => {
221258
// Handle subscription cases
222259
if (subscription) {
223-
if (selectedPlanPeriod !== subscription.planPeriod && subscription.canceledAt) {
224-
if (selectedPlanPeriod === 'month') {
260+
if (_selectedPlanPeriod !== subscription.planPeriod && subscription.canceledAt) {
261+
if (_selectedPlanPeriod === 'month') {
225262
return localizationKeys('commerce.switchToMonthly');
226263
}
227264

@@ -234,8 +271,8 @@ export const usePlansContext = () => {
234271
return localizationKeys('commerce.reSubscribe');
235272
}
236273

237-
if (selectedPlanPeriod !== subscription.planPeriod) {
238-
if (selectedPlanPeriod === 'month') {
274+
if (_selectedPlanPeriod !== subscription.planPeriod) {
275+
if (_selectedPlanPeriod === 'month') {
239276
return localizationKeys('commerce.switchToMonthly');
240277
}
241278

@@ -264,7 +301,7 @@ export const usePlansContext = () => {
264301
isDisabled: !canManageBilling,
265302
};
266303
},
267-
[activeOrUpcomingSubscription, canManageBilling, ctx.subscriptions],
304+
[activeOrUpcomingSubscriptionWithPlanPeriod, canManageBilling, ctx.subscriptions],
268305
);
269306

270307
const captionForSubscription = useCallback((subscription: CommerceSubscriptionResource) => {
@@ -280,7 +317,7 @@ export const usePlansContext = () => {
280317
// handle the selection of a plan, either by opening the subscription details or checkout
281318
const handleSelectPlan = useCallback(
282319
({ plan, planPeriod, onSubscriptionChange, mode = 'mounted', event }: HandleSelectPlanProps) => {
283-
const subscription = activeOrUpcomingSubscription(plan);
320+
const subscription = activeOrUpcomingSubscriptionWithPlanPeriod(plan, planPeriod);
284321

285322
const portalRoot = getClosestProfileScrollBox(mode, event);
286323

@@ -324,6 +361,8 @@ export const usePlansContext = () => {
324361
...ctx,
325362
componentName,
326363
activeOrUpcomingSubscription,
364+
activeAndUpcomingSubscriptions,
365+
activeOrUpcomingSubscriptionBasedOnPlanPeriod: activeOrUpcomingSubscriptionWithPlanPeriod,
327366
isDefaultPlanImplicitlyActiveOrUpcoming,
328367
handleSelectPlan,
329368
buttonPropsForPlan,

0 commit comments

Comments
 (0)