@zuplo/zudoku-plugin-monetization 0.0.35 → 0.0.36-pre.1

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" : "outline",
815
- asChild: true,
816
- children: /* @__PURE__ */ jsx(Link$1, {
817
- to: `/subscriptions#manage`,
818
- children: "Manage Subscriptions"
819
- })
820
- }) : /* @__PURE__ */ jsx(Button, {
821
- variant: isPopular ? "default" : "outline",
822
- asChild: true,
823
- children: /* @__PURE__ */ jsx(Link$1, {
824
- to: `/checkout?planId=${encodeURIComponent(plan.id)}`,
825
- children: "Subscribe"
826
- })
827
- })
828
- ]
829
- });
830
- };
831
- //#endregion
832
456
  //#region src/pages/PricingPage.tsx
833
457
  const PricingPage = () => {
834
458
  const { pricing } = useMonetizationConfig();
@@ -836,13 +460,12 @@ const PricingPage = () => {
836
460
  const deploymentName = useDeploymentName();
837
461
  const auth = useAuth();
838
462
  const { data: pricingTable } = usePlans();
839
- const firstPlan = pricingTable.items[0];
840
- const taxLegendSentence = firstPlan ? taxBehaviorLegendSentence(collectDefaultTaxBehaviors(firstPlan)) : void 0;
841
463
  const { data: subscriptions = { items: [] } } = useQuery({
842
464
  meta: { context: zudoku },
843
465
  queryKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions`],
844
466
  enabled: auth.isAuthenticated
845
467
  });
468
+ const isSubscribed = subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status));
846
469
  return /* @__PURE__ */ jsxs("div", {
847
470
  className: "w-full px-4 pt-(--padding-content-top) pb-(--padding-content-bottom)",
848
471
  children: [
@@ -862,30 +485,26 @@ const PricingPage = () => {
862
485
  children: pricing?.subtitle ?? "See our pricing options and choose the one that best suits your needs."
863
486
  })]
864
487
  }),
865
- pricingTable.items.length === 0 ? /* @__PURE__ */ jsxs("div", {
866
- className: "text-center py-12 text-muted-foreground",
867
- children: [/* @__PURE__ */ jsx("p", { children: "No plans are currently available." }), /* @__PURE__ */ jsx("p", {
868
- className: "text-sm mt-2",
869
- children: "Make sure your plans are set up and published."
870
- })]
871
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
872
- className: "w-full grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(300px,max-content))] justify-center gap-6",
873
- children: pricingTable.items.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
874
- plan,
875
- isPopular: plan.metadata?.zuplo_most_popular === "true",
876
- isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
877
- }, plan.id))
878
- }), taxLegendSentence && /* @__PURE__ */ jsxs("div", {
879
- role: "note",
880
- className: "mt-10 pt-6 border-t border-border max-w-2xl mx-auto text-center space-y-2",
881
- children: [/* @__PURE__ */ jsx("p", {
882
- className: "text-xs font-medium text-muted-foreground",
883
- children: "Tax & Pricing"
884
- }), /* @__PURE__ */ jsx("p", {
885
- className: "text-xs text-muted-foreground",
886
- children: taxLegendSentence
887
- })]
888
- })] }),
488
+ /* @__PURE__ */ jsx(PricingTable, {
489
+ plans: pricingTable.items,
490
+ showYearlyPrice: pricing?.showYearlyPrice !== false,
491
+ units: pricing?.units,
492
+ renderAction: (plan, isPopular) => isSubscribed ? /* @__PURE__ */ jsx(Button, {
493
+ variant: isPopular ? "default" : "outline",
494
+ asChild: true,
495
+ children: /* @__PURE__ */ jsx(Link$1, {
496
+ to: `/subscriptions#manage`,
497
+ children: "Manage Subscriptions"
498
+ })
499
+ }) : /* @__PURE__ */ jsx(Button, {
500
+ variant: isPopular ? "default" : "outline",
501
+ asChild: true,
502
+ children: /* @__PURE__ */ jsx(Link$1, {
503
+ to: `/checkout?planId=${encodeURIComponent(plan.id)}`,
504
+ children: "Subscribe"
505
+ })
506
+ })
507
+ }),
889
508
  /* @__PURE__ */ jsx(Slot.Target, { name: "pricing-page-after" })
890
509
  ]
891
510
  });
@@ -1201,7 +820,7 @@ const ApiKey = ({ apiKey, createdAt, lastUsed, expiresOn, isActive = true, label
1201
820
  children: "•"
1202
821
  }),
1203
822
  /* @__PURE__ */ jsxs("span", { children: ["Last updated ", getTimeAgo(lastUsed)] }),
1204
- expiresOn && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
823
+ expiresOn && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1205
824
  className: "text-muted-foreground/40",
1206
825
  children: "•"
1207
826
  }), /* @__PURE__ */ jsxs("span", {
@@ -1433,7 +1052,7 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
1433
1052
  children: [
1434
1053
  /* @__PURE__ */ jsxs(Alert, {
1435
1054
  variant: "warning",
1436
- children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }), isTrialCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
1055
+ children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }), isTrialCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
1437
1056
  "Cancel your trial of ",
1438
1057
  planName,
1439
1058
  "?"
@@ -1441,11 +1060,11 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
1441
1060
  "Your subscription will end now and you won't be charged when the trial would have converted to ",
1442
1061
  planName,
1443
1062
  "."
1444
- ] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
1063
+ ] })] }) : isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(AlertTitle, { children: [
1445
1064
  "Cancel your ",
1446
1065
  planName,
1447
1066
  " subscription?"
1448
- ] }), /* @__PURE__ */ jsx(AlertDescription, { children: "Your subscription will end immediately. You'll lose access to its entitlements right away." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "Your plan will be canceled at the end of your billing cycle." }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
1067
+ ] }), /* @__PURE__ */ jsx(AlertDescription, { children: "Your subscription will end immediately. You'll lose access to its entitlements right away." })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "Your plan will be canceled at the end of your billing cycle." }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
1449
1068
  "You'll retain access until ",
1450
1069
  formatDate$2(billingPeriodEnd),
1451
1070
  ". After your billing period ends, this plan will not renew and you would need to subscribe again to continue."
@@ -1453,7 +1072,7 @@ const CancelSubscriptionDialog = ({ open, onOpenChange, planName, subscriptionId
1453
1072
  }),
1454
1073
  /* @__PURE__ */ jsxs(Alert, {
1455
1074
  variant: "info",
1456
- children: [/* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }), isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can subscribe again at any time" }), /* @__PURE__ */ jsx(AlertDescription, { children: "After canceling, you can return to the pricing page and start a new subscription whenever you're ready." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can still resume before then" }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
1075
+ children: [/* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }), isImmediateCancel ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can subscribe again at any time" }), /* @__PURE__ */ jsx(AlertDescription, { children: "After canceling, you can return to the pricing page and start a new subscription whenever you're ready." })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AlertTitle, { children: "You can still resume before then" }), /* @__PURE__ */ jsxs(AlertDescription, { children: [
1457
1076
  "If you change your mind you have until",
1458
1077
  " ",
1459
1078
  formatDate$2(billingPeriodEnd),
@@ -1690,7 +1309,7 @@ const comparePlans = (currentPlan, targetPlan, currentIndex, targetIndex, units)
1690
1309
  const target = targetFeatures.find((f) => f.key === key);
1691
1310
  if (current && target) {
1692
1311
  let change = "same";
1693
- if (current.value && target.value && current.value !== target.value) change = isUpgrade ? "upgraded" : "downgraded";
1312
+ if (current.value !== void 0 && target.value !== void 0 && current.value !== target.value) change = isUpgrade ? "upgraded" : "downgraded";
1694
1313
  featureChanges.push({
1695
1314
  key: key ?? "",
1696
1315
  name: target.name,
@@ -1816,7 +1435,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
1816
1435
  }) : quota.change === "removed" ? /* @__PURE__ */ jsx("span", {
1817
1436
  className: "text-destructive",
1818
1437
  children: "No longer included"
1819
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1438
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
1820
1439
  /* @__PURE__ */ jsxs("span", {
1821
1440
  className: "text-muted-foreground",
1822
1441
  children: [
@@ -1841,10 +1460,10 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
1841
1460
  ]
1842
1461
  }, quota.key)), comparison.featureChanges.map((feature) => /* @__PURE__ */ jsx("div", {
1843
1462
  className: "flex items-center gap-2 text-sm",
1844
- children: feature.change === "same" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }), /* @__PURE__ */ jsxs("span", {
1463
+ children: feature.change === "same" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }), /* @__PURE__ */ jsxs("span", {
1845
1464
  className: "text-muted-foreground",
1846
1465
  children: [feature.name, typeof feature.newValue === "string" ? `: ${feature.newValue}` : typeof feature.currentValue === "string" ? `: ${feature.currentValue}` : ""]
1847
- })] }) : feature.change === "added" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1466
+ })] }) : feature.change === "added" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1848
1467
  /* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-600 shrink-0" }),
1849
1468
  /* @__PURE__ */ jsx("span", {
1850
1469
  className: "text-muted-foreground font-medium",
@@ -1858,7 +1477,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
1858
1477
  className: "text-green-600",
1859
1478
  children: "Now included"
1860
1479
  })
1861
- ] }) : feature.change === "removed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1480
+ ] }) : feature.change === "removed" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1862
1481
  /* @__PURE__ */ jsx(XIcon, { className: "w-4 h-4 text-destructive shrink-0" }),
1863
1482
  /* @__PURE__ */ jsx("span", {
1864
1483
  className: "font-medium",
@@ -1872,7 +1491,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange,
1872
1491
  className: "text-destructive",
1873
1492
  children: "No longer included"
1874
1493
  })
1875
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1494
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
1876
1495
  /* @__PURE__ */ jsx(ChangeIndicator, { change: feature.change }),
1877
1496
  /* @__PURE__ */ jsxs("span", {
1878
1497
  className: "",
@@ -2256,19 +1875,11 @@ const getEntitlementsFromItems = (items, currency, units, fallbackBillingCadence
2256
1875
  });
2257
1876
  continue;
2258
1877
  }
2259
- try {
2260
- const parsed = JSON.parse(entitlement.config);
2261
- features.push({
2262
- entitlementType: "static",
2263
- ...base,
2264
- value: parsed?.value != null ? String(parsed.value) : void 0
2265
- });
2266
- } catch {
2267
- features.push({
2268
- entitlementType: "static",
2269
- ...base
2270
- });
2271
- }
1878
+ features.push({
1879
+ entitlementType: "static",
1880
+ ...base,
1881
+ value: formatStaticEntitlementConfig(entitlement.config)
1882
+ });
2272
1883
  }
2273
1884
  }
2274
1885
  return { features };
@@ -2316,7 +1927,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
2316
1927
  const primaryPrice = priceInfo.monthly === 0 && priceInfo.yearly === 0 ? /* @__PURE__ */ jsx("span", {
2317
1928
  className: "text-primary font-medium",
2318
1929
  children: "Free"
2319
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1930
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
2320
1931
  className: "text-primary font-medium text-lg",
2321
1932
  children: formatPrice(priceInfo.monthly, currency)
2322
1933
  }), /* @__PURE__ */ jsxs("span", {
@@ -2403,10 +2014,10 @@ const SubscriptionPlanDetails = ({ subscription }) => {
2403
2014
  className: "flex flex-col gap-1",
2404
2015
  children: [/* @__PURE__ */ jsxs("div", {
2405
2016
  className: "text-foreground font-medium",
2406
- children: [row.name, row.entitlementType === "static" && row.value ? `: ${row.value}` : ""]
2017
+ children: [row.name, row.entitlementType === "static" && row.value !== void 0 ? `: ${row.value}` : ""]
2407
2018
  }), /* @__PURE__ */ jsx("div", {
2408
2019
  className: "text-muted-foreground",
2409
- children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
2020
+ children: row.entitlementType === "metered" && row.limit != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
2410
2021
  formatNumber(row.limit),
2411
2022
  row.period ? ` / ${row.period}` : "",
2412
2023
  row.tierPrices && row.tierPrices.length > 0 ? /* @__PURE__ */ jsx("ul", {
@@ -2417,7 +2028,7 @@ const SubscriptionPlanDetails = ({ subscription }) => {
2417
2028
  className: "text-xs mt-0.5",
2418
2029
  children: ["Overage: ", row.overagePrice]
2419
2030
  }) : null
2420
- ] }) : row.entitlementType === "static" && row.value ? null : "Included"
2031
+ ] }) : row.entitlementType === "static" && row.value !== void 0 ? null : "Included"
2421
2032
  })]
2422
2033
  })
2423
2034
  }, `${row.key}:${row.phaseId}`))
@@ -2601,7 +2212,7 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
2601
2212
  });
2602
2213
  const isPendingFirstPayment = usageQuery.data.paymentStatus.isFirstPayment === true && usageQuery.data.paymentStatus.status !== "paid" && usageQuery.data.paymentStatus.status !== "not_required";
2603
2214
  const activePhase = getActivePhase(subscription);
2604
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2215
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
2605
2216
  planSwitched && /* @__PURE__ */ jsxs(DismissibleAlert, {
2606
2217
  variant: "info",
2607
2218
  children: [