@zuplo/zudoku-plugin-monetization 0.0.35 → 0.0.36-pre.0
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/PricingTable-DfYAmAjk.mjs +443 -0
- package/dist/index.mjs +44 -433
- package/dist/pricing-ui.d.mts +296 -0
- package/dist/pricing-ui.mjs +2 -0
- package/package.json +42 -22
- package/LICENSE.md +0 -18
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _ as formatDurationAdjective, a as subscriptionTaxLegendSentence, c as PlanEntitlements, d as categorizeRateCards, f as formatTieredPriceBreakdown, g as formatDuration, h as formatPrice, i as planHasDefaultTaxBehavior, m as formatMinorCurrencyAmount, p as formatStaticEntitlementConfig, s as getPriceFromPlan, t as PricingTable, v as formatDurationInterval } from "./PricingTable-DfYAmAjk.mjs";
|
|
1
2
|
import { Suspense, createContext, use, useEffect, useMemo, useState } from "react";
|
|
2
3
|
import { cn, createPlugin, joinUrl, throwIfProblemJson } from "zudoku";
|
|
3
4
|
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";
|
|
@@ -8,8 +9,7 @@ import { Link as Link$1, Outlet, useLocation, useNavigate, useSearchParams } fro
|
|
|
8
9
|
import { Alert, AlertAction, AlertDescription, AlertTitle } from "zudoku/ui/Alert";
|
|
9
10
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "zudoku/ui/Card";
|
|
10
11
|
import { Separator } from "zudoku/ui/Separator";
|
|
11
|
-
import {
|
|
12
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { Button as Button$1 } from "zudoku/ui/Button";
|
|
14
14
|
import { Skeleton } from "zudoku/ui/Skeleton";
|
|
15
15
|
import { DismissibleAlert, DismissibleAlertAction } from "zudoku/ui/DismissibleAlert";
|
|
@@ -23,263 +23,6 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
|
|
23
23
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "zudoku/ui/Dialog";
|
|
24
24
|
import { Input } from "zudoku/ui/Input";
|
|
25
25
|
import { Progress } from "zudoku/ui/Progress";
|
|
26
|
-
//#region src/utils/formatDuration.ts
|
|
27
|
-
const formatDuration = (iso) => {
|
|
28
|
-
try {
|
|
29
|
-
const d = parse(iso);
|
|
30
|
-
if (d.months === 1) return "month";
|
|
31
|
-
if (d.months && d.months > 1) return `${d.months} months`;
|
|
32
|
-
if (d.years === 1) return "year";
|
|
33
|
-
if (d.years && d.years > 1) return `${d.years} years`;
|
|
34
|
-
if (d.weeks === 1) return "week";
|
|
35
|
-
if (d.weeks && d.weeks > 1) return `${d.weeks} weeks`;
|
|
36
|
-
if (d.days === 1) return "day";
|
|
37
|
-
if (d.days && d.days > 1) return `${d.days} days`;
|
|
38
|
-
return iso;
|
|
39
|
-
} catch {
|
|
40
|
-
return iso;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
const formatDurationInterval = (iso) => {
|
|
44
|
-
try {
|
|
45
|
-
const d = parse(iso);
|
|
46
|
-
if (d.years === 1) return "yearly";
|
|
47
|
-
if (d.years && d.years > 1) return `every ${d.years} years`;
|
|
48
|
-
if (d.months === 1) return "monthly";
|
|
49
|
-
if (d.months && d.months > 1) return `every ${d.months} months`;
|
|
50
|
-
if (d.weeks === 1) return "weekly";
|
|
51
|
-
if (d.weeks && d.weeks > 1) return `every ${d.weeks} weeks`;
|
|
52
|
-
if (d.days === 1) return "daily";
|
|
53
|
-
if (d.days && d.days > 1) return `every ${d.days} days`;
|
|
54
|
-
return iso;
|
|
55
|
-
} catch {
|
|
56
|
-
return iso;
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Returns an adjective form suitable for possessive context
|
|
61
|
-
* e.g. "your monthly quota", "your weekly limit".
|
|
62
|
-
* Falls back to "billing period" for multi-unit cadences
|
|
63
|
-
* where "every 3 months" would be grammatically awkward.
|
|
64
|
-
*/
|
|
65
|
-
const formatDurationAdjective = (iso) => {
|
|
66
|
-
try {
|
|
67
|
-
const d = parse(iso);
|
|
68
|
-
if (d.years === 1) return "yearly";
|
|
69
|
-
if (d.months === 1) return "monthly";
|
|
70
|
-
if (d.weeks === 1) return "weekly";
|
|
71
|
-
if (d.days === 1) return "daily";
|
|
72
|
-
return "billing period";
|
|
73
|
-
} catch {
|
|
74
|
-
return "billing period";
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/utils/formatPrice.ts
|
|
79
|
-
const formatPrice = (amount, currency) => new Intl.NumberFormat("en-US", {
|
|
80
|
-
style: "currency",
|
|
81
|
-
currency: currency ?? "USD",
|
|
82
|
-
minimumFractionDigits: 2,
|
|
83
|
-
maximumFractionDigits: 6,
|
|
84
|
-
trailingZeroDisplay: "stripIfInteger"
|
|
85
|
-
}).format(amount);
|
|
86
|
-
/** Amount is in the smallest currency unit (e.g. Stripe); divisor from `Intl` / ISO 4217. */
|
|
87
|
-
const formatMinorCurrencyAmount = (amountInMinorUnits, currency) => {
|
|
88
|
-
const code = (currency ?? "USD").toUpperCase();
|
|
89
|
-
const fractionDigits = new Intl.NumberFormat("en-US", {
|
|
90
|
-
style: "currency",
|
|
91
|
-
currency: code
|
|
92
|
-
}).resolvedOptions().maximumFractionDigits ?? 2;
|
|
93
|
-
const divisor = 10 ** fractionDigits;
|
|
94
|
-
return new Intl.NumberFormat("en-US", {
|
|
95
|
-
style: "currency",
|
|
96
|
-
currency: code,
|
|
97
|
-
minimumFractionDigits: fractionDigits,
|
|
98
|
-
maximumFractionDigits: fractionDigits
|
|
99
|
-
}).format(amountInMinorUnits / divisor);
|
|
100
|
-
};
|
|
101
|
-
//#endregion
|
|
102
|
-
//#region src/utils/formatTieredPriceBreakdown.ts
|
|
103
|
-
const parseAmount = (value) => {
|
|
104
|
-
if (!value) return;
|
|
105
|
-
const parsed = Number.parseFloat(value);
|
|
106
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
107
|
-
};
|
|
108
|
-
const formatTieredPriceBreakdown = (opts) => {
|
|
109
|
-
const { tiers, currency, unitLabel, includedLabel, omitIncludedUpToAmount } = opts;
|
|
110
|
-
if (!tiers || tiers.length <= 1) return;
|
|
111
|
-
const lines = [];
|
|
112
|
-
let lastUpTo;
|
|
113
|
-
for (const tier of tiers) {
|
|
114
|
-
const upTo = parseAmount(tier.upToAmount);
|
|
115
|
-
const unit = parseAmount(tier.unitPriceAmount) ?? 0;
|
|
116
|
-
const flat = parseAmount(tier.flatPriceAmount) ?? 0;
|
|
117
|
-
const prefix = upTo != null ? `Up to ${upTo.toLocaleString("en-US")}` : lastUpTo != null ? `Over ${lastUpTo.toLocaleString("en-US")}` : `Per ${unitLabel}`;
|
|
118
|
-
const unitPart = unit > 0 ? `${formatPrice(unit, currency)}/${unitLabel}` : includedLabel;
|
|
119
|
-
const flatPart = flat > 0 ? ` + ${formatPrice(flat, currency)} base` : "";
|
|
120
|
-
const line = `${prefix}: ${unitPart}${flatPart}`;
|
|
121
|
-
if (omitIncludedUpToAmount != null && upTo != null && upTo === omitIncludedUpToAmount && unitPart === includedLabel && flatPart === "") {} else lines.push(line);
|
|
122
|
-
if (upTo != null) lastUpTo = upTo;
|
|
123
|
-
}
|
|
124
|
-
return lines.length > 0 ? lines : void 0;
|
|
125
|
-
};
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/utils/categorizeRateCards.ts
|
|
128
|
-
const categorizeRateCards = (rateCards, options) => {
|
|
129
|
-
const { currency, units, planBillingCadence } = options ?? {};
|
|
130
|
-
const quotas = [];
|
|
131
|
-
const features = [];
|
|
132
|
-
for (const rc of rateCards) {
|
|
133
|
-
const et = rc.entitlementTemplate;
|
|
134
|
-
if (!et) continue;
|
|
135
|
-
if (et.type === "metered" && et.issueAfterReset != null) {
|
|
136
|
-
let overagePrice;
|
|
137
|
-
let tierPrices;
|
|
138
|
-
if (rc.price?.type === "tiered" && rc.price.tiers) {
|
|
139
|
-
const unitLabel = units?.[rc.key] ?? units?.[rc.featureKey ?? ""] ?? "unit";
|
|
140
|
-
tierPrices = formatTieredPriceBreakdown({
|
|
141
|
-
tiers: rc.price.tiers.map((t) => ({
|
|
142
|
-
upToAmount: t.upToAmount,
|
|
143
|
-
unitPriceAmount: t.unitPrice?.amount,
|
|
144
|
-
flatPriceAmount: t.flatPrice?.amount
|
|
145
|
-
})),
|
|
146
|
-
currency,
|
|
147
|
-
unitLabel,
|
|
148
|
-
includedLabel: "Included",
|
|
149
|
-
omitIncludedUpToAmount: et.issueAfterReset
|
|
150
|
-
});
|
|
151
|
-
const overageTier = rc.price.tiers.find((t) => t.unitPrice?.amount && parseFloat(t.unitPrice.amount) > 0);
|
|
152
|
-
if (et.isSoftLimit !== false && overageTier?.unitPrice) overagePrice = `${formatPrice(parseFloat(overageTier.unitPrice.amount), currency)}/${unitLabel}`;
|
|
153
|
-
}
|
|
154
|
-
quotas.push({
|
|
155
|
-
key: rc.featureKey ?? rc.key,
|
|
156
|
-
name: rc.name,
|
|
157
|
-
limit: et.issueAfterReset,
|
|
158
|
-
period: rc.billingCadence ? formatDuration(rc.billingCadence) : planBillingCadence ? formatDuration(planBillingCadence) : "month",
|
|
159
|
-
overagePrice,
|
|
160
|
-
tierPrices
|
|
161
|
-
});
|
|
162
|
-
} else if (et.type === "boolean") features.push({
|
|
163
|
-
key: rc.featureKey ?? rc.key,
|
|
164
|
-
name: rc.name
|
|
165
|
-
});
|
|
166
|
-
else if (et.type === "static" && et.config) try {
|
|
167
|
-
const config = JSON.parse(et.config);
|
|
168
|
-
features.push({
|
|
169
|
-
key: rc.featureKey ?? rc.key,
|
|
170
|
-
name: rc.name,
|
|
171
|
-
value: String(config.value)
|
|
172
|
-
});
|
|
173
|
-
} catch {
|
|
174
|
-
features.push({
|
|
175
|
-
key: rc.featureKey ?? rc.key,
|
|
176
|
-
name: rc.name
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return {
|
|
181
|
-
quotas,
|
|
182
|
-
features
|
|
183
|
-
};
|
|
184
|
-
};
|
|
185
|
-
//#endregion
|
|
186
|
-
//#region src/components/FeatureItem.tsx
|
|
187
|
-
const FeatureItem = ({ feature, className }) => {
|
|
188
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
189
|
-
className: cn("flex items-start gap-2", className),
|
|
190
|
-
children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-primary shrink-0 mt-0.5" }), /* @__PURE__ */ jsx("div", {
|
|
191
|
-
className: "text-sm",
|
|
192
|
-
children: feature.value ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
193
|
-
/* @__PURE__ */ jsxs("span", {
|
|
194
|
-
className: "font-medium",
|
|
195
|
-
children: [feature.name, ":"]
|
|
196
|
-
}),
|
|
197
|
-
" ",
|
|
198
|
-
feature.value
|
|
199
|
-
] }) : feature.name
|
|
200
|
-
})]
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
//#endregion
|
|
204
|
-
//#region src/components/QuotaItem.tsx
|
|
205
|
-
const QuotaItem = ({ quota, className }) => {
|
|
206
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
207
|
-
className: cn("flex items-start gap-2", className),
|
|
208
|
-
children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-primary shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("div", {
|
|
209
|
-
className: "text-sm",
|
|
210
|
-
children: [
|
|
211
|
-
/* @__PURE__ */ jsxs("span", {
|
|
212
|
-
className: "font-medium",
|
|
213
|
-
children: [quota.name, ":"]
|
|
214
|
-
}),
|
|
215
|
-
" ",
|
|
216
|
-
quota.limit.toLocaleString(),
|
|
217
|
-
" / ",
|
|
218
|
-
quota.period,
|
|
219
|
-
quota.tierPrices && quota.tierPrices.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
220
|
-
className: "text-xs text-muted-foreground mt-1 space-y-0.5",
|
|
221
|
-
children: quota.tierPrices.map((line) => /* @__PURE__ */ jsx("li", { children: line }, line))
|
|
222
|
-
}),
|
|
223
|
-
quota.overagePrice && /* @__PURE__ */ jsxs("div", {
|
|
224
|
-
className: "text-xs text-muted-foreground mt-0.5",
|
|
225
|
-
children: [
|
|
226
|
-
"+",
|
|
227
|
-
quota.overagePrice,
|
|
228
|
-
" after quota"
|
|
229
|
-
]
|
|
230
|
-
})
|
|
231
|
-
]
|
|
232
|
-
})]
|
|
233
|
-
});
|
|
234
|
-
};
|
|
235
|
-
//#endregion
|
|
236
|
-
//#region src/components/PlanEntitlements.tsx
|
|
237
|
-
const PhaseSection = ({ phase, currency, showName, billingCadence, units, itemClassName }) => {
|
|
238
|
-
const { quotas, features } = categorizeRateCards(phase.rateCards, {
|
|
239
|
-
currency,
|
|
240
|
-
units,
|
|
241
|
-
planBillingCadence: billingCadence
|
|
242
|
-
});
|
|
243
|
-
if (quotas.length === 0 && features.length === 0) return null;
|
|
244
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
245
|
-
className: "space-y-2",
|
|
246
|
-
children: [
|
|
247
|
-
showName && /* @__PURE__ */ jsxs("div", {
|
|
248
|
-
className: "text-sm font-medium text-card-foreground",
|
|
249
|
-
children: [phase.name, phase.duration && /* @__PURE__ */ jsxs("span", {
|
|
250
|
-
className: "text-muted-foreground font-normal",
|
|
251
|
-
children: [
|
|
252
|
-
" ",
|
|
253
|
-
"— ",
|
|
254
|
-
formatDuration(phase.duration)
|
|
255
|
-
]
|
|
256
|
-
})]
|
|
257
|
-
}),
|
|
258
|
-
quotas.map((quota) => /* @__PURE__ */ jsx(QuotaItem, {
|
|
259
|
-
quota,
|
|
260
|
-
className: itemClassName
|
|
261
|
-
}, quota.key)),
|
|
262
|
-
features.map((feature) => /* @__PURE__ */ jsx(FeatureItem, {
|
|
263
|
-
feature,
|
|
264
|
-
className: itemClassName
|
|
265
|
-
}, feature.key))
|
|
266
|
-
]
|
|
267
|
-
});
|
|
268
|
-
};
|
|
269
|
-
const PlanEntitlements = ({ phases, currency, billingCadence, units, itemClassName }) => {
|
|
270
|
-
return /* @__PURE__ */ jsx("div", {
|
|
271
|
-
className: "space-y-4",
|
|
272
|
-
children: phases.map((phase, idx) => /* @__PURE__ */ jsx(PhaseSection, {
|
|
273
|
-
phase,
|
|
274
|
-
currency,
|
|
275
|
-
showName: phases.length > 1,
|
|
276
|
-
billingCadence,
|
|
277
|
-
units,
|
|
278
|
-
itemClassName
|
|
279
|
-
}, phase.key ?? String(idx)))
|
|
280
|
-
});
|
|
281
|
-
};
|
|
282
|
-
//#endregion
|
|
283
26
|
//#region src/hooks/useDeploymentName.ts
|
|
284
27
|
const useDeploymentName = () => {
|
|
285
28
|
const deploymentName = useZudoku().env.ZUPLO_PUBLIC_DEPLOYMENT_NAME;
|
|
@@ -309,14 +52,6 @@ const formatBillingCycle = (duration) => {
|
|
|
309
52
|
return `every ${duration}`;
|
|
310
53
|
};
|
|
311
54
|
//#endregion
|
|
312
|
-
//#region src/utils/getPriceFromPlan.ts
|
|
313
|
-
const getPriceFromPlan = (plan) => {
|
|
314
|
-
return {
|
|
315
|
-
monthly: plan.monthlyPrice != null ? parseFloat(plan.monthlyPrice) : 0,
|
|
316
|
-
yearly: plan.yearlyPrice != null ? parseFloat(plan.yearlyPrice) : 0
|
|
317
|
-
};
|
|
318
|
-
};
|
|
319
|
-
//#endregion
|
|
320
55
|
//#region src/utils/purchaseDetails.ts
|
|
321
56
|
const getPlanFromPurchaseDetails = (response) => {
|
|
322
57
|
return response;
|
|
@@ -718,117 +453,6 @@ const usePlans = () => {
|
|
|
718
453
|
});
|
|
719
454
|
};
|
|
720
455
|
//#endregion
|
|
721
|
-
//#region src/utils/pricingTaxLegend.ts
|
|
722
|
-
const normalizeTaxBehavior = (behavior) => {
|
|
723
|
-
switch (behavior.trim().toLowerCase()) {
|
|
724
|
-
case "exclusive":
|
|
725
|
-
case "tax_exclusive": return "exclusive";
|
|
726
|
-
case "inclusive":
|
|
727
|
-
case "tax_inclusive": return "inclusive";
|
|
728
|
-
default: return "unspecified";
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
const planHasDefaultTaxBehavior = (plan) => {
|
|
732
|
-
const behavior = plan.defaultTaxConfig?.behavior;
|
|
733
|
-
return typeof behavior === "string" && behavior.trim().length > 0;
|
|
734
|
-
};
|
|
735
|
-
const collectDefaultTaxBehaviors = (plan) => {
|
|
736
|
-
const behavior = plan.defaultTaxConfig?.behavior;
|
|
737
|
-
return typeof behavior === "string" && behavior.trim().length > 0 ? normalizeTaxBehavior(behavior) : "unspecified";
|
|
738
|
-
};
|
|
739
|
-
const taxBehaviorLegendSentence = (behavior) => {
|
|
740
|
-
switch (normalizeTaxBehavior(behavior)) {
|
|
741
|
-
case "exclusive": return "Prices exclude tax; taxes may be added at checkout if applicable.";
|
|
742
|
-
case "inclusive": return "Prices include tax where applicable.";
|
|
743
|
-
default: return;
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
const subscriptionTaxLegendSentence = (behavior) => {
|
|
747
|
-
switch (normalizeTaxBehavior(behavior)) {
|
|
748
|
-
case "exclusive": return "Price excludes tax; taxes may be added on invoice if applicable.";
|
|
749
|
-
case "inclusive": return "Price includes tax where applicable.";
|
|
750
|
-
default: return;
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
//#endregion
|
|
754
|
-
//#region src/pages/pricing/PricingCard.tsx
|
|
755
|
-
const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
756
|
-
const { pricing } = useMonetizationConfig();
|
|
757
|
-
if (plan.phases.length === 0) return null;
|
|
758
|
-
const price = getPriceFromPlan(plan);
|
|
759
|
-
const isFree = price.monthly === 0;
|
|
760
|
-
const isCustom = plan.metadata?.isCustom === true;
|
|
761
|
-
const billingInterval = formatDuration(plan.billingCadence);
|
|
762
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
763
|
-
className: cn("relative rounded-lg border p-6 flex flex-col", isPopular && "border-primary border-2"),
|
|
764
|
-
children: [
|
|
765
|
-
isPopular && /* @__PURE__ */ jsx("div", {
|
|
766
|
-
className: "absolute top-0 -translate-y-1/2 left-1/2 -translate-x-1/2 whitespace-nowrap",
|
|
767
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
768
|
-
className: "bg-primary text-primary-foreground text-xs font-semibold px-3 py-1 rounded-full uppercase",
|
|
769
|
-
children: "Most Popular"
|
|
770
|
-
})
|
|
771
|
-
}),
|
|
772
|
-
/* @__PURE__ */ jsxs("div", {
|
|
773
|
-
className: "mb-4 pb-4 border-b",
|
|
774
|
-
children: [
|
|
775
|
-
/* @__PURE__ */ jsx("h3", {
|
|
776
|
-
className: "text-base font-semibold text-muted-foreground mb-2",
|
|
777
|
-
children: plan.name
|
|
778
|
-
}),
|
|
779
|
-
/* @__PURE__ */ jsx("div", {
|
|
780
|
-
className: "flex items-baseline gap-1 flex-wrap",
|
|
781
|
-
children: isCustom ? /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
782
|
-
className: "text-3xl font-bold text-card-foreground",
|
|
783
|
-
children: "Custom"
|
|
784
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
785
|
-
className: "text-sm text-muted-foreground mt-1",
|
|
786
|
-
children: "Contact Sales"
|
|
787
|
-
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
788
|
-
className: "text-3xl font-bold text-card-foreground",
|
|
789
|
-
children: isFree ? "Free" : formatPrice(price.monthly, plan.currency)
|
|
790
|
-
}), !isFree && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
791
|
-
className: "text-muted-foreground text-sm",
|
|
792
|
-
children: ["/", billingInterval]
|
|
793
|
-
}), pricing?.showYearlyPrice !== false && price.yearly > 0 && /* @__PURE__ */ jsxs("div", {
|
|
794
|
-
className: "w-full text-sm text-muted-foreground mt-1",
|
|
795
|
-
children: [formatPrice(price.yearly, plan.currency), "/year"]
|
|
796
|
-
})] })] })
|
|
797
|
-
}),
|
|
798
|
-
plan.paymentRequired === false && /* @__PURE__ */ jsx("div", {
|
|
799
|
-
className: "text-sm text-muted-foreground mt-1",
|
|
800
|
-
children: "No CC required"
|
|
801
|
-
})
|
|
802
|
-
]
|
|
803
|
-
}),
|
|
804
|
-
/* @__PURE__ */ jsx("div", {
|
|
805
|
-
className: "space-y-4 mb-6 grow",
|
|
806
|
-
children: /* @__PURE__ */ jsx(PlanEntitlements, {
|
|
807
|
-
phases: plan.phases,
|
|
808
|
-
currency: plan.currency,
|
|
809
|
-
billingCadence: plan.billingCadence,
|
|
810
|
-
units: pricing?.units
|
|
811
|
-
})
|
|
812
|
-
}),
|
|
813
|
-
isSubscribed ? /* @__PURE__ */ jsx(Button, {
|
|
814
|
-
variant: isPopular ? "default" : "outline",
|
|
815
|
-
asChild: true,
|
|
816
|
-
children: /* @__PURE__ */ jsx(Link$1, {
|
|
817
|
-
to: `/subscriptions#manage`,
|
|
818
|
-
children: "Manage Subscriptions"
|
|
819
|
-
})
|
|
820
|
-
}) : /* @__PURE__ */ jsx(Button, {
|
|
821
|
-
variant: isPopular ? "default" : "outline",
|
|
822
|
-
asChild: true,
|
|
823
|
-
children: /* @__PURE__ */ jsx(Link$1, {
|
|
824
|
-
to: `/checkout?planId=${encodeURIComponent(plan.id)}`,
|
|
825
|
-
children: "Subscribe"
|
|
826
|
-
})
|
|
827
|
-
})
|
|
828
|
-
]
|
|
829
|
-
});
|
|
830
|
-
};
|
|
831
|
-
//#endregion
|
|
832
456
|
//#region src/pages/PricingPage.tsx
|
|
833
457
|
const PricingPage = () => {
|
|
834
458
|
const { pricing } = useMonetizationConfig();
|
|
@@ -836,13 +460,12 @@ const PricingPage = () => {
|
|
|
836
460
|
const deploymentName = useDeploymentName();
|
|
837
461
|
const auth = useAuth();
|
|
838
462
|
const { data: pricingTable } = usePlans();
|
|
839
|
-
const firstPlan = pricingTable.items[0];
|
|
840
|
-
const taxLegendSentence = firstPlan ? taxBehaviorLegendSentence(collectDefaultTaxBehaviors(firstPlan)) : void 0;
|
|
841
463
|
const { data: subscriptions = { items: [] } } = useQuery({
|
|
842
464
|
meta: { context: zudoku },
|
|
843
465
|
queryKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions`],
|
|
844
466
|
enabled: auth.isAuthenticated
|
|
845
467
|
});
|
|
468
|
+
const isSubscribed = subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status));
|
|
846
469
|
return /* @__PURE__ */ jsxs("div", {
|
|
847
470
|
className: "w-full px-4 pt-(--padding-content-top) pb-(--padding-content-bottom)",
|
|
848
471
|
children: [
|
|
@@ -862,30 +485,26 @@ const PricingPage = () => {
|
|
|
862
485
|
children: pricing?.subtitle ?? "See our pricing options and choose the one that best suits your needs."
|
|
863
486
|
})]
|
|
864
487
|
}),
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
className: "text-xs text-muted-foreground",
|
|
886
|
-
children: taxLegendSentence
|
|
887
|
-
})]
|
|
888
|
-
})] }),
|
|
488
|
+
/* @__PURE__ */ jsx(PricingTable, {
|
|
489
|
+
plans: pricingTable.items,
|
|
490
|
+
showYearlyPrice: pricing?.showYearlyPrice !== false,
|
|
491
|
+
units: pricing?.units,
|
|
492
|
+
renderAction: (plan, isPopular) => isSubscribed ? /* @__PURE__ */ jsx(Button, {
|
|
493
|
+
variant: isPopular ? "default" : "outline",
|
|
494
|
+
asChild: true,
|
|
495
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
496
|
+
to: `/subscriptions#manage`,
|
|
497
|
+
children: "Manage Subscriptions"
|
|
498
|
+
})
|
|
499
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
500
|
+
variant: isPopular ? "default" : "outline",
|
|
501
|
+
asChild: true,
|
|
502
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
503
|
+
to: `/checkout?planId=${encodeURIComponent(plan.id)}`,
|
|
504
|
+
children: "Subscribe"
|
|
505
|
+
})
|
|
506
|
+
})
|
|
507
|
+
}),
|
|
889
508
|
/* @__PURE__ */ jsx(Slot.Target, { name: "pricing-page-after" })
|
|
890
509
|
]
|
|
891
510
|
});
|
|
@@ -1201,7 +820,7 @@ const ApiKey = ({ apiKey, createdAt, lastUsed, expiresOn, isActive = true, label
|
|
|
1201
820
|
children: "•"
|
|
1202
821
|
}),
|
|
1203
822
|
/* @__PURE__ */ jsxs("span", { children: ["Last updated ", getTimeAgo(lastUsed)] }),
|
|
1204
|
-
expiresOn && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
823
|
+
expiresOn && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
1205
824
|
className: "text-muted-foreground/40",
|
|
1206
825
|
children: "•"
|
|
1207
826
|
}), /* @__PURE__ */ jsxs("span", {
|
|
@@ -1433,7 +1052,7 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1433
1052
|
children: [
|
|
1434
1053
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1435
1054
|
variant: "warning",
|
|
1436
|
-
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }), isTrialCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1055
|
+
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }), isTrialCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1437
1056
|
"Cancel your trial of ",
|
|
1438
1057
|
planName,
|
|
1439
1058
|
"?"
|
|
@@ -1441,11 +1060,11 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1441
1060
|
"Your subscription will end now and you won't be charged when the trial would have converted to ",
|
|
1442
1061
|
planName,
|
|
1443
1062
|
"."
|
|
1444
|
-
] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1063
|
+
] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1445
1064
|
"Cancel your ",
|
|
1446
1065
|
planName,
|
|
1447
1066
|
" subscription?"
|
|
1448
|
-
] }), /* @__PURE__ */ jsx(AlertDescription, { children: "Your subscription will end immediately. You'll lose access to its entitlements right away." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "Your plan will be canceled at the end of your billing cycle." }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1067
|
+
] }), /* @__PURE__ */ jsx(AlertDescription, { children: "Your subscription will end immediately. You'll lose access to its entitlements right away." })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "Your plan will be canceled at the end of your billing cycle." }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1449
1068
|
"You'll retain access until ",
|
|
1450
1069
|
formatDate$2(billingPeriodEnd),
|
|
1451
1070
|
". After your billing period ends, this plan will not renew and you would need to subscribe again to continue."
|
|
@@ -1453,7 +1072,7 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1453
1072
|
}),
|
|
1454
1073
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1455
1074
|
variant: "info",
|
|
1456
|
-
children: [/* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }), isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can subscribe again at any time" }), /* @__PURE__ */ jsx(AlertDescription, { children: "After canceling, you can return to the pricing page and start a new subscription whenever you're ready." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can still resume before then" }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1075
|
+
children: [/* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }), isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can subscribe again at any time" }), /* @__PURE__ */ jsx(AlertDescription, { children: "After canceling, you can return to the pricing page and start a new subscription whenever you're ready." })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can still resume before then" }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1457
1076
|
"If you change your mind you have until",
|
|
1458
1077
|
" ",
|
|
1459
1078
|
formatDate$2(billingPeriodEnd),
|
|
@@ -1690,7 +1309,7 @@ const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex, units)
|
|
|
1690
1309
|
const target = targetFeatures.find((f) => f.key === key);
|
|
1691
1310
|
if (current && target) {
|
|
1692
1311
|
let change = "same";
|
|
1693
|
-
if (current.value && target.value && current.value !== target.value) change = isUpgrade ? "upgraded" : "downgraded";
|
|
1312
|
+
if (current.value !== void 0 && target.value !== void 0 && current.value !== target.value) change = isUpgrade ? "upgraded" : "downgraded";
|
|
1694
1313
|
featureChanges.push({
|
|
1695
1314
|
key: key ?? "",
|
|
1696
1315
|
name: target.name,
|
|
@@ -1816,7 +1435,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1816
1435
|
}) : quota.change === "removed" ? /* @__PURE__ */ jsx("span", {
|
|
1817
1436
|
className: "text-destructive",
|
|
1818
1437
|
children: "No longer included"
|
|
1819
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1438
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1820
1439
|
/* @__PURE__ */ jsxs("span", {
|
|
1821
1440
|
className: "text-muted-foreground",
|
|
1822
1441
|
children: [
|
|
@@ -1841,10 +1460,10 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1841
1460
|
]
|
|
1842
1461
|
}, quota.key)), comparison.featureChanges.map((feature) => /* @__PURE__ */ jsx("div", {
|
|
1843
1462
|
className: "flex items-center gap-2 text-sm",
|
|
1844
|
-
children: feature.change === "same" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }), /* @__PURE__ */ jsxs("span", {
|
|
1463
|
+
children: feature.change === "same" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }), /* @__PURE__ */ jsxs("span", {
|
|
1845
1464
|
className: "text-muted-foreground",
|
|
1846
1465
|
children: [feature.name, typeof feature.newValue === "string" ? `: ${feature.newValue}` : typeof feature.currentValue === "string" ? `: ${feature.currentValue}` : ""]
|
|
1847
|
-
})] }) : feature.change === "added" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1466
|
+
})] }) : feature.change === "added" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1848
1467
|
/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }),
|
|
1849
1468
|
/* @__PURE__ */ jsx("span", {
|
|
1850
1469
|
className: "text-muted-foreground font-medium",
|
|
@@ -1858,7 +1477,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1858
1477
|
className: "text-green-600",
|
|
1859
1478
|
children: "Now included"
|
|
1860
1479
|
})
|
|
1861
|
-
] }) : feature.change === "removed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1480
|
+
] }) : feature.change === "removed" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1862
1481
|
/* @__PURE__ */ jsx(XIcon, { className: "w-4 h-4 text-destructive shrink-0" }),
|
|
1863
1482
|
/* @__PURE__ */ jsx("span", {
|
|
1864
1483
|
className: "font-medium",
|
|
@@ -1872,7 +1491,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1872
1491
|
className: "text-destructive",
|
|
1873
1492
|
children: "No longer included"
|
|
1874
1493
|
})
|
|
1875
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1494
|
+
] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1876
1495
|
/* @__PURE__ */ jsx(ChangeIndicator, { change: feature.change }),
|
|
1877
1496
|
/* @__PURE__ */ jsxs("span", {
|
|
1878
1497
|
className: "",
|
|
@@ -2256,19 +1875,11 @@ const getEntitlementsFromItems = (items, currency, units, fallbackBillingCadence
|
|
|
2256
1875
|
});
|
|
2257
1876
|
continue;
|
|
2258
1877
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
value: parsed?.value != null ? String(parsed.value) : void 0
|
|
2265
|
-
});
|
|
2266
|
-
} catch {
|
|
2267
|
-
features.push({
|
|
2268
|
-
entitlementType: "static",
|
|
2269
|
-
...base
|
|
2270
|
-
});
|
|
2271
|
-
}
|
|
1878
|
+
features.push({
|
|
1879
|
+
entitlementType: "static",
|
|
1880
|
+
...base,
|
|
1881
|
+
value: formatStaticEntitlementConfig(entitlement.config)
|
|
1882
|
+
});
|
|
2272
1883
|
}
|
|
2273
1884
|
}
|
|
2274
1885
|
return { features };
|
|
@@ -2316,7 +1927,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2316
1927
|
const primaryPrice = priceInfo.monthly === 0 && priceInfo.yearly === 0 ? /* @__PURE__ */ jsx("span", {
|
|
2317
1928
|
className: "text-primary font-medium",
|
|
2318
1929
|
children: "Free"
|
|
2319
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1930
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
2320
1931
|
className: "text-primary font-medium text-lg",
|
|
2321
1932
|
children: formatPrice(priceInfo.monthly, currency)
|
|
2322
1933
|
}), /* @__PURE__ */ jsxs("span", {
|
|
@@ -2403,10 +2014,10 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2403
2014
|
className: "flex flex-col gap-1",
|
|
2404
2015
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2405
2016
|
className: "text-foreground font-medium",
|
|
2406
|
-
children: [row.name, row.entitlementType === "static" && row.value ? `: ${row.value}` : ""]
|
|
2017
|
+
children: [row.name, row.entitlementType === "static" && row.value !== void 0 ? `: ${row.value}` : ""]
|
|
2407
2018
|
}), /* @__PURE__ */ jsx("div", {
|
|
2408
2019
|
className: "text-muted-foreground",
|
|
2409
|
-
children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2020
|
+
children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2410
2021
|
formatNumber(row.limit),
|
|
2411
2022
|
row.period ? ` / ${row.period}` : "",
|
|
2412
2023
|
row.tierPrices && row.tierPrices.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
@@ -2417,7 +2028,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2417
2028
|
className: "text-xs mt-0.5",
|
|
2418
2029
|
children: ["Overage: ", row.overagePrice]
|
|
2419
2030
|
}) : null
|
|
2420
|
-
] }) : row.entitlementType === "static" && row.value ? null : "Included"
|
|
2031
|
+
] }) : row.entitlementType === "static" && row.value !== void 0 ? null : "Included"
|
|
2421
2032
|
})]
|
|
2422
2033
|
})
|
|
2423
2034
|
}, `${row.key}:${row.phaseId}`))
|
|
@@ -2601,7 +2212,7 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
|
|
|
2601
2212
|
});
|
|
2602
2213
|
const isPendingFirstPayment = usageQuery.data.paymentStatus.isFirstPayment === true && usageQuery.data.paymentStatus.status !== "paid" && usageQuery.data.paymentStatus.status !== "not_required";
|
|
2603
2214
|
const activePhase = getActivePhase(subscription);
|
|
2604
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2215
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2605
2216
|
planSwitched && /* @__PURE__ */ jsxs(DismissibleAlert, {
|
|
2606
2217
|
variant: "info",
|
|
2607
2218
|
children: [
|