@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.
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +85 -84
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +176 -195
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +408 -206
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +216 -215
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +7 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +261 -0
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -0
- package/dist/billing/modules/checkout/components/index.js +1 -0
- package/dist/billing/modules/checkout/hooks/useCheckout.js +2 -0
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/checkout/index.js +1 -0
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +354 -340
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +79 -78
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +29 -20
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/generated/global-operations.js +8 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,73 +1,74 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
7
|
-
let
|
|
8
|
-
for (let
|
|
9
|
-
if (!
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
quotaId:
|
|
13
|
-
name:
|
|
14
|
-
totalValue:
|
|
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(
|
|
18
|
+
return Array.from(n.values());
|
|
18
19
|
}
|
|
19
|
-
var
|
|
20
|
-
let [b, x] =
|
|
21
|
-
|
|
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 =
|
|
28
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
32
|
+
/* @__PURE__ */ m("header", {
|
|
32
33
|
className: "p-4 border-b border-border",
|
|
33
|
-
children: /* @__PURE__ */
|
|
34
|
+
children: /* @__PURE__ */ m("div", {
|
|
34
35
|
className: "flex items-start justify-between gap-3",
|
|
35
|
-
children: /* @__PURE__ */
|
|
36
|
+
children: /* @__PURE__ */ h("div", {
|
|
36
37
|
className: "flex items-center gap-3",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
40
|
-
}), /* @__PURE__ */
|
|
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:
|
|
43
|
-
}), /* @__PURE__ */
|
|
43
|
+
children: e.name
|
|
44
|
+
}), /* @__PURE__ */ h("p", {
|
|
44
45
|
className: "text-sm text-muted-foreground",
|
|
45
46
|
children: [
|
|
46
|
-
C(
|
|
47
|
+
C(e.price, e.currency),
|
|
47
48
|
"/",
|
|
48
|
-
w(
|
|
49
|
+
w(e.duration)
|
|
49
50
|
]
|
|
50
51
|
})] })]
|
|
51
52
|
})
|
|
52
53
|
})
|
|
53
54
|
}),
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ m("div", {
|
|
55
56
|
className: "p-4 flex-1",
|
|
56
|
-
children: T.length > 0 ? /* @__PURE__ */
|
|
57
|
+
children: T.length > 0 ? /* @__PURE__ */ h("ul", {
|
|
57
58
|
className: "space-y-2",
|
|
58
|
-
children: [T.slice(0, 2).map((e) => /* @__PURE__ */
|
|
59
|
+
children: [T.slice(0, 2).map((e) => /* @__PURE__ */ h("li", {
|
|
59
60
|
className: "flex items-center justify-between text-sm",
|
|
60
|
-
children: [/* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ h("div", {
|
|
61
62
|
className: "flex items-center gap-2",
|
|
62
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
84
|
+
/* @__PURE__ */ h("footer", {
|
|
84
85
|
className: "p-4 border-t border-border bg-muted/30 flex items-center gap-2",
|
|
85
|
-
children: [/* @__PURE__ */
|
|
86
|
+
children: [/* @__PURE__ */ m("button", {
|
|
86
87
|
type: "button",
|
|
87
|
-
onClick:
|
|
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
|
-
}),
|
|
91
|
+
}), d && v ? /* @__PURE__ */ h("div", {
|
|
91
92
|
className: "flex items-center gap-1",
|
|
92
93
|
children: [
|
|
93
|
-
/* @__PURE__ */
|
|
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__ */
|
|
99
|
+
children: /* @__PURE__ */ m(f, { className: "size-4" })
|
|
99
100
|
}),
|
|
100
|
-
/* @__PURE__ */
|
|
101
|
+
/* @__PURE__ */ m("button", {
|
|
101
102
|
type: "button",
|
|
102
|
-
onClick: () => v(
|
|
103
|
+
onClick: () => v(g - 1),
|
|
103
104
|
className: "p-2 text-foreground hover:bg-muted rounded-lg transition-colors",
|
|
104
|
-
children: /* @__PURE__ */
|
|
105
|
+
children: /* @__PURE__ */ m(s, { className: "size-4" })
|
|
105
106
|
}),
|
|
106
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ m("span", {
|
|
107
108
|
className: "w-8 text-center font-medium text-foreground",
|
|
108
|
-
children:
|
|
109
|
+
children: g
|
|
109
110
|
}),
|
|
110
|
-
/* @__PURE__ */
|
|
111
|
+
/* @__PURE__ */ m("button", {
|
|
111
112
|
type: "button",
|
|
112
|
-
onClick: () => v(
|
|
113
|
+
onClick: () => v(g + 1),
|
|
113
114
|
className: "p-2 text-foreground hover:bg-muted rounded-lg transition-colors",
|
|
114
|
-
children: /* @__PURE__ */
|
|
115
|
+
children: /* @__PURE__ */ m(l, { className: "size-4" })
|
|
115
116
|
})
|
|
116
117
|
]
|
|
117
|
-
}) : /* @__PURE__ */
|
|
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__ */
|
|
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
|
-
},
|
|
128
|
-
let { t: S } =
|
|
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,
|
|
134
|
-
return y ? /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
144
|
+
})] }), /* @__PURE__ */ m("div", {
|
|
144
145
|
className: "flex items-center justify-center py-12",
|
|
145
|
-
children: /* @__PURE__ */
|
|
146
|
+
children: /* @__PURE__ */ m(o, { className: "size-6 animate-spin text-muted-foreground" })
|
|
146
147
|
})]
|
|
147
|
-
}) : w.length === 0 ? null : /* @__PURE__ */
|
|
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__ */
|
|
152
|
+
/* @__PURE__ */ h("header", {
|
|
152
153
|
className: "flex items-center justify-between",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
154
|
+
children: [/* @__PURE__ */ h("div", {
|
|
154
155
|
className: "flex items-center gap-2",
|
|
155
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
167
|
+
children: ["View All", /* @__PURE__ */ m(a, { className: "size-4" })]
|
|
167
168
|
})]
|
|
168
169
|
}),
|
|
169
|
-
/* @__PURE__ */
|
|
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__ */
|
|
172
|
+
children: w.map((e) => /* @__PURE__ */ m(v, {
|
|
172
173
|
addon: e,
|
|
173
|
-
onView: () =>
|
|
174
|
-
onPurchase: () =>
|
|
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:
|
|
178
|
+
cartQuantity: u ? u(e.id) : 0,
|
|
178
179
|
onUpdateQuantity: f ? (t) => f(e.id, t) : void 0,
|
|
179
|
-
onRemoveFromCart:
|
|
180
|
+
onRemoveFromCart: p ? () => p(e.id) : void 0
|
|
180
181
|
}, e.id))
|
|
181
182
|
}),
|
|
182
|
-
e.length >
|
|
183
|
+
e.length > _ && /* @__PURE__ */ m("div", {
|
|
183
184
|
className: "flex justify-center pt-2",
|
|
184
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
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 {
|
|
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":";;;;;AAmCA,SAAS,EAAc,GAAmD;CACxE,IAAM,oBAAW,IAAI,KAA4B;AAEjD,MAAK,IAAM,KAAW,GAAU;AAC9B,MAAI,CAAC,EAAQ,MAAO;EAEpB,IAAM,IAAU,EAAQ,MAAM,IAExB,IADS,EAAQ,MAAM,QACF,SAAS,GAE9B,IAAW,EAAS,IAAI,EAAQ;AACtC,EAAI,IACF,EAAS,cAAc,IAEvB,EAAS,IAAI,GAAS;GACpB;GACA,MAAM,EAAQ,MAAM,QAAQ;GAC5B,YAAY;GACb,CAAC;;AAIN,QAAO,MAAM,KAAK,EAAS,QAAQ,CAAC;;AA2BtC,IAAM,KASA,EACJ,UACA,WACA,eACA,gBACA,cAAW,IACX,kBAAe,GACf,qBACA,0BACI;CACJ,IAAM,CAAC,GAAW,KAAgB,EAAS,GAAM,EAE3C,UAAwB;AAC5B,EAAI,KACF,GAAa,EACb,EAAa,GAAK,EAClB,iBAAiB,EAAa,GAAM,EAAE,IAAK,IAE3C,GAAY;IAIV,KAAkB,GAAgB,MAC/B,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU,KAAY;EACtB,uBAAuB;EACvB,uBAAuB;EACxB,CAAC,CAAC,OAAO,EAAO,EAGb,KAAoB,MACjB,MAAa,WAAW,SAAS,SAIpC,IAAiB,QAEd,EADU,EAAM,YAAY,EAAE,CACmB,EACvD,CAAC,EAAM,SAAS,CAAC;AAEpB,QACE,kBAAC,WAAD;EAAS,WAAU;YAAnB;GAEE,kBAAC,UAAD;IAAQ,WAAU;cAChB,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAS,WAAU,mCAAoC,CAAA;OACnD,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAiC,EAAM;OAAU,CAAA,EAC/D,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAe,EAAM,OAAO,EAAM,SAAS;QAAC;QAAE,EAAiB,EAAM,SAAS;QAC7E;SACA,EAAA,CAAA,CACF;;KACF,CAAA;IACC,CAAA;GAGT,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAe,SAAS,IACvB,kBAAC,MAAD;KAAI,WAAU;eAAd,CACG,EAAe,MAAM,GAAG,EAAE,CAAC,KAAK,MAC/B,kBAAC,MAAD;MAAwB,WAAU;gBAAlC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD,EAAM,WAAU,8CAA+C,CAAA,EAC/D,kBAAC,QAAD;QAAM,WAAU;kBAAyB,EAAM;QAAY,CAAA,CACvD;UACN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAM,WAAW,gBAAgB;OAC7B,CAAA,CACJ;QARI,EAAM,QAQV,CACL,EACD,EAAe,SAAS,KACvB,kBAAC,MAAD;MAAI,WAAU;gBAAd;OAA8C;OAC1C,EAAe,SAAS;OAAE;OACzB;QAEJ;SAEL,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAEzC,CAAA;IAEF,CAAA;GAGN,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;KAEQ,CAAA,EACR,KAAY,IAEX,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;OACtB,CAAA;MACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAiB,IAAe,EAAE;OACjD,WAAU;iBAEV,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OACrB,CAAA;MACT,kBAAC,QAAD;OAAM,WAAU;iBAA+C;OAAoB,CAAA;MACnF,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAiB,IAAe,EAAE;OACjD,WAAU;iBAEV,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;OACpB,CAAA;MACL;SAGN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAW,0HACT,IACI,+EACA;eAGL,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,EAAA,QAE3B,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,EAAA,cAElC,EAAA,CAAA;KAEE,CAAA,CAEJ;;GACD;;GAID,KAA+D,EAC1E,WACA,oBACA,gBACA,oBACA,oBACA,gBACA,aACA,oBACA,qBACA,qBACA,eAAY,GACZ,eAAY,IACZ,eAAY,IACZ,kBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;;AAGzC,KAAI,CAAC,EACH,QAAO;CAIT,IAAM,IAAgB,EAAO,MAAM,GAAG,EAAU;AAwBhD,QAtBI,IAEA,kBAAC,WAAD;EAAS,WAAW,aAAa;EAAa,aAAW;YAAzD,CACE,kBAAC,UAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aACX,EAAG,sCAAsC,sBAAsB;GAC7D,CAAA,EACL,kBAAC,KAAD;GAAG,WAAU;aAAgC;GAEzC,CAAA,CACG,EAAA,CAAA,EACT,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,WAAU,6CAA8C,CAAA;GAC7D,CAAA,CACE;MAIV,EAAc,WAAW,IACpB,OAIP,kBAAC,WAAD;EAAS,WAAW,aAAa;EAAa,aAAW;YAAzD;GAEE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,WAAU,mCAAoC,CAAA,EACxD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAwC;MAAwB,CAAA,EAC9E,kBAAC,KAAD;MAAG,WAAU;gBAAgC;MAEzC,CAAA,CACA,EAAA,CAAA,CACF;QACN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAIC,YAEC,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,CAC5B;OACF;;GAGT,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAc,KAAK,MAClB,kBAAC,GAAD;KAES;KACP,cAAc,EAAY,EAAM,GAAG;KACnC,kBAAkB,EAAgB,EAAM,GAAG;KAC3C,aAAa,UAAoB,EAAY,EAAM,GAAG,KAAA;KACtD,UAAU,IAAW,EAAS,EAAM,GAAG,GAAG;KAC1C,cAAc,IAAkB,EAAgB,EAAM,GAAG,GAAG;KAC5D,kBACE,KAAoB,MAAQ,EAAiB,EAAM,IAAI,EAAI,GAAG,KAAA;KAEhE,kBAAkB,UAAyB,EAAiB,EAAM,GAAG,GAAG,KAAA;KACxE,EAXK,EAAM,GAWX,CACF;IACE,CAAA;GAGL,EAAO,SAAS,KACf,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ;MAIC;MACW,EAAO;MAAO;MACxB,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;MAC1B;;IACL,CAAA;GAEA"}
|
|
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(),
|
|
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 (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
}, [
|
|
39
|
-
if (
|
|
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 (
|
|
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: () =>
|
|
66
|
+
onRetry: () => D(),
|
|
58
67
|
showRetry: !0
|
|
59
68
|
})
|
|
60
69
|
});
|
|
61
|
-
if (!
|
|
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: ${
|
|
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
|
|
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 =
|
|
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
|
-
}),
|
|
324
|
+
}), O.map((e) => /* @__PURE__ */ g("option", {
|
|
316
325
|
value: e.id,
|
|
317
326
|
children: e.name
|
|
318
327
|
}, e.id))]
|