@zuplo/zudoku-plugin-monetization 0.0.20 → 0.0.22
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.d.mts +1 -0
- package/dist/index.mjs +83 -49
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { cn, createPlugin, joinUrl } from "zudoku";
|
|
2
|
-
import { Button, ClientOnly, Head, Heading, Link } from "zudoku/components";
|
|
3
2
|
import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, CalendarIcon, CheckCheckIcon, CheckIcon, CircleAlert, CircleSlashIcon, ClockIcon, CreditCardIcon, Grid2x2XIcon, InfoIcon, Loader2Icon, LockIcon, MoreVerticalIcon, RefreshCcw, RefreshCwIcon, Settings, ShieldIcon, StarsIcon, Trash2Icon, XIcon } from "zudoku/icons";
|
|
4
|
-
import {
|
|
3
|
+
import { Button, ClientOnly, Head, Heading, Link } from "zudoku/components";
|
|
5
4
|
import { useAuth, useZudoku } from "zudoku/hooks";
|
|
6
5
|
import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient, useSuspenseQuery } from "zudoku/react-query";
|
|
6
|
+
import { Link as Link$1, Outlet, useLocation, useNavigate, useParams, useSearchParams } from "zudoku/router";
|
|
7
7
|
import { Alert, AlertAction, AlertDescription, AlertTitle } from "zudoku/ui/Alert";
|
|
8
8
|
import { Card, CardContent, CardHeader, CardTitle } from "zudoku/ui/Card";
|
|
9
9
|
import { Separator } from "zudoku/ui/Separator";
|
|
@@ -127,15 +127,17 @@ const formatDurationInterval = (iso) => {
|
|
|
127
127
|
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/utils/formatPrice.ts
|
|
130
|
-
const formatPrice = (amount, currency) => new Intl.NumberFormat(
|
|
130
|
+
const formatPrice = (amount, currency) => new Intl.NumberFormat("en-US", {
|
|
131
131
|
style: "currency",
|
|
132
|
-
currency: currency
|
|
133
|
-
minimumFractionDigits:
|
|
132
|
+
currency: currency ?? "USD",
|
|
133
|
+
minimumFractionDigits: 2,
|
|
134
|
+
maximumFractionDigits: 6,
|
|
135
|
+
trailingZeroDisplay: "stripIfInteger"
|
|
134
136
|
}).format(amount);
|
|
135
137
|
|
|
136
138
|
//#endregion
|
|
137
139
|
//#region src/utils/categorizeRateCards.ts
|
|
138
|
-
const categorizeRateCards = (rateCards, currency) => {
|
|
140
|
+
const categorizeRateCards = (rateCards, currency, units) => {
|
|
139
141
|
const quotas = [];
|
|
140
142
|
const features = [];
|
|
141
143
|
for (const rc of rateCards) {
|
|
@@ -144,8 +146,12 @@ const categorizeRateCards = (rateCards, currency) => {
|
|
|
144
146
|
if (et.type === "metered" && et.issueAfterReset != null) {
|
|
145
147
|
let overagePrice;
|
|
146
148
|
if (et.isSoftLimit !== false && rc.price?.type === "tiered" && rc.price.tiers) {
|
|
147
|
-
const overageTier = rc.price.tiers.find((t) => t.unitPrice?.amount);
|
|
148
|
-
if (overageTier?.unitPrice)
|
|
149
|
+
const overageTier = rc.price.tiers.find((t) => t.unitPrice?.amount && parseFloat(t.unitPrice.amount) > 0);
|
|
150
|
+
if (overageTier?.unitPrice) {
|
|
151
|
+
const amount = parseFloat(overageTier.unitPrice.amount);
|
|
152
|
+
const unitLabel = units?.[rc.key] ?? units?.[rc.featureKey ?? ""] ?? "unit";
|
|
153
|
+
overagePrice = `${formatPrice(amount, currency)}/${unitLabel}`;
|
|
154
|
+
}
|
|
149
155
|
}
|
|
150
156
|
quotas.push({
|
|
151
157
|
key: rc.featureKey ?? rc.key,
|
|
@@ -391,7 +397,8 @@ const CheckoutConfirmPage = () => {
|
|
|
391
397
|
}), /* @__PURE__ */ jsx(Button, {
|
|
392
398
|
variant: "ghost",
|
|
393
399
|
className: "w-full",
|
|
394
|
-
|
|
400
|
+
disabled: createSubscriptionMutation.isPending,
|
|
401
|
+
asChild: !createSubscriptionMutation.isPending,
|
|
395
402
|
children: /* @__PURE__ */ jsx(Link$1, {
|
|
396
403
|
to: "/pricing",
|
|
397
404
|
children: "Cancel"
|
|
@@ -572,13 +579,36 @@ const ManagePaymentPage = () => {
|
|
|
572
579
|
|
|
573
580
|
//#endregion
|
|
574
581
|
//#region src/pages/pricing/PricingCard.tsx
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
const
|
|
582
|
+
const PhaseSection = ({ phase, currency, showName, excludeKeys, units }) => {
|
|
583
|
+
const { quotas, features } = categorizeRateCards(phase.rateCards, currency, units);
|
|
584
|
+
const filteredQuotas = quotas.filter((q) => !excludeKeys.has(q.key));
|
|
585
|
+
const filteredFeatures = features.filter((f) => !excludeKeys.has(f.key));
|
|
586
|
+
if (filteredQuotas.length === 0 && filteredFeatures.length === 0) return null;
|
|
587
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
588
|
+
className: "space-y-2",
|
|
589
|
+
children: [
|
|
590
|
+
showName && /* @__PURE__ */ jsxs("div", {
|
|
591
|
+
className: "text-sm font-medium text-card-foreground",
|
|
592
|
+
children: [phase.name, phase.duration && /* @__PURE__ */ jsxs("span", {
|
|
593
|
+
className: "text-muted-foreground font-normal",
|
|
594
|
+
children: [
|
|
595
|
+
" ",
|
|
596
|
+
"— ",
|
|
597
|
+
formatDuration(phase.duration)
|
|
598
|
+
]
|
|
599
|
+
})]
|
|
600
|
+
}),
|
|
601
|
+
filteredQuotas.map((quota) => /* @__PURE__ */ jsx(QuotaItem, { quota }, quota.key)),
|
|
602
|
+
filteredFeatures.map((feature) => /* @__PURE__ */ jsx(FeatureItem, { feature }, feature.key))
|
|
603
|
+
]
|
|
604
|
+
});
|
|
605
|
+
};
|
|
606
|
+
const PricingCard = ({ plan, isPopular = false, isSubscribed = false, units }) => {
|
|
607
|
+
if (plan.phases.length === 0) return null;
|
|
579
608
|
const price = getPriceFromPlan(plan);
|
|
580
609
|
const isFree = price.monthly === 0;
|
|
581
610
|
const isCustom = plan.metadata?.isCustom === true;
|
|
611
|
+
const hasMultiplePhases = plan.phases.length > 1;
|
|
582
612
|
return /* @__PURE__ */ jsxs("div", {
|
|
583
613
|
className: cn("relative rounded-lg border p-6 flex flex-col", isPopular && "border-primary border-2"),
|
|
584
614
|
children: [
|
|
@@ -621,15 +651,18 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
|
621
651
|
})
|
|
622
652
|
]
|
|
623
653
|
}),
|
|
624
|
-
/* @__PURE__ */
|
|
625
|
-
className: "space-y-4 mb-6
|
|
626
|
-
children:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
654
|
+
/* @__PURE__ */ jsx("div", {
|
|
655
|
+
className: "space-y-4 mb-6 grow",
|
|
656
|
+
children: plan.phases.map((phase, index) => {
|
|
657
|
+
const laterKeys = new Set(plan.phases.slice(index + 1).flatMap((p) => p.rateCards.map((rc) => rc.featureKey ?? rc.key)));
|
|
658
|
+
return /* @__PURE__ */ jsx(PhaseSection, {
|
|
659
|
+
phase,
|
|
660
|
+
currency: plan.currency,
|
|
661
|
+
showName: hasMultiplePhases,
|
|
662
|
+
excludeKeys: laterKeys,
|
|
663
|
+
units
|
|
664
|
+
}, phase.key);
|
|
665
|
+
})
|
|
633
666
|
}),
|
|
634
667
|
isSubscribed ? /* @__PURE__ */ jsx(Button, {
|
|
635
668
|
variant: isPopular ? "default" : "secondary",
|
|
@@ -652,7 +685,7 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
|
652
685
|
|
|
653
686
|
//#endregion
|
|
654
687
|
//#region src/pages/PricingPage.tsx
|
|
655
|
-
const PricingPage = ({ subtitle = "See our pricing options and choose the one that best suits your needs.", title = "Pricing" }) => {
|
|
688
|
+
const PricingPage = ({ subtitle = "See our pricing options and choose the one that best suits your needs.", title = "Pricing", units }) => {
|
|
656
689
|
const zudoku = useZudoku();
|
|
657
690
|
const deploymentName = useDeploymentName();
|
|
658
691
|
const auth = useAuth();
|
|
@@ -685,6 +718,7 @@ const PricingPage = ({ subtitle = "See our pricing options and choose the one th
|
|
|
685
718
|
className: "w-full grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(300px,max-content))] justify-center gap-6",
|
|
686
719
|
children: pricingTable.items.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
|
|
687
720
|
plan,
|
|
721
|
+
units,
|
|
688
722
|
isPopular: plan.metadata?.zuplo_most_popular === "true",
|
|
689
723
|
isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
|
|
690
724
|
}, plan.id))
|
|
@@ -1100,9 +1134,8 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1100
1134
|
|
|
1101
1135
|
//#endregion
|
|
1102
1136
|
//#region src/pages/subscriptions/SwitchPlanModal.tsx
|
|
1103
|
-
const comparePlans = (currentPlan, targetPlan) => {
|
|
1104
|
-
const
|
|
1105
|
-
const isUpgrade = getPriceFromPlan(targetPlan).monthly > currentPrice;
|
|
1137
|
+
const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex) => {
|
|
1138
|
+
const isUpgrade = targetIndex > currentIndex;
|
|
1106
1139
|
const currentPhase = currentPlan?.phases.at(-1);
|
|
1107
1140
|
const targetPhase = targetPlan.phases.at(-1);
|
|
1108
1141
|
const { quotas: currentQuotas, features: currentFeatures } = currentPhase ? categorizeRateCards(currentPhase.rateCards, currentPlan?.currency) : {
|
|
@@ -1390,8 +1423,11 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1390
1423
|
downgrades: [],
|
|
1391
1424
|
privatePlans: []
|
|
1392
1425
|
};
|
|
1393
|
-
const isPrivatePlan = (plan) => plan.metadata?.
|
|
1394
|
-
const
|
|
1426
|
+
const isPrivatePlan = (plan) => plan.metadata?.zuplo_private_plan === "true";
|
|
1427
|
+
const currentIndex = plansData.items.findIndex((p) => p.id === currentPlan.id);
|
|
1428
|
+
const allComparisons = plansData.items.filter((p) => p.id !== currentPlan.id).map((plan) => {
|
|
1429
|
+
return comparePlans(currentPlan, plan, currentIndex, plansData.items.indexOf(plan));
|
|
1430
|
+
});
|
|
1395
1431
|
return {
|
|
1396
1432
|
upgrades: allComparisons.filter((c) => c.isUpgrade && !isPrivatePlan(c.plan)),
|
|
1397
1433
|
downgrades: allComparisons.filter((c) => !c.isUpgrade && !isPrivatePlan(c.plan)),
|
|
@@ -1901,16 +1937,12 @@ const SubscriptionsPage = () => {
|
|
|
1901
1937
|
//#region src/ZuploMonetizationPlugin.tsx
|
|
1902
1938
|
const PRICING_PATH = "/pricing";
|
|
1903
1939
|
const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
1904
|
-
transformConfig: ({ merge }) => merge({
|
|
1940
|
+
transformConfig: ({ config, merge }) => merge({
|
|
1905
1941
|
apiKeys: { enabled: false },
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
to: PRICING_PATH,
|
|
1911
|
-
children: "Pricing"
|
|
1912
|
-
})
|
|
1913
|
-
}) }
|
|
1942
|
+
header: { navigation: [...config.header?.navigation ?? [], {
|
|
1943
|
+
label: "Pricing",
|
|
1944
|
+
to: PRICING_PATH
|
|
1945
|
+
}] }
|
|
1914
1946
|
}),
|
|
1915
1947
|
getIdentities: async (context) => {
|
|
1916
1948
|
const deploymentName = context.env.ZUPLO_PUBLIC_DEPLOYMENT_NAME;
|
|
@@ -1954,20 +1986,22 @@ const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
|
1954
1986
|
{
|
|
1955
1987
|
path: "/manage-payment",
|
|
1956
1988
|
element: /* @__PURE__ */ jsx(ManagePaymentPage, {})
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
path: PRICING_PATH,
|
|
1992
|
+
handle: { layout: "default" },
|
|
1993
|
+
element: /* @__PURE__ */ jsx(PricingPage, {
|
|
1994
|
+
subtitle: options?.pricing?.subtitle,
|
|
1995
|
+
title: options?.pricing?.title,
|
|
1996
|
+
units: options?.pricing?.units
|
|
1997
|
+
})
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
handle: { layout: "default" },
|
|
2001
|
+
path: "/subscriptions/:subscriptionId?",
|
|
2002
|
+
element: /* @__PURE__ */ jsx(SubscriptionsPage, {})
|
|
1957
2003
|
}
|
|
1958
2004
|
]
|
|
1959
|
-
}, {
|
|
1960
|
-
Component: ZuploMonetizationWrapper,
|
|
1961
|
-
children: [{
|
|
1962
|
-
path: "/pricing",
|
|
1963
|
-
element: /* @__PURE__ */ jsx(PricingPage, {
|
|
1964
|
-
subtitle: options?.pricing?.subtitle,
|
|
1965
|
-
title: options?.pricing?.title
|
|
1966
|
-
})
|
|
1967
|
-
}, {
|
|
1968
|
-
path: "/subscriptions/:subscriptionId?",
|
|
1969
|
-
element: /* @__PURE__ */ jsx(SubscriptionsPage, {})
|
|
1970
|
-
}]
|
|
1971
2005
|
}];
|
|
1972
2006
|
},
|
|
1973
2007
|
getProtectedRoutes: () => {
|
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.22",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zuplo/zudoku",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@testing-library/react": "16.3.2",
|
|
28
28
|
"@types/react": "19.2.14",
|
|
29
29
|
"@types/react-dom": "19.2.3",
|
|
30
|
-
"happy-dom": "20.
|
|
30
|
+
"happy-dom": "20.7.0",
|
|
31
31
|
"react": "19.2.4",
|
|
32
32
|
"react-dom": "19.2.4",
|
|
33
33
|
"tsdown": "0.20.3",
|
|
34
|
-
"zudoku": "0.70.
|
|
34
|
+
"zudoku": "0.70.4"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=19.2.0",
|