@lukoweb/apitogo-plugin-dev-portal-billing 0.2.25 → 0.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +92 -2
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -231,7 +231,7 @@ const accountCtaLabel = (plan) => {
|
|
|
231
231
|
};
|
|
232
232
|
/** Custom-priced plans skip checkout and open the contact-sales form. */
|
|
233
233
|
const planCtaPath = (plan) => `${isPlanCustomPriced(plan) ? CONTACT_SALES_PATH : CHECKOUT_PATH}?planSku=${encodeURIComponent(plan.sku)}`;
|
|
234
|
-
const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, isCanceling = false, cancelDisabled = false, featured = false }) => {
|
|
234
|
+
const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, onSwitch, isSwitching = false, isCanceling = false, cancelDisabled = false, featured = false }) => {
|
|
235
235
|
const isActive = isSubscribed || isActiveSubscription(subscriptionStatus ?? "");
|
|
236
236
|
const features = listPlanFeatureLines(plan);
|
|
237
237
|
const displayPrice = formatPlanDisplayPrice(plan);
|
|
@@ -296,7 +296,31 @@ const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed =
|
|
|
296
296
|
className: "text-center text-xs text-muted-foreground",
|
|
297
297
|
children: "Unsubscribe is available after the portal is published."
|
|
298
298
|
}) : null
|
|
299
|
-
] }) : /* @__PURE__ */ jsx(
|
|
299
|
+
] }) : onSwitch && !isPlanCustomPriced(plan) ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
|
|
300
|
+
asChild: true,
|
|
301
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
302
|
+
variant: featured ? "default" : "outline",
|
|
303
|
+
className: "w-full",
|
|
304
|
+
disabled: isSwitching,
|
|
305
|
+
"data-testid": `switch-${plan.sku}`,
|
|
306
|
+
children: isSwitching ? "Switching…" : "Switch to this plan"
|
|
307
|
+
})
|
|
308
|
+
}), /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
|
|
309
|
+
"Switch to ",
|
|
310
|
+
plan.displayName,
|
|
311
|
+
"?"
|
|
312
|
+
] }), /* @__PURE__ */ jsxs(AlertDialogDescription, { children: [
|
|
313
|
+
"Your current plan is replaced by ",
|
|
314
|
+
plan.displayName,
|
|
315
|
+
". The API keys of your current plan stop working. Create a key for",
|
|
316
|
+
" ",
|
|
317
|
+
plan.displayName,
|
|
318
|
+
" on the API keys page."
|
|
319
|
+
] })] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, { children: "Keep current plan" }), /* @__PURE__ */ jsx(AlertDialogAction, {
|
|
320
|
+
onClick: () => onSwitch(plan.sku),
|
|
321
|
+
disabled: isSwitching,
|
|
322
|
+
children: "Switch plan"
|
|
323
|
+
})] })] })] }) : /* @__PURE__ */ jsx(Button, {
|
|
300
324
|
variant: featured ? "default" : "outline",
|
|
301
325
|
className: "w-full",
|
|
302
326
|
asChild: true,
|
|
@@ -386,6 +410,42 @@ function useCancelSubscription() {
|
|
|
386
410
|
});
|
|
387
411
|
}
|
|
388
412
|
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/hooks/useChangePlan.ts
|
|
415
|
+
function useChangePlan() {
|
|
416
|
+
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
417
|
+
const queryClient = useQueryClient();
|
|
418
|
+
return useMutation({
|
|
419
|
+
mutationFn: async ({ fromPlanSku, toPlanSku }) => {
|
|
420
|
+
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
421
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/subscriptions/change`, {
|
|
422
|
+
method: "POST",
|
|
423
|
+
headers: {
|
|
424
|
+
Accept: "application/json",
|
|
425
|
+
"Content-Type": "application/json"
|
|
426
|
+
},
|
|
427
|
+
credentials: "include",
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
fromPlanSku,
|
|
430
|
+
toPlanSku
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
if (!response.ok) {
|
|
434
|
+
const detail = await response.text();
|
|
435
|
+
throw new Error(detail || "Failed to change plan.");
|
|
436
|
+
}
|
|
437
|
+
return await response.json();
|
|
438
|
+
},
|
|
439
|
+
onSuccess: async (result) => {
|
|
440
|
+
if (result.checkoutUrl) {
|
|
441
|
+
window.location.assign(result.checkoutUrl);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
await queryClient.invalidateQueries({ queryKey: ["dev-portal-me", apiBaseUrl] });
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
389
449
|
//#endregion
|
|
390
450
|
//#region src/utils/demo-session.ts
|
|
391
451
|
const DEMO_SUBSCRIPTION_KEY = "demo-subscription-key";
|
|
@@ -533,8 +593,24 @@ const AccountPlansPage = () => {
|
|
|
533
593
|
const isError = isPreview ? false : livePlans.isError;
|
|
534
594
|
const { data: me } = useMySubscriptions();
|
|
535
595
|
const cancelMutation = useCancelSubscription();
|
|
596
|
+
const changeMutation = useChangePlan();
|
|
536
597
|
const [cancelingSku, setCancelingSku] = useState(null);
|
|
598
|
+
const [switchingSku, setSwitchingSku] = useState(null);
|
|
537
599
|
const subscriptionBySku = new Map((me?.subscriptions ?? []).map((sub) => [sub.planSku, sub]));
|
|
600
|
+
const activePlanSkus = (me?.subscriptions ?? []).filter((sub) => isActiveSubscription(sub.status)).map((sub) => sub.planSku);
|
|
601
|
+
const currentPlanSku = activePlanSkus.length === 1 ? activePlanSkus[0] : null;
|
|
602
|
+
const handleSwitch = async (toPlanSku) => {
|
|
603
|
+
if (!currentPlanSku) return;
|
|
604
|
+
setSwitchingSku(toPlanSku);
|
|
605
|
+
try {
|
|
606
|
+
await changeMutation.mutateAsync({
|
|
607
|
+
fromPlanSku: currentPlanSku,
|
|
608
|
+
toPlanSku
|
|
609
|
+
});
|
|
610
|
+
} finally {
|
|
611
|
+
setSwitchingSku(null);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
538
614
|
const handleUnsubscribe = async (planSku) => {
|
|
539
615
|
setCancelingSku(planSku);
|
|
540
616
|
try {
|
|
@@ -571,6 +647,10 @@ const AccountPlansPage = () => {
|
|
|
571
647
|
className: "mb-6 rounded-xl border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
|
|
572
648
|
children: cancelMutation.error instanceof Error ? cancelMutation.error.message : "Failed to cancel subscription."
|
|
573
649
|
}) : null,
|
|
650
|
+
changeMutation.isError ? /* @__PURE__ */ jsx("div", {
|
|
651
|
+
className: "mb-6 rounded-xl border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
|
|
652
|
+
children: changeMutation.error instanceof Error ? changeMutation.error.message : "Failed to change plan."
|
|
653
|
+
}) : null,
|
|
574
654
|
isLoading ? /* @__PURE__ */ jsx("p", {
|
|
575
655
|
className: "text-muted-foreground",
|
|
576
656
|
children: "Loading plans…"
|
|
@@ -590,6 +670,8 @@ const AccountPlansPage = () => {
|
|
|
590
670
|
subscriptionStatus: subscription?.status,
|
|
591
671
|
isSubscribed,
|
|
592
672
|
onUnsubscribe: isPreview || isDemoSession ? void 0 : handleUnsubscribe,
|
|
673
|
+
onSwitch: currentPlanSku && !isPreview && !isDemoSession ? handleSwitch : void 0,
|
|
674
|
+
isSwitching: switchingSku === plan.sku,
|
|
593
675
|
isCanceling: cancelingSku === plan.sku,
|
|
594
676
|
cancelDisabled: isPreview || isDemoSession
|
|
595
677
|
}, plan.sku);
|
|
@@ -2570,6 +2652,14 @@ const devPortalBillingPlugin = createPlugin((options = {}) => ({
|
|
|
2570
2652
|
handle: { layout: "default" },
|
|
2571
2653
|
element: /* @__PURE__ */ jsx(ContactSalesPage, {})
|
|
2572
2654
|
},
|
|
2655
|
+
{
|
|
2656
|
+
path: ACCOUNT_PLANS_PATH,
|
|
2657
|
+
handle: { layout: "default" },
|
|
2658
|
+
element: /* @__PURE__ */ jsx(AccountLayout, {
|
|
2659
|
+
includeBillingNav: true,
|
|
2660
|
+
children: /* @__PURE__ */ jsx(AccountPlansPage, {})
|
|
2661
|
+
})
|
|
2662
|
+
},
|
|
2573
2663
|
{
|
|
2574
2664
|
path: SUBSCRIPTIONS_PATH,
|
|
2575
2665
|
handle: { layout: "default" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukoweb/apitogo-plugin-dev-portal-billing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.30",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zuplo/zudoku",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"react": "19.2.4",
|
|
47
47
|
"react-dom": "19.2.4",
|
|
48
48
|
"tsdown": "0.20.3",
|
|
49
|
-
"@lukoweb/apitogo": "0.2.
|
|
50
|
-
"@lukoweb/apitogo-module-user-panel": "0.2.
|
|
49
|
+
"@lukoweb/apitogo": "0.2.30",
|
|
50
|
+
"@lukoweb/apitogo-module-user-panel": "0.2.30"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": ">=19.2.0",
|