@zuplo/zudoku-plugin-monetization 0.0.34 → 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 -432
- 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" : "secondary",
|
|
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" : "secondary",
|
|
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,12 +460,12 @@ const PricingPage = () => {
|
|
|
836
460
|
const deploymentName = useDeploymentName();
|
|
837
461
|
const auth = useAuth();
|
|
838
462
|
const { data: pricingTable } = usePlans();
|
|
839
|
-
const taxLegendSentence = taxBehaviorLegendSentence(collectDefaultTaxBehaviors(pricingTable.items[0]));
|
|
840
463
|
const { data: subscriptions = { items: [] } } = useQuery({
|
|
841
464
|
meta: { context: zudoku },
|
|
842
465
|
queryKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions`],
|
|
843
466
|
enabled: auth.isAuthenticated
|
|
844
467
|
});
|
|
468
|
+
const isSubscribed = subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status));
|
|
845
469
|
return /* @__PURE__ */ jsxs("div", {
|
|
846
470
|
className: "w-full px-4 pt-(--padding-content-top) pb-(--padding-content-bottom)",
|
|
847
471
|
children: [
|
|
@@ -861,30 +485,26 @@ const PricingPage = () => {
|
|
|
861
485
|
children: pricing?.subtitle ?? "See our pricing options and choose the one that best suits your needs."
|
|
862
486
|
})]
|
|
863
487
|
}),
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
className: "text-xs text-muted-foreground",
|
|
885
|
-
children: taxLegendSentence
|
|
886
|
-
})]
|
|
887
|
-
})] }),
|
|
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
|
+
}),
|
|
888
508
|
/* @__PURE__ */ jsx(Slot.Target, { name: "pricing-page-after" })
|
|
889
509
|
]
|
|
890
510
|
});
|
|
@@ -1200,7 +820,7 @@ const ApiKey = ({ apiKey, createdAt, lastUsed, expiresOn, isActive = true, label
|
|
|
1200
820
|
children: "•"
|
|
1201
821
|
}),
|
|
1202
822
|
/* @__PURE__ */ jsxs("span", { children: ["Last updated ", getTimeAgo(lastUsed)] }),
|
|
1203
|
-
expiresOn && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
823
|
+
expiresOn && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
1204
824
|
className: "text-muted-foreground/40",
|
|
1205
825
|
children: "•"
|
|
1206
826
|
}), /* @__PURE__ */ jsxs("span", {
|
|
@@ -1432,7 +1052,7 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1432
1052
|
children: [
|
|
1433
1053
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1434
1054
|
variant: "warning",
|
|
1435
|
-
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: [
|
|
1436
1056
|
"Cancel your trial of ",
|
|
1437
1057
|
planName,
|
|
1438
1058
|
"?"
|
|
@@ -1440,11 +1060,11 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1440
1060
|
"Your subscription will end now and you won't be charged when the trial would have converted to ",
|
|
1441
1061
|
planName,
|
|
1442
1062
|
"."
|
|
1443
|
-
] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1063
|
+
] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1444
1064
|
"Cancel your ",
|
|
1445
1065
|
planName,
|
|
1446
1066
|
" subscription?"
|
|
1447
|
-
] }), /* @__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: [
|
|
1448
1068
|
"You'll retain access until ",
|
|
1449
1069
|
formatDate$2(billingPeriodEnd),
|
|
1450
1070
|
". After your billing period ends, this plan will not renew and you would need to subscribe again to continue."
|
|
@@ -1452,7 +1072,7 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1452
1072
|
}),
|
|
1453
1073
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1454
1074
|
variant: "info",
|
|
1455
|
-
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: [
|
|
1456
1076
|
"If you change your mind you have until",
|
|
1457
1077
|
" ",
|
|
1458
1078
|
formatDate$2(billingPeriodEnd),
|
|
@@ -1689,7 +1309,7 @@ const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex, units)
|
|
|
1689
1309
|
const target = targetFeatures.find((f) => f.key === key);
|
|
1690
1310
|
if (current && target) {
|
|
1691
1311
|
let change = "same";
|
|
1692
|
-
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";
|
|
1693
1313
|
featureChanges.push({
|
|
1694
1314
|
key: key ?? "",
|
|
1695
1315
|
name: target.name,
|
|
@@ -1815,7 +1435,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1815
1435
|
}) : quota.change === "removed" ? /* @__PURE__ */ jsx("span", {
|
|
1816
1436
|
className: "text-destructive",
|
|
1817
1437
|
children: "No longer included"
|
|
1818
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1438
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1819
1439
|
/* @__PURE__ */ jsxs("span", {
|
|
1820
1440
|
className: "text-muted-foreground",
|
|
1821
1441
|
children: [
|
|
@@ -1840,10 +1460,10 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1840
1460
|
]
|
|
1841
1461
|
}, quota.key)), comparison.featureChanges.map((feature) => /* @__PURE__ */ jsx("div", {
|
|
1842
1462
|
className: "flex items-center gap-2 text-sm",
|
|
1843
|
-
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", {
|
|
1844
1464
|
className: "text-muted-foreground",
|
|
1845
1465
|
children: [feature.name, typeof feature.newValue === "string" ? `: ${feature.newValue}` : typeof feature.currentValue === "string" ? `: ${feature.currentValue}` : ""]
|
|
1846
|
-
})] }) : feature.change === "added" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1466
|
+
})] }) : feature.change === "added" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1847
1467
|
/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }),
|
|
1848
1468
|
/* @__PURE__ */ jsx("span", {
|
|
1849
1469
|
className: "text-muted-foreground font-medium",
|
|
@@ -1857,7 +1477,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1857
1477
|
className: "text-green-600",
|
|
1858
1478
|
children: "Now included"
|
|
1859
1479
|
})
|
|
1860
|
-
] }) : feature.change === "removed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1480
|
+
] }) : feature.change === "removed" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1861
1481
|
/* @__PURE__ */ jsx(XIcon, { className: "w-4 h-4 text-destructive shrink-0" }),
|
|
1862
1482
|
/* @__PURE__ */ jsx("span", {
|
|
1863
1483
|
className: "font-medium",
|
|
@@ -1871,7 +1491,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
|
|
|
1871
1491
|
className: "text-destructive",
|
|
1872
1492
|
children: "No longer included"
|
|
1873
1493
|
})
|
|
1874
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1494
|
+
] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1875
1495
|
/* @__PURE__ */ jsx(ChangeIndicator, { change: feature.change }),
|
|
1876
1496
|
/* @__PURE__ */ jsxs("span", {
|
|
1877
1497
|
className: "",
|
|
@@ -2255,19 +1875,11 @@ const getEntitlementsFromItems = (items, currency, units, fallbackBillingCadence
|
|
|
2255
1875
|
});
|
|
2256
1876
|
continue;
|
|
2257
1877
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
value: parsed?.value != null ? String(parsed.value) : void 0
|
|
2264
|
-
});
|
|
2265
|
-
} catch {
|
|
2266
|
-
features.push({
|
|
2267
|
-
entitlementType: "static",
|
|
2268
|
-
...base
|
|
2269
|
-
});
|
|
2270
|
-
}
|
|
1878
|
+
features.push({
|
|
1879
|
+
entitlementType: "static",
|
|
1880
|
+
...base,
|
|
1881
|
+
value: formatStaticEntitlementConfig(entitlement.config)
|
|
1882
|
+
});
|
|
2271
1883
|
}
|
|
2272
1884
|
}
|
|
2273
1885
|
return { features };
|
|
@@ -2315,7 +1927,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2315
1927
|
const primaryPrice = priceInfo.monthly === 0 && priceInfo.yearly === 0 ? /* @__PURE__ */ jsx("span", {
|
|
2316
1928
|
className: "text-primary font-medium",
|
|
2317
1929
|
children: "Free"
|
|
2318
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1930
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
2319
1931
|
className: "text-primary font-medium text-lg",
|
|
2320
1932
|
children: formatPrice(priceInfo.monthly, currency)
|
|
2321
1933
|
}), /* @__PURE__ */ jsxs("span", {
|
|
@@ -2402,10 +2014,10 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2402
2014
|
className: "flex flex-col gap-1",
|
|
2403
2015
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2404
2016
|
className: "text-foreground font-medium",
|
|
2405
|
-
children: [row.name, row.entitlementType === "static" && row.value ? `: ${row.value}` : ""]
|
|
2017
|
+
children: [row.name, row.entitlementType === "static" && row.value !== void 0 ? `: ${row.value}` : ""]
|
|
2406
2018
|
}), /* @__PURE__ */ jsx("div", {
|
|
2407
2019
|
className: "text-muted-foreground",
|
|
2408
|
-
children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2020
|
+
children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2409
2021
|
formatNumber(row.limit),
|
|
2410
2022
|
row.period ? ` / ${row.period}` : "",
|
|
2411
2023
|
row.tierPrices && row.tierPrices.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
@@ -2416,7 +2028,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2416
2028
|
className: "text-xs mt-0.5",
|
|
2417
2029
|
children: ["Overage: ", row.overagePrice]
|
|
2418
2030
|
}) : null
|
|
2419
|
-
] }) : row.entitlementType === "static" && row.value ? null : "Included"
|
|
2031
|
+
] }) : row.entitlementType === "static" && row.value !== void 0 ? null : "Included"
|
|
2420
2032
|
})]
|
|
2421
2033
|
})
|
|
2422
2034
|
}, `${row.key}:${row.phaseId}`))
|
|
@@ -2600,7 +2212,7 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
|
|
|
2600
2212
|
});
|
|
2601
2213
|
const isPendingFirstPayment = usageQuery.data.paymentStatus.isFirstPayment === true && usageQuery.data.paymentStatus.status !== "paid" && usageQuery.data.paymentStatus.status !== "not_required";
|
|
2602
2214
|
const activePhase = getActivePhase(subscription);
|
|
2603
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2215
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2604
2216
|
planSwitched && /* @__PURE__ */ jsxs(DismissibleAlert, {
|
|
2605
2217
|
variant: "info",
|
|
2606
2218
|
children: [
|