@burdenoff/microfe-billing 2026.1027.2 → 2026.1028.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/billing/modules/addons/hooks/index.js +1 -0
- package/dist/billing/modules/addons/hooks/useAddonPurchaseEligibility.js +27 -0
- package/dist/billing/modules/addons/hooks/useAddonPurchaseEligibility.js.map +1 -0
- package/dist/billing/modules/addons/index.js +1 -0
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +124 -108
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +172 -151
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +296 -260
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +113 -101
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/shared/types/graphql.js.map +1 -1
- package/dist/generated/global-operations.js +335 -311
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useGetAddonPurchaseEligibilityQuery as e } from "../../../../generated/global-operations.js";
|
|
2
|
+
import { useMemo as t } from "react";
|
|
3
|
+
//#region src/billing/modules/addons/hooks/useAddonPurchaseEligibility.ts
|
|
4
|
+
function n(n) {
|
|
5
|
+
let r = t(() => [...new Set(n)].sort(), [n]), { data: i, loading: a } = e({
|
|
6
|
+
variables: { addonIds: r },
|
|
7
|
+
skip: r.length === 0,
|
|
8
|
+
fetchPolicy: "cache-and-network"
|
|
9
|
+
}), o = t(() => {
|
|
10
|
+
let e = /* @__PURE__ */ new Map();
|
|
11
|
+
for (let t of i?.addonPurchaseEligibility ?? []) !t.eligible && t.reason !== "NOT_FOUND" && e.set(t.addonId, {
|
|
12
|
+
addonId: t.addonId,
|
|
13
|
+
productId: t.productId ?? null,
|
|
14
|
+
reason: t.reason
|
|
15
|
+
});
|
|
16
|
+
return e;
|
|
17
|
+
}, [i]);
|
|
18
|
+
return {
|
|
19
|
+
blocked: o,
|
|
20
|
+
isBlocked: (e) => o.has(e),
|
|
21
|
+
isLoading: a
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { n as useAddonPurchaseEligibility };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=useAddonPurchaseEligibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAddonPurchaseEligibility.js","names":[],"sources":["../../../../../src/billing/modules/addons/hooks/useAddonPurchaseEligibility.ts"],"sourcesContent":["/**\n * Addons Module - useAddonPurchaseEligibility Hook\n *\n * BOFF-9889: add-ons require a paid subscription to their product (the\n * catalogs' \"Free tier exclusion\" rule). The backend enforces it at checkout;\n * this hook asks the same rule up front so the browse and detail pages can\n * explain the restriction instead of letting a Free workspace fill a cart it\n * cannot check out.\n */\n\nimport { useMemo } from 'react';\nimport { useGetAddonPurchaseEligibilityQuery } from '../../../../generated/global-operations';\n\nexport interface AddonPurchaseBlock {\n addonId: string;\n productId: string | null;\n reason: string;\n}\n\ninterface UseAddonPurchaseEligibilityResult {\n /** Add-ons the organization cannot buy yet, keyed by add-on id. */\n blocked: Map<string, AddonPurchaseBlock>;\n isBlocked: (addonId: string) => boolean;\n isLoading: boolean;\n}\n\nexport function useAddonPurchaseEligibility(addonIds: string[]): UseAddonPurchaseEligibilityResult {\n const ids = useMemo(() => [...new Set(addonIds)].sort(), [addonIds]);\n const { data, loading } = useGetAddonPurchaseEligibilityQuery({\n variables: { addonIds: ids },\n skip: ids.length === 0,\n fetchPolicy: 'cache-and-network',\n });\n\n const blocked = useMemo(() => {\n const map = new Map<string, AddonPurchaseBlock>();\n for (const result of data?.addonPurchaseEligibility ?? []) {\n if (!result.eligible && result.reason !== 'NOT_FOUND') {\n map.set(result.addonId, {\n addonId: result.addonId,\n productId: result.productId ?? null,\n reason: result.reason,\n });\n }\n }\n return map;\n }, [data]);\n\n return {\n blocked,\n isBlocked: (addonId: string) => blocked.has(addonId),\n isLoading: loading,\n };\n}\n"],"mappings":";;;AA0BA,SAAgB,EAA4B,GAAuD;CACjG,IAAM,IAAM,QAAc,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC,EAAE,KAAK,GAAG,CAAC,CAAQ,CAAC,GAC7D,EAAE,SAAM,eAAY,EAAoC;EAC5D,WAAW,EAAE,UAAU,EAAI;EAC3B,MAAM,EAAI,WAAW;EACrB,aAAa;CACf,CAAC,GAEK,IAAU,QAAc;EAC5B,IAAM,oBAAM,IAAI,IAAgC;EAChD,KAAK,IAAM,KAAU,GAAM,4BAA4B,CAAC,GACtD,AAAI,CAAC,EAAO,YAAY,EAAO,WAAW,eACxC,EAAI,IAAI,EAAO,SAAS;GACtB,SAAS,EAAO;GAChB,WAAW,EAAO,aAAa;GAC/B,QAAQ,EAAO;EACjB,CAAC;EAGL,OAAO;CACT,GAAG,CAAC,CAAI,CAAC;CAET,OAAO;EACL;EACA,YAAY,MAAoB,EAAQ,IAAI,CAAO;EACnD,WAAW;CACb;AACF"}
|
|
@@ -7,15 +7,16 @@ import "../../../shared/utils/index.js";
|
|
|
7
7
|
import { useAddonCartStore as o } from "../store/addonCartStore.js";
|
|
8
8
|
import { useBillingNavigate as s } from "../../../hooks/useBillingNavigate.js";
|
|
9
9
|
import { useAddon as c } from "../hooks/useAddons.js";
|
|
10
|
+
import { useAddonPurchaseEligibility as l } from "../hooks/useAddonPurchaseEligibility.js";
|
|
10
11
|
import "../hooks/index.js";
|
|
11
|
-
import { useMemo as
|
|
12
|
-
import { Minus as
|
|
13
|
-
import { useParams as
|
|
14
|
-
import { jsx as
|
|
15
|
-
import { useI18n as
|
|
16
|
-
import { EmphasisPanel as
|
|
12
|
+
import { useMemo as u } from "react";
|
|
13
|
+
import { Lock as d, Minus as f, Plus as p, ShoppingCart as m, Trash2 as h } from "lucide-react";
|
|
14
|
+
import { useParams as g } from "react-router";
|
|
15
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
16
|
+
import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
17
|
+
import { EmphasisPanel as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/billing/modules/addons/pages/AddonBrowseDetailPage.tsx
|
|
18
|
-
function
|
|
19
|
+
function S(e) {
|
|
19
20
|
let t = /* @__PURE__ */ new Map();
|
|
20
21
|
for (let n of e) {
|
|
21
22
|
if (!n.quota) continue;
|
|
@@ -29,123 +30,123 @@ function b(e) {
|
|
|
29
30
|
}
|
|
30
31
|
return Array.from(t.values());
|
|
31
32
|
}
|
|
32
|
-
var
|
|
33
|
-
let { t:
|
|
34
|
-
let n =
|
|
33
|
+
var C = () => {
|
|
34
|
+
let { t: C } = y(), w = (e, t) => {
|
|
35
|
+
let n = C(e);
|
|
35
36
|
return n === e ? t : n;
|
|
36
|
-
}, { addonId:
|
|
37
|
-
return
|
|
37
|
+
}, { addonId: T } = g(), E = s(), { addon: D, isLoading: O, error: k, refetch: A } = c(T), { addToCart: j, removeFromCart: M, updateQuantity: N, isInCart: P, getCartItem: F } = o(), I = u(() => S(D?.features || []), [D?.features]), L = D ? P(D.id) : !1, R = (D ? F(D.id) : void 0)?.quantity || 0, { isBlocked: z } = l(u(() => D ? [D.id] : [], [D])), B = D ? z(D.id) : !1;
|
|
38
|
+
return O ? /* @__PURE__ */ v("div", {
|
|
38
39
|
className: "space-y-6 p-6",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
+
children: [/* @__PURE__ */ _("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ v("div", {
|
|
40
41
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
41
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ _("div", {
|
|
42
43
|
className: "lg:col-span-2 space-y-4",
|
|
43
44
|
children: [
|
|
44
45
|
1,
|
|
45
46
|
2,
|
|
46
47
|
3
|
|
47
|
-
].map((e) => /* @__PURE__ */
|
|
48
|
-
}), /* @__PURE__ */
|
|
48
|
+
].map((e) => /* @__PURE__ */ _("div", { className: "h-24 bg-bg-sunken animate-pulse rounded" }, e))
|
|
49
|
+
}), /* @__PURE__ */ _("div", { className: "h-64 bg-bg-sunken animate-pulse rounded" })]
|
|
49
50
|
})]
|
|
50
|
-
}) :
|
|
51
|
+
}) : k && a(k) ? /* @__PURE__ */ _("div", {
|
|
51
52
|
className: "p-6",
|
|
52
|
-
children: /* @__PURE__ */
|
|
53
|
-
title:
|
|
54
|
-
message:
|
|
55
|
-
onRetry: () =>
|
|
53
|
+
children: /* @__PURE__ */ _(t, {
|
|
54
|
+
title: w("billing.addons.serverUnavailable", "Server Unavailable"),
|
|
55
|
+
message: w("billing.addons.unableToLoadAddonDetails", "Unable to load addon details."),
|
|
56
|
+
onRetry: () => A(),
|
|
56
57
|
showRetry: !0
|
|
57
58
|
})
|
|
58
|
-
}) :
|
|
59
|
+
}) : D ? /* @__PURE__ */ v("div", {
|
|
59
60
|
className: "space-y-6 p-6",
|
|
60
61
|
children: [
|
|
61
|
-
/* @__PURE__ */
|
|
62
|
-
title:
|
|
63
|
-
description: `${i(
|
|
62
|
+
/* @__PURE__ */ _(e, {
|
|
63
|
+
title: D.name,
|
|
64
|
+
description: `${i(D.duration)} addon`
|
|
64
65
|
}),
|
|
65
|
-
/* @__PURE__ */
|
|
66
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ _(x, { children: w("billing.addons.browseDetailPurpose", "See exactly what this add-on costs and the extra capacity it gives you on top of your plan before you buy it. Add it to your cart from here when it fits your needs.") }),
|
|
67
|
+
/* @__PURE__ */ v("div", {
|
|
67
68
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
68
|
-
children: [/* @__PURE__ */
|
|
69
|
+
children: [/* @__PURE__ */ v("div", {
|
|
69
70
|
className: "lg:col-span-2 space-y-6",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
71
|
+
children: [/* @__PURE__ */ v(b, {
|
|
71
72
|
className: "overflow-hidden p-0",
|
|
72
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ _("div", {
|
|
73
74
|
className: "p-4 border-b border-border-subtle",
|
|
74
|
-
children: /* @__PURE__ */
|
|
75
|
+
children: /* @__PURE__ */ _("h2", {
|
|
75
76
|
className: "font-semibold text-text-primary",
|
|
76
|
-
children:
|
|
77
|
+
children: w("billing.addons.pricing", "Pricing")
|
|
77
78
|
})
|
|
78
|
-
}), /* @__PURE__ */
|
|
79
|
+
}), /* @__PURE__ */ v("div", {
|
|
79
80
|
className: "p-6",
|
|
80
|
-
children: [/* @__PURE__ */
|
|
81
|
+
children: [/* @__PURE__ */ v("div", {
|
|
81
82
|
className: "flex items-baseline gap-2",
|
|
82
|
-
children: [/* @__PURE__ */
|
|
83
|
+
children: [/* @__PURE__ */ _("span", {
|
|
83
84
|
className: "text-4xl font-bold text-text-primary",
|
|
84
|
-
children: n(
|
|
85
|
-
}), /* @__PURE__ */
|
|
85
|
+
children: n(D.price, D.currency)
|
|
86
|
+
}), /* @__PURE__ */ v("span", {
|
|
86
87
|
className: "text-lg text-text-secondary",
|
|
87
|
-
children: ["/ ", r(
|
|
88
|
+
children: ["/ ", r(D.duration)]
|
|
88
89
|
})]
|
|
89
|
-
}), /* @__PURE__ */
|
|
90
|
+
}), /* @__PURE__ */ v("p", {
|
|
90
91
|
className: "text-sm text-text-secondary mt-2",
|
|
91
92
|
children: [
|
|
92
|
-
|
|
93
|
+
w("billing.addons.billed", "Billed"),
|
|
93
94
|
" ",
|
|
94
|
-
|
|
95
|
+
D.duration === "monthly" ? w("billing.addons.everyMonth", "every month") : w("billing.addons.annually", "annually")
|
|
95
96
|
]
|
|
96
97
|
})]
|
|
97
98
|
})]
|
|
98
|
-
}), /* @__PURE__ */
|
|
99
|
+
}), /* @__PURE__ */ v("div", {
|
|
99
100
|
className: "border border-border-subtle rounded-lg bg-bg-surface overflow-hidden",
|
|
100
|
-
children: [/* @__PURE__ */
|
|
101
|
+
children: [/* @__PURE__ */ _("div", {
|
|
101
102
|
className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
|
|
102
|
-
children: /* @__PURE__ */
|
|
103
|
+
children: /* @__PURE__ */ v("h2", {
|
|
103
104
|
className: "font-semibold text-text-primary",
|
|
104
105
|
children: [
|
|
105
|
-
|
|
106
|
+
w("billing.addons.includedQuotas", "Included Quotas"),
|
|
106
107
|
" (",
|
|
107
|
-
|
|
108
|
+
I.length,
|
|
108
109
|
")"
|
|
109
110
|
]
|
|
110
111
|
})
|
|
111
|
-
}), /* @__PURE__ */
|
|
112
|
+
}), /* @__PURE__ */ _("div", {
|
|
112
113
|
className: "p-4",
|
|
113
|
-
children:
|
|
114
|
+
children: I.length === 0 ? /* @__PURE__ */ _("p", {
|
|
114
115
|
className: "text-sm text-text-secondary py-4 text-center",
|
|
115
|
-
children:
|
|
116
|
-
}) : /* @__PURE__ */
|
|
116
|
+
children: w("billing.addons.noQuotas", "No quotas associated with this addon.")
|
|
117
|
+
}) : /* @__PURE__ */ _("div", {
|
|
117
118
|
className: "space-y-3",
|
|
118
|
-
children:
|
|
119
|
+
children: I.map((e) => /* @__PURE__ */ v("div", {
|
|
119
120
|
className: "flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20",
|
|
120
|
-
children: [/* @__PURE__ */
|
|
121
|
+
children: [/* @__PURE__ */ v("div", {
|
|
121
122
|
className: "flex items-center gap-3",
|
|
122
|
-
children: [/* @__PURE__ */
|
|
123
|
+
children: [/* @__PURE__ */ _("div", {
|
|
123
124
|
className: "size-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center",
|
|
124
|
-
children: /* @__PURE__ */
|
|
125
|
+
children: /* @__PURE__ */ _("svg", {
|
|
125
126
|
className: "size-4 text-primary",
|
|
126
127
|
fill: "none",
|
|
127
128
|
viewBox: "0 0 24 24",
|
|
128
129
|
stroke: "currentColor",
|
|
129
|
-
children: /* @__PURE__ */
|
|
130
|
+
children: /* @__PURE__ */ _("path", {
|
|
130
131
|
strokeLinecap: "round",
|
|
131
132
|
strokeLinejoin: "round",
|
|
132
133
|
strokeWidth: 2,
|
|
133
134
|
d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
134
135
|
})
|
|
135
136
|
})
|
|
136
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
137
138
|
className: "font-medium text-text-primary",
|
|
138
139
|
children: e.name
|
|
139
|
-
}), /* @__PURE__ */
|
|
140
|
+
}), /* @__PURE__ */ v("p", {
|
|
140
141
|
className: "text-xs text-text-secondary capitalize",
|
|
141
142
|
children: ["Type: ", e.type]
|
|
142
143
|
})] })]
|
|
143
|
-
}), /* @__PURE__ */
|
|
144
|
+
}), /* @__PURE__ */ v("div", {
|
|
144
145
|
className: "text-right",
|
|
145
|
-
children: [/* @__PURE__ */
|
|
146
|
+
children: [/* @__PURE__ */ _("span", {
|
|
146
147
|
className: "text-lg font-semibold text-status-success-text",
|
|
147
148
|
children: e.totalValue.toLocaleString()
|
|
148
|
-
}), /* @__PURE__ */
|
|
149
|
+
}), /* @__PURE__ */ _("p", {
|
|
149
150
|
className: "text-xs text-text-secondary",
|
|
150
151
|
children: "total"
|
|
151
152
|
})]
|
|
@@ -154,117 +155,132 @@ var x = () => {
|
|
|
154
155
|
})
|
|
155
156
|
})]
|
|
156
157
|
})]
|
|
157
|
-
}), /* @__PURE__ */
|
|
158
|
+
}), /* @__PURE__ */ v("div", {
|
|
158
159
|
className: "space-y-6",
|
|
159
|
-
children: [/* @__PURE__ */
|
|
160
|
+
children: [/* @__PURE__ */ v("div", {
|
|
160
161
|
className: "border border-border-subtle rounded-lg bg-bg-surface overflow-hidden",
|
|
161
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ _("div", {
|
|
162
163
|
className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
|
|
163
|
-
children: /* @__PURE__ */
|
|
164
|
+
children: /* @__PURE__ */ _("h2", {
|
|
164
165
|
className: "font-semibold text-text-primary",
|
|
165
|
-
children:
|
|
166
|
+
children: w("billing.addons.purchase", "Purchase")
|
|
166
167
|
})
|
|
167
|
-
}), /* @__PURE__ */
|
|
168
|
+
}), /* @__PURE__ */ v("div", {
|
|
168
169
|
className: "p-4 space-y-4",
|
|
169
170
|
children: [
|
|
170
|
-
/* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ v("div", {
|
|
171
172
|
className: "flex items-center justify-between",
|
|
172
|
-
children: [/* @__PURE__ */
|
|
173
|
+
children: [/* @__PURE__ */ _("span", {
|
|
173
174
|
className: "text-sm text-text-secondary",
|
|
174
|
-
children:
|
|
175
|
-
}), /* @__PURE__ */
|
|
175
|
+
children: w("billing.addons.price", "Price")
|
|
176
|
+
}), /* @__PURE__ */ v("span", {
|
|
176
177
|
className: "text-sm font-medium text-text-primary",
|
|
177
178
|
children: [
|
|
178
|
-
n(
|
|
179
|
+
n(D.price, D.currency),
|
|
179
180
|
" /",
|
|
180
181
|
" ",
|
|
181
|
-
r(
|
|
182
|
+
r(D.duration)
|
|
182
183
|
]
|
|
183
184
|
})]
|
|
184
185
|
}),
|
|
185
|
-
/* @__PURE__ */
|
|
186
|
+
/* @__PURE__ */ v("div", {
|
|
186
187
|
className: "flex items-center justify-between",
|
|
187
|
-
children: [/* @__PURE__ */
|
|
188
|
+
children: [/* @__PURE__ */ _("span", {
|
|
188
189
|
className: "text-sm text-text-secondary",
|
|
189
|
-
children:
|
|
190
|
-
}), /* @__PURE__ */
|
|
190
|
+
children: w("billing.addons.billingLabel", "Billing")
|
|
191
|
+
}), /* @__PURE__ */ _("span", {
|
|
191
192
|
className: "text-sm font-medium text-text-primary capitalize",
|
|
192
|
-
children:
|
|
193
|
+
children: D.duration
|
|
193
194
|
})]
|
|
194
195
|
}),
|
|
195
|
-
/* @__PURE__ */
|
|
196
|
+
/* @__PURE__ */ _("div", {
|
|
196
197
|
className: "pt-2 space-y-2",
|
|
197
|
-
children:
|
|
198
|
+
children: L ? /* @__PURE__ */ v("div", {
|
|
198
199
|
className: "space-y-2",
|
|
199
|
-
children: [/* @__PURE__ */
|
|
200
|
+
children: [/* @__PURE__ */ v("div", {
|
|
200
201
|
className: "flex items-center justify-between gap-2",
|
|
201
|
-
children: [/* @__PURE__ */
|
|
202
|
+
children: [/* @__PURE__ */ v("div", {
|
|
202
203
|
className: "flex items-center gap-1",
|
|
203
204
|
children: [
|
|
204
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ _("button", {
|
|
205
206
|
type: "button",
|
|
206
|
-
onClick: () =>
|
|
207
|
+
onClick: () => N(D.id, R - 1),
|
|
207
208
|
className: "p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors",
|
|
208
|
-
children: /* @__PURE__ */
|
|
209
|
+
children: /* @__PURE__ */ _(f, { className: "size-4" })
|
|
209
210
|
}),
|
|
210
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ _("span", {
|
|
211
212
|
className: "w-10 text-center font-medium",
|
|
212
|
-
children:
|
|
213
|
+
children: R
|
|
213
214
|
}),
|
|
214
|
-
/* @__PURE__ */
|
|
215
|
+
/* @__PURE__ */ _("button", {
|
|
215
216
|
type: "button",
|
|
216
|
-
onClick: () =>
|
|
217
|
+
onClick: () => N(D.id, R + 1),
|
|
217
218
|
className: "p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors",
|
|
218
|
-
children: /* @__PURE__ */
|
|
219
|
+
children: /* @__PURE__ */ _(p, { className: "size-4" })
|
|
219
220
|
})
|
|
220
221
|
]
|
|
221
|
-
}), /* @__PURE__ */
|
|
222
|
+
}), /* @__PURE__ */ _("button", {
|
|
222
223
|
type: "button",
|
|
223
|
-
onClick: () =>
|
|
224
|
+
onClick: () => M(D.id),
|
|
224
225
|
className: "p-1.5 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors",
|
|
225
226
|
title: "Remove from cart",
|
|
226
|
-
children: /* @__PURE__ */ h
|
|
227
|
+
children: /* @__PURE__ */ _(h, { className: "size-4" })
|
|
227
228
|
})]
|
|
228
|
-
}), /* @__PURE__ */
|
|
229
|
+
}), /* @__PURE__ */ _("button", {
|
|
229
230
|
type: "button",
|
|
230
|
-
onClick: () =>
|
|
231
|
+
onClick: () => E("/checkout/addons"),
|
|
231
232
|
className: "w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors",
|
|
232
233
|
children: "Proceed to Checkout"
|
|
233
234
|
})]
|
|
234
|
-
}) : /* @__PURE__ */
|
|
235
|
+
}) : B ? /* @__PURE__ */ v("div", {
|
|
236
|
+
className: "space-y-3",
|
|
237
|
+
"data-testid": "addon-paid-plan-required",
|
|
238
|
+
children: [/* @__PURE__ */ v("p", {
|
|
239
|
+
className: "flex items-start gap-2 text-sm text-text-secondary",
|
|
240
|
+
children: [/* @__PURE__ */ _(d, {
|
|
241
|
+
className: "mt-0.5 size-4 shrink-0 text-status-warning-text",
|
|
242
|
+
"aria-hidden": "true"
|
|
243
|
+
}), w("billing.addons.paidPlanRequiredBody", "Add-ons extend a paid subscription. You are on the Free plan for this product, so upgrade to a paid plan first, then come back to add extra capacity.")]
|
|
244
|
+
}), /* @__PURE__ */ _("button", {
|
|
245
|
+
type: "button",
|
|
246
|
+
onClick: () => E("/plans"),
|
|
247
|
+
className: "w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors",
|
|
248
|
+
children: w("billing.addons.viewPlans", "View plans")
|
|
249
|
+
})]
|
|
250
|
+
}) : /* @__PURE__ */ v("button", {
|
|
235
251
|
type: "button",
|
|
236
|
-
onClick: () =>
|
|
252
|
+
onClick: () => j(D),
|
|
237
253
|
className: "w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center justify-center gap-2",
|
|
238
|
-
children: [/* @__PURE__ */
|
|
254
|
+
children: [/* @__PURE__ */ _(m, { className: "size-4" }), "Add to Cart"]
|
|
239
255
|
})
|
|
240
256
|
})
|
|
241
257
|
]
|
|
242
258
|
})]
|
|
243
|
-
}), /* @__PURE__ */
|
|
259
|
+
}), /* @__PURE__ */ _("button", {
|
|
244
260
|
type: "button",
|
|
245
|
-
onClick: () =>
|
|
261
|
+
onClick: () => E("/addons"),
|
|
246
262
|
className: "w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors",
|
|
247
263
|
children: "Back to Addons"
|
|
248
264
|
})]
|
|
249
265
|
})]
|
|
250
266
|
})
|
|
251
267
|
]
|
|
252
|
-
}) : /* @__PURE__ */
|
|
268
|
+
}) : /* @__PURE__ */ _("div", {
|
|
253
269
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
254
|
-
children: /* @__PURE__ */
|
|
270
|
+
children: /* @__PURE__ */ v("div", {
|
|
255
271
|
className: "text-center space-y-4",
|
|
256
272
|
children: [
|
|
257
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ _("h2", {
|
|
258
274
|
className: "text-lg font-semibold text-text-primary",
|
|
259
|
-
children:
|
|
275
|
+
children: w("billing.addons.addonNotFound", "Addon not found")
|
|
260
276
|
}),
|
|
261
|
-
/* @__PURE__ */
|
|
277
|
+
/* @__PURE__ */ _("p", {
|
|
262
278
|
className: "text-sm text-text-secondary",
|
|
263
|
-
children:
|
|
279
|
+
children: w("billing.addons.addonNotFoundMessage", "The addon you're looking for doesn't exist or has been removed.")
|
|
264
280
|
}),
|
|
265
|
-
/* @__PURE__ */
|
|
281
|
+
/* @__PURE__ */ _("button", {
|
|
266
282
|
type: "button",
|
|
267
|
-
onClick: () =>
|
|
283
|
+
onClick: () => E("/addons"),
|
|
268
284
|
className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors",
|
|
269
285
|
children: "Back to Addons"
|
|
270
286
|
})
|
|
@@ -273,6 +289,6 @@ var x = () => {
|
|
|
273
289
|
});
|
|
274
290
|
};
|
|
275
291
|
//#endregion
|
|
276
|
-
export {
|
|
292
|
+
export { C as AddonBrowseDetailPage };
|
|
277
293
|
|
|
278
294
|
//# sourceMappingURL=AddonBrowseDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddonBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Browse Detail Page (User-facing)\n * View addon details and add to cart. No edit/toggle actions.\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport { ShoppingCart, Plus, Minus, Trash2 } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon } from '../hooks';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport { PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\n}\n\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number; type?: string } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\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 type: limitType,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const AddonBrowseDetailPage: FC = () => {\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 const { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n const inCart = addon ? isInCart(addon.id) : false;\n const cartItem = addon ? getCartItem(addon.id) : undefined;\n const cartQuantity = cartItem?.quantity || 0;\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.addons.unableToLoadAddonDetails', 'Unable to load addon details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!addon) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Back to Addons\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.browseDetailPurpose',\n 'See exactly what this add-on costs and the extra capacity it gives you on top of your plan before you buy it. Add it to your cart from here when it fits your needs.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-4 border-b border-border-subtle\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(addon.price, addon.currency)}\n </span>\n <span className=\"text-lg text-text-secondary\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Quotas Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n Type: {quota.type}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-text-secondary\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Purchase Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.purchase', 'Purchase')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.price', 'Price')}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(addon.price, addon.currency)} /{' '}\n {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-text-primary capitalize\">\n {addon.duration}\n </span>\n </div>\n\n <div className=\"pt-2 space-y-2\">\n {inCart ? (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-10 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/addons')}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Proceed to Checkout\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center justify-center gap-2\"\n >\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n Back to Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AddonBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Browse Detail Page (User-facing)\n * View addon details and add to cart. No edit/toggle actions.\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport { ShoppingCart, Plus, Minus, Trash2, Lock } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonPurchaseEligibility } from '../hooks';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport { PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\n}\n\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number; type?: string } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\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 type: limitType,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const AddonBrowseDetailPage: FC = () => {\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 const { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n const inCart = addon ? isInCart(addon.id) : false;\n const cartItem = addon ? getCartItem(addon.id) : undefined;\n const cartQuantity = cartItem?.quantity || 0;\n\n // BOFF-9889: add-ons require a paid subscription to their product.\n const eligibilityIds = useMemo(() => (addon ? [addon.id] : []), [addon]);\n const { isBlocked } = useAddonPurchaseEligibility(eligibilityIds);\n const purchaseBlocked = addon ? isBlocked(addon.id) : false;\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.addons.unableToLoadAddonDetails', 'Unable to load addon details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!addon) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Back to Addons\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.browseDetailPurpose',\n 'See exactly what this add-on costs and the extra capacity it gives you on top of your plan before you buy it. Add it to your cart from here when it fits your needs.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-4 border-b border-border-subtle\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(addon.price, addon.currency)}\n </span>\n <span className=\"text-lg text-text-secondary\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Quotas Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n Type: {quota.type}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-text-secondary\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Purchase Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.purchase', 'Purchase')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.price', 'Price')}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(addon.price, addon.currency)} /{' '}\n {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-text-primary capitalize\">\n {addon.duration}\n </span>\n </div>\n\n <div className=\"pt-2 space-y-2\">\n {inCart ? (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-10 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/addons')}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Proceed to Checkout\n </button>\n </div>\n ) : purchaseBlocked ? (\n <div className=\"space-y-3\" data-testid=\"addon-paid-plan-required\">\n <p className=\"flex items-start gap-2 text-sm text-text-secondary\">\n <Lock\n className=\"mt-0.5 size-4 shrink-0 text-status-warning-text\"\n aria-hidden=\"true\"\n />\n {tr(\n 'billing.addons.paidPlanRequiredBody',\n 'Add-ons extend a paid subscription. You are on the Free plan for this product, so upgrade to a paid plan first, then come back to add extra capacity.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr('billing.addons.viewPlans', 'View plans')}\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center justify-center gap-2\"\n >\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n Back to Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|