@zuplo/zudoku-plugin-monetization 0.0.21 → 0.0.23
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 +2 -0
- package/dist/index.mjs +74 -39
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -5,6 +5,8 @@ type ZudokuMonetizationPluginOptions = {
|
|
|
5
5
|
pricing?: {
|
|
6
6
|
subtitle?: string;
|
|
7
7
|
title?: string;
|
|
8
|
+
units?: Record<string, string>;
|
|
9
|
+
showYearlyPrice?: boolean;
|
|
8
10
|
};
|
|
9
11
|
};
|
|
10
12
|
declare const zuploMonetizationPlugin: (options?: ZudokuMonetizationPluginOptions | undefined) => zudoku.ZudokuPlugin;
|
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, Slot } 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, showYearlyPrice = true, 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: [
|
|
@@ -611,7 +640,7 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
|
611
640
|
}), !isFree && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
612
641
|
className: "text-muted-foreground text-sm",
|
|
613
642
|
children: "/mo"
|
|
614
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
643
|
+
}), showYearlyPrice && /* @__PURE__ */ jsxs("div", {
|
|
615
644
|
className: "w-full text-sm text-muted-foreground mt-1",
|
|
616
645
|
children: [formatPrice(price.yearly, plan.currency), "/year"]
|
|
617
646
|
})] })] })
|
|
@@ -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, showYearlyPrice = true }) => {
|
|
657
689
|
const zudoku = useZudoku();
|
|
658
690
|
const deploymentName = useDeploymentName();
|
|
659
691
|
const auth = useAuth();
|
|
@@ -686,10 +718,13 @@ 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,
|
|
722
|
+
showYearlyPrice,
|
|
689
723
|
isPopular: plan.metadata?.zuplo_most_popular === "true",
|
|
690
724
|
isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
|
|
691
725
|
}, plan.id))
|
|
692
|
-
})
|
|
726
|
+
}),
|
|
727
|
+
/* @__PURE__ */ jsx(Slot.Target, { name: "pricing-page-after" })
|
|
693
728
|
]
|
|
694
729
|
});
|
|
695
730
|
};
|
|
@@ -1101,9 +1136,8 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1101
1136
|
|
|
1102
1137
|
//#endregion
|
|
1103
1138
|
//#region src/pages/subscriptions/SwitchPlanModal.tsx
|
|
1104
|
-
const comparePlans = (currentPlan, targetPlan) => {
|
|
1105
|
-
const
|
|
1106
|
-
const isUpgrade = getPriceFromPlan(targetPlan).monthly > currentPrice;
|
|
1139
|
+
const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex) => {
|
|
1140
|
+
const isUpgrade = targetIndex > currentIndex;
|
|
1107
1141
|
const currentPhase = currentPlan?.phases.at(-1);
|
|
1108
1142
|
const targetPhase = targetPlan.phases.at(-1);
|
|
1109
1143
|
const { quotas: currentQuotas, features: currentFeatures } = currentPhase ? categorizeRateCards(currentPhase.rateCards, currentPlan?.currency) : {
|
|
@@ -1391,8 +1425,11 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1391
1425
|
downgrades: [],
|
|
1392
1426
|
privatePlans: []
|
|
1393
1427
|
};
|
|
1394
|
-
const isPrivatePlan = (plan) => plan.metadata?.
|
|
1395
|
-
const
|
|
1428
|
+
const isPrivatePlan = (plan) => plan.metadata?.zuplo_private_plan === "true";
|
|
1429
|
+
const currentIndex = plansData.items.findIndex((p) => p.id === currentPlan.id);
|
|
1430
|
+
const allComparisons = plansData.items.filter((p) => p.id !== currentPlan.id).map((plan) => {
|
|
1431
|
+
return comparePlans(currentPlan, plan, currentIndex, plansData.items.indexOf(plan));
|
|
1432
|
+
});
|
|
1396
1433
|
return {
|
|
1397
1434
|
upgrades: allComparisons.filter((c) => c.isUpgrade && !isPrivatePlan(c.plan)),
|
|
1398
1435
|
downgrades: allComparisons.filter((c) => !c.isUpgrade && !isPrivatePlan(c.plan)),
|
|
@@ -1902,16 +1939,12 @@ const SubscriptionsPage = () => {
|
|
|
1902
1939
|
//#region src/ZuploMonetizationPlugin.tsx
|
|
1903
1940
|
const PRICING_PATH = "/pricing";
|
|
1904
1941
|
const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
1905
|
-
transformConfig: ({ merge }) => merge({
|
|
1942
|
+
transformConfig: ({ config, merge }) => merge({
|
|
1906
1943
|
apiKeys: { enabled: false },
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
to: PRICING_PATH,
|
|
1912
|
-
children: "Pricing"
|
|
1913
|
-
})
|
|
1914
|
-
}) }
|
|
1944
|
+
header: { navigation: [...config.header?.navigation ?? [], {
|
|
1945
|
+
label: "Pricing",
|
|
1946
|
+
to: PRICING_PATH
|
|
1947
|
+
}] }
|
|
1915
1948
|
}),
|
|
1916
1949
|
getIdentities: async (context) => {
|
|
1917
1950
|
const deploymentName = context.env.ZUPLO_PUBLIC_DEPLOYMENT_NAME;
|
|
@@ -1961,7 +1994,9 @@ const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
|
1961
1994
|
handle: { layout: "default" },
|
|
1962
1995
|
element: /* @__PURE__ */ jsx(PricingPage, {
|
|
1963
1996
|
subtitle: options?.pricing?.subtitle,
|
|
1964
|
-
title: options?.pricing?.title
|
|
1997
|
+
title: options?.pricing?.title,
|
|
1998
|
+
units: options?.pricing?.units,
|
|
1999
|
+
showYearlyPrice: options?.pricing?.showYearlyPrice
|
|
1965
2000
|
})
|
|
1966
2001
|
},
|
|
1967
2002
|
{
|
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.23",
|
|
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.8.3",
|
|
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.71.2"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=19.2.0",
|