@zuplo/zudoku-plugin-monetization 0.0.26 → 0.0.28
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 +308 -161
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cn, createPlugin, joinUrl, throwIfProblemJson } from "zudoku";
|
|
2
|
-
import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, CalendarIcon, CheckCheckIcon, CheckIcon,
|
|
2
|
+
import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, CalendarIcon, CheckCheckIcon, CheckIcon, CircleSlashIcon, ClockIcon, CreditCardIcon, Grid2x2XIcon, InfoIcon, Loader2Icon, LockIcon, MoreVerticalIcon, RefreshCcw, RefreshCwIcon, Settings, ShieldIcon, StarsIcon, Trash2Icon, XIcon } from "zudoku/icons";
|
|
3
3
|
import { Button, ClientOnly, Head, Heading, Link, Slot } from "zudoku/components";
|
|
4
4
|
import { useAuth, useZudoku } from "zudoku/hooks";
|
|
5
5
|
import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient, useSuspenseQuery } from "zudoku/react-query";
|
|
@@ -19,7 +19,6 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|
|
19
19
|
import { Frame, FrameFooter, FramePanel } from "zudoku/ui/Frame";
|
|
20
20
|
import { Secret } from "zudoku/ui/Secret";
|
|
21
21
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "zudoku/ui/AlertDialog";
|
|
22
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "zudoku/ui/Tooltip";
|
|
23
22
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "zudoku/ui/Dialog";
|
|
24
23
|
import { Input } from "zudoku/ui/Input";
|
|
25
24
|
import { Progress } from "zudoku/ui/Progress";
|
|
@@ -80,13 +79,12 @@ const useDeploymentName = () => {
|
|
|
80
79
|
};
|
|
81
80
|
|
|
82
81
|
//#endregion
|
|
83
|
-
//#region src/hooks/
|
|
84
|
-
const
|
|
82
|
+
//#region src/hooks/usePurchaseDetails.ts
|
|
83
|
+
const usePurchaseDetails = (planId) => {
|
|
85
84
|
const zudoku = useZudoku();
|
|
86
|
-
const auth = useAuth();
|
|
87
85
|
return useSuspenseQuery({
|
|
88
|
-
queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/
|
|
89
|
-
meta: { context:
|
|
86
|
+
queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/plans/${planId}/purchase-details`],
|
|
87
|
+
meta: { context: zudoku }
|
|
90
88
|
});
|
|
91
89
|
};
|
|
92
90
|
|
|
@@ -227,6 +225,24 @@ const getPriceFromPlan = (plan) => {
|
|
|
227
225
|
};
|
|
228
226
|
};
|
|
229
227
|
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/utils/purchaseDetails.ts
|
|
230
|
+
const getPlanFromPurchaseDetails = (response) => {
|
|
231
|
+
return response;
|
|
232
|
+
};
|
|
233
|
+
const getTaxAmountFromPurchaseDetails = (response) => {
|
|
234
|
+
const taxAmount = response?.tax?.taxAmount;
|
|
235
|
+
const numericAmount = typeof taxAmount === "number" ? taxAmount : Number.parseFloat(taxAmount ?? "");
|
|
236
|
+
if (!Number.isFinite(numericAmount)) return;
|
|
237
|
+
return numericAmount;
|
|
238
|
+
};
|
|
239
|
+
const getTaxLabelFromPurchaseDetails = (response) => {
|
|
240
|
+
return (response.tax?.taxes ?? []).some((tax) => tax.taxType?.toLowerCase() === "vat") ? "VAT" : "tax";
|
|
241
|
+
};
|
|
242
|
+
const isTaxInclusiveFromPurchaseDetails = (response) => {
|
|
243
|
+
return response.tax?.taxInclusive === true;
|
|
244
|
+
};
|
|
245
|
+
|
|
230
246
|
//#endregion
|
|
231
247
|
//#region src/ZuploMonetizationWrapper.tsx
|
|
232
248
|
const DEFAULT_GATEWAY_URL = "https://api.zuploedge.com";
|
|
@@ -307,10 +323,13 @@ const CheckoutConfirmPage = () => {
|
|
|
307
323
|
const zudoku = useZudoku();
|
|
308
324
|
const deploymentName = useDeploymentName();
|
|
309
325
|
const navigate = useNavigate();
|
|
310
|
-
const { data: plans } = usePlans();
|
|
311
326
|
const { pricing } = useMonetizationConfig();
|
|
312
|
-
const selectedPlan = plans?.items?.find((plan) => plan.id === planId);
|
|
313
327
|
if (!planId) throw new Error("Parameter `planId` missing");
|
|
328
|
+
const purchaseDetails = usePurchaseDetails(planId);
|
|
329
|
+
const selectedPlan = getPlanFromPurchaseDetails(purchaseDetails.data);
|
|
330
|
+
const taxAmount = getTaxAmountFromPurchaseDetails(purchaseDetails.data);
|
|
331
|
+
const taxLabel = getTaxLabelFromPurchaseDetails(purchaseDetails.data);
|
|
332
|
+
const taxInclusive = isTaxInclusiveFromPurchaseDetails(purchaseDetails.data);
|
|
314
333
|
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
315
334
|
const { quotas, features } = categorizeRateCards(rateCards ?? [], {
|
|
316
335
|
currency: selectedPlan?.currency,
|
|
@@ -386,13 +405,20 @@ const CheckoutConfirmPage = () => {
|
|
|
386
405
|
}),
|
|
387
406
|
price && price.monthly > 0 && /* @__PURE__ */ jsxs("div", {
|
|
388
407
|
className: "text-right",
|
|
389
|
-
children: [
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
408
|
+
children: [
|
|
409
|
+
/* @__PURE__ */ jsx("div", {
|
|
410
|
+
className: "text-2xl font-bold",
|
|
411
|
+
children: formatPrice(price.monthly, selectedPlan?.currency)
|
|
412
|
+
}),
|
|
413
|
+
billingCycle && /* @__PURE__ */ jsxs("div", {
|
|
414
|
+
className: "text-sm text-muted-foreground font-normal",
|
|
415
|
+
children: ["Billed ", formatBillingCycle(billingCycle)]
|
|
416
|
+
}),
|
|
417
|
+
taxAmount != null && /* @__PURE__ */ jsx("div", {
|
|
418
|
+
className: "text-xs text-muted-foreground font-normal mt-1",
|
|
419
|
+
children: taxInclusive ? `${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel} included` : `+ ${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel}`
|
|
420
|
+
})
|
|
421
|
+
]
|
|
396
422
|
}),
|
|
397
423
|
price && price.monthly === 0 && /* @__PURE__ */ jsx("div", {
|
|
398
424
|
className: "text-2xl text-muted-foreground font-bold",
|
|
@@ -422,7 +448,7 @@ const CheckoutConfirmPage = () => {
|
|
|
422
448
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
423
449
|
className: "w-full",
|
|
424
450
|
onClick: () => createSubscriptionMutation.mutate(),
|
|
425
|
-
disabled: createSubscriptionMutation.isPending,
|
|
451
|
+
disabled: createSubscriptionMutation.isPending || !selectedPlan,
|
|
426
452
|
children: createSubscriptionMutation.isPending ? "Processing Payment..." : "Confirm & Subscribe"
|
|
427
453
|
}), /* @__PURE__ */ jsx(Button, {
|
|
428
454
|
variant: "ghost",
|
|
@@ -606,6 +632,17 @@ const ManagePaymentPage = () => {
|
|
|
606
632
|
});
|
|
607
633
|
};
|
|
608
634
|
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/hooks/usePlans.ts
|
|
637
|
+
const usePlans = () => {
|
|
638
|
+
const zudoku = useZudoku();
|
|
639
|
+
const auth = useAuth();
|
|
640
|
+
return useSuspenseQuery({
|
|
641
|
+
queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/pricing-page`],
|
|
642
|
+
meta: { context: auth.isAuthenticated ? zudoku : void 0 }
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
|
|
609
646
|
//#endregion
|
|
610
647
|
//#region src/pages/pricing/PricingCard.tsx
|
|
611
648
|
const PhaseSection = ({ phase, currency, showName, billingCadence }) => {
|
|
@@ -764,14 +801,18 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
764
801
|
const [search] = useSearchParams();
|
|
765
802
|
const planId = search.get("planId");
|
|
766
803
|
const subscriptionId = search.get("subscriptionId");
|
|
804
|
+
const mode = search.get("mode");
|
|
767
805
|
const zudoku = useZudoku();
|
|
768
806
|
const deploymentName = useDeploymentName();
|
|
769
807
|
const navigate = useNavigate();
|
|
770
|
-
const { data: plans } = usePlans();
|
|
771
808
|
const { pricing } = useMonetizationConfig();
|
|
772
|
-
const selectedPlan = plans?.items?.find((plan) => plan.id === planId);
|
|
773
809
|
if (!planId) throw new Error("Parameter `planId` missing");
|
|
774
810
|
if (!subscriptionId) throw new Error("Parameter `subscriptionId` missing");
|
|
811
|
+
const purchaseDetails = usePurchaseDetails(planId);
|
|
812
|
+
const selectedPlan = getPlanFromPurchaseDetails(purchaseDetails.data);
|
|
813
|
+
const taxAmount = getTaxAmountFromPurchaseDetails(purchaseDetails.data);
|
|
814
|
+
const taxLabel = getTaxLabelFromPurchaseDetails(purchaseDetails.data);
|
|
815
|
+
const taxInclusive = isTaxInclusiveFromPurchaseDetails(purchaseDetails.data);
|
|
775
816
|
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
776
817
|
const { quotas, features } = categorizeRateCards(rateCards ?? [], {
|
|
777
818
|
currency: selectedPlan?.currency,
|
|
@@ -780,6 +821,7 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
780
821
|
});
|
|
781
822
|
const price = selectedPlan ? getPriceFromPlan(selectedPlan) : null;
|
|
782
823
|
const billingCycle = selectedPlan?.billingCadence ? formatDuration(selectedPlan.billingCadence) : null;
|
|
824
|
+
const effectiveChangeMessage = mode === "downgrade" ? "This change will take effect at the start of your next billing cycle." : "This change will take effect immediately.";
|
|
783
825
|
const changeMutation = useMutation({
|
|
784
826
|
mutationKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/change`],
|
|
785
827
|
meta: {
|
|
@@ -814,13 +856,20 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
814
856
|
}),
|
|
815
857
|
/* @__PURE__ */ jsxs("div", {
|
|
816
858
|
className: "text-center mb-8",
|
|
817
|
-
children: [
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
859
|
+
children: [
|
|
860
|
+
/* @__PURE__ */ jsx("h1", {
|
|
861
|
+
className: "text-2xl font-bold text-card-foreground mb-3",
|
|
862
|
+
children: "Confirm plan change"
|
|
863
|
+
}),
|
|
864
|
+
/* @__PURE__ */ jsx("p", {
|
|
865
|
+
className: "text-muted-foreground text-base",
|
|
866
|
+
children: effectiveChangeMessage
|
|
867
|
+
}),
|
|
868
|
+
/* @__PURE__ */ jsx("p", {
|
|
869
|
+
className: "text-muted-foreground text-base",
|
|
870
|
+
children: "Please confirm the details below to change your subscription."
|
|
871
|
+
})
|
|
872
|
+
]
|
|
824
873
|
}),
|
|
825
874
|
selectedPlan && /* @__PURE__ */ jsxs(Card, {
|
|
826
875
|
className: "bg-muted/50",
|
|
@@ -845,13 +894,20 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
845
894
|
}),
|
|
846
895
|
price && price.monthly > 0 && /* @__PURE__ */ jsxs("div", {
|
|
847
896
|
className: "text-right",
|
|
848
|
-
children: [
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
897
|
+
children: [
|
|
898
|
+
/* @__PURE__ */ jsx("div", {
|
|
899
|
+
className: "text-2xl font-bold",
|
|
900
|
+
children: formatPrice(price.monthly, selectedPlan?.currency)
|
|
901
|
+
}),
|
|
902
|
+
billingCycle && /* @__PURE__ */ jsxs("div", {
|
|
903
|
+
className: "text-sm text-muted-foreground font-normal",
|
|
904
|
+
children: ["Billed ", formatBillingCycle(billingCycle)]
|
|
905
|
+
}),
|
|
906
|
+
taxAmount != null && /* @__PURE__ */ jsx("div", {
|
|
907
|
+
className: "text-xs text-muted-foreground font-normal mt-1",
|
|
908
|
+
children: taxInclusive ? `${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel} included` : `+ ${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel}`
|
|
909
|
+
})
|
|
910
|
+
]
|
|
855
911
|
}),
|
|
856
912
|
price && price.monthly === 0 && /* @__PURE__ */ jsx("div", {
|
|
857
913
|
className: "text-2xl text-muted-foreground font-bold",
|
|
@@ -1251,15 +1307,24 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1251
1307
|
children: [
|
|
1252
1308
|
/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }),
|
|
1253
1309
|
/* @__PURE__ */ jsx(AlertTitle, { children: "Your plan will be canceled at the end of your billing cycle." }),
|
|
1254
|
-
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1310
|
+
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1311
|
+
"You'll retain access until ",
|
|
1312
|
+
formatDate$1(billingPeriodEnd),
|
|
1313
|
+
". After your billing period ends, this plan will not renew and you would need to subscribe again to continue."
|
|
1314
|
+
] })
|
|
1255
1315
|
]
|
|
1256
1316
|
}),
|
|
1257
1317
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1258
|
-
variant: "
|
|
1318
|
+
variant: "info",
|
|
1259
1319
|
children: [
|
|
1260
|
-
/* @__PURE__ */ jsx(
|
|
1261
|
-
/* @__PURE__ */ jsx(AlertTitle, { children: "
|
|
1262
|
-
/* @__PURE__ */
|
|
1320
|
+
/* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }),
|
|
1321
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "You can still resume before then" }),
|
|
1322
|
+
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1323
|
+
"If you change your mind you have until",
|
|
1324
|
+
" ",
|
|
1325
|
+
formatDate$1(billingPeriodEnd),
|
|
1326
|
+
" to remove this cancellation from Manage subscription."
|
|
1327
|
+
] })
|
|
1263
1328
|
]
|
|
1264
1329
|
}),
|
|
1265
1330
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -1312,6 +1377,88 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1312
1377
|
});
|
|
1313
1378
|
};
|
|
1314
1379
|
|
|
1380
|
+
//#endregion
|
|
1381
|
+
//#region src/pages/subscriptions/RestoreSubscriptionDialog.tsx
|
|
1382
|
+
const RestoreSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId, billingPeriodEnd }) => {
|
|
1383
|
+
const deploymentName = useDeploymentName();
|
|
1384
|
+
const context = useZudoku();
|
|
1385
|
+
const queryClient = useQueryClient();
|
|
1386
|
+
const restoreSubscriptionMutation = useMutation({
|
|
1387
|
+
mutationKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/restore`],
|
|
1388
|
+
meta: {
|
|
1389
|
+
context,
|
|
1390
|
+
request: { method: "POST" }
|
|
1391
|
+
},
|
|
1392
|
+
onSuccess: async () => {
|
|
1393
|
+
await queryClient.invalidateQueries();
|
|
1394
|
+
onOpenChange(false);
|
|
1395
|
+
}
|
|
1396
|
+
});
|
|
1397
|
+
useEffect(() => {
|
|
1398
|
+
if (open) restoreSubscriptionMutation.reset();
|
|
1399
|
+
}, [open, restoreSubscriptionMutation]);
|
|
1400
|
+
const handleOpenChange = (nextOpen) => {
|
|
1401
|
+
if (!nextOpen) restoreSubscriptionMutation.reset();
|
|
1402
|
+
onOpenChange(nextOpen);
|
|
1403
|
+
};
|
|
1404
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
1405
|
+
open,
|
|
1406
|
+
onOpenChange: handleOpenChange,
|
|
1407
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
1408
|
+
className: "sm:max-w-md",
|
|
1409
|
+
children: [
|
|
1410
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Resume subscription" }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
1411
|
+
"You scheduled ",
|
|
1412
|
+
/* @__PURE__ */ jsx("span", {
|
|
1413
|
+
className: "font-medium",
|
|
1414
|
+
children: planName
|
|
1415
|
+
}),
|
|
1416
|
+
" to end. You can still change your mind before the current billing period ends."
|
|
1417
|
+
] })] }),
|
|
1418
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1419
|
+
className: "space-y-4 mt-4",
|
|
1420
|
+
children: [/* @__PURE__ */ jsxs(Alert, {
|
|
1421
|
+
variant: "info",
|
|
1422
|
+
children: [
|
|
1423
|
+
/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }),
|
|
1424
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "What happens if you resume" }),
|
|
1425
|
+
/* @__PURE__ */ jsxs(AlertDescription, {
|
|
1426
|
+
className: "space-y-2",
|
|
1427
|
+
children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
1428
|
+
"Your access stays in place until ",
|
|
1429
|
+
formatDate$1(billingPeriodEnd),
|
|
1430
|
+
" ",
|
|
1431
|
+
"either way."
|
|
1432
|
+
] }), /* @__PURE__ */ jsx("p", { children: "Confirming will remove the pending cancellation. Your subscription will remain active and continue to renew on your normal billing schedule, and charges will apply as usual." })]
|
|
1433
|
+
})
|
|
1434
|
+
]
|
|
1435
|
+
}), restoreSubscriptionMutation.isError && /* @__PURE__ */ jsxs(Alert, {
|
|
1436
|
+
variant: "destructive",
|
|
1437
|
+
children: [
|
|
1438
|
+
/* @__PURE__ */ jsx(CircleSlashIcon, { className: "size-4" }),
|
|
1439
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Could not resume subscription" }),
|
|
1440
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: restoreSubscriptionMutation.error.message })
|
|
1441
|
+
]
|
|
1442
|
+
})]
|
|
1443
|
+
}),
|
|
1444
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1445
|
+
className: "flex flex-col gap-2",
|
|
1446
|
+
children: [/* @__PURE__ */ jsx(ActionButton, {
|
|
1447
|
+
disabled: restoreSubscriptionMutation.isPending,
|
|
1448
|
+
isPending: restoreSubscriptionMutation.isPending || restoreSubscriptionMutation.isSuccess,
|
|
1449
|
+
onClick: () => restoreSubscriptionMutation.mutate(),
|
|
1450
|
+
children: "Resume subscription"
|
|
1451
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
1452
|
+
variant: "ghost",
|
|
1453
|
+
onClick: () => handleOpenChange(false),
|
|
1454
|
+
children: "Keep cancellation"
|
|
1455
|
+
})]
|
|
1456
|
+
})
|
|
1457
|
+
]
|
|
1458
|
+
})
|
|
1459
|
+
});
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1315
1462
|
//#endregion
|
|
1316
1463
|
//#region src/pages/subscriptions/SwitchPlanModal.tsx
|
|
1317
1464
|
const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex, units) => {
|
|
@@ -1415,7 +1562,12 @@ const modeLabelMap = {
|
|
|
1415
1562
|
downgrade: "Downgrade",
|
|
1416
1563
|
private: "Switch"
|
|
1417
1564
|
};
|
|
1418
|
-
const
|
|
1565
|
+
const isSwitchPlanTarget = (value) => {
|
|
1566
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1567
|
+
if (!("subscriptionId" in value) || !("plan" in value) || !("mode" in value)) return false;
|
|
1568
|
+
return true;
|
|
1569
|
+
};
|
|
1570
|
+
const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange, isSwitching }) => {
|
|
1419
1571
|
const price = getPriceFromPlan(comparison.plan);
|
|
1420
1572
|
const isCustom = comparison.plan.key === "enterprise";
|
|
1421
1573
|
const displayPrice = price.monthly;
|
|
@@ -1455,6 +1607,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange
|
|
|
1455
1607
|
mode
|
|
1456
1608
|
}),
|
|
1457
1609
|
size: "sm",
|
|
1610
|
+
disabled: isSwitching,
|
|
1458
1611
|
children: modeLabelMap[mode]
|
|
1459
1612
|
})]
|
|
1460
1613
|
}), hasChanges && /* @__PURE__ */ jsxs("div", {
|
|
@@ -1549,24 +1702,32 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange
|
|
|
1549
1702
|
})]
|
|
1550
1703
|
});
|
|
1551
1704
|
};
|
|
1552
|
-
const
|
|
1705
|
+
const SwitchPlanModal = ({ subscription, children }) => {
|
|
1706
|
+
const [open, setOpen] = useState(false);
|
|
1707
|
+
const { data: plansData } = usePlans();
|
|
1708
|
+
const { pricing } = useMonetizationConfig();
|
|
1553
1709
|
const deploymentName = useDeploymentName();
|
|
1554
1710
|
const context = useZudoku();
|
|
1555
1711
|
const { generateUrl } = useUrlUtils();
|
|
1556
|
-
const
|
|
1712
|
+
const switchPlanMutation = useMutation({
|
|
1557
1713
|
mutationKey: [`/v3/zudoku-metering/${deploymentName}/stripe/checkout`],
|
|
1558
1714
|
meta: {
|
|
1559
1715
|
context,
|
|
1560
|
-
request: {
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1716
|
+
request: (variables) => {
|
|
1717
|
+
if (!isSwitchPlanTarget(variables)) throw new Error("Couldn't start the plan change. Please refresh and try again.");
|
|
1718
|
+
const switchTo = variables;
|
|
1719
|
+
return {
|
|
1720
|
+
method: "POST",
|
|
1721
|
+
body: JSON.stringify({
|
|
1565
1722
|
planId: switchTo.plan.id,
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1723
|
+
successURL: generateUrl(`/subscription-change-confirm`, { searchParams: {
|
|
1724
|
+
planId: switchTo.plan.id,
|
|
1725
|
+
subscriptionId: switchTo.subscriptionId,
|
|
1726
|
+
mode: switchTo.mode
|
|
1727
|
+
} }),
|
|
1728
|
+
cancelURL: generateUrl("/subscriptions", { searchParams: { subscriptionId: switchTo.subscriptionId } })
|
|
1729
|
+
})
|
|
1730
|
+
};
|
|
1570
1731
|
}
|
|
1571
1732
|
},
|
|
1572
1733
|
retry: false,
|
|
@@ -1574,38 +1735,6 @@ const ConfirmSwitchAlert = ({ switchTo, onRequestClose }) => {
|
|
|
1574
1735
|
window.location.href = data.url;
|
|
1575
1736
|
}
|
|
1576
1737
|
});
|
|
1577
|
-
return /* @__PURE__ */ jsx(AlertDialog, {
|
|
1578
|
-
open: true,
|
|
1579
|
-
onOpenChange: onRequestClose,
|
|
1580
|
-
children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
|
|
1581
|
-
/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
|
|
1582
|
-
"Confirm",
|
|
1583
|
-
" ",
|
|
1584
|
-
switchTo.mode === "private" ? "plan change" : switchTo.mode === "upgrade" ? "upgrade" : "downgrade"
|
|
1585
|
-
] }),
|
|
1586
|
-
mutation.isError && /* @__PURE__ */ jsx(Alert, {
|
|
1587
|
-
variant: "destructive",
|
|
1588
|
-
children: /* @__PURE__ */ jsx(AlertDescription, {
|
|
1589
|
-
className: "first-letter:uppercase",
|
|
1590
|
-
children: mutation.error.message
|
|
1591
|
-
})
|
|
1592
|
-
}),
|
|
1593
|
-
/* @__PURE__ */ jsx(AlertDialogDescription, { children: switchTo.mode === "private" ? `Are you sure you want to switch to ${switchTo.plan.name}? This will take effect immediately.` : switchTo.mode === "upgrade" ? `Are you sure you want to upgrade to ${switchTo.plan.name}? This will take effect immediately.` : `Are you sure you want to downgrade to ${switchTo.plan.name}? This will take effect at the start of your next billing cycle.` })
|
|
1594
|
-
] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, {
|
|
1595
|
-
disabled: mutation.isPending,
|
|
1596
|
-
children: "Cancel"
|
|
1597
|
-
}), /* @__PURE__ */ jsx(ActionButton, {
|
|
1598
|
-
isPending: mutation.isPending,
|
|
1599
|
-
onClick: () => mutation.mutate(),
|
|
1600
|
-
children: modeLabelMap[switchTo.mode]
|
|
1601
|
-
})] })] })
|
|
1602
|
-
});
|
|
1603
|
-
};
|
|
1604
|
-
const SwitchPlanModal = ({ subscription, children }) => {
|
|
1605
|
-
const [open, setOpen] = useState(false);
|
|
1606
|
-
const { data: plansData } = usePlans();
|
|
1607
|
-
const [switchTo, setSwitchTo] = useState(null);
|
|
1608
|
-
const { pricing } = useMonetizationConfig();
|
|
1609
1738
|
const currentPlan = plansData?.items.find((p) => p.id === subscription.plan.id);
|
|
1610
1739
|
const { upgrades, downgrades, privatePlans } = useMemo(() => {
|
|
1611
1740
|
if (!plansData?.items || !currentPlan) return {
|
|
@@ -1628,10 +1757,7 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1628
1757
|
currentPlan,
|
|
1629
1758
|
pricing?.units
|
|
1630
1759
|
]);
|
|
1631
|
-
return /* @__PURE__ */ jsxs(
|
|
1632
|
-
switchTo,
|
|
1633
|
-
onRequestClose: () => setSwitchTo(null)
|
|
1634
|
-
}), /* @__PURE__ */ jsxs(Dialog, {
|
|
1760
|
+
return /* @__PURE__ */ jsxs(Dialog, {
|
|
1635
1761
|
open,
|
|
1636
1762
|
onOpenChange: setOpen,
|
|
1637
1763
|
children: [/* @__PURE__ */ jsx(DialogTrigger, {
|
|
@@ -1652,6 +1778,13 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1652
1778
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1653
1779
|
className: "mt-4 space-y-6",
|
|
1654
1780
|
children: [
|
|
1781
|
+
switchPlanMutation.isError && /* @__PURE__ */ jsx(Alert, {
|
|
1782
|
+
variant: "destructive",
|
|
1783
|
+
children: /* @__PURE__ */ jsx(AlertDescription, {
|
|
1784
|
+
className: "first-letter:uppercase",
|
|
1785
|
+
children: switchPlanMutation.error.message
|
|
1786
|
+
})
|
|
1787
|
+
}),
|
|
1655
1788
|
currentPlan && /* @__PURE__ */ jsx(Item, {
|
|
1656
1789
|
variant: "outline",
|
|
1657
1790
|
children: /* @__PURE__ */ jsxs(ItemContent, { children: [/* @__PURE__ */ jsx(ItemTitle, { children: "Current Plan" }), /* @__PURE__ */ jsx(ItemDescription, {
|
|
@@ -1677,7 +1810,8 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1677
1810
|
comparison,
|
|
1678
1811
|
subscriptionId: subscription.id,
|
|
1679
1812
|
mode: "upgrade",
|
|
1680
|
-
onRequestChange:
|
|
1813
|
+
onRequestChange: (target) => switchPlanMutation.mutate(target),
|
|
1814
|
+
isSwitching: switchPlanMutation.isPending
|
|
1681
1815
|
}, comparison.plan.id))
|
|
1682
1816
|
})] }),
|
|
1683
1817
|
downgrades.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -1698,7 +1832,8 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1698
1832
|
comparison,
|
|
1699
1833
|
subscriptionId: subscription.id,
|
|
1700
1834
|
mode: "downgrade",
|
|
1701
|
-
onRequestChange:
|
|
1835
|
+
onRequestChange: (target) => switchPlanMutation.mutate(target),
|
|
1836
|
+
isSwitching: switchPlanMutation.isPending
|
|
1702
1837
|
}, comparison.plan.id))
|
|
1703
1838
|
})] }),
|
|
1704
1839
|
privatePlans.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -1719,94 +1854,106 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1719
1854
|
comparison,
|
|
1720
1855
|
subscriptionId: subscription.id,
|
|
1721
1856
|
mode: "private",
|
|
1722
|
-
onRequestChange:
|
|
1857
|
+
onRequestChange: (target) => switchPlanMutation.mutate(target),
|
|
1858
|
+
isSwitching: switchPlanMutation.isPending
|
|
1723
1859
|
}, comparison.plan.id))
|
|
1724
1860
|
})] })
|
|
1725
1861
|
]
|
|
1726
1862
|
})]
|
|
1727
1863
|
}) })]
|
|
1728
|
-
})
|
|
1864
|
+
});
|
|
1729
1865
|
};
|
|
1730
1866
|
|
|
1731
1867
|
//#endregion
|
|
1732
1868
|
//#region src/pages/subscriptions/ManageSubscription.tsx
|
|
1733
1869
|
const ManageSubscription = ({ subscription, planName }) => {
|
|
1734
1870
|
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
})
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1871
|
+
const [restoreDialogOpen, setRestoreDialogOpen] = useState(false);
|
|
1872
|
+
const billingPeriodEnd = subscription.alignment.currentAlignedBillingPeriod.to;
|
|
1873
|
+
const canResumeCanceledSubscription = subscription.status === "canceled" && new Date(billingPeriodEnd) > /* @__PURE__ */ new Date();
|
|
1874
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
1875
|
+
/* @__PURE__ */ jsx(CancelSubscriptionDialog, {
|
|
1876
|
+
open: cancelDialogOpen,
|
|
1877
|
+
onOpenChange: setCancelDialogOpen,
|
|
1878
|
+
planName,
|
|
1879
|
+
subscriptionId: subscription.id,
|
|
1880
|
+
billingPeriodEnd
|
|
1881
|
+
}),
|
|
1882
|
+
/* @__PURE__ */ jsx(RestoreSubscriptionDialog, {
|
|
1883
|
+
open: restoreDialogOpen,
|
|
1884
|
+
onOpenChange: setRestoreDialogOpen,
|
|
1885
|
+
planName,
|
|
1886
|
+
subscriptionId: subscription.id,
|
|
1887
|
+
billingPeriodEnd
|
|
1888
|
+
}),
|
|
1889
|
+
/* @__PURE__ */ jsx(CardContent, {
|
|
1890
|
+
className: "p-6",
|
|
1891
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1892
|
+
className: "flex gap-4",
|
|
1893
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1894
|
+
className: "flex items-center justify-center w-12 h-12 rounded-full bg-primary/10 shrink-0",
|
|
1895
|
+
children: /* @__PURE__ */ jsx(Settings, { className: "w-6 h-6 text-primary" })
|
|
1896
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1897
|
+
className: "flex-1",
|
|
1898
|
+
id: "manage",
|
|
1899
|
+
children: [
|
|
1900
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1901
|
+
className: "text-lg font-semibold text-foreground mb-1",
|
|
1902
|
+
children: "Manage Subscription"
|
|
1903
|
+
}),
|
|
1904
|
+
/* @__PURE__ */ jsx("p", {
|
|
1905
|
+
className: "text-sm text-muted-foreground mb-4",
|
|
1906
|
+
children: "Switch to a different plan or cancel your current subscription."
|
|
1907
|
+
}),
|
|
1908
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1909
|
+
className: "flex flex-wrap gap-3",
|
|
1910
|
+
children: [
|
|
1911
|
+
subscription.status === "canceled" && /* @__PURE__ */ jsx(Button$1, {
|
|
1912
|
+
variant: "outline",
|
|
1913
|
+
size: "sm",
|
|
1776
1914
|
asChild: true,
|
|
1777
|
-
children: /* @__PURE__ */
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
children:
|
|
1794
|
-
|
|
1795
|
-
|
|
1915
|
+
children: /* @__PURE__ */ jsxs(Link, {
|
|
1916
|
+
to: "/pricing",
|
|
1917
|
+
children: [/* @__PURE__ */ jsx(RefreshCcw, { className: "w-4 h-4 mr-2" }), "New subscription"]
|
|
1918
|
+
})
|
|
1919
|
+
}),
|
|
1920
|
+
subscription.status === "active" && /* @__PURE__ */ jsx(SwitchPlanModal, { subscription }),
|
|
1921
|
+
subscription.status === "active" && /* @__PURE__ */ jsx(Button$1, {
|
|
1922
|
+
variant: "outline",
|
|
1923
|
+
size: "sm",
|
|
1924
|
+
onClick: () => setCancelDialogOpen(true),
|
|
1925
|
+
children: "Cancel subscription"
|
|
1926
|
+
}),
|
|
1927
|
+
canResumeCanceledSubscription && /* @__PURE__ */ jsx(Button$1, {
|
|
1928
|
+
variant: "outline",
|
|
1929
|
+
size: "sm",
|
|
1930
|
+
onClick: () => setRestoreDialogOpen(true),
|
|
1931
|
+
children: "Resume subscription"
|
|
1932
|
+
}),
|
|
1933
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
1934
|
+
asChild: true,
|
|
1935
|
+
size: "sm",
|
|
1936
|
+
variant: "secondary",
|
|
1937
|
+
children: /* @__PURE__ */ jsx(Link, {
|
|
1938
|
+
to: "/manage-payment",
|
|
1939
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1940
|
+
className: "flex items-center gap-2",
|
|
1941
|
+
children: [/* @__PURE__ */ jsx(CreditCardIcon, {}), "Manage payment details"]
|
|
1942
|
+
})
|
|
1796
1943
|
})
|
|
1797
1944
|
})
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
]
|
|
1807
|
-
})
|
|
1945
|
+
]
|
|
1946
|
+
}),
|
|
1947
|
+
/* @__PURE__ */ jsx(Separator, { className: "my-4" }),
|
|
1948
|
+
/* @__PURE__ */ jsx("span", {
|
|
1949
|
+
className: "text-sm text-muted-foreground",
|
|
1950
|
+
children: "Your payment is securely managed by Stripe."
|
|
1951
|
+
})
|
|
1952
|
+
]
|
|
1953
|
+
})]
|
|
1954
|
+
})
|
|
1808
1955
|
})
|
|
1809
|
-
|
|
1956
|
+
] });
|
|
1810
1957
|
};
|
|
1811
1958
|
|
|
1812
1959
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuplo/zudoku-plugin-monetization",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zuplo/zudoku",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"react": "19.2.4",
|
|
32
32
|
"react-dom": "19.2.4",
|
|
33
33
|
"tsdown": "0.20.3",
|
|
34
|
-
"zudoku": "0.
|
|
34
|
+
"zudoku": "0.73.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=19.2.0",
|