@burdenoff/microfe-billing 2026.530.4 → 2026.531.2

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.
Files changed (27) hide show
  1. package/dist/billing/modules/addons/pages/AddonDetailPage.js +85 -84
  2. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonEditPage.js +176 -195
  4. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  5. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +408 -206
  6. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  7. package/dist/billing/modules/addons/pages/AddonsListPage.js +216 -215
  8. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  9. package/dist/billing/modules/billing/pages/InvoicesListPage.js +7 -1
  10. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  11. package/dist/billing/modules/billing/pages/TransactionsPage.js +1 -1
  12. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  13. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +261 -0
  14. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -0
  15. package/dist/billing/modules/checkout/components/index.js +1 -0
  16. package/dist/billing/modules/checkout/hooks/useCheckout.js +2 -0
  17. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  18. package/dist/billing/modules/checkout/index.js +1 -0
  19. package/dist/billing/modules/checkout/pages/CheckoutPage.js +354 -340
  20. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  21. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +79 -78
  22. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  23. package/dist/billing/modules/plans/pages/PlanEditPage.js +29 -20
  24. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  25. package/dist/generated/global-operations.js +8 -0
  26. package/dist/generated/global-operations.js.map +1 -1
  27. package/package.json +1 -1
@@ -1,73 +1,74 @@
1
- import { useMemo as e, useState as t } from "react";
2
- import { ArrowRight as n, Check as r, ChevronRight as i, Loader2 as a, Minus as o, Package as s, Plus as c, ShoppingCart as l, Sparkles as u, Trash2 as d } from "lucide-react";
3
- import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
4
- import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
1
+ import { getPlanFeatureQuantity as e } from "../../../shared/utils/planFeatureQuantity.js";
2
+ import { useMemo as t, useState as n } from "react";
3
+ import { ArrowRight as r, Check as i, ChevronRight as a, Loader2 as o, Minus as s, Package as c, Plus as l, ShoppingCart as u, Sparkles as d, Trash2 as f } from "lucide-react";
4
+ import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
5
+ import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
5
6
  //#region src/billing/modules/dashboard/components/RecommendedAddonsSection.tsx
6
- function g(e) {
7
- let t = /* @__PURE__ */ new Map();
8
- for (let n of e) {
9
- if (!n.quota) continue;
10
- let e = n.quota.id, r = n.quota.limits?.value ?? 0, i = t.get(e);
11
- i ? i.totalValue += r : t.set(e, {
12
- quotaId: e,
13
- name: n.quota.name ?? "Unknown Quota",
14
- totalValue: r
7
+ function _(t) {
8
+ let n = /* @__PURE__ */ new Map();
9
+ for (let r of t) {
10
+ if (!r.quota) continue;
11
+ let t = r.quota.id, i = e(r), a = n.get(t);
12
+ a ? a.totalValue += i : n.set(t, {
13
+ quotaId: t,
14
+ name: r.quota.name ?? "Unknown Quota",
15
+ totalValue: i
15
16
  });
16
17
  }
17
- return Array.from(t.values());
18
+ return Array.from(n.values());
18
19
  }
19
- var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1, cartQuantity: _ = 0, onUpdateQuantity: v, onRemoveFromCart: y }) => {
20
- let [b, x] = t(!1), S = () => {
21
- u ? (u(), x(!0), setTimeout(() => x(!1), 2e3)) : a();
20
+ var v = ({ addon: e, onView: r, onPurchase: a, onAddToCart: o, isInCart: d = !1, cartQuantity: g = 0, onUpdateQuantity: v, onRemoveFromCart: y }) => {
21
+ let [b, x] = n(!1), S = () => {
22
+ o ? (o(), x(!0), setTimeout(() => x(!1), 2e3)) : a();
22
23
  }, C = (e, t) => new Intl.NumberFormat("en-US", {
23
24
  style: "currency",
24
25
  currency: t || "USD",
25
26
  minimumFractionDigits: 0,
26
27
  maximumFractionDigits: 2
27
- }).format(e), w = (e) => e === "yearly" ? "year" : "month", T = e(() => g(n.features || []), [n.features]);
28
- return /* @__PURE__ */ m("article", {
28
+ }).format(e), w = (e) => e === "yearly" ? "year" : "month", T = t(() => _(e.features || []), [e.features]);
29
+ return /* @__PURE__ */ h("article", {
29
30
  className: "flex flex-col border border-border rounded-lg bg-card overflow-hidden hover:shadow-md transition-shadow",
30
31
  children: [
31
- /* @__PURE__ */ p("header", {
32
+ /* @__PURE__ */ m("header", {
32
33
  className: "p-4 border-b border-border",
33
- children: /* @__PURE__ */ p("div", {
34
+ children: /* @__PURE__ */ m("div", {
34
35
  className: "flex items-start justify-between gap-3",
35
- children: /* @__PURE__ */ m("div", {
36
+ children: /* @__PURE__ */ h("div", {
36
37
  className: "flex items-center gap-3",
37
- children: [/* @__PURE__ */ p("div", {
38
+ children: [/* @__PURE__ */ m("div", {
38
39
  className: "size-10 rounded-lg bg-status-success-bg-subtle flex items-center justify-center",
39
- children: /* @__PURE__ */ p(s, { className: "size-5 text-status-success-text" })
40
- }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h4", {
40
+ children: /* @__PURE__ */ m(c, { className: "size-5 text-status-success-text" })
41
+ }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h4", {
41
42
  className: "font-semibold text-foreground",
42
- children: n.name
43
- }), /* @__PURE__ */ m("p", {
43
+ children: e.name
44
+ }), /* @__PURE__ */ h("p", {
44
45
  className: "text-sm text-muted-foreground",
45
46
  children: [
46
- C(n.price, n.currency),
47
+ C(e.price, e.currency),
47
48
  "/",
48
- w(n.duration)
49
+ w(e.duration)
49
50
  ]
50
51
  })] })]
51
52
  })
52
53
  })
53
54
  }),
54
- /* @__PURE__ */ p("div", {
55
+ /* @__PURE__ */ m("div", {
55
56
  className: "p-4 flex-1",
56
- children: T.length > 0 ? /* @__PURE__ */ m("ul", {
57
+ children: T.length > 0 ? /* @__PURE__ */ h("ul", {
57
58
  className: "space-y-2",
58
- children: [T.slice(0, 2).map((e) => /* @__PURE__ */ m("li", {
59
+ children: [T.slice(0, 2).map((e) => /* @__PURE__ */ h("li", {
59
60
  className: "flex items-center justify-between text-sm",
60
- children: [/* @__PURE__ */ m("div", {
61
+ children: [/* @__PURE__ */ h("div", {
61
62
  className: "flex items-center gap-2",
62
- children: [/* @__PURE__ */ p("span", { className: "size-1.5 rounded-full bg-status-success-bg" }), /* @__PURE__ */ p("span", {
63
+ children: [/* @__PURE__ */ m("span", { className: "size-1.5 rounded-full bg-status-success-bg" }), /* @__PURE__ */ m("span", {
63
64
  className: "text-muted-foreground",
64
65
  children: e.name
65
66
  })]
66
- }), /* @__PURE__ */ p("span", {
67
+ }), /* @__PURE__ */ m("span", {
67
68
  className: "font-medium text-foreground",
68
69
  children: e.totalValue.toLocaleString()
69
70
  })]
70
- }, e.quotaId)), T.length > 2 && /* @__PURE__ */ m("li", {
71
+ }, e.quotaId)), T.length > 2 && /* @__PURE__ */ h("li", {
71
72
  className: "text-xs text-muted-foreground",
72
73
  children: [
73
74
  "+",
@@ -75,113 +76,113 @@ var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1,
75
76
  " more quotas"
76
77
  ]
77
78
  })]
78
- }) : /* @__PURE__ */ p("p", {
79
+ }) : /* @__PURE__ */ m("p", {
79
80
  className: "text-sm text-muted-foreground",
80
81
  children: "Enhance your subscription with additional features"
81
82
  })
82
83
  }),
83
- /* @__PURE__ */ m("footer", {
84
+ /* @__PURE__ */ h("footer", {
84
85
  className: "p-4 border-t border-border bg-muted/30 flex items-center gap-2",
85
- children: [/* @__PURE__ */ p("button", {
86
+ children: [/* @__PURE__ */ m("button", {
86
87
  type: "button",
87
- onClick: i,
88
+ onClick: r,
88
89
  className: "flex-1 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors text-center",
89
90
  children: "View Details"
90
- }), h && v ? /* @__PURE__ */ m("div", {
91
+ }), d && v ? /* @__PURE__ */ h("div", {
91
92
  className: "flex items-center gap-1",
92
93
  children: [
93
- /* @__PURE__ */ p("button", {
94
+ /* @__PURE__ */ m("button", {
94
95
  type: "button",
95
96
  onClick: y,
96
97
  className: "p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors",
97
98
  title: "Remove from cart",
98
- children: /* @__PURE__ */ p(d, { className: "size-4" })
99
+ children: /* @__PURE__ */ m(f, { className: "size-4" })
99
100
  }),
100
- /* @__PURE__ */ p("button", {
101
+ /* @__PURE__ */ m("button", {
101
102
  type: "button",
102
- onClick: () => v(_ - 1),
103
+ onClick: () => v(g - 1),
103
104
  className: "p-2 text-foreground hover:bg-muted rounded-lg transition-colors",
104
- children: /* @__PURE__ */ p(o, { className: "size-4" })
105
+ children: /* @__PURE__ */ m(s, { className: "size-4" })
105
106
  }),
106
- /* @__PURE__ */ p("span", {
107
+ /* @__PURE__ */ m("span", {
107
108
  className: "w-8 text-center font-medium text-foreground",
108
- children: _
109
+ children: g
109
110
  }),
110
- /* @__PURE__ */ p("button", {
111
+ /* @__PURE__ */ m("button", {
111
112
  type: "button",
112
- onClick: () => v(_ + 1),
113
+ onClick: () => v(g + 1),
113
114
  className: "p-2 text-foreground hover:bg-muted rounded-lg transition-colors",
114
- children: /* @__PURE__ */ p(c, { className: "size-4" })
115
+ children: /* @__PURE__ */ m(l, { className: "size-4" })
115
116
  })
116
117
  ]
117
- }) : /* @__PURE__ */ p("button", {
118
+ }) : /* @__PURE__ */ m("button", {
118
119
  type: "button",
119
120
  onClick: S,
120
121
  disabled: b,
121
122
  className: `flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-center flex items-center justify-center gap-1.5 ${b ? "bg-status-success-bg text-primary-foreground hover:bg-status-success-bg/90" : "text-primary-foreground bg-primary hover:bg-primary/90"}`,
122
- children: b ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(r, { className: "size-4" }), "Added"] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(l, { className: "size-4" }), "Add to Cart"] })
123
+ children: b ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(i, { className: "size-4" }), "Added"] }) : /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(u, { className: "size-4" }), "Add to Cart"] })
123
124
  })]
124
125
  })
125
126
  ]
126
127
  });
127
- }, v = ({ addons: e, hasSubscription: t, onViewAddon: r, onPurchaseAddon: o, onViewAllAddons: s, onAddToCart: c, isInCart: l, getCartQuantity: d, onUpdateQuantity: f, onRemoveFromCart: g, maxAddons: v = 3, isLoading: y = !1, className: b = "", dataTour: x }) => {
128
- let { t: S } = h(), C = (e, t) => {
128
+ }, y = ({ addons: e, hasSubscription: t, onViewAddon: n, onPurchaseAddon: i, onViewAllAddons: s, onAddToCart: c, isInCart: l, getCartQuantity: u, onUpdateQuantity: f, onRemoveFromCart: p, maxAddons: _ = 3, isLoading: y = !1, className: b = "", dataTour: x }) => {
129
+ let { t: S } = g(), C = (e, t) => {
129
130
  let n = S(e);
130
131
  return n === e ? t : n;
131
132
  };
132
133
  if (!t) return null;
133
- let w = e.slice(0, v);
134
- return y ? /* @__PURE__ */ m("section", {
134
+ let w = e.slice(0, _);
135
+ return y ? /* @__PURE__ */ h("section", {
135
136
  className: `space-y-4 ${b}`,
136
137
  "data-tour": x,
137
- children: [/* @__PURE__ */ m("header", { children: [/* @__PURE__ */ p("h3", {
138
+ children: [/* @__PURE__ */ h("header", { children: [/* @__PURE__ */ m("h3", {
138
139
  className: "text-lg font-semibold text-foreground",
139
140
  children: C("billing.overview.recommendedAddons", "Recommended Add-ons")
140
- }), /* @__PURE__ */ p("p", {
141
+ }), /* @__PURE__ */ m("p", {
141
142
  className: "text-sm text-muted-foreground",
142
143
  children: "Enhance your subscription with these popular add-ons"
143
- })] }), /* @__PURE__ */ p("div", {
144
+ })] }), /* @__PURE__ */ m("div", {
144
145
  className: "flex items-center justify-center py-12",
145
- children: /* @__PURE__ */ p(a, { className: "size-6 animate-spin text-muted-foreground" })
146
+ children: /* @__PURE__ */ m(o, { className: "size-6 animate-spin text-muted-foreground" })
146
147
  })]
147
- }) : w.length === 0 ? null : /* @__PURE__ */ m("section", {
148
+ }) : w.length === 0 ? null : /* @__PURE__ */ h("section", {
148
149
  className: `space-y-4 ${b}`,
149
150
  "data-tour": x,
150
151
  children: [
151
- /* @__PURE__ */ m("header", {
152
+ /* @__PURE__ */ h("header", {
152
153
  className: "flex items-center justify-between",
153
- children: [/* @__PURE__ */ m("div", {
154
+ children: [/* @__PURE__ */ h("div", {
154
155
  className: "flex items-center gap-2",
155
- children: [/* @__PURE__ */ p(u, { className: "size-5 text-status-warning-text" }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h3", {
156
+ children: [/* @__PURE__ */ m(d, { className: "size-5 text-status-warning-text" }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h3", {
156
157
  className: "text-lg font-semibold text-foreground",
157
158
  children: "Recommended Add-ons"
158
- }), /* @__PURE__ */ p("p", {
159
+ }), /* @__PURE__ */ m("p", {
159
160
  className: "text-sm text-muted-foreground",
160
161
  children: "Enhance your subscription with these popular add-ons"
161
162
  })] })]
162
- }), /* @__PURE__ */ m("button", {
163
+ }), /* @__PURE__ */ h("button", {
163
164
  type: "button",
164
165
  onClick: s,
165
166
  className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary hover:text-primary/80 transition-colors",
166
- children: ["View All", /* @__PURE__ */ p(i, { className: "size-4" })]
167
+ children: ["View All", /* @__PURE__ */ m(a, { className: "size-4" })]
167
168
  })]
168
169
  }),
169
- /* @__PURE__ */ p("div", {
170
+ /* @__PURE__ */ m("div", {
170
171
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
171
- children: w.map((e) => /* @__PURE__ */ p(_, {
172
+ children: w.map((e) => /* @__PURE__ */ m(v, {
172
173
  addon: e,
173
- onView: () => r(e.id),
174
- onPurchase: () => o(e.id),
174
+ onView: () => n(e.id),
175
+ onPurchase: () => i(e.id),
175
176
  onAddToCart: c ? () => c(e) : void 0,
176
177
  isInCart: l ? l(e.id) : !1,
177
- cartQuantity: d ? d(e.id) : 0,
178
+ cartQuantity: u ? u(e.id) : 0,
178
179
  onUpdateQuantity: f ? (t) => f(e.id, t) : void 0,
179
- onRemoveFromCart: g ? () => g(e.id) : void 0
180
+ onRemoveFromCart: p ? () => p(e.id) : void 0
180
181
  }, e.id))
181
182
  }),
182
- e.length > v && /* @__PURE__ */ p("div", {
183
+ e.length > _ && /* @__PURE__ */ m("div", {
183
184
  className: "flex justify-center pt-2",
184
- children: /* @__PURE__ */ m("button", {
185
+ children: /* @__PURE__ */ h("button", {
185
186
  type: "button",
186
187
  onClick: s,
187
188
  className: "inline-flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors",
@@ -189,7 +190,7 @@ var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1,
189
190
  "View all ",
190
191
  e.length,
191
192
  " add-ons",
192
- /* @__PURE__ */ p(n, { className: "size-4" })
193
+ /* @__PURE__ */ m(r, { className: "size-4" })
193
194
  ]
194
195
  })
195
196
  })
@@ -197,6 +198,6 @@ var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1,
197
198
  });
198
199
  };
199
200
  //#endregion
200
- export { v as RecommendedAddonsSection };
201
+ export { y as RecommendedAddonsSection };
201
202
 
202
203
  //# sourceMappingURL=RecommendedAddonsSection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RecommendedAddonsSection.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/RecommendedAddonsSection.tsx"],"sourcesContent":["/**\n * Recommended Addons Section Component\n * Displays recommended addons that users might want to purchase\n * Supports add to cart functionality\n */\n\nimport { type FC, useState, useMemo } from 'react';\nimport {\n Package,\n ArrowRight,\n Sparkles,\n ChevronRight,\n Loader2,\n ShoppingCart,\n Check,\n Plus,\n Minus,\n Trash2,\n} from 'lucide-react';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n * e.g., 10 bots + 10 bots = 20 bots (shown as one feature)\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\ninterface RecommendedAddonsSectionProps {\n addons: Addon[];\n hasSubscription: boolean;\n onViewAddon: (addonId: string) => void;\n onPurchaseAddon: (addonId: string) => void;\n onViewAllAddons: () => void;\n /** Handler for adding addon to cart */\n onAddToCart?: (addon: Addon) => void;\n /** Check if addon is in cart */\n isInCart?: (addonId: string) => boolean;\n /** Get cart quantity for addon */\n getCartQuantity?: (addonId: string) => number;\n /** Update quantity in cart */\n onUpdateQuantity?: (addonId: string, quantity: number) => void;\n /** Remove from cart */\n onRemoveFromCart?: (addonId: string) => void;\n /** Maximum number of addons to show (default: 3) */\n maxAddons?: number;\n isLoading?: boolean;\n className?: string;\n /** Optional tour anchor applied to the section root (for onboarding tours). */\n dataTour?: string;\n}\n\nconst AddonCard: FC<{\n addon: Addon;\n onView: () => void;\n onPurchase: () => void;\n onAddToCart?: () => void;\n isInCart?: boolean;\n cartQuantity?: number;\n onUpdateQuantity?: (quantity: number) => void;\n onRemoveFromCart?: () => void;\n}> = ({\n addon,\n onView,\n onPurchase,\n onAddToCart,\n isInCart = false,\n cartQuantity = 0,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const [justAdded, setJustAdded] = useState(false);\n\n const handleAddToCart = () => {\n if (onAddToCart) {\n onAddToCart();\n setJustAdded(true);\n setTimeout(() => setJustAdded(false), 2000);\n } else {\n onPurchase();\n }\n };\n\n const formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(amount);\n };\n\n const getDurationLabel = (duration: PlanDuration) => {\n return duration === 'yearly' ? 'year' : 'month';\n };\n\n // Combine duplicate quotas (e.g., 10 bots + 10 bots = 20 bots)\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features as SubscriptionFeatures[]);\n }, [addon.features]);\n\n return (\n <article className=\"flex flex-col border border-border rounded-lg bg-card overflow-hidden hover:shadow-md transition-shadow\">\n {/* Header */}\n <header className=\"p-4 border-b border-border\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Package className=\"size-5 text-status-success-text\" />\n </div>\n <div>\n <h4 className=\"font-semibold text-foreground\">{addon.name}</h4>\n <p className=\"text-sm text-muted-foreground\">\n {formatCurrency(addon.price, addon.currency)}/{getDurationLabel(addon.duration)}\n </p>\n </div>\n </div>\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 flex-1\">\n {combinedQuotas.length > 0 ? (\n <ul className=\"space-y-2\">\n {combinedQuotas.slice(0, 2).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <span className=\"size-1.5 rounded-full bg-status-success-bg\" />\n <span className=\"text-muted-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 2 && (\n <li className=\"text-xs text-muted-foreground\">\n +{combinedQuotas.length - 2} more quotas\n </li>\n )}\n </ul>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with additional features\n </p>\n )}\n </div>\n\n {/* Footer */}\n <footer className=\"p-4 border-t border-border bg-muted/30 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onView}\n className=\"flex-1 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors text-center\"\n >\n View Details\n </button>\n {isInCart && onUpdateQuantity ? (\n // Quantity controls when in cart\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={onRemoveFromCart}\n className=\"p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity - 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium text-foreground\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity + 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n ) : (\n // Add to cart button\n <button\n type=\"button\"\n onClick={handleAddToCart}\n disabled={justAdded}\n className={`flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-center flex items-center justify-center gap-1.5 ${\n justAdded\n ? 'bg-status-success-bg text-primary-foreground hover:bg-status-success-bg/90'\n : 'text-primary-foreground bg-primary hover:bg-primary/90'\n }`}\n >\n {justAdded ? (\n <>\n <Check className=\"size-4\" />\n Added\n </>\n ) : (\n <>\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </>\n )}\n </button>\n )}\n </footer>\n </article>\n );\n};\n\nexport const RecommendedAddonsSection: FC<RecommendedAddonsSectionProps> = ({\n addons,\n hasSubscription,\n onViewAddon,\n onPurchaseAddon,\n onViewAllAddons,\n onAddToCart,\n isInCart,\n getCartQuantity,\n onUpdateQuantity,\n onRemoveFromCart,\n maxAddons = 3,\n isLoading = false,\n className = '',\n dataTour,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n // Only show if user has a subscription\n if (!hasSubscription) {\n return null;\n }\n\n // Limit addons to maxAddons\n const displayAddons = addons.slice(0, maxAddons);\n\n if (isLoading) {\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n <header>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.overview.recommendedAddons', 'Recommended Add-ons')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </header>\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n </div>\n </section>\n );\n }\n\n if (displayAddons.length === 0) {\n return null;\n }\n\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n {/* Header */}\n <header className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Sparkles className=\"size-5 text-status-warning-text\" />\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">Recommended Add-ons</h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary hover:text-primary/80 transition-colors\"\n >\n View All\n <ChevronRight className=\"size-4\" />\n </button>\n </header>\n\n {/* Addons Grid - Limited to maxAddons */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {displayAddons.map((addon) => (\n <AddonCard\n key={addon.id}\n addon={addon}\n onView={() => onViewAddon(addon.id)}\n onPurchase={() => onPurchaseAddon(addon.id)}\n onAddToCart={onAddToCart ? () => onAddToCart(addon) : undefined}\n isInCart={isInCart ? isInCart(addon.id) : false}\n cartQuantity={getCartQuantity ? getCartQuantity(addon.id) : 0}\n onUpdateQuantity={\n onUpdateQuantity ? (qty) => onUpdateQuantity(addon.id, qty) : undefined\n }\n onRemoveFromCart={onRemoveFromCart ? () => onRemoveFromCart(addon.id) : undefined}\n />\n ))}\n </div>\n\n {/* See More Link - show if there are more addons */}\n {addons.length > maxAddons && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View all {addons.length} add-ons\n <ArrowRight className=\"size-4\" />\n </button>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"RecommendedAddonsSection.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/RecommendedAddonsSection.tsx"],"sourcesContent":["/**\n * Recommended Addons Section Component\n * Displays recommended addons that users might want to purchase\n * Supports add to cart functionality\n */\n\nimport { type FC, useState, useMemo } from 'react';\nimport {\n Package,\n ArrowRight,\n Sparkles,\n ChevronRight,\n Loader2,\n ShoppingCart,\n Check,\n Plus,\n Minus,\n Trash2,\n} from 'lucide-react';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n * e.g., 10 bots + 10 bots = 20 bots (shown as one feature)\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\ninterface RecommendedAddonsSectionProps {\n addons: Addon[];\n hasSubscription: boolean;\n onViewAddon: (addonId: string) => void;\n onPurchaseAddon: (addonId: string) => void;\n onViewAllAddons: () => void;\n /** Handler for adding addon to cart */\n onAddToCart?: (addon: Addon) => void;\n /** Check if addon is in cart */\n isInCart?: (addonId: string) => boolean;\n /** Get cart quantity for addon */\n getCartQuantity?: (addonId: string) => number;\n /** Update quantity in cart */\n onUpdateQuantity?: (addonId: string, quantity: number) => void;\n /** Remove from cart */\n onRemoveFromCart?: (addonId: string) => void;\n /** Maximum number of addons to show (default: 3) */\n maxAddons?: number;\n isLoading?: boolean;\n className?: string;\n /** Optional tour anchor applied to the section root (for onboarding tours). */\n dataTour?: string;\n}\n\nconst AddonCard: FC<{\n addon: Addon;\n onView: () => void;\n onPurchase: () => void;\n onAddToCart?: () => void;\n isInCart?: boolean;\n cartQuantity?: number;\n onUpdateQuantity?: (quantity: number) => void;\n onRemoveFromCart?: () => void;\n}> = ({\n addon,\n onView,\n onPurchase,\n onAddToCart,\n isInCart = false,\n cartQuantity = 0,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const [justAdded, setJustAdded] = useState(false);\n\n const handleAddToCart = () => {\n if (onAddToCart) {\n onAddToCart();\n setJustAdded(true);\n setTimeout(() => setJustAdded(false), 2000);\n } else {\n onPurchase();\n }\n };\n\n const formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(amount);\n };\n\n const getDurationLabel = (duration: PlanDuration) => {\n return duration === 'yearly' ? 'year' : 'month';\n };\n\n // Combine duplicate quotas (e.g., 10 bots + 10 bots = 20 bots)\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features as SubscriptionFeatures[]);\n }, [addon.features]);\n\n return (\n <article className=\"flex flex-col border border-border rounded-lg bg-card overflow-hidden hover:shadow-md transition-shadow\">\n {/* Header */}\n <header className=\"p-4 border-b border-border\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Package className=\"size-5 text-status-success-text\" />\n </div>\n <div>\n <h4 className=\"font-semibold text-foreground\">{addon.name}</h4>\n <p className=\"text-sm text-muted-foreground\">\n {formatCurrency(addon.price, addon.currency)}/{getDurationLabel(addon.duration)}\n </p>\n </div>\n </div>\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 flex-1\">\n {combinedQuotas.length > 0 ? (\n <ul className=\"space-y-2\">\n {combinedQuotas.slice(0, 2).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <span className=\"size-1.5 rounded-full bg-status-success-bg\" />\n <span className=\"text-muted-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 2 && (\n <li className=\"text-xs text-muted-foreground\">\n +{combinedQuotas.length - 2} more quotas\n </li>\n )}\n </ul>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with additional features\n </p>\n )}\n </div>\n\n {/* Footer */}\n <footer className=\"p-4 border-t border-border bg-muted/30 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onView}\n className=\"flex-1 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors text-center\"\n >\n View Details\n </button>\n {isInCart && onUpdateQuantity ? (\n // Quantity controls when in cart\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={onRemoveFromCart}\n className=\"p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity - 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium text-foreground\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity + 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n ) : (\n // Add to cart button\n <button\n type=\"button\"\n onClick={handleAddToCart}\n disabled={justAdded}\n className={`flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-center flex items-center justify-center gap-1.5 ${\n justAdded\n ? 'bg-status-success-bg text-primary-foreground hover:bg-status-success-bg/90'\n : 'text-primary-foreground bg-primary hover:bg-primary/90'\n }`}\n >\n {justAdded ? (\n <>\n <Check className=\"size-4\" />\n Added\n </>\n ) : (\n <>\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </>\n )}\n </button>\n )}\n </footer>\n </article>\n );\n};\n\nexport const RecommendedAddonsSection: FC<RecommendedAddonsSectionProps> = ({\n addons,\n hasSubscription,\n onViewAddon,\n onPurchaseAddon,\n onViewAllAddons,\n onAddToCart,\n isInCart,\n getCartQuantity,\n onUpdateQuantity,\n onRemoveFromCart,\n maxAddons = 3,\n isLoading = false,\n className = '',\n dataTour,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n // Only show if user has a subscription\n if (!hasSubscription) {\n return null;\n }\n\n // Limit addons to maxAddons\n const displayAddons = addons.slice(0, maxAddons);\n\n if (isLoading) {\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n <header>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.overview.recommendedAddons', 'Recommended Add-ons')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </header>\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n </div>\n </section>\n );\n }\n\n if (displayAddons.length === 0) {\n return null;\n }\n\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n {/* Header */}\n <header className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Sparkles className=\"size-5 text-status-warning-text\" />\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">Recommended Add-ons</h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary hover:text-primary/80 transition-colors\"\n >\n View All\n <ChevronRight className=\"size-4\" />\n </button>\n </header>\n\n {/* Addons Grid - Limited to maxAddons */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {displayAddons.map((addon) => (\n <AddonCard\n key={addon.id}\n addon={addon}\n onView={() => onViewAddon(addon.id)}\n onPurchase={() => onPurchaseAddon(addon.id)}\n onAddToCart={onAddToCart ? () => onAddToCart(addon) : undefined}\n isInCart={isInCart ? isInCart(addon.id) : false}\n cartQuantity={getCartQuantity ? getCartQuantity(addon.id) : 0}\n onUpdateQuantity={\n onUpdateQuantity ? (qty) => onUpdateQuantity(addon.id, qty) : undefined\n }\n onRemoveFromCart={onRemoveFromCart ? () => onRemoveFromCart(addon.id) : undefined}\n />\n ))}\n </div>\n\n {/* See More Link - show if there are more addons */}\n {addons.length > maxAddons && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View all {addons.length} add-ons\n <ArrowRight className=\"size-4\" />\n </button>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
@@ -21,22 +21,31 @@ var y = () => {
21
21
  let { t: y } = v(), b = (e, t) => {
22
22
  let n = y(e);
23
23
  return n === e ? t : n;
24
- }, { planId: x } = m(), S = c(), ee = s(), { plan: C, isLoading: w, error: T, refetch: E } = l(x), { quotas: D, isLoading: O } = d({ onlyActive: !0 }), { updatePlan: k, isUpdating: A } = u(), [j, M] = p(""), [N, P] = p(""), [F, I] = p("USD"), [L, R] = p(i.MONTHLY), [z, B] = p(!0), [V, H] = p(!0), [U, W] = p([]), [G, K] = p([]), [q, J] = p(null), [Y, X] = p(!1);
24
+ }, { planId: x } = m(), S = c(), C = s(), { plan: w, isLoading: T, error: E, refetch: D } = l(x), { quotas: O, isLoading: k } = d({ onlyActive: !0 }), { updatePlan: ee, isUpdating: A } = u(), [j, M] = p(""), [N, P] = p(""), [F, I] = p("USD"), [L, R] = p(i.MONTHLY), [z, B] = p(!0), [V, H] = p(!0), [U, W] = p([]), [G, K] = p([]), [q, J] = p(null), [Y, X] = p(!1);
25
25
  if (f(() => {
26
- if (C && (M(C.name ?? ""), P(String(C.price ?? "")), I(C.currency ?? "USD"), R(C.duration ?? i.MONTHLY), B(C.isActive ?? !0), H(C.isPurchasable ?? !0), C.features && C.features.length > 0)) {
27
- let e = [], t = /* @__PURE__ */ new Map();
28
- for (let e of C.features) if (e.quota?.id) {
29
- let n = e.quota.id, r = o(e) || 1;
30
- t.set(n, r);
26
+ if (w) {
27
+ if (M(w.name ?? ""), P(String(w.price ?? "")), I(w.currency ?? "USD"), R(w.duration ?? i.MONTHLY), B(w.isActive ?? !0), H(w.isPurchasable ?? !0), w.currencyPrices && typeof w.currencyPrices == "object") {
28
+ let e = w.currencyPrices;
29
+ K(Object.entries(e).filter(([, e]) => typeof e == "number").map(([e, t]) => ({
30
+ currency: e,
31
+ amount: t
32
+ })));
33
+ }
34
+ if (w.features && w.features.length > 0) {
35
+ let e = [], t = /* @__PURE__ */ new Map();
36
+ for (let e of w.features) if (e.quota?.id) {
37
+ let n = e.quota.id, r = o(e) || 1;
38
+ t.set(n, r);
39
+ }
40
+ for (let [n, r] of t) e.push({
41
+ quotaId: n,
42
+ quantity: r
43
+ });
44
+ W(e);
31
45
  }
32
- for (let [n, r] of t) e.push({
33
- quotaId: n,
34
- quantity: r
35
- });
36
- W(e);
37
46
  }
38
- }, [C]), !ee.canManagePlans) return /* @__PURE__ */ g(e, { message: b("billing.plans.noEditPermission", "You don't have permission to edit plans.") });
39
- if (w || O) return /* @__PURE__ */ _("div", {
47
+ }, [w]), !C.canManagePlans) return /* @__PURE__ */ g(e, { message: b("billing.plans.noEditPermission", "You don't have permission to edit plans.") });
48
+ if (T || k) return /* @__PURE__ */ _("div", {
40
49
  className: "space-y-6 p-6",
41
50
  children: [/* @__PURE__ */ g("div", { className: "h-8 w-48 bg-muted animate-pulse rounded" }), /* @__PURE__ */ g("div", {
42
51
  className: "max-w-2xl space-y-4",
@@ -49,16 +58,16 @@ var y = () => {
49
58
  ].map((e) => /* @__PURE__ */ g("div", { className: "h-16 bg-muted animate-pulse rounded" }, e))
50
59
  })]
51
60
  });
52
- if (T && a(T)) return /* @__PURE__ */ g("div", {
61
+ if (E && a(E)) return /* @__PURE__ */ g("div", {
53
62
  className: "p-6",
54
63
  children: /* @__PURE__ */ g(n, {
55
64
  title: b("billing.plans.serverUnavailable", "Server Unavailable"),
56
65
  message: b("billing.plans.serverUnavailableLoadPlan", "Unable to load plan. The server might be down or experiencing issues."),
57
- onRetry: () => E(),
66
+ onRetry: () => D(),
58
67
  showRetry: !0
59
68
  })
60
69
  });
61
- if (!C) return /* @__PURE__ */ g("div", {
70
+ if (!w) return /* @__PURE__ */ g("div", {
62
71
  className: "flex items-center justify-center h-full min-h-[400px]",
63
72
  children: /* @__PURE__ */ _("div", {
64
73
  className: "text-center space-y-4",
@@ -87,7 +96,7 @@ var y = () => {
87
96
  return /* @__PURE__ */ _("div", {
88
97
  className: "space-y-6 p-6",
89
98
  children: [/* @__PURE__ */ g(t, {
90
- title: `Edit Plan: ${C.name}`,
99
+ title: `Edit Plan: ${w.name}`,
91
100
  description: b("billing.plans.editPlanDesc", "Update the plan details and quota allocations")
92
101
  }), /* @__PURE__ */ _("form", {
93
102
  onSubmit: async (e) => {
@@ -122,7 +131,7 @@ var y = () => {
122
131
  ...G.length > 0 ? { currencyPriceOverrides: G } : {}
123
132
  };
124
133
  try {
125
- await k(t), X(!0), setTimeout(() => S(`/plans/${x}`), 1500);
134
+ await ee(t), X(!0), setTimeout(() => S(`/plans/${x}`), 1500);
126
135
  } catch (e) {
127
136
  J(e instanceof Error ? e.message : "Failed to update plan");
128
137
  }
@@ -299,7 +308,7 @@ var y = () => {
299
308
  className: "text-sm text-muted-foreground",
300
309
  children: b("billing.plans.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
301
310
  }) : U.map((e, t) => {
302
- let n = D.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
311
+ let n = O.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
303
312
  return /* @__PURE__ */ _("div", {
304
313
  className: "p-3 border border-border rounded-lg bg-muted/20 space-y-3",
305
314
  children: [/* @__PURE__ */ _("div", {
@@ -312,7 +321,7 @@ var y = () => {
312
321
  value: "",
313
322
  disabled: !0,
314
323
  children: b("billing.plans.selectQuota", "Select a quota...")
315
- }), D.map((e) => /* @__PURE__ */ g("option", {
324
+ }), O.map((e) => /* @__PURE__ */ g("option", {
316
325
  value: e.id,
317
326
  children: e.name
318
327
  }, e.id))]