@burdenoff/microfe-billing 2026.1005.6 → 2026.1012.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/dashboard/pages/OverviewPage.js +165 -162
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/shared/components/PushPermissionNudge.js +73 -0
- package/dist/billing/shared/components/PushPermissionNudge.js.map +1 -0
- package/dist/billing/shared/components/index.js +1 -0
- package/package.json +2 -2
|
@@ -3,137 +3,139 @@ import { useTr as t } from "../../../shared/hooks/useTr.js";
|
|
|
3
3
|
import { AccessDenied as n } from "../../../shared/components/AccessDenied.js";
|
|
4
4
|
import { StatCard as r } from "../../../shared/components/StatCard.js";
|
|
5
5
|
import { ServerError as ee } from "../../../shared/components/ServerError.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { PushPermissionNudge as te } from "../../../shared/components/PushPermissionNudge.js";
|
|
7
|
+
import { useBillingEventEmitter as ne } from "../../../hooks/useBillingEventEmitter.js";
|
|
8
|
+
import { getStoredBillingAccountId as re, setStoredBillingAccountId as ie } from "../../../hooks/useBillingAccountSelection.js";
|
|
9
|
+
import { isServerError as ae } from "../../../shared/utils/error.js";
|
|
9
10
|
import "../../../shared/utils/index.js";
|
|
10
|
-
import { useBillingPermissions as
|
|
11
|
-
import { useDashboardMutations as
|
|
12
|
-
import { useBillingOverviewData as
|
|
11
|
+
import { useBillingPermissions as oe } from "../../../hooks/useBillingPermissions.js";
|
|
12
|
+
import { useDashboardMutations as se } from "../hooks/useDashboard.js";
|
|
13
|
+
import { useBillingOverviewData as ce } from "../hooks/useBillingOverviewData.js";
|
|
13
14
|
import "../hooks/index.js";
|
|
14
|
-
import { BillingAccountCard as
|
|
15
|
-
import { PaymentMethodsSection as
|
|
16
|
-
import { ActiveSubscriptionCard as
|
|
17
|
-
import { RecommendedAddonsSection as
|
|
15
|
+
import { BillingAccountCard as le } from "../components/BillingAccountCard.js";
|
|
16
|
+
import { PaymentMethodsSection as ue } from "../components/PaymentMethodsSection.js";
|
|
17
|
+
import { ActiveSubscriptionCard as de } from "../components/ActiveSubscriptionCard.js";
|
|
18
|
+
import { RecommendedAddonsSection as fe } from "../components/RecommendedAddonsSection.js";
|
|
18
19
|
import { CreateBillingAccountModal as i } from "../components/CreateBillingAccountModal.js";
|
|
19
|
-
import { AddPaymentMethodModal as
|
|
20
|
+
import { AddPaymentMethodModal as pe } from "../components/AddPaymentMethodModal.js";
|
|
20
21
|
import "../components/index.js";
|
|
21
|
-
import { useAddonCartStore as
|
|
22
|
-
import { FloatingCartDrawer as
|
|
22
|
+
import { useAddonCartStore as me } from "../../addons/store/addonCartStore.js";
|
|
23
|
+
import { FloatingCartDrawer as he } from "../../addons/components/FloatingCartDrawer.js";
|
|
23
24
|
import { withBillingAccountId as a } from "../../../shared/utils/navigation.js";
|
|
24
25
|
import { useCallback as o, useEffect as s, useState as c } from "react";
|
|
25
|
-
import { Activity as
|
|
26
|
-
import { Fragment as
|
|
27
|
-
import { AuroraBackground as _, Button as
|
|
26
|
+
import { Activity as ge, ArrowRight as _e, Building2 as l, Check as u, ChevronDown as d, Coins as ve, Crown as f, Loader2 as ye, Plus as be, Receipt as xe, RefreshCw as p, Sparkles as Se, Wallet as m } from "lucide-react";
|
|
27
|
+
import { Fragment as Ce, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
28
|
+
import { AuroraBackground as _, Button as we, CTAOverflowMenu as Te, GlassCard as Ee, GradientText as v, IllustratedEmptyState as De, PagePurpose as Oe } from "@burdenoff/fe-libs/ui";
|
|
29
|
+
import { shouldPromptForPushPermission as ke } from "@burdenoff/fe-libs/shared/native";
|
|
28
30
|
//#region src/billing/modules/dashboard/pages/OverviewPage.tsx
|
|
29
31
|
var y = () => {
|
|
30
|
-
let y = t(), { navigate: b, basePath: x, orgId: S, productId:
|
|
31
|
-
|
|
32
|
-
}, [S]), { addToCart:
|
|
32
|
+
let y = t(), { navigate: b, basePath: x, orgId: S, productId: Ae } = e(), C = oe(), { emit: w } = ne(), [T, je] = c(() => re(S)), [E, D] = c(!1), [O, k] = c(null), [Me, A] = c(!1), [j, M] = c(!1), [Ne, N] = c(!1), [P, F] = c(0), [I, L] = c(!1), { billingAccounts: R, currentAccount: z, paymentMethods: B, activeSubscriptions: V, hasActiveSubscription: H, recommendedAddons: Pe, isLoading: U, error: W, refetchAll: G } = ce(T, Ae), K = z?.id, Fe = U, q = o((e) => {
|
|
33
|
+
je(e), ie(S, e);
|
|
34
|
+
}, [S]), { addToCart: J, isInCart: Y, getCartItem: Ie, updateQuantity: Le, removeFromCart: X } = me(), { createBillingAccount: Re, setBillingAddress: ze, setDefaultPaymentMethod: Be, deletePaymentMethod: Ve, isCreatingAccount: He } = se();
|
|
33
35
|
s(() => {
|
|
34
|
-
|
|
35
|
-
}, [
|
|
36
|
-
T &&
|
|
36
|
+
F(0), L(!1);
|
|
37
|
+
}, [K, V.length]), s(() => {
|
|
38
|
+
T && R.length > 0 && (R.some((e) => e.id === T) || q(null));
|
|
37
39
|
}, [
|
|
38
|
-
|
|
40
|
+
R,
|
|
39
41
|
T,
|
|
40
|
-
|
|
42
|
+
q
|
|
41
43
|
]), s(() => {
|
|
42
|
-
!T &&
|
|
44
|
+
!T && R.length > 0 && q((R.find((e) => e.isDefault) ?? R[0]).id);
|
|
43
45
|
}, [
|
|
44
|
-
|
|
46
|
+
R,
|
|
45
47
|
T,
|
|
46
|
-
|
|
48
|
+
q
|
|
47
49
|
]);
|
|
48
|
-
let Z =
|
|
50
|
+
let Z = V[P] || V[0] || null, Q = o((e) => {
|
|
49
51
|
let t = e;
|
|
50
52
|
x && x !== "/" && (t = `${x.endsWith("/") ? x.slice(0, -1) : x}${e.startsWith("/") ? e : `/${e}`}`), b(t);
|
|
51
|
-
}, [b, x]),
|
|
52
|
-
|
|
53
|
+
}, [b, x]), Ue = o(async (e, t) => {
|
|
54
|
+
k(null);
|
|
53
55
|
try {
|
|
54
|
-
let n = await
|
|
55
|
-
t && n.id && await
|
|
56
|
+
let n = await Re(e);
|
|
57
|
+
t && n.id && await ze({
|
|
56
58
|
billingAccountId: n.id,
|
|
57
59
|
...t
|
|
58
|
-
}),
|
|
60
|
+
}), D(!1), q(n.id), w("billing.billing_account.created", {
|
|
59
61
|
route: "/billing/overview",
|
|
60
62
|
entityId: n.id,
|
|
61
63
|
source: "overview"
|
|
62
|
-
}), await
|
|
64
|
+
}), await G(), ke() && A(!0);
|
|
63
65
|
} catch (e) {
|
|
64
|
-
|
|
66
|
+
k(e instanceof Error ? e.message : "Failed to create billing account");
|
|
65
67
|
}
|
|
66
68
|
}, [
|
|
67
|
-
|
|
69
|
+
Re,
|
|
68
70
|
w,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
]),
|
|
73
|
-
|
|
71
|
+
G,
|
|
72
|
+
ze,
|
|
73
|
+
q
|
|
74
|
+
]), We = o(() => {
|
|
75
|
+
N(!0);
|
|
74
76
|
}, []), $ = o(async () => {
|
|
75
|
-
await
|
|
76
|
-
}, [
|
|
77
|
+
await G();
|
|
78
|
+
}, [G]), Ge = o(async () => {
|
|
77
79
|
w("billing.payment_method.added", {
|
|
78
80
|
route: "/billing/overview",
|
|
79
|
-
entityId:
|
|
81
|
+
entityId: K,
|
|
80
82
|
source: "overview"
|
|
81
83
|
}), await $();
|
|
82
84
|
}, [
|
|
83
|
-
|
|
85
|
+
K,
|
|
84
86
|
w,
|
|
85
87
|
$
|
|
86
|
-
]),
|
|
87
|
-
|
|
88
|
+
]), Ke = o(async (e) => {
|
|
89
|
+
K && (await Be(e, K), w("billing.payment_method.default_set", {
|
|
88
90
|
route: "/billing/overview",
|
|
89
91
|
entityId: e,
|
|
90
92
|
source: "overview"
|
|
91
93
|
}), await $());
|
|
92
94
|
}, [
|
|
93
|
-
|
|
95
|
+
K,
|
|
94
96
|
w,
|
|
95
|
-
|
|
97
|
+
Be,
|
|
96
98
|
$
|
|
97
|
-
]),
|
|
98
|
-
|
|
99
|
+
]), qe = o(async (e) => {
|
|
100
|
+
K && (await Ve(e, K), w("billing.payment_method.deleted", {
|
|
99
101
|
route: "/billing/overview",
|
|
100
102
|
entityId: e,
|
|
101
103
|
source: "overview"
|
|
102
104
|
}), await $());
|
|
103
105
|
}, [
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
K,
|
|
107
|
+
Ve,
|
|
106
108
|
w,
|
|
107
109
|
$
|
|
108
|
-
]),
|
|
109
|
-
|
|
110
|
-
}, [
|
|
111
|
-
await
|
|
112
|
-
}, [
|
|
113
|
-
|
|
114
|
-
}, [
|
|
115
|
-
t <= 0 ?
|
|
116
|
-
}, [
|
|
117
|
-
|
|
118
|
-
}, [
|
|
119
|
-
return C.canViewBillingAccount ?
|
|
110
|
+
]), Je = o((e) => {
|
|
111
|
+
q(e.id), M(!1);
|
|
112
|
+
}, [q]), Ye = o(async () => {
|
|
113
|
+
await G();
|
|
114
|
+
}, [G]), Xe = o((e) => {
|
|
115
|
+
J(e);
|
|
116
|
+
}, [J]), Ze = o((e) => Y(e), [Y]), Qe = o((e) => Ie(e)?.quantity || 0, [Ie]), $e = o((e, t) => {
|
|
117
|
+
t <= 0 ? X(e) : Le(e, t);
|
|
118
|
+
}, [Le, X]), et = o((e) => {
|
|
119
|
+
X(e);
|
|
120
|
+
}, [X]);
|
|
121
|
+
return C.canViewBillingAccount ? W && ae(W) && !U ? /* @__PURE__ */ h("div", {
|
|
120
122
|
className: "p-6",
|
|
121
123
|
children: /* @__PURE__ */ h(ee, {
|
|
122
124
|
title: y("billing.serverError.title", "Server Unavailable"),
|
|
123
125
|
message: y("billing.overview.serverUnavailableMessage", "Unable to load billing overview. The server might be down or experiencing issues."),
|
|
124
|
-
onRetry:
|
|
126
|
+
onRetry: Ye,
|
|
125
127
|
showRetry: !0
|
|
126
128
|
})
|
|
127
|
-
}) :
|
|
129
|
+
}) : U && !z ? /* @__PURE__ */ h("div", {
|
|
128
130
|
className: "flex items-center justify-center min-h-[400px]",
|
|
129
131
|
children: /* @__PURE__ */ g("div", {
|
|
130
132
|
className: "flex flex-col items-center gap-3",
|
|
131
|
-
children: [/* @__PURE__ */ h(
|
|
133
|
+
children: [/* @__PURE__ */ h(ye, { className: "size-8 animate-spin text-text-link" }), /* @__PURE__ */ h("p", {
|
|
132
134
|
className: "text-sm text-text-secondary",
|
|
133
135
|
children: y("billing.overview.loadingInfo", "Loading billing information...")
|
|
134
136
|
})]
|
|
135
137
|
})
|
|
136
|
-
}) : !
|
|
138
|
+
}) : !U && R.length === 0 && !z ? /* @__PURE__ */ g("div", {
|
|
137
139
|
className: "px-4 sm:px-6 lg:px-8 py-4 sm:py-6",
|
|
138
140
|
children: [
|
|
139
141
|
/* @__PURE__ */ g("header", {
|
|
@@ -150,25 +152,25 @@ var y = () => {
|
|
|
150
152
|
})
|
|
151
153
|
]
|
|
152
154
|
}),
|
|
153
|
-
/* @__PURE__ */ h(
|
|
155
|
+
/* @__PURE__ */ h(De, {
|
|
154
156
|
illustration: "onboarding",
|
|
155
157
|
title: y("billing.overview.emptyTitle", "No billing account"),
|
|
156
158
|
description: y("billing.overview.emptyDescription", "Create a billing account to start managing your subscriptions and payments"),
|
|
157
159
|
action: C.canManageBillingAccount ? /* @__PURE__ */ g("button", {
|
|
158
160
|
type: "button",
|
|
159
|
-
onClick: () =>
|
|
161
|
+
onClick: () => D(!0),
|
|
160
162
|
className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200",
|
|
161
|
-
children: [/* @__PURE__ */ h(
|
|
163
|
+
children: [/* @__PURE__ */ h(be, { className: "size-4" }), y("billing.overview.createBillingAccount", "Create Billing Account")]
|
|
162
164
|
}) : void 0
|
|
163
165
|
}),
|
|
164
166
|
/* @__PURE__ */ h(i, {
|
|
165
|
-
isOpen:
|
|
167
|
+
isOpen: E,
|
|
166
168
|
onClose: () => {
|
|
167
|
-
|
|
169
|
+
D(!1), k(null);
|
|
168
170
|
},
|
|
169
|
-
onSubmit:
|
|
170
|
-
isSubmitting:
|
|
171
|
-
error:
|
|
171
|
+
onSubmit: Ue,
|
|
172
|
+
isSubmitting: He,
|
|
173
|
+
error: O
|
|
172
174
|
})
|
|
173
175
|
]
|
|
174
176
|
}) : /* @__PURE__ */ g("div", {
|
|
@@ -187,33 +189,33 @@ var y = () => {
|
|
|
187
189
|
className: "text-sm text-text-secondary mt-1",
|
|
188
190
|
children: y("billing.overview.description", "Manage your billing accounts, subscriptions, and payment methods")
|
|
189
191
|
}),
|
|
190
|
-
/* @__PURE__ */ h(
|
|
192
|
+
/* @__PURE__ */ h(Oe, {
|
|
191
193
|
className: "mt-3",
|
|
192
194
|
children: y("billing.overview.purpose", "Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.")
|
|
193
195
|
})
|
|
194
196
|
] }),
|
|
195
|
-
/* @__PURE__ */ h(
|
|
196
|
-
primary: C.canManageBillingAccount &&
|
|
197
|
+
/* @__PURE__ */ h(Te, {
|
|
198
|
+
primary: C.canManageBillingAccount && R.length > 0 ? {
|
|
197
199
|
label: y("billing.overview.newAccount", "New Account"),
|
|
198
|
-
onSelect: () =>
|
|
199
|
-
icon: /* @__PURE__ */ h(
|
|
200
|
+
onSelect: () => D(!0),
|
|
201
|
+
icon: /* @__PURE__ */ h(be, { className: "size-4" })
|
|
200
202
|
} : void 0,
|
|
201
203
|
actions: [{
|
|
202
204
|
label: y("billing.overview.refresh", "Refresh"),
|
|
203
205
|
onSelect: () => {
|
|
204
|
-
|
|
206
|
+
Ye();
|
|
205
207
|
},
|
|
206
|
-
icon: /* @__PURE__ */ h(
|
|
208
|
+
icon: /* @__PURE__ */ h(p, { className: `size-4 ${U ? "animate-spin" : ""}` })
|
|
207
209
|
}]
|
|
208
210
|
})
|
|
209
211
|
]
|
|
210
212
|
}),
|
|
211
|
-
|
|
213
|
+
R.length > 1 && /* @__PURE__ */ g("div", {
|
|
212
214
|
className: "relative",
|
|
213
215
|
"data-tour": "billing-overview-account-selector",
|
|
214
216
|
children: [/* @__PURE__ */ g("button", {
|
|
215
217
|
type: "button",
|
|
216
|
-
onClick: () =>
|
|
218
|
+
onClick: () => M(!j),
|
|
217
219
|
className: "w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200",
|
|
218
220
|
children: [/* @__PURE__ */ g("div", {
|
|
219
221
|
className: "flex items-center gap-3",
|
|
@@ -224,21 +226,21 @@ var y = () => {
|
|
|
224
226
|
className: "text-left",
|
|
225
227
|
children: [/* @__PURE__ */ h("p", {
|
|
226
228
|
className: "text-sm font-medium text-text-primary",
|
|
227
|
-
children:
|
|
229
|
+
children: z?.name || y("billing.overview.selectAccount", "Select Account")
|
|
228
230
|
}), /* @__PURE__ */ h("p", {
|
|
229
231
|
className: "text-xs text-text-secondary",
|
|
230
|
-
children:
|
|
232
|
+
children: z?.email
|
|
231
233
|
})]
|
|
232
234
|
})]
|
|
233
|
-
}), /* @__PURE__ */ h(
|
|
234
|
-
}),
|
|
235
|
+
}), /* @__PURE__ */ h(d, { className: `size-4 text-text-muted transition-transform ${j ? "rotate-180" : ""}` })]
|
|
236
|
+
}), j && /* @__PURE__ */ g(Ce, { children: [/* @__PURE__ */ h("div", {
|
|
235
237
|
className: "fixed inset-0 z-10",
|
|
236
|
-
onClick: () =>
|
|
238
|
+
onClick: () => M(!1)
|
|
237
239
|
}), /* @__PURE__ */ h("div", {
|
|
238
240
|
className: "absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto",
|
|
239
|
-
children:
|
|
241
|
+
children: R.map((e) => /* @__PURE__ */ g("button", {
|
|
240
242
|
type: "button",
|
|
241
|
-
onClick: () =>
|
|
243
|
+
onClick: () => Je(e),
|
|
242
244
|
className: "w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors",
|
|
243
245
|
children: [/* @__PURE__ */ g("div", {
|
|
244
246
|
className: "flex items-center gap-3 min-w-0",
|
|
@@ -255,36 +257,36 @@ var y = () => {
|
|
|
255
257
|
children: e.email
|
|
256
258
|
})]
|
|
257
259
|
})]
|
|
258
|
-
}),
|
|
260
|
+
}), z?.id === e.id && /* @__PURE__ */ h(u, { className: "size-4 text-text-link flex-shrink-0" })]
|
|
259
261
|
}, e.id))
|
|
260
262
|
})] })]
|
|
261
263
|
}),
|
|
262
|
-
|
|
264
|
+
z && /* @__PURE__ */ g("div", {
|
|
263
265
|
className: "grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4",
|
|
264
266
|
"data-tour": "billing-overview-stats-row",
|
|
265
267
|
children: [
|
|
266
268
|
/* @__PURE__ */ h(r, {
|
|
267
269
|
className: "p-3 sm:p-5",
|
|
268
270
|
label: y("billing.overview.stats.creditBalance", "Credit Balance"),
|
|
269
|
-
value: `${(
|
|
270
|
-
icon: /* @__PURE__ */ h(
|
|
271
|
+
value: `${(z.creditAmount || 0).toLocaleString()} Credits`,
|
|
272
|
+
icon: /* @__PURE__ */ h(m, { className: "size-5" })
|
|
271
273
|
}),
|
|
272
274
|
/* @__PURE__ */ h(r, {
|
|
273
275
|
className: "p-3 sm:p-5",
|
|
274
276
|
label: y("billing.overview.stats.activePlans", "Active Plans"),
|
|
275
|
-
value:
|
|
276
|
-
icon: /* @__PURE__ */ h(
|
|
277
|
+
value: V.length > 0 ? V.length === 1 ? V[0]?.plan?.name || "1 Plan" : `${V.length} ${y("billing.overview.stats.plansSuffix", "Plans")}` : y("billing.overview.stats.none", "None"),
|
|
278
|
+
icon: /* @__PURE__ */ h(p, { className: "size-5" })
|
|
277
279
|
}),
|
|
278
280
|
/* @__PURE__ */ h(r, {
|
|
279
281
|
className: "p-3 sm:p-5",
|
|
280
282
|
label: y("billing.overview.stats.paymentMethods", "Payment Methods"),
|
|
281
|
-
value:
|
|
283
|
+
value: B.length,
|
|
282
284
|
icon: /* @__PURE__ */ h(l, { className: "size-5" })
|
|
283
285
|
}),
|
|
284
286
|
/* @__PURE__ */ h(r, {
|
|
285
287
|
className: "p-3 sm:p-5",
|
|
286
288
|
label: y("billing.overview.stats.paymentCurrency", "Payment Currency"),
|
|
287
|
-
value:
|
|
289
|
+
value: z.currency || "USD",
|
|
288
290
|
icon: /* @__PURE__ */ h(l, { className: "size-5" })
|
|
289
291
|
})
|
|
290
292
|
]
|
|
@@ -294,27 +296,27 @@ var y = () => {
|
|
|
294
296
|
children: [/* @__PURE__ */ g("div", {
|
|
295
297
|
className: "lg:col-span-2 space-y-4 sm:space-y-6",
|
|
296
298
|
children: [
|
|
297
|
-
|
|
299
|
+
z && /* @__PURE__ */ h(le, {
|
|
298
300
|
dataTour: "billing-overview-billing-account-card",
|
|
299
|
-
account:
|
|
300
|
-
isDefault:
|
|
301
|
-
onEdit: () =>
|
|
302
|
-
onViewTransactions: () => Q(`/transactions/${
|
|
303
|
-
onViewCreditTransactions: () => Q(`/creditTransactions/${
|
|
301
|
+
account: z,
|
|
302
|
+
isDefault: z.isDefault ?? !1,
|
|
303
|
+
onEdit: () => z && Q(`/settings?billingAccountId=${z.id}`),
|
|
304
|
+
onViewTransactions: () => Q(`/transactions/${z.id}`),
|
|
305
|
+
onViewCreditTransactions: () => Q(`/creditTransactions/${z.id}`)
|
|
304
306
|
}),
|
|
305
307
|
/* @__PURE__ */ g("div", {
|
|
306
308
|
className: "space-y-3",
|
|
307
|
-
children: [
|
|
309
|
+
children: [V.length > 1 && /* @__PURE__ */ g("div", {
|
|
308
310
|
className: "relative",
|
|
309
311
|
children: [/* @__PURE__ */ g("button", {
|
|
310
312
|
type: "button",
|
|
311
|
-
onClick: () =>
|
|
313
|
+
onClick: () => L(!I),
|
|
312
314
|
className: "w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200",
|
|
313
315
|
children: [/* @__PURE__ */ g("div", {
|
|
314
316
|
className: "flex items-center gap-3",
|
|
315
317
|
children: [/* @__PURE__ */ h("div", {
|
|
316
318
|
className: "size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
|
|
317
|
-
children: /* @__PURE__ */ h(
|
|
319
|
+
children: /* @__PURE__ */ h(f, { className: "size-4 text-text-link" })
|
|
318
320
|
}), /* @__PURE__ */ g("div", {
|
|
319
321
|
className: "text-left",
|
|
320
322
|
children: [/* @__PURE__ */ g("p", {
|
|
@@ -330,29 +332,29 @@ var y = () => {
|
|
|
330
332
|
}), /* @__PURE__ */ g("p", {
|
|
331
333
|
className: "text-xs text-text-secondary",
|
|
332
334
|
children: [
|
|
333
|
-
|
|
335
|
+
V.length,
|
|
334
336
|
" ",
|
|
335
337
|
y("billing.overview.activeSubscriptions", "active subscriptions")
|
|
336
338
|
]
|
|
337
339
|
})]
|
|
338
340
|
})]
|
|
339
|
-
}), /* @__PURE__ */ h(
|
|
340
|
-
}),
|
|
341
|
+
}), /* @__PURE__ */ h(d, { className: `size-4 text-text-muted transition-transform ${I ? "rotate-180" : ""}` })]
|
|
342
|
+
}), I && /* @__PURE__ */ g(Ce, { children: [/* @__PURE__ */ h("div", {
|
|
341
343
|
className: "fixed inset-0 z-10",
|
|
342
|
-
onClick: () =>
|
|
344
|
+
onClick: () => L(!1)
|
|
343
345
|
}), /* @__PURE__ */ h("div", {
|
|
344
346
|
className: "absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto",
|
|
345
|
-
children:
|
|
347
|
+
children: V.map((e, t) => /* @__PURE__ */ g("button", {
|
|
346
348
|
type: "button",
|
|
347
349
|
onClick: () => {
|
|
348
|
-
|
|
350
|
+
F(t), L(!1);
|
|
349
351
|
},
|
|
350
352
|
className: "w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors",
|
|
351
353
|
children: [/* @__PURE__ */ g("div", {
|
|
352
354
|
className: "flex items-center gap-3 min-w-0",
|
|
353
355
|
children: [/* @__PURE__ */ h("div", {
|
|
354
356
|
className: "size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0",
|
|
355
|
-
children: /* @__PURE__ */ h(
|
|
357
|
+
children: /* @__PURE__ */ h(f, { className: "size-4 text-text-secondary" })
|
|
356
358
|
}), /* @__PURE__ */ g("div", {
|
|
357
359
|
className: "text-left min-w-0",
|
|
358
360
|
children: [/* @__PURE__ */ h("p", {
|
|
@@ -370,26 +372,26 @@ var y = () => {
|
|
|
370
372
|
]
|
|
371
373
|
})]
|
|
372
374
|
})]
|
|
373
|
-
}),
|
|
375
|
+
}), P === t && /* @__PURE__ */ h(u, { className: "size-4 text-text-link flex-shrink-0" })]
|
|
374
376
|
}, e.id))
|
|
375
377
|
})] })]
|
|
376
|
-
}), /* @__PURE__ */ h(
|
|
378
|
+
}), /* @__PURE__ */ h(de, {
|
|
377
379
|
dataTour: "billing-overview-active-subscription-card",
|
|
378
380
|
subscription: Z,
|
|
379
|
-
onViewDetails: () => Z && Q(a(`/subscriptions/${Z.id}`, Z.billingAccountId ??
|
|
380
|
-
onUpgrade: () => Z && Q(a(`/subscriptions/${Z.id}/upgrade`, Z.billingAccountId ??
|
|
381
|
-
onManage: () =>
|
|
381
|
+
onViewDetails: () => Z && Q(a(`/subscriptions/${Z.id}`, Z.billingAccountId ?? z?.id)),
|
|
382
|
+
onUpgrade: () => Z && Q(a(`/subscriptions/${Z.id}/upgrade`, Z.billingAccountId ?? z?.id)),
|
|
383
|
+
onManage: () => z && Q(`/subscriptions?billingAccountId=${z.id}`),
|
|
382
384
|
onPurchasePlan: () => Q("/plans")
|
|
383
385
|
})]
|
|
384
386
|
}),
|
|
385
|
-
|
|
387
|
+
z && /* @__PURE__ */ h(ue, {
|
|
386
388
|
dataTour: "billing-overview-payment-methods-section",
|
|
387
|
-
paymentMethods:
|
|
388
|
-
billingAccountId:
|
|
389
|
-
onAddPaymentMethod:
|
|
390
|
-
onSetDefault:
|
|
391
|
-
onDelete:
|
|
392
|
-
isLoading:
|
|
389
|
+
paymentMethods: B,
|
|
390
|
+
billingAccountId: z.id,
|
|
391
|
+
onAddPaymentMethod: We,
|
|
392
|
+
onSetDefault: Ke,
|
|
393
|
+
onDelete: qe,
|
|
394
|
+
isLoading: U
|
|
393
395
|
})
|
|
394
396
|
]
|
|
395
397
|
}), /* @__PURE__ */ g("div", {
|
|
@@ -405,8 +407,8 @@ var y = () => {
|
|
|
405
407
|
children: [
|
|
406
408
|
/* @__PURE__ */ g("button", {
|
|
407
409
|
type: "button",
|
|
408
|
-
onClick: () =>
|
|
409
|
-
disabled: !
|
|
410
|
+
onClick: () => z && Q(`/transactions/${z.id}`),
|
|
411
|
+
disabled: !z,
|
|
410
412
|
className: "w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
411
413
|
children: [/* @__PURE__ */ h("div", {
|
|
412
414
|
className: "size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center",
|
|
@@ -415,12 +417,12 @@ var y = () => {
|
|
|
415
417
|
}),
|
|
416
418
|
/* @__PURE__ */ g("button", {
|
|
417
419
|
type: "button",
|
|
418
|
-
onClick: () =>
|
|
419
|
-
disabled: !
|
|
420
|
+
onClick: () => z && Q(`/creditTransactions/${z.id}`),
|
|
421
|
+
disabled: !z,
|
|
420
422
|
className: "w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
421
423
|
children: [/* @__PURE__ */ h("div", {
|
|
422
424
|
className: "size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center",
|
|
423
|
-
children: /* @__PURE__ */ h(
|
|
425
|
+
children: /* @__PURE__ */ h(m, { className: "size-4 text-status-warning-text" })
|
|
424
426
|
}), y("billing.overview.creditHistory", "Credit History")]
|
|
425
427
|
}),
|
|
426
428
|
/* @__PURE__ */ g("button", {
|
|
@@ -434,17 +436,17 @@ var y = () => {
|
|
|
434
436
|
}),
|
|
435
437
|
/* @__PURE__ */ g("button", {
|
|
436
438
|
type: "button",
|
|
437
|
-
onClick: () =>
|
|
438
|
-
disabled: !
|
|
439
|
+
onClick: () => z && Q(`/usage?billingAccountId=${z.id}`),
|
|
440
|
+
disabled: !z,
|
|
439
441
|
className: "w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
440
442
|
children: [/* @__PURE__ */ h("div", {
|
|
441
443
|
className: "size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
|
|
442
|
-
children: /* @__PURE__ */ h(
|
|
444
|
+
children: /* @__PURE__ */ h(ge, { className: "size-4 text-text-link" })
|
|
443
445
|
}), y("billing.overview.usageAnalytics", "Usage Analytics")]
|
|
444
446
|
})
|
|
445
447
|
]
|
|
446
448
|
})]
|
|
447
|
-
}), !
|
|
449
|
+
}), !H && /* @__PURE__ */ g(Ee, {
|
|
448
450
|
treatment: "glass",
|
|
449
451
|
glow: !0,
|
|
450
452
|
className: "p-4 sm:p-5",
|
|
@@ -460,47 +462,48 @@ var y = () => {
|
|
|
460
462
|
className: "text-sm text-text-secondary mt-1",
|
|
461
463
|
children: y("billing.overview.getStartedDescription", "Choose a plan that fits your needs to unlock all features")
|
|
462
464
|
})] })]
|
|
463
|
-
}), /* @__PURE__ */ g(
|
|
465
|
+
}), /* @__PURE__ */ g(we, {
|
|
464
466
|
type: "button",
|
|
465
467
|
onClick: () => Q("/plans"),
|
|
466
468
|
className: "w-full",
|
|
467
|
-
children: [y("billing.overview.browsePlans", "Browse Plans"), /* @__PURE__ */ h(
|
|
469
|
+
children: [y("billing.overview.browsePlans", "Browse Plans"), /* @__PURE__ */ h(_e, { className: "size-4" })]
|
|
468
470
|
})]
|
|
469
471
|
})]
|
|
470
472
|
})]
|
|
471
473
|
}),
|
|
472
|
-
/* @__PURE__ */ h(
|
|
474
|
+
/* @__PURE__ */ h(fe, {
|
|
473
475
|
dataTour: "billing-overview-recommended-addons",
|
|
474
|
-
addons:
|
|
475
|
-
hasSubscription:
|
|
476
|
+
addons: Pe,
|
|
477
|
+
hasSubscription: H,
|
|
476
478
|
onViewAddon: (e) => Q(`/addons/${e}`),
|
|
477
479
|
onPurchaseAddon: (e) => Q(`/addons/${e}?action=purchase`),
|
|
478
480
|
onViewAllAddons: () => Q("/addons"),
|
|
479
|
-
onAddToCart:
|
|
480
|
-
isInCart:
|
|
481
|
-
getCartQuantity:
|
|
482
|
-
onUpdateQuantity:
|
|
483
|
-
onRemoveFromCart:
|
|
481
|
+
onAddToCart: Xe,
|
|
482
|
+
isInCart: Ze,
|
|
483
|
+
getCartQuantity: Qe,
|
|
484
|
+
onUpdateQuantity: $e,
|
|
485
|
+
onRemoveFromCart: et,
|
|
484
486
|
maxAddons: 3,
|
|
485
|
-
isLoading:
|
|
487
|
+
isLoading: Fe
|
|
486
488
|
}),
|
|
487
|
-
/* @__PURE__ */ h(
|
|
489
|
+
/* @__PURE__ */ h(he, {}),
|
|
490
|
+
Me && /* @__PURE__ */ h(te, { onDismiss: () => A(!1) }),
|
|
488
491
|
/* @__PURE__ */ h(i, {
|
|
489
|
-
isOpen:
|
|
492
|
+
isOpen: E,
|
|
490
493
|
onClose: () => {
|
|
491
|
-
|
|
494
|
+
D(!1), k(null);
|
|
492
495
|
},
|
|
493
|
-
onSubmit:
|
|
494
|
-
isSubmitting:
|
|
495
|
-
error:
|
|
496
|
+
onSubmit: Ue,
|
|
497
|
+
isSubmitting: He,
|
|
498
|
+
error: O
|
|
496
499
|
}),
|
|
497
|
-
|
|
498
|
-
isOpen:
|
|
499
|
-
onClose: () =>
|
|
500
|
-
billingAccountId:
|
|
501
|
-
currency:
|
|
502
|
-
country:
|
|
503
|
-
onSuccess:
|
|
500
|
+
z && /* @__PURE__ */ h(pe, {
|
|
501
|
+
isOpen: Ne,
|
|
502
|
+
onClose: () => N(!1),
|
|
503
|
+
billingAccountId: z.id,
|
|
504
|
+
currency: z.currency,
|
|
505
|
+
country: z.billingAddresses?.[0]?.country,
|
|
506
|
+
onSuccess: Ge
|
|
504
507
|
})
|
|
505
508
|
]
|
|
506
509
|
}) : /* @__PURE__ */ h(n, { message: "You don't have permission to view billing information." });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverviewPage.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/pages/OverviewPage.tsx"],"sourcesContent":["/**\n * Billing Overview Page\n * Main dashboard for billing showing accounts, subscriptions, payment methods, and addons\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Building2,\n RefreshCw,\n Loader2,\n Wallet,\n ChevronDown,\n Check,\n Activity,\n Receipt,\n ArrowRight,\n Sparkles,\n Crown,\n Coins,\n} from 'lucide-react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { StatCard } from '../../../shared/components/StatCard';\nimport { ServerError } from '../../../shared/components/ServerError';\nimport { AccessDenied } from '../../../shared/components/AccessDenied';\nimport { isServerError } from '../../../shared/utils';\nimport { useBillingOverviewData, useDashboardMutations } from '../hooks';\nimport {\n BillingAccountCard,\n PaymentMethodsSection,\n ActiveSubscriptionCard,\n RecommendedAddonsSection,\n CreateBillingAccountModal,\n AddPaymentMethodModal,\n} from '../components';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\nimport { FloatingCartDrawer } from '../../addons/components/FloatingCartDrawer';\nimport {\n getStoredBillingAccountId,\n setStoredBillingAccountId,\n} from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n BillingAccount,\n CreateBillingAccountInput,\n SetBillingAddressInput,\n Addon,\n} from '../../../shared/types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nexport const OverviewPage: FC = () => {\n const tr = useTr();\n const { navigate, basePath, orgId, productId } = useBilling();\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n\n const [selectedAccountId, setSelectedAccountIdState] = useState<string | null>(() =>\n getStoredBillingAccountId(orgId)\n );\n\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);\n const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0);\n const [isSubscriptionSelectorOpen, setIsSubscriptionSelectorOpen] = useState(false);\n\n const {\n billingAccounts,\n currentAccount,\n paymentMethods,\n activeSubscriptions,\n hasActiveSubscription,\n recommendedAddons,\n isLoading,\n error,\n refetchAll,\n } = useBillingOverviewData(selectedAccountId, productId);\n\n const currentAccountId = currentAccount?.id;\n const isLoadingAddons = isLoading;\n\n const setSelectedAccountId = useCallback(\n (accountId: string | null) => {\n setSelectedAccountIdState(accountId);\n setStoredBillingAccountId(orgId, accountId);\n },\n [orgId]\n );\n\n // Addon cart store for add to cart functionality\n const { addToCart, isInCart, getCartItem, updateQuantity, removeFromCart } = useAddonCartStore();\n\n const {\n createBillingAccount,\n setBillingAddress,\n setDefaultPaymentMethod,\n deletePaymentMethod,\n isCreatingAccount,\n } = useDashboardMutations();\n\n // Reset subscription selector when account changes or subscriptions change\n useEffect(() => {\n setSelectedSubscriptionIndex(0);\n setIsSubscriptionSelectorOpen(false);\n }, [currentAccountId, activeSubscriptions.length]);\n\n useEffect(() => {\n if (selectedAccountId && billingAccounts.length > 0) {\n const accountExists = billingAccounts.some((account) => account.id === selectedAccountId);\n if (!accountExists) {\n setSelectedAccountId(null);\n }\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n useEffect(() => {\n if (!selectedAccountId && billingAccounts.length > 0) {\n const defaultAccount =\n billingAccounts.find((account) => account.isDefault) ?? billingAccounts[0];\n setSelectedAccountId(defaultAccount.id);\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n // Derive selected subscription from active subscriptions\n const selectedSubscription =\n activeSubscriptions[selectedSubscriptionIndex] || activeSubscriptions[0] || null;\n\n // Helper for navigation - properly joins basePath and path\n const navigateTo = useCallback(\n (path: string) => {\n // Handle path joining to avoid double slashes (e.g., / + /plans = //plans)\n let fullPath = path;\n if (basePath && basePath !== '/') {\n // Remove trailing slash from basePath and leading slash from path if needed\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n fullPath = `${base}${suffix}`;\n }\n navigate(fullPath);\n },\n [navigate, basePath]\n );\n\n // Handlers\n const handleCreateAccount = useCallback(\n async (\n input: CreateBillingAccountInput,\n address?: Omit<SetBillingAddressInput, 'billingAccountId'>\n ) => {\n setCreateError(null);\n try {\n const newAccount = await createBillingAccount(input);\n\n // If address data provided, set the billing address\n if (address && newAccount.id) {\n await setBillingAddress({\n billingAccountId: newAccount.id,\n ...address,\n });\n }\n\n setIsCreateModalOpen(false);\n setSelectedAccountId(newAccount.id);\n emit('billing.billing_account.created', {\n route: '/billing/overview',\n entityId: newAccount.id,\n source: 'overview',\n });\n await refetchAll();\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create billing account');\n }\n },\n [createBillingAccount, emit, refetchAll, setBillingAddress, setSelectedAccountId]\n );\n\n const handleAddPaymentMethod = useCallback(() => {\n setIsAddPaymentModalOpen(true);\n }, []);\n\n // Helper to refetch the current account data\n const refetchCurrentAccount = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n const handlePaymentMethodAdded = useCallback(async () => {\n emit('billing.payment_method.added', {\n route: '/billing/overview',\n entityId: currentAccountId,\n source: 'overview',\n });\n await refetchCurrentAccount();\n }, [currentAccountId, emit, refetchCurrentAccount]);\n\n const handleSetDefaultPaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await setDefaultPaymentMethod(id, currentAccountId);\n emit('billing.payment_method.default_set', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, emit, setDefaultPaymentMethod, refetchCurrentAccount]\n );\n\n const handleDeletePaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await deletePaymentMethod(id, currentAccountId);\n emit('billing.payment_method.deleted', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, deletePaymentMethod, emit, refetchCurrentAccount]\n );\n\n const handleSelectAccount = useCallback(\n (account: BillingAccount) => {\n setSelectedAccountId(account.id);\n setIsAccountSelectorOpen(false);\n },\n [setSelectedAccountId]\n );\n\n const handleRefresh = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n // Handler for adding addon to cart\n const handleAddToCart = useCallback(\n (addon: Addon) => {\n addToCart(addon);\n },\n [addToCart]\n );\n\n // Handler to check if addon is in cart\n const handleIsInCart = useCallback(\n (addonId: string) => {\n return isInCart(addonId);\n },\n [isInCart]\n );\n\n // Handler to get cart quantity for addon\n const handleGetCartQuantity = useCallback(\n (addonId: string) => {\n const item = getCartItem(addonId);\n return item?.quantity || 0;\n },\n [getCartItem]\n );\n\n // Handler to update quantity in cart\n const handleUpdateQuantity = useCallback(\n (addonId: string, quantity: number) => {\n if (quantity <= 0) {\n removeFromCart(addonId);\n } else {\n updateQuantity(addonId, quantity);\n }\n },\n [updateQuantity, removeFromCart]\n );\n\n // Handler to remove from cart\n const handleRemoveFromCart = useCallback(\n (addonId: string) => {\n removeFromCart(addonId);\n },\n [removeFromCart]\n );\n\n // Permission check - must be after all hooks are defined\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view billing information.\" />;\n }\n\n // Error state\n if (error && isServerError(error) && !isLoading) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.serverError.title', 'Server Unavailable')}\n message={tr(\n 'billing.overview.serverUnavailableMessage',\n 'Unable to load billing overview. The server might be down or experiencing issues.'\n )}\n onRetry={handleRefresh}\n showRetry\n />\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !currentAccount) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"flex flex-col items-center gap-3\">\n <Loader2 className=\"size-8 animate-spin text-text-link\" />\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state. Checked against `billingAccounts` AND\n // `currentAccount` (not just the list) because getDefaultBillingAccount\n // lazy-creates a default account for a brand-new org — on that exact\n // first request, getBillingAccountsByOrg's plain read can still observe\n // zero rows in the same response that returned a real, freshly-created\n // currentAccount. A valid currentAccount always means there's a real\n // billing account regardless of what the (possibly momentarily stale)\n // list says.\n if (!isLoading && billingAccounts.length === 0 && !currentAccount) {\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6\">\n <header className=\"relative overflow-hidden mb-6 sm:mb-8 rounded-card\">\n <AuroraBackground intensity=\"subtle\" />\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title={tr('billing.overview.emptyTitle', 'No billing account')}\n description={tr(\n 'billing.overview.emptyDescription',\n 'Create a billing account to start managing your subscriptions and payments'\n )}\n action={\n permissions.canManageBillingAccount ? (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n <Plus className=\"size-4\" />\n {tr('billing.overview.createBillingAccount', 'Create Billing Account')}\n </button>\n ) : undefined\n }\n />\n\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n </div>\n );\n }\n\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6 space-y-4 sm:space-y-8\">\n {/* Header */}\n <header className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.overview.purpose',\n 'Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.'\n )}\n </PagePurpose>\n </div>\n <CTAOverflowMenu\n primary={\n permissions.canManageBillingAccount && billingAccounts.length > 0\n ? {\n label: tr('billing.overview.newAccount', 'New Account'),\n onSelect: () => setIsCreateModalOpen(true),\n icon: <Plus className=\"size-4\" />,\n }\n : undefined\n }\n actions={[\n {\n label: tr('billing.overview.refresh', 'Refresh'),\n onSelect: () => {\n void handleRefresh();\n },\n icon: <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />,\n },\n ]}\n />\n </header>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\" data-tour=\"billing-overview-account-selector\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Building2 className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Stats Row */}\n {currentAccount && (\n <div\n className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\"\n data-tour=\"billing-overview-stats-row\"\n >\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.creditBalance', 'Credit Balance')}\n value={`${(currentAccount.creditAmount || 0).toLocaleString()} Credits`}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.activePlans', 'Active Plans')}\n value={\n activeSubscriptions.length > 0\n ? activeSubscriptions.length === 1\n ? activeSubscriptions[0]?.plan?.name || '1 Plan'\n : `${activeSubscriptions.length} ${tr('billing.overview.stats.plansSuffix', 'Plans')}`\n : tr('billing.overview.stats.none', 'None')\n }\n icon={<RefreshCw className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentCurrency', 'Payment Currency')}\n value={currentAccount.currency || 'USD'}\n icon={<Building2 className=\"size-5\" />}\n />\n </div>\n )}\n\n {/* Main Content Grid */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n {/* Billing Account Card */}\n {currentAccount && (\n <BillingAccountCard\n dataTour=\"billing-overview-billing-account-card\"\n account={currentAccount}\n isDefault={currentAccount.isDefault ?? false}\n onEdit={() =>\n currentAccount && navigateTo(`/settings?billingAccountId=${currentAccount.id}`)\n }\n onViewTransactions={() => navigateTo(`/transactions/${currentAccount.id}`)}\n onViewCreditTransactions={() =>\n navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n />\n )}\n\n {/* Active Subscriptions */}\n <div className=\"space-y-3\">\n {/* Subscription Selector (if multiple subscriptions) */}\n {activeSubscriptions.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsSubscriptionSelectorOpen(!isSubscriptionSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Crown className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-text-secondary font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isSubscriptionSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isSubscriptionSelectorOpen && (\n <>\n <div\n className=\"fixed inset-0 z-10\"\n onClick={() => setIsSubscriptionSelectorOpen(false)}\n />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {activeSubscriptions.map((subscription, index) => (\n <button\n type=\"button\"\n key={subscription.id}\n onClick={() => {\n setSelectedSubscriptionIndex(index);\n setIsSubscriptionSelectorOpen(false);\n }}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {subscription.plan?.product?.name ||\n tr('billing.overview.unknownProduct', 'Unknown Product')}{' '}\n · {tr('billing.overview.ends', 'Ends')}{' '}\n {new Date(subscription.endDate).toLocaleDateString()}\n </p>\n </div>\n </div>\n {selectedSubscriptionIndex === index && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Single Subscription Card */}\n <ActiveSubscriptionCard\n dataTour=\"billing-overview-active-subscription-card\"\n subscription={selectedSubscription}\n onViewDetails={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onUpgrade={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}/upgrade`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onManage={() =>\n currentAccount && navigateTo(`/subscriptions?billingAccountId=${currentAccount.id}`)\n }\n onPurchasePlan={() => navigateTo('/plans')}\n />\n </div>\n\n {/* Payment Methods */}\n {currentAccount && (\n <PaymentMethodsSection\n dataTour=\"billing-overview-payment-methods-section\"\n paymentMethods={paymentMethods}\n billingAccountId={currentAccount.id}\n onAddPaymentMethod={handleAddPaymentMethod}\n onSetDefault={handleSetDefaultPaymentMethod}\n onDelete={handleDeletePaymentMethod}\n isLoading={isLoading}\n />\n )}\n </div>\n\n {/* Right Column - Quick Actions & Info */}\n <div className=\"space-y-4 sm:space-y-6\">\n {/* Quick Actions */}\n <section\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-4 sm:p-5\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('billing.overview.quickActions', 'Quick Actions')}\n </h3>\n <div className=\"space-y-1.5\">\n <button\n type=\"button\"\n onClick={() => currentAccount && navigateTo(`/transactions/${currentAccount.id}`)}\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-status-info-text\" />\n </div>\n {tr('billing.overview.viewTransactions', 'View Transactions')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n {tr('billing.overview.creditHistory', 'Credit History')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/credits')}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <div className=\"size-9 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n {tr('billing.overview.purchaseCredits', 'Purchase Credits')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/usage?billingAccountId=${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Activity className=\"size-4 text-text-link\" />\n </div>\n {tr('billing.overview.usageAnalytics', 'Usage Analytics')}\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) — primary emphasis zone */}\n {!hasActiveSubscription && (\n <GlassCard treatment=\"glass\" glow className=\"p-4 sm:p-5\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center flex-shrink-0 shadow-elevation-1\">\n <Sparkles className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.overview.getStarted', 'Get Started')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.getStartedDescription',\n 'Choose a plan that fits your needs to unlock all features'\n )}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigateTo('/plans')} className=\"w-full\">\n {tr('billing.overview.browsePlans', 'Browse Plans')}\n <ArrowRight className=\"size-4\" />\n </Button>\n </GlassCard>\n )}\n </div>\n </div>\n\n {/* Recommended Addons (only if has subscription) - Limited to 3 with cart support */}\n <RecommendedAddonsSection\n dataTour=\"billing-overview-recommended-addons\"\n addons={recommendedAddons}\n hasSubscription={hasActiveSubscription}\n onViewAddon={(addonId) => navigateTo(`/addons/${addonId}`)}\n onPurchaseAddon={(addonId) => navigateTo(`/addons/${addonId}?action=purchase`)}\n onViewAllAddons={() => navigateTo('/addons')}\n onAddToCart={handleAddToCart}\n isInCart={handleIsInCart}\n getCartQuantity={handleGetCartQuantity}\n onUpdateQuantity={handleUpdateQuantity}\n onRemoveFromCart={handleRemoveFromCart}\n maxAddons={3}\n isLoading={isLoadingAddons}\n />\n\n {/* Floating Cart Drawer - shows when items are in cart */}\n <FloatingCartDrawer />\n\n {/* Create Billing Account Modal */}\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n\n {/* Add Payment Method Modal */}\n {currentAccount && (\n <AddPaymentMethodModal\n isOpen={isAddPaymentModalOpen}\n onClose={() => setIsAddPaymentModalOpen(false)}\n billingAccountId={currentAccount.id}\n currency={currentAccount.currency}\n country={currentAccount.billingAddresses?.[0]?.country}\n onSuccess={handlePaymentMethodAdded}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OverviewPage.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/pages/OverviewPage.tsx"],"sourcesContent":["/**\n * Billing Overview Page\n * Main dashboard for billing showing accounts, subscriptions, payment methods, and addons\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Building2,\n RefreshCw,\n Loader2,\n Wallet,\n ChevronDown,\n Check,\n Activity,\n Receipt,\n ArrowRight,\n Sparkles,\n Crown,\n Coins,\n} from 'lucide-react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { StatCard } from '../../../shared/components/StatCard';\nimport { ServerError } from '../../../shared/components/ServerError';\nimport { AccessDenied } from '../../../shared/components/AccessDenied';\nimport { PushPermissionNudge } from '../../../shared/components/PushPermissionNudge';\nimport { isServerError } from '../../../shared/utils';\nimport { shouldPromptForPushPermission } from '@burdenoff/fe-libs/shared/native';\nimport { useBillingOverviewData, useDashboardMutations } from '../hooks';\nimport {\n BillingAccountCard,\n PaymentMethodsSection,\n ActiveSubscriptionCard,\n RecommendedAddonsSection,\n CreateBillingAccountModal,\n AddPaymentMethodModal,\n} from '../components';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\nimport { FloatingCartDrawer } from '../../addons/components/FloatingCartDrawer';\nimport {\n getStoredBillingAccountId,\n setStoredBillingAccountId,\n} from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n BillingAccount,\n CreateBillingAccountInput,\n SetBillingAddressInput,\n Addon,\n} from '../../../shared/types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nexport const OverviewPage: FC = () => {\n const tr = useTr();\n const { navigate, basePath, orgId, productId } = useBilling();\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n\n const [selectedAccountId, setSelectedAccountIdState] = useState<string | null>(() =>\n getStoredBillingAccountId(orgId)\n );\n\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [showPushNudge, setShowPushNudge] = useState(false);\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);\n const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0);\n const [isSubscriptionSelectorOpen, setIsSubscriptionSelectorOpen] = useState(false);\n\n const {\n billingAccounts,\n currentAccount,\n paymentMethods,\n activeSubscriptions,\n hasActiveSubscription,\n recommendedAddons,\n isLoading,\n error,\n refetchAll,\n } = useBillingOverviewData(selectedAccountId, productId);\n\n const currentAccountId = currentAccount?.id;\n const isLoadingAddons = isLoading;\n\n const setSelectedAccountId = useCallback(\n (accountId: string | null) => {\n setSelectedAccountIdState(accountId);\n setStoredBillingAccountId(orgId, accountId);\n },\n [orgId]\n );\n\n // Addon cart store for add to cart functionality\n const { addToCart, isInCart, getCartItem, updateQuantity, removeFromCart } = useAddonCartStore();\n\n const {\n createBillingAccount,\n setBillingAddress,\n setDefaultPaymentMethod,\n deletePaymentMethod,\n isCreatingAccount,\n } = useDashboardMutations();\n\n // Reset subscription selector when account changes or subscriptions change\n useEffect(() => {\n setSelectedSubscriptionIndex(0);\n setIsSubscriptionSelectorOpen(false);\n }, [currentAccountId, activeSubscriptions.length]);\n\n useEffect(() => {\n if (selectedAccountId && billingAccounts.length > 0) {\n const accountExists = billingAccounts.some((account) => account.id === selectedAccountId);\n if (!accountExists) {\n setSelectedAccountId(null);\n }\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n useEffect(() => {\n if (!selectedAccountId && billingAccounts.length > 0) {\n const defaultAccount =\n billingAccounts.find((account) => account.isDefault) ?? billingAccounts[0];\n setSelectedAccountId(defaultAccount.id);\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n // Derive selected subscription from active subscriptions\n const selectedSubscription =\n activeSubscriptions[selectedSubscriptionIndex] || activeSubscriptions[0] || null;\n\n // Helper for navigation - properly joins basePath and path\n const navigateTo = useCallback(\n (path: string) => {\n // Handle path joining to avoid double slashes (e.g., / + /plans = //plans)\n let fullPath = path;\n if (basePath && basePath !== '/') {\n // Remove trailing slash from basePath and leading slash from path if needed\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n fullPath = `${base}${suffix}`;\n }\n navigate(fullPath);\n },\n [navigate, basePath]\n );\n\n // Handlers\n const handleCreateAccount = useCallback(\n async (\n input: CreateBillingAccountInput,\n address?: Omit<SetBillingAddressInput, 'billingAccountId'>\n ) => {\n setCreateError(null);\n try {\n const newAccount = await createBillingAccount(input);\n\n // If address data provided, set the billing address\n if (address && newAccount.id) {\n await setBillingAddress({\n billingAccountId: newAccount.id,\n ...address,\n });\n }\n\n setIsCreateModalOpen(false);\n setSelectedAccountId(newAccount.id);\n emit('billing.billing_account.created', {\n route: '/billing/overview',\n entityId: newAccount.id,\n source: 'overview',\n });\n await refetchAll();\n\n // Contextual push-permission ask, right after a meaningful action —\n // not hidden behind the notification-centre bell. Only when the OS/\n // browser has never been asked AND this device hasn't dismissed the\n // nudge before; see shouldPromptForPushPermission's own doc for why.\n if (shouldPromptForPushPermission()) {\n setShowPushNudge(true);\n }\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create billing account');\n }\n },\n [createBillingAccount, emit, refetchAll, setBillingAddress, setSelectedAccountId]\n );\n\n const handleAddPaymentMethod = useCallback(() => {\n setIsAddPaymentModalOpen(true);\n }, []);\n\n // Helper to refetch the current account data\n const refetchCurrentAccount = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n const handlePaymentMethodAdded = useCallback(async () => {\n emit('billing.payment_method.added', {\n route: '/billing/overview',\n entityId: currentAccountId,\n source: 'overview',\n });\n await refetchCurrentAccount();\n }, [currentAccountId, emit, refetchCurrentAccount]);\n\n const handleSetDefaultPaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await setDefaultPaymentMethod(id, currentAccountId);\n emit('billing.payment_method.default_set', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, emit, setDefaultPaymentMethod, refetchCurrentAccount]\n );\n\n const handleDeletePaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await deletePaymentMethod(id, currentAccountId);\n emit('billing.payment_method.deleted', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, deletePaymentMethod, emit, refetchCurrentAccount]\n );\n\n const handleSelectAccount = useCallback(\n (account: BillingAccount) => {\n setSelectedAccountId(account.id);\n setIsAccountSelectorOpen(false);\n },\n [setSelectedAccountId]\n );\n\n const handleRefresh = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n // Handler for adding addon to cart\n const handleAddToCart = useCallback(\n (addon: Addon) => {\n addToCart(addon);\n },\n [addToCart]\n );\n\n // Handler to check if addon is in cart\n const handleIsInCart = useCallback(\n (addonId: string) => {\n return isInCart(addonId);\n },\n [isInCart]\n );\n\n // Handler to get cart quantity for addon\n const handleGetCartQuantity = useCallback(\n (addonId: string) => {\n const item = getCartItem(addonId);\n return item?.quantity || 0;\n },\n [getCartItem]\n );\n\n // Handler to update quantity in cart\n const handleUpdateQuantity = useCallback(\n (addonId: string, quantity: number) => {\n if (quantity <= 0) {\n removeFromCart(addonId);\n } else {\n updateQuantity(addonId, quantity);\n }\n },\n [updateQuantity, removeFromCart]\n );\n\n // Handler to remove from cart\n const handleRemoveFromCart = useCallback(\n (addonId: string) => {\n removeFromCart(addonId);\n },\n [removeFromCart]\n );\n\n // Permission check - must be after all hooks are defined\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view billing information.\" />;\n }\n\n // Error state\n if (error && isServerError(error) && !isLoading) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.serverError.title', 'Server Unavailable')}\n message={tr(\n 'billing.overview.serverUnavailableMessage',\n 'Unable to load billing overview. The server might be down or experiencing issues.'\n )}\n onRetry={handleRefresh}\n showRetry\n />\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !currentAccount) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"flex flex-col items-center gap-3\">\n <Loader2 className=\"size-8 animate-spin text-text-link\" />\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state. Checked against `billingAccounts` AND\n // `currentAccount` (not just the list) because getDefaultBillingAccount\n // lazy-creates a default account for a brand-new org — on that exact\n // first request, getBillingAccountsByOrg's plain read can still observe\n // zero rows in the same response that returned a real, freshly-created\n // currentAccount. A valid currentAccount always means there's a real\n // billing account regardless of what the (possibly momentarily stale)\n // list says.\n if (!isLoading && billingAccounts.length === 0 && !currentAccount) {\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6\">\n <header className=\"relative overflow-hidden mb-6 sm:mb-8 rounded-card\">\n <AuroraBackground intensity=\"subtle\" />\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title={tr('billing.overview.emptyTitle', 'No billing account')}\n description={tr(\n 'billing.overview.emptyDescription',\n 'Create a billing account to start managing your subscriptions and payments'\n )}\n action={\n permissions.canManageBillingAccount ? (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n <Plus className=\"size-4\" />\n {tr('billing.overview.createBillingAccount', 'Create Billing Account')}\n </button>\n ) : undefined\n }\n />\n\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n </div>\n );\n }\n\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6 space-y-4 sm:space-y-8\">\n {/* Header */}\n <header className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.overview.purpose',\n 'Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.'\n )}\n </PagePurpose>\n </div>\n <CTAOverflowMenu\n primary={\n permissions.canManageBillingAccount && billingAccounts.length > 0\n ? {\n label: tr('billing.overview.newAccount', 'New Account'),\n onSelect: () => setIsCreateModalOpen(true),\n icon: <Plus className=\"size-4\" />,\n }\n : undefined\n }\n actions={[\n {\n label: tr('billing.overview.refresh', 'Refresh'),\n onSelect: () => {\n void handleRefresh();\n },\n icon: <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />,\n },\n ]}\n />\n </header>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\" data-tour=\"billing-overview-account-selector\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Building2 className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Stats Row */}\n {currentAccount && (\n <div\n className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\"\n data-tour=\"billing-overview-stats-row\"\n >\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.creditBalance', 'Credit Balance')}\n value={`${(currentAccount.creditAmount || 0).toLocaleString()} Credits`}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.activePlans', 'Active Plans')}\n value={\n activeSubscriptions.length > 0\n ? activeSubscriptions.length === 1\n ? activeSubscriptions[0]?.plan?.name || '1 Plan'\n : `${activeSubscriptions.length} ${tr('billing.overview.stats.plansSuffix', 'Plans')}`\n : tr('billing.overview.stats.none', 'None')\n }\n icon={<RefreshCw className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentCurrency', 'Payment Currency')}\n value={currentAccount.currency || 'USD'}\n icon={<Building2 className=\"size-5\" />}\n />\n </div>\n )}\n\n {/* Main Content Grid */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n {/* Billing Account Card */}\n {currentAccount && (\n <BillingAccountCard\n dataTour=\"billing-overview-billing-account-card\"\n account={currentAccount}\n isDefault={currentAccount.isDefault ?? false}\n onEdit={() =>\n currentAccount && navigateTo(`/settings?billingAccountId=${currentAccount.id}`)\n }\n onViewTransactions={() => navigateTo(`/transactions/${currentAccount.id}`)}\n onViewCreditTransactions={() =>\n navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n />\n )}\n\n {/* Active Subscriptions */}\n <div className=\"space-y-3\">\n {/* Subscription Selector (if multiple subscriptions) */}\n {activeSubscriptions.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsSubscriptionSelectorOpen(!isSubscriptionSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Crown className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-text-secondary font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isSubscriptionSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isSubscriptionSelectorOpen && (\n <>\n <div\n className=\"fixed inset-0 z-10\"\n onClick={() => setIsSubscriptionSelectorOpen(false)}\n />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {activeSubscriptions.map((subscription, index) => (\n <button\n type=\"button\"\n key={subscription.id}\n onClick={() => {\n setSelectedSubscriptionIndex(index);\n setIsSubscriptionSelectorOpen(false);\n }}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {subscription.plan?.product?.name ||\n tr('billing.overview.unknownProduct', 'Unknown Product')}{' '}\n · {tr('billing.overview.ends', 'Ends')}{' '}\n {new Date(subscription.endDate).toLocaleDateString()}\n </p>\n </div>\n </div>\n {selectedSubscriptionIndex === index && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Single Subscription Card */}\n <ActiveSubscriptionCard\n dataTour=\"billing-overview-active-subscription-card\"\n subscription={selectedSubscription}\n onViewDetails={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onUpgrade={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}/upgrade`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onManage={() =>\n currentAccount && navigateTo(`/subscriptions?billingAccountId=${currentAccount.id}`)\n }\n onPurchasePlan={() => navigateTo('/plans')}\n />\n </div>\n\n {/* Payment Methods */}\n {currentAccount && (\n <PaymentMethodsSection\n dataTour=\"billing-overview-payment-methods-section\"\n paymentMethods={paymentMethods}\n billingAccountId={currentAccount.id}\n onAddPaymentMethod={handleAddPaymentMethod}\n onSetDefault={handleSetDefaultPaymentMethod}\n onDelete={handleDeletePaymentMethod}\n isLoading={isLoading}\n />\n )}\n </div>\n\n {/* Right Column - Quick Actions & Info */}\n <div className=\"space-y-4 sm:space-y-6\">\n {/* Quick Actions */}\n <section\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-4 sm:p-5\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('billing.overview.quickActions', 'Quick Actions')}\n </h3>\n <div className=\"space-y-1.5\">\n <button\n type=\"button\"\n onClick={() => currentAccount && navigateTo(`/transactions/${currentAccount.id}`)}\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-status-info-text\" />\n </div>\n {tr('billing.overview.viewTransactions', 'View Transactions')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n {tr('billing.overview.creditHistory', 'Credit History')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/credits')}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <div className=\"size-9 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n {tr('billing.overview.purchaseCredits', 'Purchase Credits')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/usage?billingAccountId=${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Activity className=\"size-4 text-text-link\" />\n </div>\n {tr('billing.overview.usageAnalytics', 'Usage Analytics')}\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) — primary emphasis zone */}\n {!hasActiveSubscription && (\n <GlassCard treatment=\"glass\" glow className=\"p-4 sm:p-5\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center flex-shrink-0 shadow-elevation-1\">\n <Sparkles className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.overview.getStarted', 'Get Started')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.getStartedDescription',\n 'Choose a plan that fits your needs to unlock all features'\n )}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigateTo('/plans')} className=\"w-full\">\n {tr('billing.overview.browsePlans', 'Browse Plans')}\n <ArrowRight className=\"size-4\" />\n </Button>\n </GlassCard>\n )}\n </div>\n </div>\n\n {/* Recommended Addons (only if has subscription) - Limited to 3 with cart support */}\n <RecommendedAddonsSection\n dataTour=\"billing-overview-recommended-addons\"\n addons={recommendedAddons}\n hasSubscription={hasActiveSubscription}\n onViewAddon={(addonId) => navigateTo(`/addons/${addonId}`)}\n onPurchaseAddon={(addonId) => navigateTo(`/addons/${addonId}?action=purchase`)}\n onViewAllAddons={() => navigateTo('/addons')}\n onAddToCart={handleAddToCart}\n isInCart={handleIsInCart}\n getCartQuantity={handleGetCartQuantity}\n onUpdateQuantity={handleUpdateQuantity}\n onRemoveFromCart={handleRemoveFromCart}\n maxAddons={3}\n isLoading={isLoadingAddons}\n />\n\n {/* Floating Cart Drawer - shows when items are in cart */}\n <FloatingCartDrawer />\n\n {/* Contextual push-permission nudge, shown once after creating an account */}\n {showPushNudge && <PushPermissionNudge onDismiss={() => setShowPushNudge(false)} />}\n\n {/* Create Billing Account Modal */}\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n\n {/* Add Payment Method Modal */}\n {currentAccount && (\n <AddPaymentMethodModal\n isOpen={isAddPaymentModalOpen}\n onClose={() => setIsAddPaymentModalOpen(false)}\n billingAccountId={currentAccount.id}\n currency={currentAccount.currency}\n country={currentAccount.billingAddresses?.[0]?.country}\n onSuccess={handlePaymentMethodAdded}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useTr as e } from "../hooks/useTr.js";
|
|
2
|
+
import { useState as t } from "react";
|
|
3
|
+
import { Bell as n, X as r } from "lucide-react";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
import { getPushOptInController as o, recordPushPermissionPromptDismissed as s } from "@burdenoff/fe-libs/shared/native";
|
|
6
|
+
//#region src/billing/shared/components/PushPermissionNudge.tsx
|
|
7
|
+
var c = ({ onDismiss: c }) => {
|
|
8
|
+
let l = e(), [u, d] = t(!1), f = async () => {
|
|
9
|
+
let e = o();
|
|
10
|
+
if (!(!e || u)) {
|
|
11
|
+
d(!0);
|
|
12
|
+
try {
|
|
13
|
+
await e.enable();
|
|
14
|
+
} finally {
|
|
15
|
+
d(!1), c();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}, p = () => {
|
|
19
|
+
s(), c();
|
|
20
|
+
};
|
|
21
|
+
return /* @__PURE__ */ i("div", {
|
|
22
|
+
role: "status",
|
|
23
|
+
className: "fixed bottom-6 right-6 z-50 w-full max-w-sm bg-bg-surface border border-border-subtle rounded-card shadow-elevation-3 p-4",
|
|
24
|
+
children: /* @__PURE__ */ a("div", {
|
|
25
|
+
className: "flex items-start gap-3",
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ i("div", {
|
|
28
|
+
className: "size-9 rounded-full bg-bg-sunken flex items-center justify-center shrink-0",
|
|
29
|
+
children: /* @__PURE__ */ i(n, { className: "size-4 text-text-secondary" })
|
|
30
|
+
}),
|
|
31
|
+
/* @__PURE__ */ a("div", {
|
|
32
|
+
className: "flex-1 min-w-0",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ i("p", {
|
|
35
|
+
className: "text-sm font-medium text-text-primary",
|
|
36
|
+
children: l("billing.pushNudge.title", "Stay on top of your account")
|
|
37
|
+
}),
|
|
38
|
+
/* @__PURE__ */ i("p", {
|
|
39
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
40
|
+
children: l("billing.pushNudge.body", "Turn on notifications and we'll let you know about billing updates like this one.")
|
|
41
|
+
}),
|
|
42
|
+
/* @__PURE__ */ a("div", {
|
|
43
|
+
className: "mt-3 flex items-center gap-2",
|
|
44
|
+
children: [/* @__PURE__ */ i("button", {
|
|
45
|
+
type: "button",
|
|
46
|
+
onClick: f,
|
|
47
|
+
disabled: u,
|
|
48
|
+
className: "px-3 py-1.5 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 disabled:opacity-60",
|
|
49
|
+
children: u ? l("billing.pushNudge.enabling", "Enabling…") : l("billing.pushNudge.enable", "Enable notifications")
|
|
50
|
+
}), /* @__PURE__ */ i("button", {
|
|
51
|
+
type: "button",
|
|
52
|
+
onClick: p,
|
|
53
|
+
className: "px-3 py-1.5 text-sm font-medium text-text-secondary hover:bg-bg-sunken rounded-button transition-colors duration-200",
|
|
54
|
+
children: l("billing.pushNudge.notNow", "Not now")
|
|
55
|
+
})]
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ i("button", {
|
|
60
|
+
type: "button",
|
|
61
|
+
onClick: p,
|
|
62
|
+
"aria-label": l("billing.pushNudge.close", "Close"),
|
|
63
|
+
className: "p-1 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors duration-200 shrink-0",
|
|
64
|
+
children: /* @__PURE__ */ i(r, { className: "size-4" })
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
export { c as PushPermissionNudge };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=PushPermissionNudge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PushPermissionNudge.js","names":[],"sources":["../../../../src/billing/shared/components/PushPermissionNudge.tsx"],"sourcesContent":["/**\n * Push Permission Nudge\n *\n * A one-time, contextual ask for push permission shown right after a\n * meaningful action (e.g. creating a billing account) rather than hidden\n * behind the notification-centre bell, which many users never open.\n *\n * Still respects the one-shot-prompt rule fe-libs enforces everywhere else:\n * it only renders when `shouldPromptForPushPermission()` says permission has\n * never been asked and the nudge was never dismissed before, and it never\n * re-prompts itself — `enable()`/dismissal both make it disappear for good\n * on this device.\n */\nimport { useState, type FC } from 'react';\nimport { Bell, X } from 'lucide-react';\nimport {\n getPushOptInController,\n recordPushPermissionPromptDismissed,\n} from '@burdenoff/fe-libs/shared/native';\nimport { useTr } from '../hooks/useTr';\n\ninterface PushPermissionNudgeProps {\n onDismiss: () => void;\n}\n\nexport const PushPermissionNudge: FC<PushPermissionNudgeProps> = ({ onDismiss }) => {\n const tr = useTr();\n const [enabling, setEnabling] = useState(false);\n\n const handleEnable = async () => {\n const controller = getPushOptInController();\n if (!controller || enabling) return;\n setEnabling(true);\n try {\n await controller.enable();\n } finally {\n setEnabling(false);\n onDismiss();\n }\n };\n\n const handleDismiss = () => {\n recordPushPermissionPromptDismissed();\n onDismiss();\n };\n\n return (\n <div\n role=\"status\"\n className=\"fixed bottom-6 right-6 z-50 w-full max-w-sm bg-bg-surface border border-border-subtle rounded-card shadow-elevation-3 p-4\"\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"size-9 rounded-full bg-bg-sunken flex items-center justify-center shrink-0\">\n <Bell className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.pushNudge.title', 'Stay on top of your account')}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'billing.pushNudge.body',\n \"Turn on notifications and we'll let you know about billing updates like this one.\"\n )}\n </p>\n <div className=\"mt-3 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleEnable}\n disabled={enabling}\n className=\"px-3 py-1.5 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 disabled:opacity-60\"\n >\n {enabling\n ? tr('billing.pushNudge.enabling', 'Enabling…')\n : tr('billing.pushNudge.enable', 'Enable notifications')}\n </button>\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"px-3 py-1.5 text-sm font-medium text-text-secondary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n {tr('billing.pushNudge.notNow', 'Not now')}\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={handleDismiss}\n aria-label={tr('billing.pushNudge.close', 'Close')}\n className=\"p-1 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors duration-200 shrink-0\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAyBA,IAAa,KAAqD,EAAE,mBAAgB;CAClF,IAAM,IAAK,EAAM,GACX,CAAC,GAAU,KAAe,EAAS,EAAK,GAExC,IAAe,YAAY;EAC/B,IAAM,IAAa,EAAuB;EACtC,OAAC,KAAc,IACnB;KAAY,EAAI;GAChB,IAAI;IACF,MAAM,EAAW,OAAO;GAC1B,UAAU;IAER,AADA,EAAY,EAAK,GACjB,EAAU;GACZ;EANgB;CAOlB,GAEM,UAAsB;EAE1B,AADA,EAAoC,GACpC,EAAU;CACZ;CAEA,OACE,kBAAC,OAAD;EACE,MAAK;EACL,WAAU;YAEV,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAM,WAAU,6BAA8B,CAAA;IAC3C,CAAA;IACL,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,2BAA2B,6BAA6B;MAC3D,CAAA;MACH,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,0BACA,mFACF;MACC,CAAA;MACH,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAU;kBAET,IACG,EAAG,8BAA8B,WAAW,IAC5C,EAAG,4BAA4B,sBAAsB;OACnD,CAAA,GACR,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAU;kBAET,EAAG,4BAA4B,SAAS;OACnC,CAAA,CACL;;KACF;;IACL,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,cAAY,EAAG,2BAA2B,OAAO;KACjD,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;IACjB,CAAA;GACL;;CACF,CAAA;AAET"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-billing",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1012.1",
|
|
4
4
|
"description": "Billing microfrontend for Burdenoff products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@apollo/client": "^4.1.4",
|
|
47
|
-
"@burdenoff/fe-libs": "2026.
|
|
47
|
+
"@burdenoff/fe-libs": "2026.1012.1",
|
|
48
48
|
"@tanstack/react-query": "^5.90.16",
|
|
49
49
|
"clsx": "^2.1.1",
|
|
50
50
|
"graphql": "^16.10.0",
|