@burdenoff/microfe-billing 2026.624.3 → 2026.625.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingAdminRoutes.js +1 -1
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +2 -2
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +4 -4
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +2 -2
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +98 -99
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +88 -85
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +6 -6
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +2 -2
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +360 -370
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +2 -2
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +2 -2
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +102 -104
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +5 -5
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +29 -28
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +2 -2
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +3 -3
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +2 -2
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +139 -142
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +6 -6
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +4 -4
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +142 -133
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +2 -2
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +4 -4
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +2 -2
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +4 -4
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +2 -2
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +2 -2
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +2 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +5 -5
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +2 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +259 -265
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +7 -7
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +13 -13
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/ui/Card.js +1 -1
- package/dist/billing/shared/ui/Card.js.map +1 -1
- package/dist/billing/shared/ui/MetricCard.js +28 -21
- package/dist/billing/shared/ui/MetricCard.js.map +1 -1
- package/package.json +2 -2
|
@@ -14,9 +14,9 @@ import { addAffiliateCodeNote as d, createAffiliateCode as f, deactivateAffiliat
|
|
|
14
14
|
import { useEffect as v, useMemo as y, useState as b } from "react";
|
|
15
15
|
import { Link2 as x, Plus as S, RefreshCw as C, StickyNote as w, Ticket as T, TrendingUp as E, Users as D } from "lucide-react";
|
|
16
16
|
import { jsx as O, jsxs as k } from "react/jsx-runtime";
|
|
17
|
-
import {
|
|
17
|
+
import { CTAOverflowMenu as A, ResponsiveTable as j } from "@burdenoff/fe-libs/ui";
|
|
18
18
|
//#region src/billing/modules/affiliate/pages/AffiliateDashboardPage.tsx
|
|
19
|
-
var
|
|
19
|
+
var M = [
|
|
20
20
|
{
|
|
21
21
|
key: "code",
|
|
22
22
|
header: "Code",
|
|
@@ -48,86 +48,86 @@ var j = [
|
|
|
48
48
|
header: "Visited",
|
|
49
49
|
cell: (e) => c(e.lastVisitedAt)
|
|
50
50
|
}
|
|
51
|
-
],
|
|
52
|
-
let c = u(), { emit:
|
|
51
|
+
], N = () => {
|
|
52
|
+
let c = u(), { emit: N } = o(), { apiGatewayUrl: P, authToken: F, orgId: I } = e(), [L, R] = b([]), [z, B] = b([]), [V, H] = b(null), [U, W] = b(!0), [G, K] = b(null), [q, J] = b(null), [Y, X] = b({
|
|
53
53
|
label: "",
|
|
54
54
|
description: "",
|
|
55
55
|
notes: "",
|
|
56
56
|
productSlug: "",
|
|
57
57
|
destinationPath: "",
|
|
58
58
|
destinationUrl: ""
|
|
59
|
-
}),
|
|
60
|
-
apiGatewayUrl:
|
|
61
|
-
authToken:
|
|
62
|
-
orgId:
|
|
59
|
+
}), Z = y(() => ({
|
|
60
|
+
apiGatewayUrl: P,
|
|
61
|
+
authToken: F,
|
|
62
|
+
orgId: I
|
|
63
63
|
}), [
|
|
64
|
-
N,
|
|
65
64
|
P,
|
|
66
|
-
F
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
F,
|
|
66
|
+
I
|
|
67
|
+
]), Q = async () => {
|
|
68
|
+
W(!0), K(null);
|
|
69
69
|
try {
|
|
70
70
|
let [e, t, n] = await Promise.all([
|
|
71
|
-
h(
|
|
72
|
-
m(
|
|
73
|
-
g(
|
|
71
|
+
h(Z),
|
|
72
|
+
m(Z),
|
|
73
|
+
g(Z)
|
|
74
74
|
]);
|
|
75
|
-
|
|
75
|
+
R(e), H(t), B(n);
|
|
76
76
|
} catch (e) {
|
|
77
|
-
|
|
77
|
+
K(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to load affiliate dashboard"));
|
|
78
78
|
} finally {
|
|
79
|
-
|
|
79
|
+
W(!1);
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
if (v(() => {
|
|
83
|
-
|
|
84
|
-
}, [
|
|
85
|
-
if (
|
|
83
|
+
Q();
|
|
84
|
+
}, [Z]), !c.canViewBillingAccount) return /* @__PURE__ */ O(t, { message: "You don't have permission to view affiliate analytics." });
|
|
85
|
+
if (G && l(G)) return /* @__PURE__ */ O(a, {
|
|
86
86
|
title: "Affiliate service unavailable",
|
|
87
|
-
message:
|
|
88
|
-
onRetry: () => void
|
|
87
|
+
message: G.message,
|
|
88
|
+
onRetry: () => void Q(),
|
|
89
89
|
showRetry: !0
|
|
90
90
|
});
|
|
91
|
-
let
|
|
92
|
-
if (!
|
|
93
|
-
|
|
91
|
+
let $ = L.find((e) => e.id === q) ?? null, ee = async () => {
|
|
92
|
+
if (!Y.productSlug.trim()) {
|
|
93
|
+
K(/* @__PURE__ */ Error("Product slug is required"));
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
try {
|
|
97
|
-
|
|
98
|
-
label:
|
|
99
|
-
description:
|
|
100
|
-
notes:
|
|
101
|
-
destinationPath:
|
|
102
|
-
destinationUrl:
|
|
103
|
-
},
|
|
97
|
+
$ ? (await _($.id, {
|
|
98
|
+
label: Y.label,
|
|
99
|
+
description: Y.description,
|
|
100
|
+
notes: Y.notes,
|
|
101
|
+
destinationPath: Y.destinationPath,
|
|
102
|
+
destinationUrl: Y.destinationUrl
|
|
103
|
+
}, Z), N("billing.affiliate_code.updated", {
|
|
104
104
|
route: "/billing/affiliate",
|
|
105
|
-
entityId:
|
|
105
|
+
entityId: $.id,
|
|
106
106
|
source: "affiliate-dashboard"
|
|
107
|
-
})) :
|
|
107
|
+
})) : N("billing.affiliate_code.created", {
|
|
108
108
|
route: "/billing/affiliate",
|
|
109
109
|
entityId: (await f({
|
|
110
|
-
label:
|
|
111
|
-
description:
|
|
112
|
-
notes:
|
|
113
|
-
productSlug:
|
|
114
|
-
destinationPath:
|
|
115
|
-
destinationUrl:
|
|
116
|
-
},
|
|
110
|
+
label: Y.label,
|
|
111
|
+
description: Y.description,
|
|
112
|
+
notes: Y.notes,
|
|
113
|
+
productSlug: Y.productSlug,
|
|
114
|
+
destinationPath: Y.destinationPath,
|
|
115
|
+
destinationUrl: Y.destinationUrl
|
|
116
|
+
}, Z)).id,
|
|
117
117
|
source: "affiliate-dashboard"
|
|
118
|
-
}),
|
|
118
|
+
}), J(null), X({
|
|
119
119
|
label: "",
|
|
120
120
|
description: "",
|
|
121
121
|
notes: "",
|
|
122
122
|
productSlug: "",
|
|
123
123
|
destinationPath: "",
|
|
124
124
|
destinationUrl: ""
|
|
125
|
-
}), await
|
|
125
|
+
}), await Q();
|
|
126
126
|
} catch (e) {
|
|
127
|
-
|
|
127
|
+
K(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to save affiliate code"));
|
|
128
128
|
}
|
|
129
|
-
},
|
|
130
|
-
|
|
129
|
+
}, te = (e) => {
|
|
130
|
+
J(e.id), X({
|
|
131
131
|
label: e.label ?? "",
|
|
132
132
|
description: e.description ?? "",
|
|
133
133
|
notes: e.notes ?? "",
|
|
@@ -135,38 +135,38 @@ var j = [
|
|
|
135
135
|
destinationPath: e.destinationPath ?? "",
|
|
136
136
|
destinationUrl: e.destinationUrl ?? ""
|
|
137
137
|
});
|
|
138
|
-
},
|
|
139
|
-
await p(e,
|
|
138
|
+
}, ne = async (e) => {
|
|
139
|
+
await p(e, Z), N("billing.affiliate_code.deactivated", {
|
|
140
140
|
route: "/billing/affiliate",
|
|
141
141
|
entityId: e,
|
|
142
142
|
source: "affiliate-dashboard"
|
|
143
|
-
}), await
|
|
144
|
-
},
|
|
143
|
+
}), await Q();
|
|
144
|
+
}, re = async (e) => {
|
|
145
145
|
let t = window.prompt("Add an internal note for this affiliate code");
|
|
146
|
-
t && (await d(e, t,
|
|
146
|
+
t && (await d(e, t, Z), N("billing.affiliate_code.note_added", {
|
|
147
147
|
route: "/billing/affiliate",
|
|
148
148
|
entityId: e,
|
|
149
149
|
source: "affiliate-dashboard"
|
|
150
|
-
}), await
|
|
151
|
-
},
|
|
150
|
+
}), await Q());
|
|
151
|
+
}, ie = V ? [
|
|
152
152
|
{
|
|
153
153
|
title: "Codes",
|
|
154
|
-
value:
|
|
154
|
+
value: V.totalCodes,
|
|
155
155
|
icon: /* @__PURE__ */ O(T, { className: "size-5" })
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
title: "Visits",
|
|
159
|
-
value:
|
|
159
|
+
value: V.totalVisits,
|
|
160
160
|
icon: /* @__PURE__ */ O(D, { className: "size-5" })
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
title: "Journeys",
|
|
164
|
-
value:
|
|
164
|
+
value: V.totalJourneys,
|
|
165
165
|
icon: /* @__PURE__ */ O(x, { className: "size-5" })
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
title: "Revenue influenced",
|
|
169
|
-
value: s(
|
|
169
|
+
value: s(V.totalRevenueAmount, "USD"),
|
|
170
170
|
icon: /* @__PURE__ */ O(E, { className: "size-5" })
|
|
171
171
|
}
|
|
172
172
|
] : [];
|
|
@@ -176,14 +176,13 @@ var j = [
|
|
|
176
176
|
/* @__PURE__ */ O(n, {
|
|
177
177
|
title: "Affiliate codes",
|
|
178
178
|
description: "Create referral codes, add internal notes, and monitor attributed journeys.",
|
|
179
|
-
actions: /* @__PURE__ */
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
})
|
|
179
|
+
actions: /* @__PURE__ */ O(A, { actions: [{
|
|
180
|
+
label: "Refresh",
|
|
181
|
+
onSelect: () => void Q(),
|
|
182
|
+
icon: /* @__PURE__ */ O(C, { className: "size-4" })
|
|
183
|
+
}] })
|
|
185
184
|
}),
|
|
186
|
-
|
|
185
|
+
U ? /* @__PURE__ */ O("div", {
|
|
187
186
|
className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4",
|
|
188
187
|
children: [
|
|
189
188
|
1,
|
|
@@ -191,36 +190,36 @@ var j = [
|
|
|
191
190
|
3,
|
|
192
191
|
4
|
|
193
192
|
].map((e) => /* @__PURE__ */ O("div", { className: "h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface" }, e))
|
|
194
|
-
}) :
|
|
193
|
+
}) : V ? /* @__PURE__ */ O("div", {
|
|
195
194
|
className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4",
|
|
196
|
-
children:
|
|
195
|
+
children: ie.map((e) => /* @__PURE__ */ O(r, {
|
|
197
196
|
label: e.title,
|
|
198
197
|
value: e.value,
|
|
199
198
|
icon: e.icon
|
|
200
199
|
}, e.title))
|
|
201
200
|
}) : null,
|
|
202
|
-
|
|
201
|
+
G && !l(G) ? /* @__PURE__ */ O("div", {
|
|
203
202
|
className: "rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text",
|
|
204
|
-
children:
|
|
203
|
+
children: G.message
|
|
205
204
|
}) : null,
|
|
206
205
|
/* @__PURE__ */ k("div", {
|
|
207
206
|
className: "grid gap-6 xl:grid-cols-[1.1fr,1.9fr]",
|
|
208
207
|
children: [/* @__PURE__ */ k("section", {
|
|
209
|
-
className: "rounded-lg border border-border-
|
|
208
|
+
className: "rounded-lg border border-border-seam bg-bg-surface p-4 xl:sticky xl:top-6 xl:self-start",
|
|
210
209
|
children: [
|
|
211
210
|
/* @__PURE__ */ k("div", {
|
|
212
211
|
className: "mb-4 flex items-center justify-between",
|
|
213
212
|
children: [/* @__PURE__ */ O("h2", {
|
|
214
213
|
className: "text-lg font-semibold text-text-primary",
|
|
215
|
-
children:
|
|
216
|
-
}),
|
|
214
|
+
children: $ ? "Update affiliate code" : "Create affiliate code"
|
|
215
|
+
}), $ ? null : /* @__PURE__ */ O(S, { className: "size-4 text-text-secondary" })]
|
|
217
216
|
}),
|
|
218
217
|
/* @__PURE__ */ k("div", {
|
|
219
218
|
className: "space-y-3",
|
|
220
219
|
children: [
|
|
221
220
|
/* @__PURE__ */ O("input", {
|
|
222
|
-
value:
|
|
223
|
-
onChange: (e) =>
|
|
221
|
+
value: Y.label,
|
|
222
|
+
onChange: (e) => X((t) => ({
|
|
224
223
|
...t,
|
|
225
224
|
label: e.target.value
|
|
226
225
|
})),
|
|
@@ -228,18 +227,18 @@ var j = [
|
|
|
228
227
|
className: "w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary"
|
|
229
228
|
}),
|
|
230
229
|
/* @__PURE__ */ O("input", {
|
|
231
|
-
value:
|
|
232
|
-
onChange: (e) =>
|
|
230
|
+
value: Y.productSlug,
|
|
231
|
+
onChange: (e) => X((t) => ({
|
|
233
232
|
...t,
|
|
234
233
|
productSlug: e.target.value
|
|
235
234
|
})),
|
|
236
235
|
placeholder: "Product slug (eg. bigconsole)",
|
|
237
236
|
className: "w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary",
|
|
238
|
-
disabled:
|
|
237
|
+
disabled: !!$
|
|
239
238
|
}),
|
|
240
239
|
/* @__PURE__ */ O("input", {
|
|
241
|
-
value:
|
|
242
|
-
onChange: (e) =>
|
|
240
|
+
value: Y.destinationPath,
|
|
241
|
+
onChange: (e) => X((t) => ({
|
|
243
242
|
...t,
|
|
244
243
|
destinationPath: e.target.value
|
|
245
244
|
})),
|
|
@@ -247,8 +246,8 @@ var j = [
|
|
|
247
246
|
className: "w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary"
|
|
248
247
|
}),
|
|
249
248
|
/* @__PURE__ */ O("input", {
|
|
250
|
-
value:
|
|
251
|
-
onChange: (e) =>
|
|
249
|
+
value: Y.destinationUrl,
|
|
250
|
+
onChange: (e) => X((t) => ({
|
|
252
251
|
...t,
|
|
253
252
|
destinationUrl: e.target.value
|
|
254
253
|
})),
|
|
@@ -256,8 +255,8 @@ var j = [
|
|
|
256
255
|
className: "w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary"
|
|
257
256
|
}),
|
|
258
257
|
/* @__PURE__ */ O("textarea", {
|
|
259
|
-
value:
|
|
260
|
-
onChange: (e) =>
|
|
258
|
+
value: Y.description,
|
|
259
|
+
onChange: (e) => X((t) => ({
|
|
261
260
|
...t,
|
|
262
261
|
description: e.target.value
|
|
263
262
|
})),
|
|
@@ -265,8 +264,8 @@ var j = [
|
|
|
265
264
|
className: "min-h-24 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary"
|
|
266
265
|
}),
|
|
267
266
|
/* @__PURE__ */ O("textarea", {
|
|
268
|
-
value:
|
|
269
|
-
onChange: (e) =>
|
|
267
|
+
value: Y.notes,
|
|
268
|
+
onChange: (e) => X((t) => ({
|
|
270
269
|
...t,
|
|
271
270
|
notes: e.target.value
|
|
272
271
|
})),
|
|
@@ -279,13 +278,13 @@ var j = [
|
|
|
279
278
|
className: "mt-4 flex flex-wrap gap-3",
|
|
280
279
|
children: [/* @__PURE__ */ O("button", {
|
|
281
280
|
type: "button",
|
|
282
|
-
onClick: () => void
|
|
281
|
+
onClick: () => void ee(),
|
|
283
282
|
className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text",
|
|
284
|
-
children:
|
|
285
|
-
}),
|
|
283
|
+
children: $ ? "Save changes" : "Create code"
|
|
284
|
+
}), $ ? /* @__PURE__ */ O("button", {
|
|
286
285
|
type: "button",
|
|
287
286
|
onClick: () => {
|
|
288
|
-
|
|
287
|
+
J(null), X({
|
|
289
288
|
label: "",
|
|
290
289
|
description: "",
|
|
291
290
|
notes: "",
|
|
@@ -306,13 +305,13 @@ var j = [
|
|
|
306
305
|
children: [/* @__PURE__ */ O("h2", {
|
|
307
306
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
308
307
|
children: "My codes"
|
|
309
|
-
}),
|
|
308
|
+
}), L.length === 0 && !U ? /* @__PURE__ */ O(i, {
|
|
310
309
|
icon: /* @__PURE__ */ O(T, { className: "size-8" }),
|
|
311
310
|
title: "No affiliate codes yet",
|
|
312
311
|
description: "Create your first code to start tracking attributable journeys."
|
|
313
312
|
}) : /* @__PURE__ */ O("div", {
|
|
314
313
|
className: "space-y-4",
|
|
315
|
-
children:
|
|
314
|
+
children: L.map((e) => {
|
|
316
315
|
let t = e.destinationUrl || `https://${e.productSlug}.com${e.destinationPath || ""}?ref=${e.code}`;
|
|
317
316
|
return /* @__PURE__ */ k("div", {
|
|
318
317
|
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
@@ -341,19 +340,19 @@ var j = [
|
|
|
341
340
|
children: [
|
|
342
341
|
/* @__PURE__ */ O("button", {
|
|
343
342
|
type: "button",
|
|
344
|
-
onClick: () =>
|
|
343
|
+
onClick: () => te(e),
|
|
345
344
|
className: "rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary",
|
|
346
345
|
children: "Edit"
|
|
347
346
|
}),
|
|
348
347
|
/* @__PURE__ */ k("button", {
|
|
349
348
|
type: "button",
|
|
350
|
-
onClick: () => void
|
|
349
|
+
onClick: () => void re(e.id),
|
|
351
350
|
className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary",
|
|
352
351
|
children: [/* @__PURE__ */ O(w, { className: "size-3.5" }), "Note"]
|
|
353
352
|
}),
|
|
354
353
|
e.status === "ACTIVE" ? /* @__PURE__ */ O("button", {
|
|
355
354
|
type: "button",
|
|
356
|
-
onClick: () => void
|
|
355
|
+
onClick: () => void ne(e.id),
|
|
357
356
|
className: "rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text",
|
|
358
357
|
children: "Deactivate"
|
|
359
358
|
}) : null
|
|
@@ -418,14 +417,14 @@ var j = [
|
|
|
418
417
|
children: [/* @__PURE__ */ O("h2", {
|
|
419
418
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
420
419
|
children: "Recent attributed journeys"
|
|
421
|
-
}),
|
|
420
|
+
}), z.length === 0 ? /* @__PURE__ */ O(i, {
|
|
422
421
|
icon: /* @__PURE__ */ O(D, { className: "size-8" }),
|
|
423
422
|
title: "No journeys yet",
|
|
424
423
|
description: "Journeys will appear as visitors land on your affiliate links."
|
|
425
|
-
}) : /* @__PURE__ */ O(
|
|
426
|
-
data:
|
|
424
|
+
}) : /* @__PURE__ */ O(j, {
|
|
425
|
+
data: z,
|
|
427
426
|
rowKey: (e) => e.id,
|
|
428
|
-
columns:
|
|
427
|
+
columns: M
|
|
429
428
|
})]
|
|
430
429
|
})]
|
|
431
430
|
})]
|
|
@@ -434,6 +433,6 @@ var j = [
|
|
|
434
433
|
});
|
|
435
434
|
};
|
|
436
435
|
//#endregion
|
|
437
|
-
export {
|
|
436
|
+
export { N as AffiliateDashboardPage };
|
|
438
437
|
|
|
439
438
|
//# sourceMappingURL=AffiliateDashboardPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AffiliateDashboardPage.js","names":[],"sources":["../../../../../src/billing/modules/affiliate/pages/AffiliateDashboardPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport { Link2, Plus, RefreshCw, StickyNote, Ticket, TrendingUp, Users } from 'lucide-react';\nimport { ResponsiveTable, type ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\n\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n EmptyState,\n PageHeader,\n ServerError,\n StatCard,\n} from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\n\nconst journeyColumns: ResponsiveTableColumn<AffiliateJourney>[] = [\n { key: 'code', header: 'Code', priority: 'primary', cell: (journey) => journey.codeValue },\n { key: 'product', header: 'Product', cell: (journey) => journey.productSlug },\n {\n key: 'path',\n header: 'Path',\n cell: (journey) => journey.currentPath || journey.landingPath || '-',\n },\n { key: 'country', header: 'Country', cell: (journey) => journey.geographyCountry || '-' },\n { key: 'status', header: 'Status', cell: (journey) => journey.status },\n {\n key: 'visited',\n header: 'Visited',\n cell: (journey) => formatDateTime(journey.lastVisitedAt),\n },\n];\nimport {\n addAffiliateCodeNote,\n createAffiliateCode,\n deactivateAffiliateCode,\n fetchMyAffiliateAnalytics,\n fetchMyAffiliateCodes,\n fetchMyAffiliateJourneys,\n updateAffiliateCode,\n type AffiliateCode,\n type AffiliateJourney,\n type AffiliateAnalyticsSummary,\n} from '../api';\n\nexport const AffiliateDashboardPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n const [codes, setCodes] = useState<AffiliateCode[]>([]);\n const [journeys, setJourneys] = useState<AffiliateJourney[]>([]);\n const [analytics, setAnalytics] = useState<AffiliateAnalyticsSummary | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [selectedCodeId, setSelectedCodeId] = useState<string | null>(null);\n const [form, setForm] = useState({\n label: '',\n description: '',\n notes: '',\n productSlug: '',\n destinationPath: '',\n destinationUrl: '',\n });\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n orgId,\n }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [loadedCodes, loadedAnalytics, loadedJourneys] = await Promise.all([\n fetchMyAffiliateCodes(requestContext),\n fetchMyAffiliateAnalytics(requestContext),\n fetchMyAffiliateJourneys(requestContext),\n ]);\n setCodes(loadedCodes);\n setAnalytics(loadedAnalytics);\n setJourneys(loadedJourneys);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError : new Error('Failed to load affiliate dashboard')\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view affiliate analytics.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Affiliate service unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const selectedCode = codes.find((item) => item.id === selectedCodeId) ?? null;\n\n const handleSubmit = async () => {\n if (!form.productSlug.trim()) {\n setError(new Error('Product slug is required'));\n return;\n }\n\n try {\n if (selectedCode) {\n await updateAffiliateCode(\n selectedCode.id,\n {\n label: form.label,\n description: form.description,\n notes: form.notes,\n destinationPath: form.destinationPath,\n destinationUrl: form.destinationUrl,\n },\n requestContext\n );\n emit('billing.affiliate_code.updated', {\n route: '/billing/affiliate',\n entityId: selectedCode.id,\n source: 'affiliate-dashboard',\n });\n } else {\n const createdCode = await createAffiliateCode(\n {\n label: form.label,\n description: form.description,\n notes: form.notes,\n productSlug: form.productSlug,\n destinationPath: form.destinationPath,\n destinationUrl: form.destinationUrl,\n },\n requestContext\n );\n emit('billing.affiliate_code.created', {\n route: '/billing/affiliate',\n entityId: createdCode.id,\n source: 'affiliate-dashboard',\n });\n }\n\n setSelectedCodeId(null);\n setForm({\n label: '',\n description: '',\n notes: '',\n productSlug: '',\n destinationPath: '',\n destinationUrl: '',\n });\n await loadData();\n } catch (submitError) {\n setError(\n submitError instanceof Error ? submitError : new Error('Failed to save affiliate code')\n );\n }\n };\n\n const handleEdit = (code: AffiliateCode) => {\n setSelectedCodeId(code.id);\n setForm({\n label: code.label ?? '',\n description: code.description ?? '',\n notes: code.notes ?? '',\n productSlug: code.productSlug,\n destinationPath: code.destinationPath ?? '',\n destinationUrl: code.destinationUrl ?? '',\n });\n };\n\n const handleDeactivate = async (codeId: string) => {\n await deactivateAffiliateCode(codeId, requestContext);\n emit('billing.affiliate_code.deactivated', {\n route: '/billing/affiliate',\n entityId: codeId,\n source: 'affiliate-dashboard',\n });\n await loadData();\n };\n\n const handleQuickNote = async (codeId: string) => {\n const note = window.prompt('Add an internal note for this affiliate code');\n if (!note) return;\n await addAffiliateCodeNote(codeId, note, requestContext);\n emit('billing.affiliate_code.note_added', {\n route: '/billing/affiliate',\n entityId: codeId,\n source: 'affiliate-dashboard',\n });\n await loadData();\n };\n\n const metricCards = analytics\n ? [\n { title: 'Codes', value: analytics.totalCodes, icon: <Ticket className=\"size-5\" /> },\n { title: 'Visits', value: analytics.totalVisits, icon: <Users className=\"size-5\" /> },\n { title: 'Journeys', value: analytics.totalJourneys, icon: <Link2 className=\"size-5\" /> },\n {\n title: 'Revenue influenced',\n value: formatCurrency(analytics.totalRevenueAmount, 'USD'),\n icon: <TrendingUp className=\"size-5\" />,\n },\n ]\n : [];\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Affiliate codes\"\n description=\"Create referral codes, add internal notes, and monitor attributed journeys.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n }\n />\n\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : analytics ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {metricCards.map((card) => (\n <StatCard key={card.title} label={card.title} value={card.value} icon={card.icon} />\n ))}\n </div>\n ) : null}\n\n {error && !isServerError(error) ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.1fr,1.9fr]\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {selectedCode ? 'Update affiliate code' : 'Create affiliate code'}\n </h2>\n {!selectedCode ? <Plus className=\"size-4 text-text-secondary\" /> : null}\n </div>\n\n <div className=\"space-y-3\">\n <input\n value={form.label}\n onChange={(event) =>\n setForm((current) => ({ ...current, label: event.target.value }))\n }\n placeholder=\"Label\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={form.productSlug}\n onChange={(event) =>\n setForm((current) => ({ ...current, productSlug: event.target.value }))\n }\n placeholder=\"Product slug (eg. bigconsole)\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n disabled={Boolean(selectedCode)}\n />\n <input\n value={form.destinationPath}\n onChange={(event) =>\n setForm((current) => ({ ...current, destinationPath: event.target.value }))\n }\n placeholder=\"Destination path (eg. /pricing)\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={form.destinationUrl}\n onChange={(event) =>\n setForm((current) => ({ ...current, destinationUrl: event.target.value }))\n }\n placeholder=\"Absolute destination URL (optional)\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <textarea\n value={form.description}\n onChange={(event) =>\n setForm((current) => ({ ...current, description: event.target.value }))\n }\n placeholder=\"Description\"\n className=\"min-h-24 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <textarea\n value={form.notes}\n onChange={(event) =>\n setForm((current) => ({ ...current, notes: event.target.value }))\n }\n placeholder=\"Internal notes\"\n className=\"min-h-24 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={() => void handleSubmit()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n {selectedCode ? 'Save changes' : 'Create code'}\n </button>\n {selectedCode ? (\n <button\n type=\"button\"\n onClick={() => {\n setSelectedCodeId(null);\n setForm({\n label: '',\n description: '',\n notes: '',\n productSlug: '',\n destinationPath: '',\n destinationUrl: '',\n });\n }}\n className=\"rounded-md border border-border-subtle bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary\"\n >\n Cancel\n </button>\n ) : null}\n </div>\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">My codes</h2>\n {codes.length === 0 && !loading ? (\n <EmptyState\n icon={<Ticket className=\"size-8\" />}\n title=\"No affiliate codes yet\"\n description=\"Create your first code to start tracking attributable journeys.\"\n />\n ) : (\n <div className=\"space-y-4\">\n {codes.map((code) => {\n const shareUrl =\n code.destinationUrl ||\n `https://${code.productSlug}.com${code.destinationPath || ''}?ref=${code.code}`;\n return (\n <div\n key={code.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {code.label || code.code}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {code.code} · {code.productSlug}\n </p>\n <p className=\"mt-1 break-all text-xs text-text-secondary\">{shareUrl}</p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => handleEdit(code)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n onClick={() => void handleQuickNote(code.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary\"\n >\n <StickyNote className=\"size-3.5\" />\n Note\n </button>\n {code.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => void handleDeactivate(code.id)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n Deactivate\n </button>\n ) : null}\n </div>\n </div>\n <div className=\"mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-4\">\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Visits</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {code.totalVisits}\n </p>\n </div>\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Signups</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {code.totalSignups}\n </p>\n </div>\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Paid conversions</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {code.totalPaidConversions + code.totalStorePurchases}\n </p>\n </div>\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Rewards earned</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {formatCurrency(code.totalRewardAmount, 'USD')}\n </p>\n </div>\n </div>\n {code.notes ? (\n <p className=\"mt-3 text-sm text-text-secondary\">{code.notes}</p>\n ) : null}\n </div>\n );\n })}\n </div>\n )}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Recent attributed journeys\n </h2>\n {journeys.length === 0 ? (\n <EmptyState\n icon={<Users className=\"size-8\" />}\n title=\"No journeys yet\"\n description=\"Journeys will appear as visitors land on your affiliate links.\"\n />\n ) : (\n <ResponsiveTable\n data={journeys}\n rowKey={(journey) => journey.id}\n columns={journeyColumns}\n />\n )}\n </div>\n </section>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AffiliateDashboardPage.js","names":[],"sources":["../../../../../src/billing/modules/affiliate/pages/AffiliateDashboardPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport { Link2, Plus, RefreshCw, StickyNote, Ticket, TrendingUp, Users } from 'lucide-react';\nimport { ResponsiveTable, type ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\n\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n EmptyState,\n PageHeader,\n ServerError,\n StatCard,\n} from '../../../shared/components';\nimport { CTAOverflowMenu } from '@burdenoff/fe-libs/ui';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\n\nconst journeyColumns: ResponsiveTableColumn<AffiliateJourney>[] = [\n { key: 'code', header: 'Code', priority: 'primary', cell: (journey) => journey.codeValue },\n { key: 'product', header: 'Product', cell: (journey) => journey.productSlug },\n {\n key: 'path',\n header: 'Path',\n cell: (journey) => journey.currentPath || journey.landingPath || '-',\n },\n { key: 'country', header: 'Country', cell: (journey) => journey.geographyCountry || '-' },\n { key: 'status', header: 'Status', cell: (journey) => journey.status },\n {\n key: 'visited',\n header: 'Visited',\n cell: (journey) => formatDateTime(journey.lastVisitedAt),\n },\n];\nimport {\n addAffiliateCodeNote,\n createAffiliateCode,\n deactivateAffiliateCode,\n fetchMyAffiliateAnalytics,\n fetchMyAffiliateCodes,\n fetchMyAffiliateJourneys,\n updateAffiliateCode,\n type AffiliateCode,\n type AffiliateJourney,\n type AffiliateAnalyticsSummary,\n} from '../api';\n\nexport const AffiliateDashboardPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n const [codes, setCodes] = useState<AffiliateCode[]>([]);\n const [journeys, setJourneys] = useState<AffiliateJourney[]>([]);\n const [analytics, setAnalytics] = useState<AffiliateAnalyticsSummary | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [selectedCodeId, setSelectedCodeId] = useState<string | null>(null);\n const [form, setForm] = useState({\n label: '',\n description: '',\n notes: '',\n productSlug: '',\n destinationPath: '',\n destinationUrl: '',\n });\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n orgId,\n }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [loadedCodes, loadedAnalytics, loadedJourneys] = await Promise.all([\n fetchMyAffiliateCodes(requestContext),\n fetchMyAffiliateAnalytics(requestContext),\n fetchMyAffiliateJourneys(requestContext),\n ]);\n setCodes(loadedCodes);\n setAnalytics(loadedAnalytics);\n setJourneys(loadedJourneys);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError : new Error('Failed to load affiliate dashboard')\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view affiliate analytics.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Affiliate service unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const selectedCode = codes.find((item) => item.id === selectedCodeId) ?? null;\n\n const handleSubmit = async () => {\n if (!form.productSlug.trim()) {\n setError(new Error('Product slug is required'));\n return;\n }\n\n try {\n if (selectedCode) {\n await updateAffiliateCode(\n selectedCode.id,\n {\n label: form.label,\n description: form.description,\n notes: form.notes,\n destinationPath: form.destinationPath,\n destinationUrl: form.destinationUrl,\n },\n requestContext\n );\n emit('billing.affiliate_code.updated', {\n route: '/billing/affiliate',\n entityId: selectedCode.id,\n source: 'affiliate-dashboard',\n });\n } else {\n const createdCode = await createAffiliateCode(\n {\n label: form.label,\n description: form.description,\n notes: form.notes,\n productSlug: form.productSlug,\n destinationPath: form.destinationPath,\n destinationUrl: form.destinationUrl,\n },\n requestContext\n );\n emit('billing.affiliate_code.created', {\n route: '/billing/affiliate',\n entityId: createdCode.id,\n source: 'affiliate-dashboard',\n });\n }\n\n setSelectedCodeId(null);\n setForm({\n label: '',\n description: '',\n notes: '',\n productSlug: '',\n destinationPath: '',\n destinationUrl: '',\n });\n await loadData();\n } catch (submitError) {\n setError(\n submitError instanceof Error ? submitError : new Error('Failed to save affiliate code')\n );\n }\n };\n\n const handleEdit = (code: AffiliateCode) => {\n setSelectedCodeId(code.id);\n setForm({\n label: code.label ?? '',\n description: code.description ?? '',\n notes: code.notes ?? '',\n productSlug: code.productSlug,\n destinationPath: code.destinationPath ?? '',\n destinationUrl: code.destinationUrl ?? '',\n });\n };\n\n const handleDeactivate = async (codeId: string) => {\n await deactivateAffiliateCode(codeId, requestContext);\n emit('billing.affiliate_code.deactivated', {\n route: '/billing/affiliate',\n entityId: codeId,\n source: 'affiliate-dashboard',\n });\n await loadData();\n };\n\n const handleQuickNote = async (codeId: string) => {\n const note = window.prompt('Add an internal note for this affiliate code');\n if (!note) return;\n await addAffiliateCodeNote(codeId, note, requestContext);\n emit('billing.affiliate_code.note_added', {\n route: '/billing/affiliate',\n entityId: codeId,\n source: 'affiliate-dashboard',\n });\n await loadData();\n };\n\n const metricCards = analytics\n ? [\n { title: 'Codes', value: analytics.totalCodes, icon: <Ticket className=\"size-5\" /> },\n { title: 'Visits', value: analytics.totalVisits, icon: <Users className=\"size-5\" /> },\n { title: 'Journeys', value: analytics.totalJourneys, icon: <Link2 className=\"size-5\" /> },\n {\n title: 'Revenue influenced',\n value: formatCurrency(analytics.totalRevenueAmount, 'USD'),\n icon: <TrendingUp className=\"size-5\" />,\n },\n ]\n : [];\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Affiliate codes\"\n description=\"Create referral codes, add internal notes, and monitor attributed journeys.\"\n actions={\n <CTAOverflowMenu\n actions={[\n {\n label: 'Refresh',\n onSelect: () => void loadData(),\n icon: <RefreshCw className=\"size-4\" />,\n },\n ]}\n />\n }\n />\n\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : analytics ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {metricCards.map((card) => (\n <StatCard key={card.title} label={card.title} value={card.value} icon={card.icon} />\n ))}\n </div>\n ) : null}\n\n {error && !isServerError(error) ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.1fr,1.9fr]\">\n <section className=\"rounded-lg border border-border-seam bg-bg-surface p-4 xl:sticky xl:top-6 xl:self-start\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {selectedCode ? 'Update affiliate code' : 'Create affiliate code'}\n </h2>\n {!selectedCode ? <Plus className=\"size-4 text-text-secondary\" /> : null}\n </div>\n\n <div className=\"space-y-3\">\n <input\n value={form.label}\n onChange={(event) =>\n setForm((current) => ({ ...current, label: event.target.value }))\n }\n placeholder=\"Label\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={form.productSlug}\n onChange={(event) =>\n setForm((current) => ({ ...current, productSlug: event.target.value }))\n }\n placeholder=\"Product slug (eg. bigconsole)\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n disabled={Boolean(selectedCode)}\n />\n <input\n value={form.destinationPath}\n onChange={(event) =>\n setForm((current) => ({ ...current, destinationPath: event.target.value }))\n }\n placeholder=\"Destination path (eg. /pricing)\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={form.destinationUrl}\n onChange={(event) =>\n setForm((current) => ({ ...current, destinationUrl: event.target.value }))\n }\n placeholder=\"Absolute destination URL (optional)\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <textarea\n value={form.description}\n onChange={(event) =>\n setForm((current) => ({ ...current, description: event.target.value }))\n }\n placeholder=\"Description\"\n className=\"min-h-24 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <textarea\n value={form.notes}\n onChange={(event) =>\n setForm((current) => ({ ...current, notes: event.target.value }))\n }\n placeholder=\"Internal notes\"\n className=\"min-h-24 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={() => void handleSubmit()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n {selectedCode ? 'Save changes' : 'Create code'}\n </button>\n {selectedCode ? (\n <button\n type=\"button\"\n onClick={() => {\n setSelectedCodeId(null);\n setForm({\n label: '',\n description: '',\n notes: '',\n productSlug: '',\n destinationPath: '',\n destinationUrl: '',\n });\n }}\n className=\"rounded-md border border-border-subtle bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary\"\n >\n Cancel\n </button>\n ) : null}\n </div>\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">My codes</h2>\n {codes.length === 0 && !loading ? (\n <EmptyState\n icon={<Ticket className=\"size-8\" />}\n title=\"No affiliate codes yet\"\n description=\"Create your first code to start tracking attributable journeys.\"\n />\n ) : (\n <div className=\"space-y-4\">\n {codes.map((code) => {\n const shareUrl =\n code.destinationUrl ||\n `https://${code.productSlug}.com${code.destinationPath || ''}?ref=${code.code}`;\n return (\n <div\n key={code.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {code.label || code.code}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {code.code} · {code.productSlug}\n </p>\n <p className=\"mt-1 break-all text-xs text-text-secondary\">{shareUrl}</p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => handleEdit(code)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n onClick={() => void handleQuickNote(code.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary\"\n >\n <StickyNote className=\"size-3.5\" />\n Note\n </button>\n {code.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => void handleDeactivate(code.id)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n Deactivate\n </button>\n ) : null}\n </div>\n </div>\n <div className=\"mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-4\">\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Visits</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {code.totalVisits}\n </p>\n </div>\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Signups</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {code.totalSignups}\n </p>\n </div>\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Paid conversions</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {code.totalPaidConversions + code.totalStorePurchases}\n </p>\n </div>\n <div className=\"rounded-md border border-border-subtle bg-bg-surface p-3\">\n <p className=\"text-xs text-text-secondary\">Rewards earned</p>\n <p className=\"text-lg font-semibold text-text-primary\">\n {formatCurrency(code.totalRewardAmount, 'USD')}\n </p>\n </div>\n </div>\n {code.notes ? (\n <p className=\"mt-3 text-sm text-text-secondary\">{code.notes}</p>\n ) : null}\n </div>\n );\n })}\n </div>\n )}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Recent attributed journeys\n </h2>\n {journeys.length === 0 ? (\n <EmptyState\n icon={<Users className=\"size-8\" />}\n title=\"No journeys yet\"\n description=\"Journeys will appear as visitors land on your affiliate links.\"\n />\n ) : (\n <ResponsiveTable\n data={journeys}\n rowKey={(journey) => journey.id}\n columns={journeyColumns}\n />\n )}\n </div>\n </section>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|