@zuplo/zudoku-plugin-monetization 0.0.32 → 0.0.34
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.mjs +347 -199
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -8,8 +8,8 @@ import { Link as Link$1, Outlet, useLocation, useNavigate, useSearchParams } fro
|
|
|
8
8
|
import { Alert, AlertAction, AlertDescription, AlertTitle } from "zudoku/ui/Alert";
|
|
9
9
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "zudoku/ui/Card";
|
|
10
10
|
import { Separator } from "zudoku/ui/Separator";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
11
|
import { parse } from "tinyduration";
|
|
12
|
+
import { Fragment, 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,72 +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/components/FeatureItem.tsx
|
|
27
|
-
const FeatureItem = ({ feature, className }) => {
|
|
28
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
29
|
-
className: cn("flex items-start gap-2", className),
|
|
30
|
-
children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-primary shrink-0 mt-0.5" }), /* @__PURE__ */ jsx("div", {
|
|
31
|
-
className: "text-sm",
|
|
32
|
-
children: feature.value ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33
|
-
/* @__PURE__ */ jsxs("span", {
|
|
34
|
-
className: "font-medium",
|
|
35
|
-
children: [feature.name, ":"]
|
|
36
|
-
}),
|
|
37
|
-
" ",
|
|
38
|
-
feature.value
|
|
39
|
-
] }) : feature.name
|
|
40
|
-
})]
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
//#endregion
|
|
44
|
-
//#region src/components/QuotaItem.tsx
|
|
45
|
-
const QuotaItem = ({ quota, className }) => {
|
|
46
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
47
|
-
className: cn("flex items-start gap-2", className),
|
|
48
|
-
children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-primary shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("div", {
|
|
49
|
-
className: "text-sm",
|
|
50
|
-
children: [
|
|
51
|
-
/* @__PURE__ */ jsxs("span", {
|
|
52
|
-
className: "font-medium",
|
|
53
|
-
children: [quota.name, ":"]
|
|
54
|
-
}),
|
|
55
|
-
" ",
|
|
56
|
-
quota.limit.toLocaleString(),
|
|
57
|
-
" / ",
|
|
58
|
-
quota.period,
|
|
59
|
-
quota.overagePrice && /* @__PURE__ */ jsxs("div", {
|
|
60
|
-
className: "text-xs text-muted-foreground mt-0.5",
|
|
61
|
-
children: [
|
|
62
|
-
"+",
|
|
63
|
-
quota.overagePrice,
|
|
64
|
-
" after quota"
|
|
65
|
-
]
|
|
66
|
-
})
|
|
67
|
-
]
|
|
68
|
-
})]
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/hooks/useDeploymentName.ts
|
|
73
|
-
const useDeploymentName = () => {
|
|
74
|
-
const deploymentName = useZudoku().env.ZUPLO_PUBLIC_DEPLOYMENT_NAME;
|
|
75
|
-
if (!deploymentName) throw new Error("ZUPLO_PUBLIC_DEPLOYMENT_NAME is not set");
|
|
76
|
-
return deploymentName;
|
|
77
|
-
};
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/hooks/usePurchaseDetails.ts
|
|
80
|
-
const usePurchaseDetails = (planId) => {
|
|
81
|
-
const zudoku = useZudoku();
|
|
82
|
-
return useSuspenseQuery({
|
|
83
|
-
queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/plans/${planId}/purchase-details`],
|
|
84
|
-
meta: { context: zudoku }
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
//#endregion
|
|
88
|
-
//#region src/MonetizationContext.tsx
|
|
89
|
-
const MonetizationContext = createContext({});
|
|
90
|
-
const useMonetizationConfig = () => use(MonetizationContext);
|
|
91
|
-
//#endregion
|
|
92
26
|
//#region src/utils/formatDuration.ts
|
|
93
27
|
const formatDuration = (iso) => {
|
|
94
28
|
try {
|
|
@@ -165,6 +99,31 @@ const formatMinorCurrencyAmount = (amountInMinorUnits, currency) => {
|
|
|
165
99
|
}).format(amountInMinorUnits / divisor);
|
|
166
100
|
};
|
|
167
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
|
|
168
127
|
//#region src/utils/categorizeRateCards.ts
|
|
169
128
|
const categorizeRateCards = (rateCards, options) => {
|
|
170
129
|
const { currency, units, planBillingCadence } = options ?? {};
|
|
@@ -175,20 +134,30 @@ const categorizeRateCards = (rateCards, options) => {
|
|
|
175
134
|
if (!et) continue;
|
|
176
135
|
if (et.type === "metered" && et.issueAfterReset != null) {
|
|
177
136
|
let overagePrice;
|
|
178
|
-
|
|
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
|
+
});
|
|
179
151
|
const overageTier = rc.price.tiers.find((t) => t.unitPrice?.amount && parseFloat(t.unitPrice.amount) > 0);
|
|
180
|
-
if (overageTier?.unitPrice) {
|
|
181
|
-
const amount = parseFloat(overageTier.unitPrice.amount);
|
|
182
|
-
const unitLabel = units?.[rc.key] ?? units?.[rc.featureKey ?? ""] ?? "unit";
|
|
183
|
-
overagePrice = `${formatPrice(amount, currency)}/${unitLabel}`;
|
|
184
|
-
}
|
|
152
|
+
if (et.isSoftLimit !== false && overageTier?.unitPrice) overagePrice = `${formatPrice(parseFloat(overageTier.unitPrice.amount), currency)}/${unitLabel}`;
|
|
185
153
|
}
|
|
186
154
|
quotas.push({
|
|
187
155
|
key: rc.featureKey ?? rc.key,
|
|
188
156
|
name: rc.name,
|
|
189
157
|
limit: et.issueAfterReset,
|
|
190
158
|
period: rc.billingCadence ? formatDuration(rc.billingCadence) : planBillingCadence ? formatDuration(planBillingCadence) : "month",
|
|
191
|
-
overagePrice
|
|
159
|
+
overagePrice,
|
|
160
|
+
tierPrices
|
|
192
161
|
});
|
|
193
162
|
} else if (et.type === "boolean") features.push({
|
|
194
163
|
key: rc.featureKey ?? rc.key,
|
|
@@ -214,6 +183,123 @@ const categorizeRateCards = (rateCards, options) => {
|
|
|
214
183
|
};
|
|
215
184
|
};
|
|
216
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
|
+
//#region src/hooks/useDeploymentName.ts
|
|
284
|
+
const useDeploymentName = () => {
|
|
285
|
+
const deploymentName = useZudoku().env.ZUPLO_PUBLIC_DEPLOYMENT_NAME;
|
|
286
|
+
if (!deploymentName) throw new Error("ZUPLO_PUBLIC_DEPLOYMENT_NAME is not set");
|
|
287
|
+
return deploymentName;
|
|
288
|
+
};
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/hooks/usePurchaseDetails.ts
|
|
291
|
+
const usePurchaseDetails = (planId) => {
|
|
292
|
+
const zudoku = useZudoku();
|
|
293
|
+
return useSuspenseQuery({
|
|
294
|
+
queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/plans/${planId}/purchase-details`],
|
|
295
|
+
meta: { context: zudoku }
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/MonetizationContext.tsx
|
|
300
|
+
const MonetizationContext = createContext({});
|
|
301
|
+
const useMonetizationConfig = () => use(MonetizationContext);
|
|
302
|
+
//#endregion
|
|
217
303
|
//#region src/utils/formatBillingCycle.ts
|
|
218
304
|
const formatBillingCycle = (duration) => {
|
|
219
305
|
if (duration === "month") return "monthly";
|
|
@@ -333,12 +419,6 @@ const CheckoutConfirmPage = () => {
|
|
|
333
419
|
const taxAmount = getTaxAmountFromPurchaseDetails(purchaseDetails.data);
|
|
334
420
|
const taxLabel = getTaxLabelFromPurchaseDetails(purchaseDetails.data);
|
|
335
421
|
const taxInclusive = isTaxInclusiveFromPurchaseDetails(purchaseDetails.data);
|
|
336
|
-
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
337
|
-
const { quotas, features } = categorizeRateCards(rateCards ?? [], {
|
|
338
|
-
currency: selectedPlan?.currency,
|
|
339
|
-
units: pricing?.units,
|
|
340
|
-
planBillingCadence: selectedPlan?.billingCadence
|
|
341
|
-
});
|
|
342
422
|
const price = selectedPlan ? getPriceFromPlan(selectedPlan) : null;
|
|
343
423
|
const billingCycle = selectedPlan?.billingCadence ? formatDuration(selectedPlan.billingCadence) : null;
|
|
344
424
|
const createSubscriptionMutation = useMutation({
|
|
@@ -434,15 +514,12 @@ const CheckoutConfirmPage = () => {
|
|
|
434
514
|
className: "text-sm font-medium mb-3 mt-3",
|
|
435
515
|
children: "What's included:"
|
|
436
516
|
}),
|
|
437
|
-
/* @__PURE__ */
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
feature,
|
|
444
|
-
className: "text-muted-foreground"
|
|
445
|
-
}, feature.key))]
|
|
517
|
+
/* @__PURE__ */ jsx(PlanEntitlements, {
|
|
518
|
+
phases: selectedPlan.phases,
|
|
519
|
+
currency: selectedPlan.currency,
|
|
520
|
+
billingCadence: selectedPlan.billingCadence,
|
|
521
|
+
units: pricing?.units,
|
|
522
|
+
itemClassName: "text-muted-foreground"
|
|
446
523
|
})
|
|
447
524
|
] })]
|
|
448
525
|
}),
|
|
@@ -641,41 +718,46 @@ const usePlans = () => {
|
|
|
641
718
|
});
|
|
642
719
|
};
|
|
643
720
|
//#endregion
|
|
644
|
-
//#region src/
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
+
}
|
|
671
752
|
};
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region src/pages/pricing/PricingCard.tsx
|
|
672
755
|
const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
673
756
|
const { pricing } = useMonetizationConfig();
|
|
674
757
|
if (plan.phases.length === 0) return null;
|
|
675
758
|
const price = getPriceFromPlan(plan);
|
|
676
759
|
const isFree = price.monthly === 0;
|
|
677
760
|
const isCustom = plan.metadata?.isCustom === true;
|
|
678
|
-
const hasMultiplePhases = plan.phases.length > 1;
|
|
679
761
|
const billingInterval = formatDuration(plan.billingCadence);
|
|
680
762
|
return /* @__PURE__ */ jsxs("div", {
|
|
681
763
|
className: cn("relative rounded-lg border p-6 flex flex-col", isPopular && "border-primary border-2"),
|
|
@@ -721,12 +803,12 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
|
|
|
721
803
|
}),
|
|
722
804
|
/* @__PURE__ */ jsx("div", {
|
|
723
805
|
className: "space-y-4 mb-6 grow",
|
|
724
|
-
children:
|
|
725
|
-
|
|
806
|
+
children: /* @__PURE__ */ jsx(PlanEntitlements, {
|
|
807
|
+
phases: plan.phases,
|
|
726
808
|
currency: plan.currency,
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
809
|
+
billingCadence: plan.billingCadence,
|
|
810
|
+
units: pricing?.units
|
|
811
|
+
})
|
|
730
812
|
}),
|
|
731
813
|
isSubscribed ? /* @__PURE__ */ jsx(Button, {
|
|
732
814
|
variant: isPopular ? "default" : "secondary",
|
|
@@ -754,6 +836,7 @@ const PricingPage = () => {
|
|
|
754
836
|
const deploymentName = useDeploymentName();
|
|
755
837
|
const auth = useAuth();
|
|
756
838
|
const { data: pricingTable } = usePlans();
|
|
839
|
+
const taxLegendSentence = taxBehaviorLegendSentence(collectDefaultTaxBehaviors(pricingTable.items[0]));
|
|
757
840
|
const { data: subscriptions = { items: [] } } = useQuery({
|
|
758
841
|
meta: { context: zudoku },
|
|
759
842
|
queryKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions`],
|
|
@@ -784,14 +867,24 @@ const PricingPage = () => {
|
|
|
784
867
|
className: "text-sm mt-2",
|
|
785
868
|
children: "Make sure your plans are set up and published."
|
|
786
869
|
})]
|
|
787
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
870
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
788
871
|
className: "w-full grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(300px,max-content))] justify-center gap-6",
|
|
789
872
|
children: pricingTable.items.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
|
|
790
873
|
plan,
|
|
791
874
|
isPopular: plan.metadata?.zuplo_most_popular === "true",
|
|
792
875
|
isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
|
|
793
876
|
}, plan.id))
|
|
794
|
-
}),
|
|
877
|
+
}), taxLegendSentence && /* @__PURE__ */ jsxs("div", {
|
|
878
|
+
role: "note",
|
|
879
|
+
className: "mt-10 pt-6 border-t border-border max-w-2xl mx-auto text-center space-y-2",
|
|
880
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
881
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
882
|
+
children: "Tax & Pricing"
|
|
883
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
884
|
+
className: "text-xs text-muted-foreground",
|
|
885
|
+
children: taxLegendSentence
|
|
886
|
+
})]
|
|
887
|
+
})] }),
|
|
795
888
|
/* @__PURE__ */ jsx(Slot.Target, { name: "pricing-page-after" })
|
|
796
889
|
]
|
|
797
890
|
});
|
|
@@ -844,12 +937,6 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
844
937
|
const taxAmount = getTaxAmountFromPurchaseDetails(purchaseDetails.data);
|
|
845
938
|
const taxLabel = getTaxLabelFromPurchaseDetails(purchaseDetails.data);
|
|
846
939
|
const taxInclusive = isTaxInclusiveFromPurchaseDetails(purchaseDetails.data);
|
|
847
|
-
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
848
|
-
const { quotas, features } = categorizeRateCards(rateCards ?? [], {
|
|
849
|
-
currency: selectedPlan?.currency,
|
|
850
|
-
units: pricing?.units,
|
|
851
|
-
planBillingCadence: selectedPlan?.billingCadence
|
|
852
|
-
});
|
|
853
940
|
const price = selectedPlan ? getPriceFromPlan(selectedPlan) : null;
|
|
854
941
|
const billingCycle = selectedPlan?.billingCadence ? formatDuration(selectedPlan.billingCadence) : null;
|
|
855
942
|
const effectiveChangeMessage = mode === "downgrade" ? "This change will take effect at the start of your next billing cycle." : "This change will take effect immediately.";
|
|
@@ -951,15 +1038,11 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
951
1038
|
className: "text-sm font-medium mb-3 mt-3",
|
|
952
1039
|
children: "What's included:"
|
|
953
1040
|
}),
|
|
954
|
-
/* @__PURE__ */
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}, quota.key)), features.map((feature) => /* @__PURE__ */ jsx(FeatureItem, {
|
|
960
|
-
feature,
|
|
961
|
-
className: "text-muted-foreground"
|
|
962
|
-
}, feature.key))]
|
|
1041
|
+
/* @__PURE__ */ jsx(PlanEntitlements, {
|
|
1042
|
+
phases: selectedPlan.phases,
|
|
1043
|
+
currency: selectedPlan.currency,
|
|
1044
|
+
billingCadence: selectedPlan.billingCadence,
|
|
1045
|
+
units: pricing?.units
|
|
963
1046
|
})
|
|
964
1047
|
] })]
|
|
965
1048
|
}),
|
|
@@ -1011,6 +1094,17 @@ const useSubscriptions = (environmentName) => {
|
|
|
1011
1094
|
});
|
|
1012
1095
|
};
|
|
1013
1096
|
//#endregion
|
|
1097
|
+
//#region src/utils/billables.ts
|
|
1098
|
+
const getActivePhase = (sub) => {
|
|
1099
|
+
const now = Date.now();
|
|
1100
|
+
return sub.phases.filter((p) => new Date(p.activeFrom).getTime() <= now && (!p.activeTo || new Date(p.activeTo).getTime() >= now)).sort((a, b) => new Date(b.activeFrom).getTime() - new Date(a.activeFrom).getTime())[0];
|
|
1101
|
+
};
|
|
1102
|
+
const activePhaseHasBillables = (sub) => getActivePhase(sub)?.items.some((i) => i.price != null) ?? false;
|
|
1103
|
+
const hasFutureBillables = (sub) => {
|
|
1104
|
+
const now = Date.now();
|
|
1105
|
+
return sub.phases.filter((p) => new Date(p.activeFrom).getTime() > now).some((p) => p.items.some((i) => i.price != null));
|
|
1106
|
+
};
|
|
1107
|
+
//#endregion
|
|
1014
1108
|
//#region src/pages/subscriptions/ConfirmDeleteKeyAlert.tsx
|
|
1015
1109
|
const ConfirmDeleteKeyAlert = ({ children, onDelete }) => {
|
|
1016
1110
|
return /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
|
|
@@ -1303,19 +1397,22 @@ const ApiKeysList = ({ isPendingFirstPayment, apiKeys, deploymentName, consumerI
|
|
|
1303
1397
|
};
|
|
1304
1398
|
//#endregion
|
|
1305
1399
|
//#region src/pages/subscriptions/CancelSubscriptionDialog.tsx
|
|
1306
|
-
const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId, billingPeriodEnd }) => {
|
|
1400
|
+
const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId, billingPeriodEnd, hasCurrentBillables, hasFutureBillables }) => {
|
|
1307
1401
|
const [confirmationText, setConfirmationText] = useState("");
|
|
1308
1402
|
const isConfirmed = planName.startsWith(confirmationText);
|
|
1309
1403
|
const deploymentName = useDeploymentName();
|
|
1310
1404
|
const context = useZudoku();
|
|
1311
1405
|
const queryClient = useQueryClient();
|
|
1406
|
+
const cancelTiming = hasCurrentBillables ? "next_billing_cycle" : "immediate";
|
|
1407
|
+
const isImmediateCancel = !hasCurrentBillables;
|
|
1408
|
+
const isTrialCancel = isImmediateCancel && hasFutureBillables;
|
|
1312
1409
|
const cancelSubscriptionMutation = useMutation({
|
|
1313
1410
|
mutationKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/cancel`],
|
|
1314
1411
|
meta: {
|
|
1315
1412
|
context,
|
|
1316
1413
|
request: {
|
|
1317
1414
|
method: "POST",
|
|
1318
|
-
body: JSON.stringify({ timing:
|
|
1415
|
+
body: JSON.stringify({ timing: cancelTiming })
|
|
1319
1416
|
}
|
|
1320
1417
|
},
|
|
1321
1418
|
onSuccess: async () => {
|
|
@@ -1335,28 +1432,32 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
|
|
|
1335
1432
|
children: [
|
|
1336
1433
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1337
1434
|
variant: "warning",
|
|
1338
|
-
children: [
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
]
|
|
1435
|
+
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }), isTrialCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1436
|
+
"Cancel your trial of ",
|
|
1437
|
+
planName,
|
|
1438
|
+
"?"
|
|
1439
|
+
] }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1440
|
+
"Your subscription will end now and you won't be charged when the trial would have converted to ",
|
|
1441
|
+
planName,
|
|
1442
|
+
"."
|
|
1443
|
+
] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
1444
|
+
"Cancel your ",
|
|
1445
|
+
planName,
|
|
1446
|
+
" 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: [
|
|
1448
|
+
"You'll retain access until ",
|
|
1449
|
+
formatDate$2(billingPeriodEnd),
|
|
1450
|
+
". After your billing period ends, this plan will not renew and you would need to subscribe again to continue."
|
|
1451
|
+
] })] })]
|
|
1347
1452
|
}),
|
|
1348
1453
|
/* @__PURE__ */ jsxs(Alert, {
|
|
1349
1454
|
variant: "info",
|
|
1350
|
-
children: [
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
formatDate$2(billingPeriodEnd),
|
|
1357
|
-
" to remove this cancellation from Manage subscription."
|
|
1358
|
-
] })
|
|
1359
|
-
]
|
|
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: [
|
|
1456
|
+
"If you change your mind you have until",
|
|
1457
|
+
" ",
|
|
1458
|
+
formatDate$2(billingPeriodEnd),
|
|
1459
|
+
" to remove this cancellation from Manage subscription."
|
|
1460
|
+
] })] })]
|
|
1360
1461
|
}),
|
|
1361
1462
|
/* @__PURE__ */ jsxs("div", {
|
|
1362
1463
|
className: "space-y-2",
|
|
@@ -1991,7 +2092,9 @@ const ManageSubscription = ({ subscription, planName }) => {
|
|
|
1991
2092
|
onOpenChange: setCancelDialogOpen,
|
|
1992
2093
|
planName,
|
|
1993
2094
|
subscriptionId: subscription.id,
|
|
1994
|
-
billingPeriodEnd
|
|
2095
|
+
billingPeriodEnd,
|
|
2096
|
+
hasCurrentBillables: activePhaseHasBillables(subscription),
|
|
2097
|
+
hasFutureBillables: hasFutureBillables(subscription)
|
|
1995
2098
|
}),
|
|
1996
2099
|
/* @__PURE__ */ jsx(RestoreSubscriptionDialog, {
|
|
1997
2100
|
open: restoreDialogOpen,
|
|
@@ -2097,6 +2200,23 @@ const getOveragePriceFromItem = (item, currency, units) => {
|
|
|
2097
2200
|
const unitLabel = units?.[item.key] ?? units?.[item.featureKey] ?? "unit";
|
|
2098
2201
|
return `${formatPrice(parsed, currency)}/${unitLabel}`;
|
|
2099
2202
|
};
|
|
2203
|
+
const getTierPricesFromItem = (item, currency, units) => {
|
|
2204
|
+
if (item.price?.type !== "tiered") return;
|
|
2205
|
+
const tiers = item.price.tiers;
|
|
2206
|
+
if (!tiers || tiers.length <= 1) return;
|
|
2207
|
+
const unitLabel = units?.[item.key] ?? units?.[item.featureKey] ?? "unit";
|
|
2208
|
+
return formatTieredPriceBreakdown({
|
|
2209
|
+
tiers: tiers.map((t) => ({
|
|
2210
|
+
upToAmount: t.upToAmount,
|
|
2211
|
+
unitPriceAmount: t.unitPrice?.amount,
|
|
2212
|
+
flatPriceAmount: t.flatPrice?.amount
|
|
2213
|
+
})),
|
|
2214
|
+
currency,
|
|
2215
|
+
unitLabel,
|
|
2216
|
+
includedLabel: "Included",
|
|
2217
|
+
omitIncludedUpToAmount: item.included?.entitlement?.issueAfterReset
|
|
2218
|
+
});
|
|
2219
|
+
};
|
|
2100
2220
|
const getEntitlementsFromItems = (items, currency, units, fallbackBillingCadence) => {
|
|
2101
2221
|
const features = [];
|
|
2102
2222
|
for (const item of items) {
|
|
@@ -2110,7 +2230,8 @@ const getEntitlementsFromItems = (items, currency, units, fallbackBillingCadence
|
|
|
2110
2230
|
name: item.name ?? item.featureKey ?? item.key,
|
|
2111
2231
|
limit: entitlement.issueAfterReset,
|
|
2112
2232
|
period: cadence ? formatDuration(cadence) : "month",
|
|
2113
|
-
overagePrice: entitlement.isSoftLimit !== false ? getOveragePriceFromItem(item, currency, units) : void 0
|
|
2233
|
+
overagePrice: entitlement.isSoftLimit !== false ? getOveragePriceFromItem(item, currency, units) : void 0,
|
|
2234
|
+
tierPrices: getTierPricesFromItem(item, currency, units)
|
|
2114
2235
|
});
|
|
2115
2236
|
continue;
|
|
2116
2237
|
}
|
|
@@ -2154,23 +2275,32 @@ const getEntitlementsFromItems = (items, currency, units, fallbackBillingCadence
|
|
|
2154
2275
|
const getPhaseRows = (opts) => {
|
|
2155
2276
|
const { subscription, currency, units } = opts;
|
|
2156
2277
|
const phases = [...subscription.phases].sort((a, b) => new Date(a.activeFrom).getTime() - new Date(b.activeFrom).getTime());
|
|
2157
|
-
const
|
|
2278
|
+
const phaseGroups = [];
|
|
2158
2279
|
for (const phase of phases) {
|
|
2159
2280
|
const { features } = getEntitlementsFromItems(phase.items ?? [], currency, units, subscription.billingCadence);
|
|
2160
|
-
|
|
2281
|
+
const rows = [];
|
|
2282
|
+
for (const f of features) rows.push({
|
|
2161
2283
|
key: f.key,
|
|
2162
2284
|
name: f.name,
|
|
2163
2285
|
entitlementType: f.entitlementType,
|
|
2164
2286
|
limit: f.entitlementType === "metered" ? f.limit : void 0,
|
|
2165
2287
|
period: f.entitlementType === "metered" ? f.period : void 0,
|
|
2166
2288
|
overagePrice: f.entitlementType === "metered" ? f.overagePrice : void 0,
|
|
2289
|
+
tierPrices: f.entitlementType === "metered" ? f.tierPrices : void 0,
|
|
2167
2290
|
value: f.entitlementType === "static" ? f.value : void 0,
|
|
2168
2291
|
phaseId: phase.id,
|
|
2169
2292
|
activeFrom: phase.activeFrom,
|
|
2170
2293
|
activeTo: phase.activeTo
|
|
2171
2294
|
});
|
|
2295
|
+
if (rows.length > 0) phaseGroups.push({
|
|
2296
|
+
id: phase.id,
|
|
2297
|
+
name: phase.name,
|
|
2298
|
+
activeFrom: phase.activeFrom,
|
|
2299
|
+
activeTo: phase.activeTo,
|
|
2300
|
+
rows
|
|
2301
|
+
});
|
|
2172
2302
|
}
|
|
2173
|
-
return {
|
|
2303
|
+
return { phaseGroups };
|
|
2174
2304
|
};
|
|
2175
2305
|
const formatActiveRange = (activeFrom, activeTo) => {
|
|
2176
2306
|
if (!activeTo) return `Starts ${formatDate$1(activeFrom)}`;
|
|
@@ -2181,6 +2311,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2181
2311
|
const plan = subscription.plan;
|
|
2182
2312
|
const currency = subscription.currency ?? plan.currency;
|
|
2183
2313
|
const priceInfo = getPriceFromPlan(plan);
|
|
2314
|
+
const taxLegendSentence = planHasDefaultTaxBehavior(plan) ? subscriptionTaxLegendSentence(plan.defaultTaxConfig?.behavior ?? "") : void 0;
|
|
2184
2315
|
const primaryPrice = priceInfo.monthly === 0 && priceInfo.yearly === 0 ? /* @__PURE__ */ jsx("span", {
|
|
2185
2316
|
className: "text-primary font-medium",
|
|
2186
2317
|
children: "Free"
|
|
@@ -2191,7 +2322,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2191
2322
|
className: "text-muted-foreground",
|
|
2192
2323
|
children: [" / ", formatDuration(plan.billingCadence)]
|
|
2193
2324
|
})] });
|
|
2194
|
-
const {
|
|
2325
|
+
const { phaseGroups } = getPhaseRows({
|
|
2195
2326
|
subscription,
|
|
2196
2327
|
currency,
|
|
2197
2328
|
units: pricing?.units
|
|
@@ -2226,10 +2357,13 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2226
2357
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
2227
2358
|
className: detailLabelClassName,
|
|
2228
2359
|
children: "Price"
|
|
2229
|
-
}), /* @__PURE__ */
|
|
2360
|
+
}), /* @__PURE__ */ jsxs("dd", { children: [/* @__PURE__ */ jsx("div", {
|
|
2230
2361
|
className: "flex flex-wrap items-baseline gap-1",
|
|
2231
2362
|
children: primaryPrice
|
|
2232
|
-
})
|
|
2363
|
+
}), taxLegendSentence ? /* @__PURE__ */ jsx("p", {
|
|
2364
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
2365
|
+
children: taxLegendSentence
|
|
2366
|
+
}) : null] })] }),
|
|
2233
2367
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
2234
2368
|
className: detailLabelClassName,
|
|
2235
2369
|
children: "Current period"
|
|
@@ -2238,42 +2372,56 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2238
2372
|
children: subscription.alignment?.currentAlignedBillingPeriod ? formatDateRange(subscription.alignment.currentAlignedBillingPeriod.from, subscription.alignment.currentAlignedBillingPeriod.to) : "—"
|
|
2239
2373
|
})] })
|
|
2240
2374
|
]
|
|
2241
|
-
}),
|
|
2375
|
+
}), phaseGroups.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
2242
2376
|
className: "space-y-5 pt-2 border-t border-border",
|
|
2243
2377
|
children: /* @__PURE__ */ jsxs("div", {
|
|
2244
2378
|
className: "space-y-2",
|
|
2245
2379
|
children: [/* @__PURE__ */ jsx("p", {
|
|
2246
2380
|
className: cn(sectionLabelClassName, "mb-5"),
|
|
2247
2381
|
children: "Entitlements"
|
|
2248
|
-
}), /* @__PURE__ */ jsx("
|
|
2249
|
-
className: "space-y-
|
|
2250
|
-
children:
|
|
2251
|
-
className: "
|
|
2252
|
-
children: [
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2382
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2383
|
+
className: "space-y-5",
|
|
2384
|
+
children: phaseGroups.map((phase) => /* @__PURE__ */ jsxs("div", {
|
|
2385
|
+
className: "space-y-3",
|
|
2386
|
+
children: [phaseGroups.length > 1 ? /* @__PURE__ */ jsxs("div", {
|
|
2387
|
+
className: "text-sm font-medium text-card-foreground",
|
|
2388
|
+
children: [phase.name, /* @__PURE__ */ jsxs("span", {
|
|
2389
|
+
className: "text-muted-foreground font-normal",
|
|
2390
|
+
children: [
|
|
2391
|
+
" ",
|
|
2392
|
+
"—",
|
|
2393
|
+
" ",
|
|
2394
|
+
formatActiveRange(phase.activeFrom, phase.activeTo)
|
|
2395
|
+
]
|
|
2396
|
+
})]
|
|
2397
|
+
}) : null, /* @__PURE__ */ jsx("ul", {
|
|
2398
|
+
className: "space-y-3",
|
|
2399
|
+
children: phase.rows.map((row) => /* @__PURE__ */ jsx("li", {
|
|
2400
|
+
className: "text-sm",
|
|
2401
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2402
|
+
className: "flex flex-col gap-1",
|
|
2403
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2404
|
+
className: "text-foreground font-medium",
|
|
2405
|
+
children: [row.name, row.entitlementType === "static" && row.value ? `: ${row.value}` : ""]
|
|
2406
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2407
|
+
className: "text-muted-foreground",
|
|
2408
|
+
children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2409
|
+
formatNumber(row.limit),
|
|
2410
|
+
row.period ? ` / ${row.period}` : "",
|
|
2411
|
+
row.tierPrices && row.tierPrices.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
2412
|
+
className: "text-xs mt-1 space-y-0.5",
|
|
2413
|
+
children: row.tierPrices.map((line) => /* @__PURE__ */ jsx("li", { children: line }, line))
|
|
2414
|
+
}) : null,
|
|
2415
|
+
row.overagePrice ? /* @__PURE__ */ jsxs("div", {
|
|
2416
|
+
className: "text-xs mt-0.5",
|
|
2417
|
+
children: ["Overage: ", row.overagePrice]
|
|
2418
|
+
}) : null
|
|
2419
|
+
] }) : row.entitlementType === "static" && row.value ? null : "Included"
|
|
2420
|
+
})]
|
|
2421
|
+
})
|
|
2422
|
+
}, `${row.key}:${row.phaseId}`))
|
|
2423
|
+
})]
|
|
2424
|
+
}, phase.id))
|
|
2277
2425
|
})]
|
|
2278
2426
|
})
|
|
2279
2427
|
}) : null]
|
|
@@ -2451,7 +2599,7 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
|
|
|
2451
2599
|
meta: { context: zudoku }
|
|
2452
2600
|
});
|
|
2453
2601
|
const isPendingFirstPayment = usageQuery.data.paymentStatus.isFirstPayment === true && usageQuery.data.paymentStatus.status !== "paid" && usageQuery.data.paymentStatus.status !== "not_required";
|
|
2454
|
-
const activePhase = subscription
|
|
2602
|
+
const activePhase = getActivePhase(subscription);
|
|
2455
2603
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2456
2604
|
planSwitched && /* @__PURE__ */ jsxs(DismissibleAlert, {
|
|
2457
2605
|
variant: "info",
|