@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/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 { parse } from "tinyduration";
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
- pricingTable.items.length === 0 ? /* @__PURE__ */ jsxs("div", {
865
- className: "text-center py-12 text-muted-foreground",
866
- children: [/* @__PURE__ */ jsx("p", { children: "No plans are currently available." }), /* @__PURE__ */ jsx("p", {
867
- className: "text-sm mt-2",
868
- children: "Make sure your plans are set up and published."
869
- })]
870
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
871
- className: "w-full grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(300px,max-content))] justify-center gap-6",
872
- children: pricingTable.items.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
873
- plan,
874
- isPopular: plan.metadata?.zuplo_most_popular === "true",
875
- isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
876
- }, plan.id))
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
- })] }),
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
- try {
2259
- const parsed = JSON.parse(entitlement.config);
2260
- features.push({
2261
- entitlementType: "static",
2262
- ...base,
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: [