@zuplo/zudoku-plugin-monetization 0.0.21 → 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 +69 -37
- package/package.json +2 -2
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,
|
|
@@ -573,13 +579,36 @@ const ManagePaymentPage = () => {
|
|
|
573
579
|
|
|
574
580
|
//#endregion
|
|
575
581
|
//#region src/pages/pricing/PricingCard.tsx
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
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;
|
|
580
608
|
const price = getPriceFromPlan(plan);
|
|
581
609
|
const isFree = price.monthly === 0;
|
|
582
610
|
const isCustom = plan.metadata?.isCustom === true;
|
|
611
|
+
const hasMultiplePhases = plan.phases.length > 1;
|
|
583
612
|
return /* @__PURE__ */ jsxs("div", {
|
|
584
613
|
className: cn("relative rounded-lg border p-6 flex flex-col", isPopular && "border-primary border-2"),
|
|
585
614
|
children: [
|
|
@@ -622,15 +651,18 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
|
622
651
|
})
|
|
623
652
|
]
|
|
624
653
|
}),
|
|
625
|
-
/* @__PURE__ */
|
|
626
|
-
className: "space-y-4 mb-6
|
|
627
|
-
children:
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
+
})
|
|
634
666
|
}),
|
|
635
667
|
isSubscribed ? /* @__PURE__ */ jsx(Button, {
|
|
636
668
|
variant: isPopular ? "default" : "secondary",
|
|
@@ -653,7 +685,7 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
|
653
685
|
|
|
654
686
|
//#endregion
|
|
655
687
|
//#region src/pages/PricingPage.tsx
|
|
656
|
-
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 }) => {
|
|
657
689
|
const zudoku = useZudoku();
|
|
658
690
|
const deploymentName = useDeploymentName();
|
|
659
691
|
const auth = useAuth();
|
|
@@ -686,6 +718,7 @@ const PricingPage = ({ subtitle = "See our pricing options and choose the one th
|
|
|
686
718
|
className: "w-full grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(300px,max-content))] justify-center gap-6",
|
|
687
719
|
children: pricingTable.items.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
|
|
688
720
|
plan,
|
|
721
|
+
units,
|
|
689
722
|
isPopular: plan.metadata?.zuplo_most_popular === "true",
|
|
690
723
|
isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
|
|
691
724
|
}, plan.id))
|
|
@@ -1101,9 +1134,8 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1101
1134
|
|
|
1102
1135
|
//#endregion
|
|
1103
1136
|
//#region src/pages/subscriptions/SwitchPlanModal.tsx
|
|
1104
|
-
const comparePlans = (currentPlan, targetPlan) => {
|
|
1105
|
-
const
|
|
1106
|
-
const isUpgrade = getPriceFromPlan(targetPlan).monthly > currentPrice;
|
|
1137
|
+
const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex) => {
|
|
1138
|
+
const isUpgrade = targetIndex > currentIndex;
|
|
1107
1139
|
const currentPhase = currentPlan?.phases.at(-1);
|
|
1108
1140
|
const targetPhase = targetPlan.phases.at(-1);
|
|
1109
1141
|
const { quotas: currentQuotas, features: currentFeatures } = currentPhase ? categorizeRateCards(currentPhase.rateCards, currentPlan?.currency) : {
|
|
@@ -1391,8 +1423,11 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1391
1423
|
downgrades: [],
|
|
1392
1424
|
privatePlans: []
|
|
1393
1425
|
};
|
|
1394
|
-
const isPrivatePlan = (plan) => plan.metadata?.
|
|
1395
|
-
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
|
+
});
|
|
1396
1431
|
return {
|
|
1397
1432
|
upgrades: allComparisons.filter((c) => c.isUpgrade && !isPrivatePlan(c.plan)),
|
|
1398
1433
|
downgrades: allComparisons.filter((c) => !c.isUpgrade && !isPrivatePlan(c.plan)),
|
|
@@ -1902,16 +1937,12 @@ const SubscriptionsPage = () => {
|
|
|
1902
1937
|
//#region src/ZuploMonetizationPlugin.tsx
|
|
1903
1938
|
const PRICING_PATH = "/pricing";
|
|
1904
1939
|
const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
1905
|
-
transformConfig: ({ merge }) => merge({
|
|
1940
|
+
transformConfig: ({ config, merge }) => merge({
|
|
1906
1941
|
apiKeys: { enabled: false },
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
to: PRICING_PATH,
|
|
1912
|
-
children: "Pricing"
|
|
1913
|
-
})
|
|
1914
|
-
}) }
|
|
1942
|
+
header: { navigation: [...config.header?.navigation ?? [], {
|
|
1943
|
+
label: "Pricing",
|
|
1944
|
+
to: PRICING_PATH
|
|
1945
|
+
}] }
|
|
1915
1946
|
}),
|
|
1916
1947
|
getIdentities: async (context) => {
|
|
1917
1948
|
const deploymentName = context.env.ZUPLO_PUBLIC_DEPLOYMENT_NAME;
|
|
@@ -1961,7 +1992,8 @@ const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
|
1961
1992
|
handle: { layout: "default" },
|
|
1962
1993
|
element: /* @__PURE__ */ jsx(PricingPage, {
|
|
1963
1994
|
subtitle: options?.pricing?.subtitle,
|
|
1964
|
-
title: options?.pricing?.title
|
|
1995
|
+
title: options?.pricing?.title,
|
|
1996
|
+
units: options?.pricing?.units
|
|
1965
1997
|
})
|
|
1966
1998
|
},
|
|
1967
1999
|
{
|
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",
|
|
@@ -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.70.
|
|
34
|
+
"zudoku": "0.70.4"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=19.2.0",
|