@burdenoff/microfe-subscriberbot 2026.827.1 → 2026.829.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/subscriberbot/admin/AdminDashboardPage.d.ts.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.js +80 -83
- package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
- package/dist/subscriberbot/hooks/index.d.ts +1 -0
- package/dist/subscriberbot/hooks/index.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/index.js +1 -0
- package/dist/subscriberbot/hooks/useTr.d.ts +17 -0
- package/dist/subscriberbot/hooks/useTr.d.ts.map +1 -0
- package/dist/subscriberbot/hooks/useTr.js +14 -0
- package/dist/subscriberbot/hooks/useTr.js.map +1 -0
- package/dist/subscriberbot/pages/AssetVaultPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/AssetVaultPage.js +111 -109
- package/dist/subscriberbot/pages/AssetVaultPage.js.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.js +113 -105
- package/dist/subscriberbot/pages/DashboardPage.js.map +1 -1
- package/dist/subscriberbot/pages/InboxPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/InboxPage.js +404 -369
- package/dist/subscriberbot/pages/InboxPage.js.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.js +94 -93
- package/dist/subscriberbot/pages/InsightsPage.js.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.js +360 -366
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
- package/dist/subscriberbot/pages/WorkflowsPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/WorkflowsPage.js +209 -193
- package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js +268 -236
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentNewPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentNewPage.js +96 -76
- package/dist/subscriberbot/pages/agents/AgentNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentsListPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentsListPage.js +145 -161
- package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +514 -441
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderNewPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js +167 -129
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProvidersListPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js +171 -218
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +340 -279
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js +185 -166
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js +153 -182
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,8 +8,9 @@ import { ErrorView as c, LoadingView as l, Section as u } from "../components/St
|
|
|
8
8
|
import { Donut as d, Legend as f, Timeline as p } from "../components/viz.js";
|
|
9
9
|
import "../components/index.js";
|
|
10
10
|
import { useInboxMessages as m, useInvoices as h, useRelationships as g, useRenewalEvents as _ } from "../hooks/useSubscriberBotApi.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { useTr as v } from "../hooks/useTr.js";
|
|
12
|
+
import { useMemo as y } from "react";
|
|
13
|
+
import { AlertTriangle as b, ArrowRight as x, ArrowUpRight as S, Bot as C, CalendarClock as w, Inbox as T, Repeat2 as E, ShieldCheck as ee, Sparkles as D, Wallet as O } from "lucide-react";
|
|
13
14
|
import { jsx as k, jsxs as A } from "react/jsx-runtime";
|
|
14
15
|
import { GlassCard as j, PagePurpose as M } from "@burdenoff/fe-libs/ui";
|
|
15
16
|
//#region src/subscriberbot/pages/DashboardPage.tsx
|
|
@@ -24,23 +25,23 @@ var N = {
|
|
|
24
25
|
USAGE_BASED: 1
|
|
25
26
|
}, P = 1e3 * 60 * 60 * 24;
|
|
26
27
|
function F() {
|
|
27
|
-
let { navigateTo: F, currentUser: I } = e(), L = g({ pagination: {
|
|
28
|
+
let { navigateTo: F, currentUser: I } = e(), L = v(), R = g({ pagination: {
|
|
28
29
|
limit: 100,
|
|
29
30
|
offset: 0
|
|
30
|
-
} }),
|
|
31
|
+
} }), z = h({ pagination: {
|
|
31
32
|
limit: 50,
|
|
32
33
|
offset: 0
|
|
33
|
-
} }),
|
|
34
|
+
} }), B = m({ pagination: {
|
|
34
35
|
limit: 50,
|
|
35
36
|
offset: 0
|
|
36
|
-
} }),
|
|
37
|
+
} }), V = _({ upcomingOnly: !0 }), H = y(() => R.data?.items ?? [], [R.data]), U = y(() => z.data?.items ?? [], [z.data]), W = y(() => B.data?.items ?? [], [B.data]), G = y(() => V.data ?? [], [V.data]), K = y(() => {
|
|
37
38
|
let e = /* @__PURE__ */ new Map();
|
|
38
|
-
return
|
|
39
|
-
}, [
|
|
40
|
-
let e =
|
|
39
|
+
return H.forEach((t) => e.set(t.id, t)), e;
|
|
40
|
+
}, [H]), q = y(() => {
|
|
41
|
+
let e = H.filter((e) => e.status === "ACTIVE" || e.status === "TRIAL"), t = e.reduce((e, t) => e + t.amount * (N[t.interval] ?? 1), 0), n = e[0]?.currency ?? H[0]?.currency ?? "USD", r = W.filter((e) => e.actionRequired && !e.isRead).length, i = U.filter((e) => e.status === "ISSUED" || e.status === "OVERDUE" || e.status === "FAILED"), a = i.reduce((e, t) => e + t.total, 0), o = H.filter((e) => e.status === "PAST_DUE" || e.status === "LAPSED").length;
|
|
41
42
|
return {
|
|
42
43
|
activeCount: e.length,
|
|
43
|
-
totalCount:
|
|
44
|
+
totalCount: H.length,
|
|
44
45
|
monthlySpend: t,
|
|
45
46
|
primaryCurrency: n,
|
|
46
47
|
unreadActionRequired: r,
|
|
@@ -49,18 +50,18 @@ function F() {
|
|
|
49
50
|
atRisk: o
|
|
50
51
|
};
|
|
51
52
|
}, [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
]),
|
|
53
|
+
H,
|
|
54
|
+
W,
|
|
55
|
+
U
|
|
56
|
+
]), J = y(() => {
|
|
56
57
|
let e = /* @__PURE__ */ new Map();
|
|
57
|
-
return
|
|
58
|
+
return H.forEach((t) => e.set(t.status, (e.get(t.status) ?? 0) + 1)), Array.from(e.entries()).sort((e, t) => t[1] - e[1]).map(([e, t]) => ({
|
|
58
59
|
label: i(e),
|
|
59
60
|
value: t
|
|
60
61
|
}));
|
|
61
|
-
}, [
|
|
62
|
-
let e = Date.now(), t =
|
|
63
|
-
let n =
|
|
62
|
+
}, [H]), Y = y(() => {
|
|
63
|
+
let e = Date.now(), t = G.map((t) => {
|
|
64
|
+
let n = K.get(t.relationshipId), r = new Date(t.occursAt).getTime();
|
|
64
65
|
return {
|
|
65
66
|
id: t.id,
|
|
66
67
|
relationshipId: t.relationshipId,
|
|
@@ -71,7 +72,7 @@ function F() {
|
|
|
71
72
|
soon: !Number.isNaN(r) && r - e <= 7 * P
|
|
72
73
|
};
|
|
73
74
|
});
|
|
74
|
-
return t.length > 0 ? t.filter((e) => !Number.isNaN(new Date(e.when).getTime())).sort((e, t) => new Date(e.when).getTime() - new Date(t.when).getTime()) :
|
|
75
|
+
return t.length > 0 ? t.filter((e) => !Number.isNaN(new Date(e.when).getTime())).sort((e, t) => new Date(e.when).getTime() - new Date(t.when).getTime()) : H.filter((t) => {
|
|
75
76
|
if (!t.renewsAt) return !1;
|
|
76
77
|
let n = new Date(t.renewsAt).getTime();
|
|
77
78
|
return !Number.isNaN(n) && n >= e;
|
|
@@ -88,63 +89,71 @@ function F() {
|
|
|
88
89
|
};
|
|
89
90
|
}).sort((e, t) => new Date(e.when).getTime() - new Date(t.when).getTime());
|
|
90
91
|
}, [
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
]),
|
|
92
|
+
G,
|
|
93
|
+
H,
|
|
94
|
+
K
|
|
95
|
+
]), X = y(() => Y.slice(0, 6).map((e) => ({
|
|
95
96
|
title: e.title,
|
|
96
97
|
sub: a(e.when),
|
|
97
98
|
tone: e.soon ? "warning" : "default",
|
|
98
|
-
meta: e.amount == null ? void 0 : r(e.amount, e.currency ??
|
|
99
|
-
})), [
|
|
100
|
-
if (Z) return /* @__PURE__ */ k(t, {
|
|
101
|
-
title: "Overview",
|
|
102
|
-
description: "Your recurring digital relationships at a glance",
|
|
103
|
-
children: /* @__PURE__ */ k(l, { label: "Loading your subscription portfolio…" })
|
|
104
|
-
});
|
|
99
|
+
meta: e.amount == null ? void 0 : r(e.amount, e.currency ?? q.primaryCurrency, { maximumFractionDigits: 0 })
|
|
100
|
+
})), [Y, q.primaryCurrency]), Z = y(() => W.filter((e) => e.actionRequired), [W]), Q = R.isLoading || z.isLoading || B.isLoading || V.isLoading, te = R.isError && z.isError && B.isError && V.isError;
|
|
105
101
|
if (Q) return /* @__PURE__ */ k(t, {
|
|
106
|
-
title: "Overview",
|
|
107
|
-
description: "Your recurring digital relationships at a glance",
|
|
102
|
+
title: L("dashboard.title", "Overview"),
|
|
103
|
+
description: L("dashboard.description", "Your recurring digital relationships at a glance"),
|
|
104
|
+
children: /* @__PURE__ */ k(l, { label: L("dashboard.loading", "Loading your subscription portfolio…") })
|
|
105
|
+
});
|
|
106
|
+
if (te) return /* @__PURE__ */ k(t, {
|
|
107
|
+
title: L("dashboard.title", "Overview"),
|
|
108
|
+
description: L("dashboard.description", "Your recurring digital relationships at a glance"),
|
|
108
109
|
children: /* @__PURE__ */ k(c, {
|
|
109
|
-
message: "We couldn't reach the Subscriber Bot service. Please try again.",
|
|
110
|
+
message: L("dashboard.error", "We couldn't reach the Subscriber Bot service. Please try again."),
|
|
110
111
|
onRetry: () => {
|
|
111
|
-
|
|
112
|
+
R.refetch(), z.refetch(), B.refetch(), V.refetch();
|
|
112
113
|
}
|
|
113
114
|
})
|
|
114
115
|
});
|
|
115
|
-
let $ = I?.firstName || I?.name?.split(" ")[0],
|
|
116
|
+
let $ = I?.firstName || I?.name?.split(" ")[0], ne = [
|
|
116
117
|
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
labelKey: "dashboard.quickActions.addRelationship.label",
|
|
119
|
+
descriptionKey: "dashboard.quickActions.addRelationship.description",
|
|
120
|
+
fallbackLabel: "Add a relationship",
|
|
121
|
+
fallbackDescription: "Track a new subscription",
|
|
122
|
+
icon: /* @__PURE__ */ k(E, { className: "h-5 w-5" }),
|
|
120
123
|
path: "/relationships/new"
|
|
121
124
|
},
|
|
122
125
|
{
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
labelKey: "dashboard.quickActions.discoverProviders.label",
|
|
127
|
+
descriptionKey: "dashboard.quickActions.discoverProviders.description",
|
|
128
|
+
fallbackLabel: "Discover providers",
|
|
129
|
+
fallbackDescription: "Browse the registry",
|
|
125
130
|
icon: /* @__PURE__ */ k(D, { className: "h-5 w-5" }),
|
|
126
131
|
path: "/providers"
|
|
127
132
|
},
|
|
128
133
|
{
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
labelKey: "dashboard.quickActions.openInbox.label",
|
|
135
|
+
descriptionKey: "dashboard.quickActions.openInbox.description",
|
|
136
|
+
fallbackLabel: "Open inbox",
|
|
137
|
+
fallbackDescription: "Merged communications",
|
|
138
|
+
icon: /* @__PURE__ */ k(T, { className: "h-5 w-5" }),
|
|
132
139
|
path: "/inbox"
|
|
133
140
|
},
|
|
134
141
|
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
labelKey: "dashboard.quickActions.configureAgents.label",
|
|
143
|
+
descriptionKey: "dashboard.quickActions.configureAgents.description",
|
|
144
|
+
fallbackLabel: "Configure agents",
|
|
145
|
+
fallbackDescription: "Automate your portfolio",
|
|
146
|
+
icon: /* @__PURE__ */ k(C, { className: "h-5 w-5" }),
|
|
138
147
|
path: "/agents"
|
|
139
148
|
}
|
|
140
149
|
];
|
|
141
150
|
return /* @__PURE__ */ A(t, {
|
|
142
|
-
title: $ ?
|
|
143
|
-
description: "Everything you pay for on repeat — in one intelligent console.",
|
|
151
|
+
title: $ ? L("dashboard.welcomeBack", "Welcome back, {{name}}", { name: $ }) : L("dashboard.title", "Overview"),
|
|
152
|
+
description: L("dashboard.description", "Everything you pay for on repeat — in one intelligent console."),
|
|
144
153
|
children: [
|
|
145
154
|
/* @__PURE__ */ k(M, {
|
|
146
155
|
className: "mb-6",
|
|
147
|
-
children: "Subscriber Bot is your system of record for every recurring digital relationship. See total recurring spend, what renews next, what needs action, and what to optimize — all from real data across your providers."
|
|
156
|
+
children: L("dashboard.purpose", "Subscriber Bot is your system of record for every recurring digital relationship. See total recurring spend, what renews next, what needs action, and what to optimize — all from real data across your providers.")
|
|
148
157
|
}),
|
|
149
158
|
/* @__PURE__ */ A("div", {
|
|
150
159
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
@@ -152,24 +161,24 @@ function F() {
|
|
|
152
161
|
/* @__PURE__ */ k(n, {
|
|
153
162
|
featured: !0,
|
|
154
163
|
icon: /* @__PURE__ */ k(O, {}),
|
|
155
|
-
value: r(
|
|
156
|
-
label: "Recurring spend / month"
|
|
164
|
+
value: r(q.monthlySpend, q.primaryCurrency, { maximumFractionDigits: 0 }),
|
|
165
|
+
label: L("dashboard.metrics.recurringSpend", "Recurring spend / month")
|
|
157
166
|
}),
|
|
158
167
|
/* @__PURE__ */ k(n, {
|
|
159
|
-
icon: /* @__PURE__ */ k(
|
|
160
|
-
value:
|
|
161
|
-
label: "Active relationships"
|
|
168
|
+
icon: /* @__PURE__ */ k(E, {}),
|
|
169
|
+
value: q.activeCount,
|
|
170
|
+
label: L("dashboard.metrics.activeRelationships", "Active relationships")
|
|
162
171
|
}),
|
|
163
172
|
/* @__PURE__ */ k(n, {
|
|
164
|
-
icon: /* @__PURE__ */ k(
|
|
165
|
-
value:
|
|
166
|
-
label: "Upcoming renewals"
|
|
173
|
+
icon: /* @__PURE__ */ k(w, {}),
|
|
174
|
+
value: Y.length,
|
|
175
|
+
label: L("dashboard.metrics.upcomingRenewals", "Upcoming renewals")
|
|
167
176
|
}),
|
|
168
177
|
/* @__PURE__ */ k(n, {
|
|
169
|
-
icon: /* @__PURE__ */ k(
|
|
170
|
-
value:
|
|
171
|
-
label: "Need your action",
|
|
172
|
-
trend:
|
|
178
|
+
icon: /* @__PURE__ */ k(b, {}),
|
|
179
|
+
value: q.unreadActionRequired,
|
|
180
|
+
label: L("dashboard.metrics.needsAction", "Need your action"),
|
|
181
|
+
trend: q.unreadActionRequired > 0 ? "down" : void 0
|
|
173
182
|
})
|
|
174
183
|
]
|
|
175
184
|
}),
|
|
@@ -178,90 +187,94 @@ function F() {
|
|
|
178
187
|
children: [/* @__PURE__ */ k("div", {
|
|
179
188
|
className: "lg:col-span-2",
|
|
180
189
|
children: /* @__PURE__ */ k(u, {
|
|
181
|
-
title: "Upcoming renewals",
|
|
182
|
-
description: "What's charging next across your portfolio",
|
|
190
|
+
title: L("dashboard.renewals.title", "Upcoming renewals"),
|
|
191
|
+
description: L("dashboard.renewals.description", "What's charging next across your portfolio"),
|
|
183
192
|
className: "mt-0",
|
|
184
193
|
action: /* @__PURE__ */ A("button", {
|
|
185
194
|
type: "button",
|
|
186
195
|
onClick: () => F("/relationships"),
|
|
187
196
|
className: "inline-flex items-center gap-1 text-sm font-medium text-text-link hover:underline",
|
|
188
|
-
children: [
|
|
197
|
+
children: [
|
|
198
|
+
L("common.viewAll", "View all"),
|
|
199
|
+
" ",
|
|
200
|
+
/* @__PURE__ */ k(S, { className: "h-3.5 w-3.5" })
|
|
201
|
+
]
|
|
189
202
|
}),
|
|
190
203
|
children: /* @__PURE__ */ k(j, {
|
|
191
204
|
treatment: "plain",
|
|
192
205
|
className: "p-5",
|
|
193
|
-
children:
|
|
206
|
+
children: X.length === 0 ? /* @__PURE__ */ k(s, {
|
|
194
207
|
illustration: "empty-calendar",
|
|
195
|
-
title: "No upcoming renewals",
|
|
196
|
-
description: "Renewals you track will appear here ahead of time."
|
|
197
|
-
}) : /* @__PURE__ */ k(p, { items:
|
|
208
|
+
title: L("dashboard.renewals.emptyTitle", "No upcoming renewals"),
|
|
209
|
+
description: L("dashboard.renewals.emptyDescription", "Renewals you track will appear here ahead of time.")
|
|
210
|
+
}) : /* @__PURE__ */ k(p, { items: X })
|
|
198
211
|
})
|
|
199
212
|
})
|
|
200
213
|
}), /* @__PURE__ */ A("div", {
|
|
201
214
|
className: "flex flex-col gap-6",
|
|
202
215
|
children: [/* @__PURE__ */ k(u, {
|
|
203
|
-
title: "Portfolio by status",
|
|
204
|
-
description: "Where your relationships stand",
|
|
216
|
+
title: L("dashboard.portfolio.title", "Portfolio by status"),
|
|
217
|
+
description: L("dashboard.portfolio.description", "Where your relationships stand"),
|
|
205
218
|
className: "mt-0",
|
|
206
219
|
children: /* @__PURE__ */ k(j, {
|
|
207
220
|
treatment: "glass",
|
|
208
221
|
className: "p-5",
|
|
209
|
-
children:
|
|
222
|
+
children: J.length === 0 ? /* @__PURE__ */ k(s, {
|
|
210
223
|
illustration: "empty-data",
|
|
211
|
-
title: "No relationships yet",
|
|
212
|
-
description: "Add your first subscription to see the breakdown.",
|
|
224
|
+
title: L("dashboard.portfolio.emptyTitle", "No relationships yet"),
|
|
225
|
+
description: L("dashboard.portfolio.emptyDescription", "Add your first subscription to see the breakdown."),
|
|
213
226
|
action: /* @__PURE__ */ k("button", {
|
|
214
227
|
type: "button",
|
|
215
228
|
onClick: () => F("/relationships/new"),
|
|
216
229
|
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
|
|
217
|
-
children: "Add relationship"
|
|
230
|
+
children: L("dashboard.portfolio.addRelationship", "Add relationship")
|
|
218
231
|
})
|
|
219
232
|
}) : /* @__PURE__ */ A("div", {
|
|
220
233
|
className: "flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row",
|
|
221
234
|
children: [/* @__PURE__ */ k(d, {
|
|
222
|
-
data:
|
|
235
|
+
data: J,
|
|
223
236
|
size: 148,
|
|
224
237
|
thickness: 20,
|
|
225
|
-
centerLabel: String(
|
|
226
|
-
centerSub:
|
|
238
|
+
centerLabel: String(q.totalCount),
|
|
239
|
+
centerSub: L(q.totalCount === 1 ? "dashboard.portfolio.relationshipSingular" : "dashboard.portfolio.relationshipPlural", q.totalCount === 1 ? "relationship" : "relationships"),
|
|
227
240
|
className: "shrink-0"
|
|
228
241
|
}), /* @__PURE__ */ k(f, {
|
|
229
|
-
data:
|
|
242
|
+
data: J,
|
|
230
243
|
className: "w-full min-w-0 flex-1"
|
|
231
244
|
})]
|
|
232
245
|
})
|
|
233
246
|
})
|
|
234
247
|
}), /* @__PURE__ */ k(u, {
|
|
235
|
-
title: "Needs attention",
|
|
236
|
-
description: "Required actions from the Universal Inbox",
|
|
248
|
+
title: L("dashboard.needsAttention.title", "Needs attention"),
|
|
249
|
+
description: L("dashboard.needsAttention.description", "Required actions from the Universal Inbox"),
|
|
237
250
|
className: "mt-0",
|
|
238
251
|
action: /* @__PURE__ */ k("button", {
|
|
239
252
|
type: "button",
|
|
240
253
|
onClick: () => F("/inbox"),
|
|
241
254
|
className: "text-sm font-medium text-text-link hover:underline",
|
|
242
|
-
children: "Open inbox"
|
|
255
|
+
children: L("dashboard.needsAttention.openInbox", "Open inbox")
|
|
243
256
|
}),
|
|
244
257
|
children: /* @__PURE__ */ k(j, {
|
|
245
258
|
treatment: "plain",
|
|
246
259
|
className: "p-3",
|
|
247
|
-
children:
|
|
260
|
+
children: Z.length === 0 ? /* @__PURE__ */ A("div", {
|
|
248
261
|
className: "flex items-center gap-3 px-2 py-4",
|
|
249
262
|
children: [/* @__PURE__ */ k("span", {
|
|
250
263
|
className: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-status-success-bg-subtle text-status-success-text",
|
|
251
|
-
children: /* @__PURE__ */ k(
|
|
264
|
+
children: /* @__PURE__ */ k(ee, { className: "h-5 w-5" })
|
|
252
265
|
}), /* @__PURE__ */ A("div", {
|
|
253
266
|
className: "min-w-0",
|
|
254
267
|
children: [/* @__PURE__ */ k("p", {
|
|
255
268
|
className: "text-sm font-medium text-text-primary",
|
|
256
|
-
children: "You're all caught up"
|
|
269
|
+
children: L("dashboard.needsAttention.allCaughtUp", "You're all caught up")
|
|
257
270
|
}), /* @__PURE__ */ k("p", {
|
|
258
271
|
className: "text-xs text-text-secondary",
|
|
259
|
-
children: "Required actions across your channels will surface here."
|
|
272
|
+
children: L("dashboard.needsAttention.allCaughtUpDescription", "Required actions across your channels will surface here.")
|
|
260
273
|
})]
|
|
261
274
|
})]
|
|
262
275
|
}) : /* @__PURE__ */ k("ul", {
|
|
263
276
|
className: "space-y-1",
|
|
264
|
-
children:
|
|
277
|
+
children: Z.slice(0, 5).map((e) => /* @__PURE__ */ k("li", { children: /* @__PURE__ */ A("button", {
|
|
265
278
|
type: "button",
|
|
266
279
|
onClick: () => F("/inbox"),
|
|
267
280
|
className: "flex w-full items-center gap-3 rounded-lg border-l-2 border-status-warning-text/60 bg-status-warning-bg-subtle/50 p-3 text-left transition-colors hover:bg-status-warning-bg-subtle",
|
|
@@ -269,7 +282,7 @@ function F() {
|
|
|
269
282
|
className: "min-w-0 flex-1",
|
|
270
283
|
children: [/* @__PURE__ */ k("p", {
|
|
271
284
|
className: "truncate text-sm font-medium text-text-primary",
|
|
272
|
-
children: e.subject || "(no subject)"
|
|
285
|
+
children: e.subject || L("dashboard.needsAttention.noSubject", "(no subject)")
|
|
273
286
|
}), e.preview && /* @__PURE__ */ k("p", {
|
|
274
287
|
className: "truncate text-xs text-text-secondary",
|
|
275
288
|
children: e.preview
|
|
@@ -284,32 +297,27 @@ function F() {
|
|
|
284
297
|
})]
|
|
285
298
|
})]
|
|
286
299
|
}),
|
|
287
|
-
|
|
300
|
+
q.openInvoiceCount > 0 && /* @__PURE__ */ A("button", {
|
|
288
301
|
type: "button",
|
|
289
302
|
onClick: () => F("/invoices"),
|
|
290
303
|
className: "mt-6 flex w-full items-center justify-between gap-3 rounded-xl border border-status-warning-text/30 bg-status-warning-bg-subtle px-5 py-4 text-left transition-shadow hover:shadow-md",
|
|
291
304
|
children: [/* @__PURE__ */ A("div", {
|
|
292
305
|
className: "flex items-center gap-3",
|
|
293
|
-
children: [/* @__PURE__ */ k(
|
|
306
|
+
children: [/* @__PURE__ */ k(b, { className: "h-5 w-5 shrink-0 text-status-warning-text" }), /* @__PURE__ */ k("span", {
|
|
294
307
|
className: "text-sm font-medium text-status-warning-text",
|
|
295
|
-
children:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
" ·",
|
|
300
|
-
" ",
|
|
301
|
-
r(K.openInvoiceTotal, K.primaryCurrency),
|
|
302
|
-
" outstanding"
|
|
303
|
-
]
|
|
308
|
+
children: L("dashboard.openInvoices.label", "{{count}} open invoice · {{amount}} outstanding", {
|
|
309
|
+
count: q.openInvoiceCount,
|
|
310
|
+
amount: r(q.openInvoiceTotal, q.primaryCurrency)
|
|
311
|
+
})
|
|
304
312
|
})]
|
|
305
|
-
}), /* @__PURE__ */ k(
|
|
313
|
+
}), /* @__PURE__ */ k(x, { className: "h-4 w-4 shrink-0 text-status-warning-text" })]
|
|
306
314
|
}),
|
|
307
315
|
/* @__PURE__ */ k(u, {
|
|
308
|
-
title: "Quick actions",
|
|
309
|
-
description: "Jump straight into your workflow",
|
|
316
|
+
title: L("dashboard.quickActions.title", "Quick actions"),
|
|
317
|
+
description: L("dashboard.quickActions.description", "Jump straight into your workflow"),
|
|
310
318
|
children: /* @__PURE__ */ k("div", {
|
|
311
319
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4",
|
|
312
|
-
children:
|
|
320
|
+
children: ne.map((e) => /* @__PURE__ */ A("button", {
|
|
313
321
|
type: "button",
|
|
314
322
|
onClick: () => F(e.path),
|
|
315
323
|
className: "group flex items-center gap-3 rounded-xl border border-border-subtle bg-bg-surface p-4 text-left transition-all hover:-translate-y-0.5 hover:border-border-seam hover:shadow-md",
|
|
@@ -322,13 +330,13 @@ function F() {
|
|
|
322
330
|
className: "min-w-0 flex-1",
|
|
323
331
|
children: [/* @__PURE__ */ k("span", {
|
|
324
332
|
className: "block text-sm font-medium text-text-primary",
|
|
325
|
-
children: e.
|
|
333
|
+
children: L(e.labelKey, e.fallbackLabel)
|
|
326
334
|
}), /* @__PURE__ */ k("span", {
|
|
327
335
|
className: "block text-xs text-text-secondary",
|
|
328
|
-
children: e.
|
|
336
|
+
children: L(e.descriptionKey, e.fallbackDescription)
|
|
329
337
|
})]
|
|
330
338
|
}),
|
|
331
|
-
/* @__PURE__ */ k(
|
|
339
|
+
/* @__PURE__ */ k(x, { className: "h-4 w-4 shrink-0 text-text-secondary transition-transform group-hover:translate-x-0.5" })
|
|
332
340
|
]
|
|
333
341
|
}, e.path))
|
|
334
342
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/subscriberbot/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module subscriberbot/pages\n *\n * The Subscriber Bot landing page: a premium fintech console over every\n * recurring digital relationship — total recurring spend, portfolio-by-status,\n * upcoming renewals, the action-required inbox, and outstanding invoices. Every\n * number and chart is COMPUTED from real data returned by\n * `wspace-subscriberbot-svc` — nothing is mocked.\n */\n\nimport { useMemo } from 'react';\nimport {\n Repeat2,\n Wallet,\n CalendarClock,\n Inbox,\n AlertTriangle,\n ArrowRight,\n ArrowUpRight,\n Sparkles,\n Bot,\n ShieldCheck,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n Donut,\n Legend,\n Timeline,\n type Segment,\n type TimelineItem,\n} from '../components';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useRelationships,\n useInvoices,\n useInboxMessages,\n useRenewalEvents,\n} from '../hooks/useSubscriberBotApi';\nimport { formatCurrency, formatRelativeTime, formatEnumLabel } from '../utils/formatters';\nimport type { SubBillingInterval, SubRelationship, SubRenewalEvent } from '../types';\n\n// Normalize any billing interval to an approximate monthly multiplier so we can\n// express total recurring spend as one comparable \"per month\" number.\nconst MONTHLY_FACTOR: Record<SubBillingInterval, number> = {\n DAILY: 30,\n WEEKLY: 4.345,\n MONTHLY: 1,\n QUARTERLY: 1 / 3,\n YEARLY: 1 / 12,\n LIFETIME: 0,\n FREE: 0,\n USAGE_BASED: 1,\n};\n\n// A single normalized \"what charges next\" row, sourced from real renewal events\n// when present, otherwise derived from each relationship's own `renewsAt`.\ninterface UpcomingRenewal {\n id: string;\n relationshipId: string;\n title: string;\n when: string;\n amount: number | null;\n currency: string | null;\n soon: boolean;\n}\n\nconst MS_PER_DAY = 1000 * 60 * 60 * 24;\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useSubscriberBot();\n\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const invoicesQ = useInvoices({ pagination: { limit: 50, offset: 0 } });\n const inboxQ = useInboxMessages({ pagination: { limit: 50, offset: 0 } });\n const renewalsQ = useRenewalEvents({ upcomingOnly: true });\n\n const relationships = useMemo(() => relationshipsQ.data?.items ?? [], [relationshipsQ.data]);\n const invoices = useMemo(() => invoicesQ.data?.items ?? [], [invoicesQ.data]);\n const messages = useMemo(() => inboxQ.data?.items ?? [], [inboxQ.data]);\n const renewals = useMemo(() => renewalsQ.data ?? [], [renewalsQ.data]);\n\n // Fast label lookup so renewal rows can name the relationship they belong to.\n const relationshipById = useMemo(() => {\n const map = new Map<string, SubRelationship>();\n relationships.forEach((r) => map.set(r.id, r));\n return map;\n }, [relationships]);\n\n const stats = useMemo(() => {\n const active = relationships.filter((r) => r.status === 'ACTIVE' || r.status === 'TRIAL');\n const monthlySpend = active.reduce(\n (sum, r) => sum + r.amount * (MONTHLY_FACTOR[r.interval] ?? 1),\n 0\n );\n const primaryCurrency = active[0]?.currency ?? relationships[0]?.currency ?? 'USD';\n const unreadActionRequired = messages.filter((m) => m.actionRequired && !m.isRead).length;\n const openInvoices = invoices.filter(\n (i) => i.status === 'ISSUED' || i.status === 'OVERDUE' || i.status === 'FAILED'\n );\n const openInvoiceTotal = openInvoices.reduce((sum, i) => sum + i.total, 0);\n const atRisk = relationships.filter(\n (r) => r.status === 'PAST_DUE' || r.status === 'LAPSED'\n ).length;\n\n return {\n activeCount: active.length,\n totalCount: relationships.length,\n monthlySpend,\n primaryCurrency,\n unreadActionRequired,\n openInvoiceCount: openInvoices.length,\n openInvoiceTotal,\n atRisk,\n };\n }, [relationships, messages, invoices]);\n\n // Portfolio-by-status → donut segments (grouped from real relationships).\n const statusSegments = useMemo<Segment[]>(() => {\n const counts = new Map<string, number>();\n relationships.forEach((r) => counts.set(r.status, (counts.get(r.status) ?? 0) + 1));\n return Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([status, value]) => ({ label: formatEnumLabel(status), value }));\n }, [relationships]);\n\n // Normalize upcoming renewals: prefer real renewal events, fall back to\n // relationship `renewsAt` when the event stream is empty. Sorted soonest-first.\n const upcoming = useMemo<UpcomingRenewal[]>(() => {\n const now = Date.now();\n const fromEvents: UpcomingRenewal[] = renewals.map((e: SubRenewalEvent) => {\n const rel = relationshipById.get(e.relationshipId);\n const t = new Date(e.occursAt).getTime();\n return {\n id: e.id,\n relationshipId: e.relationshipId,\n title: rel?.label ?? formatEnumLabel(e.eventType),\n when: e.occursAt,\n amount: e.amount ?? rel?.amount ?? null,\n currency: e.currency ?? rel?.currency ?? null,\n soon: !Number.isNaN(t) && t - now <= 7 * MS_PER_DAY,\n };\n });\n\n if (fromEvents.length > 0) {\n return fromEvents\n .filter((u) => !Number.isNaN(new Date(u.when).getTime()))\n .sort((a, b) => new Date(a.when).getTime() - new Date(b.when).getTime());\n }\n\n // Derive from relationships that carry a future renewal date.\n return relationships\n .filter((r) => {\n if (!r.renewsAt) return false;\n const t = new Date(r.renewsAt).getTime();\n return !Number.isNaN(t) && t >= now;\n })\n .map((r) => {\n const t = new Date(r.renewsAt as string).getTime();\n return {\n id: r.id,\n relationshipId: r.id,\n title: r.label,\n when: r.renewsAt as string,\n amount: r.amount,\n currency: r.currency,\n soon: t - now <= 7 * MS_PER_DAY,\n };\n })\n .sort((a, b) => new Date(a.when).getTime() - new Date(b.when).getTime());\n }, [renewals, relationships, relationshipById]);\n\n const timelineItems = useMemo<TimelineItem[]>(\n () =>\n upcoming.slice(0, 6).map((u) => ({\n title: u.title,\n sub: formatRelativeTime(u.when),\n tone: u.soon ? 'warning' : 'default',\n meta:\n u.amount != null\n ? formatCurrency(u.amount, u.currency ?? stats.primaryCurrency, {\n maximumFractionDigits: 0,\n })\n : undefined,\n })),\n [upcoming, stats.primaryCurrency]\n );\n\n const actionMessages = useMemo(() => messages.filter((m) => m.actionRequired), [messages]);\n\n const isLoading =\n relationshipsQ.isLoading || invoicesQ.isLoading || inboxQ.isLoading || renewalsQ.isLoading;\n const isError =\n relationshipsQ.isError && invoicesQ.isError && inboxQ.isError && renewalsQ.isError;\n\n if (isLoading) {\n return (\n <PageLayout title=\"Overview\" description=\"Your recurring digital relationships at a glance\">\n <LoadingView label=\"Loading your subscription portfolio…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Overview\" description=\"Your recurring digital relationships at a glance\">\n <ErrorView\n message=\"We couldn't reach the Subscriber Bot service. Please try again.\"\n onRetry={() => {\n void relationshipsQ.refetch();\n void invoicesQ.refetch();\n void inboxQ.refetch();\n void renewalsQ.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name?.split(' ')[0];\n\n const quickActions = [\n {\n label: 'Add a relationship',\n description: 'Track a new subscription',\n icon: <Repeat2 className=\"h-5 w-5\" />,\n path: '/relationships/new',\n },\n {\n label: 'Discover providers',\n description: 'Browse the registry',\n icon: <Sparkles className=\"h-5 w-5\" />,\n path: '/providers',\n },\n {\n label: 'Open inbox',\n description: 'Merged communications',\n icon: <Inbox className=\"h-5 w-5\" />,\n path: '/inbox',\n },\n {\n label: 'Configure agents',\n description: 'Automate your portfolio',\n icon: <Bot className=\"h-5 w-5\" />,\n path: '/agents',\n },\n ];\n\n return (\n <PageLayout\n title={greetingName ? `Welcome back, ${greetingName}` : 'Overview'}\n description=\"Everything you pay for on repeat — in one intelligent console.\"\n >\n <PagePurpose className=\"mb-6\">\n Subscriber Bot is your system of record for every recurring digital relationship. See total\n recurring spend, what renews next, what needs action, and what to optimize — all from real\n data across your providers.\n </PagePurpose>\n\n {/* KPI tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<Wallet />}\n value={formatCurrency(stats.monthlySpend, stats.primaryCurrency, {\n maximumFractionDigits: 0,\n })}\n label=\"Recurring spend / month\"\n />\n <MetricsCard icon={<Repeat2 />} value={stats.activeCount} label=\"Active relationships\" />\n <MetricsCard icon={<CalendarClock />} value={upcoming.length} label=\"Upcoming renewals\" />\n <MetricsCard\n icon={<AlertTriangle />}\n value={stats.unreadActionRequired}\n label=\"Need your action\"\n trend={stats.unreadActionRequired > 0 ? 'down' : undefined}\n />\n </div>\n\n {/* Main console: renewals (left) + portfolio donut & needs-attention (right) */}\n <div className=\"mt-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Upcoming renewals timeline — spans two columns on desktop */}\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Upcoming renewals\"\n description=\"What's charging next across your portfolio\"\n className=\"mt-0\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n View all <ArrowUpRight className=\"h-3.5 w-3.5\" />\n </button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"p-5\">\n {timelineItems.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n title=\"No upcoming renewals\"\n description=\"Renewals you track will appear here ahead of time.\"\n />\n ) : (\n <Timeline items={timelineItems} />\n )}\n </GlassCard>\n </Section>\n </div>\n\n {/* Right rail: portfolio-by-status donut + needs-attention panel */}\n <div className=\"flex flex-col gap-6\">\n <Section\n title=\"Portfolio by status\"\n description=\"Where your relationships stand\"\n className=\"mt-0\"\n >\n <GlassCard treatment=\"glass\" className=\"p-5\">\n {statusSegments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No relationships yet\"\n description=\"Add your first subscription to see the breakdown.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Add relationship\n </button>\n }\n />\n ) : (\n <div className=\"flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row\">\n <Donut\n data={statusSegments}\n size={148}\n thickness={20}\n centerLabel={String(stats.totalCount)}\n centerSub={stats.totalCount === 1 ? 'relationship' : 'relationships'}\n className=\"shrink-0\"\n />\n <Legend data={statusSegments} className=\"w-full min-w-0 flex-1\" />\n </div>\n )}\n </GlassCard>\n </Section>\n\n <Section\n title=\"Needs attention\"\n description=\"Required actions from the Universal Inbox\"\n className=\"mt-0\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/inbox')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n Open inbox\n </button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"p-3\">\n {actionMessages.length === 0 ? (\n <div className=\"flex items-center gap-3 px-2 py-4\">\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-status-success-bg-subtle text-status-success-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">You're all caught up</p>\n <p className=\"text-xs text-text-secondary\">\n Required actions across your channels will surface here.\n </p>\n </div>\n </div>\n ) : (\n <ul className=\"space-y-1\">\n {actionMessages.slice(0, 5).map((message) => (\n <li key={message.id}>\n <button\n type=\"button\"\n onClick={() => navigateTo('/inbox')}\n className=\"flex w-full items-center gap-3 rounded-lg border-l-2 border-status-warning-text/60 bg-status-warning-bg-subtle/50 p-3 text-left transition-colors hover:bg-status-warning-bg-subtle\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {message.subject || '(no subject)'}\n </p>\n {message.preview && (\n <p className=\"truncate text-xs text-text-secondary\">\n {message.preview}\n </p>\n )}\n </div>\n <StatusBadge status={message.category} className=\"shrink-0\" />\n </button>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </Section>\n </div>\n </div>\n\n {/* Open-invoice call-out (only when there is real outstanding balance) */}\n {stats.openInvoiceCount > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/invoices')}\n className=\"mt-6 flex w-full items-center justify-between gap-3 rounded-xl border border-status-warning-text/30 bg-status-warning-bg-subtle px-5 py-4 text-left transition-shadow hover:shadow-md\"\n >\n <div className=\"flex items-center gap-3\">\n <AlertTriangle className=\"h-5 w-5 shrink-0 text-status-warning-text\" />\n <span className=\"text-sm font-medium text-status-warning-text\">\n {stats.openInvoiceCount} open invoice{stats.openInvoiceCount !== 1 ? 's' : ''} ·{' '}\n {formatCurrency(stats.openInvoiceTotal, stats.primaryCurrency)} outstanding\n </span>\n </div>\n <ArrowRight className=\"h-4 w-4 shrink-0 text-status-warning-text\" />\n </button>\n )}\n\n {/* Quick actions */}\n <Section title=\"Quick actions\" description=\"Jump straight into your workflow\">\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {quickActions.map((qa) => (\n <button\n key={qa.path}\n type=\"button\"\n onClick={() => navigateTo(qa.path)}\n className=\"group flex items-center gap-3 rounded-xl border border-border-subtle bg-bg-surface p-4 text-left transition-all hover:-translate-y-0.5 hover:border-border-seam hover:shadow-md\"\n >\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n {qa.icon}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-medium text-text-primary\">{qa.label}</span>\n <span className=\"block text-xs text-text-secondary\">{qa.description}</span>\n </span>\n <ArrowRight className=\"h-4 w-4 shrink-0 text-text-secondary transition-transform group-hover:translate-x-0.5\" />\n </button>\n ))}\n </div>\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/subscriberbot/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module subscriberbot/pages\n *\n * The Subscriber Bot landing page: a premium fintech console over every\n * recurring digital relationship — total recurring spend, portfolio-by-status,\n * upcoming renewals, the action-required inbox, and outstanding invoices. Every\n * number and chart is COMPUTED from real data returned by\n * `wspace-subscriberbot-svc` — nothing is mocked.\n */\n\nimport { useMemo } from 'react';\nimport {\n Repeat2,\n Wallet,\n CalendarClock,\n Inbox,\n AlertTriangle,\n ArrowRight,\n ArrowUpRight,\n Sparkles,\n Bot,\n ShieldCheck,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n Donut,\n Legend,\n Timeline,\n type Segment,\n type TimelineItem,\n} from '../components';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useRelationships,\n useInvoices,\n useInboxMessages,\n useRenewalEvents,\n} from '../hooks/useSubscriberBotApi';\nimport { useTr } from '../hooks/useTr';\nimport { formatCurrency, formatRelativeTime, formatEnumLabel } from '../utils/formatters';\nimport type { SubBillingInterval, SubRelationship, SubRenewalEvent } from '../types';\n\n// Normalize any billing interval to an approximate monthly multiplier so we can\n// express total recurring spend as one comparable \"per month\" number.\nconst MONTHLY_FACTOR: Record<SubBillingInterval, number> = {\n DAILY: 30,\n WEEKLY: 4.345,\n MONTHLY: 1,\n QUARTERLY: 1 / 3,\n YEARLY: 1 / 12,\n LIFETIME: 0,\n FREE: 0,\n USAGE_BASED: 1,\n};\n\n// A single normalized \"what charges next\" row, sourced from real renewal events\n// when present, otherwise derived from each relationship's own `renewsAt`.\ninterface UpcomingRenewal {\n id: string;\n relationshipId: string;\n title: string;\n when: string;\n amount: number | null;\n currency: string | null;\n soon: boolean;\n}\n\nconst MS_PER_DAY = 1000 * 60 * 60 * 24;\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useSubscriberBot();\n const tr = useTr();\n\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const invoicesQ = useInvoices({ pagination: { limit: 50, offset: 0 } });\n const inboxQ = useInboxMessages({ pagination: { limit: 50, offset: 0 } });\n const renewalsQ = useRenewalEvents({ upcomingOnly: true });\n\n const relationships = useMemo(() => relationshipsQ.data?.items ?? [], [relationshipsQ.data]);\n const invoices = useMemo(() => invoicesQ.data?.items ?? [], [invoicesQ.data]);\n const messages = useMemo(() => inboxQ.data?.items ?? [], [inboxQ.data]);\n const renewals = useMemo(() => renewalsQ.data ?? [], [renewalsQ.data]);\n\n // Fast label lookup so renewal rows can name the relationship they belong to.\n const relationshipById = useMemo(() => {\n const map = new Map<string, SubRelationship>();\n relationships.forEach((r) => map.set(r.id, r));\n return map;\n }, [relationships]);\n\n const stats = useMemo(() => {\n const active = relationships.filter((r) => r.status === 'ACTIVE' || r.status === 'TRIAL');\n const monthlySpend = active.reduce(\n (sum, r) => sum + r.amount * (MONTHLY_FACTOR[r.interval] ?? 1),\n 0\n );\n const primaryCurrency = active[0]?.currency ?? relationships[0]?.currency ?? 'USD';\n const unreadActionRequired = messages.filter((m) => m.actionRequired && !m.isRead).length;\n const openInvoices = invoices.filter(\n (i) => i.status === 'ISSUED' || i.status === 'OVERDUE' || i.status === 'FAILED'\n );\n const openInvoiceTotal = openInvoices.reduce((sum, i) => sum + i.total, 0);\n const atRisk = relationships.filter(\n (r) => r.status === 'PAST_DUE' || r.status === 'LAPSED'\n ).length;\n\n return {\n activeCount: active.length,\n totalCount: relationships.length,\n monthlySpend,\n primaryCurrency,\n unreadActionRequired,\n openInvoiceCount: openInvoices.length,\n openInvoiceTotal,\n atRisk,\n };\n }, [relationships, messages, invoices]);\n\n // Portfolio-by-status → donut segments (grouped from real relationships).\n const statusSegments = useMemo<Segment[]>(() => {\n const counts = new Map<string, number>();\n relationships.forEach((r) => counts.set(r.status, (counts.get(r.status) ?? 0) + 1));\n return Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([status, value]) => ({ label: formatEnumLabel(status), value }));\n }, [relationships]);\n\n // Normalize upcoming renewals: prefer real renewal events, fall back to\n // relationship `renewsAt` when the event stream is empty. Sorted soonest-first.\n const upcoming = useMemo<UpcomingRenewal[]>(() => {\n const now = Date.now();\n const fromEvents: UpcomingRenewal[] = renewals.map((e: SubRenewalEvent) => {\n const rel = relationshipById.get(e.relationshipId);\n const t = new Date(e.occursAt).getTime();\n return {\n id: e.id,\n relationshipId: e.relationshipId,\n title: rel?.label ?? formatEnumLabel(e.eventType),\n when: e.occursAt,\n amount: e.amount ?? rel?.amount ?? null,\n currency: e.currency ?? rel?.currency ?? null,\n soon: !Number.isNaN(t) && t - now <= 7 * MS_PER_DAY,\n };\n });\n\n if (fromEvents.length > 0) {\n return fromEvents\n .filter((u) => !Number.isNaN(new Date(u.when).getTime()))\n .sort((a, b) => new Date(a.when).getTime() - new Date(b.when).getTime());\n }\n\n // Derive from relationships that carry a future renewal date.\n return relationships\n .filter((r) => {\n if (!r.renewsAt) return false;\n const t = new Date(r.renewsAt).getTime();\n return !Number.isNaN(t) && t >= now;\n })\n .map((r) => {\n const t = new Date(r.renewsAt as string).getTime();\n return {\n id: r.id,\n relationshipId: r.id,\n title: r.label,\n when: r.renewsAt as string,\n amount: r.amount,\n currency: r.currency,\n soon: t - now <= 7 * MS_PER_DAY,\n };\n })\n .sort((a, b) => new Date(a.when).getTime() - new Date(b.when).getTime());\n }, [renewals, relationships, relationshipById]);\n\n const timelineItems = useMemo<TimelineItem[]>(\n () =>\n upcoming.slice(0, 6).map((u) => ({\n title: u.title,\n sub: formatRelativeTime(u.when),\n tone: u.soon ? 'warning' : 'default',\n meta:\n u.amount != null\n ? formatCurrency(u.amount, u.currency ?? stats.primaryCurrency, {\n maximumFractionDigits: 0,\n })\n : undefined,\n })),\n [upcoming, stats.primaryCurrency]\n );\n\n const actionMessages = useMemo(() => messages.filter((m) => m.actionRequired), [messages]);\n\n const isLoading =\n relationshipsQ.isLoading || invoicesQ.isLoading || inboxQ.isLoading || renewalsQ.isLoading;\n const isError =\n relationshipsQ.isError && invoicesQ.isError && inboxQ.isError && renewalsQ.isError;\n\n if (isLoading) {\n return (\n <PageLayout\n title={tr('dashboard.title', 'Overview')}\n description={tr(\n 'dashboard.description',\n 'Your recurring digital relationships at a glance'\n )}\n >\n <LoadingView label={tr('dashboard.loading', 'Loading your subscription portfolio…')} />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout\n title={tr('dashboard.title', 'Overview')}\n description={tr(\n 'dashboard.description',\n 'Your recurring digital relationships at a glance'\n )}\n >\n <ErrorView\n message={tr(\n 'dashboard.error',\n \"We couldn't reach the Subscriber Bot service. Please try again.\"\n )}\n onRetry={() => {\n void relationshipsQ.refetch();\n void invoicesQ.refetch();\n void inboxQ.refetch();\n void renewalsQ.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name?.split(' ')[0];\n\n const quickActions = [\n {\n labelKey: 'dashboard.quickActions.addRelationship.label',\n descriptionKey: 'dashboard.quickActions.addRelationship.description',\n fallbackLabel: 'Add a relationship',\n fallbackDescription: 'Track a new subscription',\n icon: <Repeat2 className=\"h-5 w-5\" />,\n path: '/relationships/new',\n },\n {\n labelKey: 'dashboard.quickActions.discoverProviders.label',\n descriptionKey: 'dashboard.quickActions.discoverProviders.description',\n fallbackLabel: 'Discover providers',\n fallbackDescription: 'Browse the registry',\n icon: <Sparkles className=\"h-5 w-5\" />,\n path: '/providers',\n },\n {\n labelKey: 'dashboard.quickActions.openInbox.label',\n descriptionKey: 'dashboard.quickActions.openInbox.description',\n fallbackLabel: 'Open inbox',\n fallbackDescription: 'Merged communications',\n icon: <Inbox className=\"h-5 w-5\" />,\n path: '/inbox',\n },\n {\n labelKey: 'dashboard.quickActions.configureAgents.label',\n descriptionKey: 'dashboard.quickActions.configureAgents.description',\n fallbackLabel: 'Configure agents',\n fallbackDescription: 'Automate your portfolio',\n icon: <Bot className=\"h-5 w-5\" />,\n path: '/agents',\n },\n ];\n\n return (\n <PageLayout\n title={\n greetingName\n ? tr('dashboard.welcomeBack', 'Welcome back, {{name}}', { name: greetingName })\n : tr('dashboard.title', 'Overview')\n }\n description={tr(\n 'dashboard.description',\n 'Everything you pay for on repeat — in one intelligent console.'\n )}\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n 'dashboard.purpose',\n 'Subscriber Bot is your system of record for every recurring digital relationship. See total recurring spend, what renews next, what needs action, and what to optimize — all from real data across your providers.'\n )}\n </PagePurpose>\n\n {/* KPI tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<Wallet />}\n value={formatCurrency(stats.monthlySpend, stats.primaryCurrency, {\n maximumFractionDigits: 0,\n })}\n label={tr('dashboard.metrics.recurringSpend', 'Recurring spend / month')}\n />\n <MetricsCard\n icon={<Repeat2 />}\n value={stats.activeCount}\n label={tr('dashboard.metrics.activeRelationships', 'Active relationships')}\n />\n <MetricsCard\n icon={<CalendarClock />}\n value={upcoming.length}\n label={tr('dashboard.metrics.upcomingRenewals', 'Upcoming renewals')}\n />\n <MetricsCard\n icon={<AlertTriangle />}\n value={stats.unreadActionRequired}\n label={tr('dashboard.metrics.needsAction', 'Need your action')}\n trend={stats.unreadActionRequired > 0 ? 'down' : undefined}\n />\n </div>\n\n {/* Main console: renewals (left) + portfolio donut & needs-attention (right) */}\n <div className=\"mt-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Upcoming renewals timeline — spans two columns on desktop */}\n <div className=\"lg:col-span-2\">\n <Section\n title={tr('dashboard.renewals.title', 'Upcoming renewals')}\n description={tr(\n 'dashboard.renewals.description',\n \"What's charging next across your portfolio\"\n )}\n className=\"mt-0\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('common.viewAll', 'View all')} <ArrowUpRight className=\"h-3.5 w-3.5\" />\n </button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"p-5\">\n {timelineItems.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n title={tr('dashboard.renewals.emptyTitle', 'No upcoming renewals')}\n description={tr(\n 'dashboard.renewals.emptyDescription',\n 'Renewals you track will appear here ahead of time.'\n )}\n />\n ) : (\n <Timeline items={timelineItems} />\n )}\n </GlassCard>\n </Section>\n </div>\n\n {/* Right rail: portfolio-by-status donut + needs-attention panel */}\n <div className=\"flex flex-col gap-6\">\n <Section\n title={tr('dashboard.portfolio.title', 'Portfolio by status')}\n description={tr('dashboard.portfolio.description', 'Where your relationships stand')}\n className=\"mt-0\"\n >\n <GlassCard treatment=\"glass\" className=\"p-5\">\n {statusSegments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('dashboard.portfolio.emptyTitle', 'No relationships yet')}\n description={tr(\n 'dashboard.portfolio.emptyDescription',\n 'Add your first subscription to see the breakdown.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('dashboard.portfolio.addRelationship', 'Add relationship')}\n </button>\n }\n />\n ) : (\n <div className=\"flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row\">\n <Donut\n data={statusSegments}\n size={148}\n thickness={20}\n centerLabel={String(stats.totalCount)}\n centerSub={tr(\n stats.totalCount === 1\n ? 'dashboard.portfolio.relationshipSingular'\n : 'dashboard.portfolio.relationshipPlural',\n stats.totalCount === 1 ? 'relationship' : 'relationships'\n )}\n className=\"shrink-0\"\n />\n <Legend data={statusSegments} className=\"w-full min-w-0 flex-1\" />\n </div>\n )}\n </GlassCard>\n </Section>\n\n <Section\n title={tr('dashboard.needsAttention.title', 'Needs attention')}\n description={tr(\n 'dashboard.needsAttention.description',\n 'Required actions from the Universal Inbox'\n )}\n className=\"mt-0\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/inbox')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('dashboard.needsAttention.openInbox', 'Open inbox')}\n </button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"p-3\">\n {actionMessages.length === 0 ? (\n <div className=\"flex items-center gap-3 px-2 py-4\">\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-status-success-bg-subtle text-status-success-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('dashboard.needsAttention.allCaughtUp', \"You're all caught up\")}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'dashboard.needsAttention.allCaughtUpDescription',\n 'Required actions across your channels will surface here.'\n )}\n </p>\n </div>\n </div>\n ) : (\n <ul className=\"space-y-1\">\n {actionMessages.slice(0, 5).map((message) => (\n <li key={message.id}>\n <button\n type=\"button\"\n onClick={() => navigateTo('/inbox')}\n className=\"flex w-full items-center gap-3 rounded-lg border-l-2 border-status-warning-text/60 bg-status-warning-bg-subtle/50 p-3 text-left transition-colors hover:bg-status-warning-bg-subtle\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {message.subject ||\n tr('dashboard.needsAttention.noSubject', '(no subject)')}\n </p>\n {message.preview && (\n <p className=\"truncate text-xs text-text-secondary\">\n {message.preview}\n </p>\n )}\n </div>\n <StatusBadge status={message.category} className=\"shrink-0\" />\n </button>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </Section>\n </div>\n </div>\n\n {/* Open-invoice call-out (only when there is real outstanding balance) */}\n {stats.openInvoiceCount > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/invoices')}\n className=\"mt-6 flex w-full items-center justify-between gap-3 rounded-xl border border-status-warning-text/30 bg-status-warning-bg-subtle px-5 py-4 text-left transition-shadow hover:shadow-md\"\n >\n <div className=\"flex items-center gap-3\">\n <AlertTriangle className=\"h-5 w-5 shrink-0 text-status-warning-text\" />\n <span className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n 'dashboard.openInvoices.label',\n '{{count}} open invoice · {{amount}} outstanding',\n {\n count: stats.openInvoiceCount,\n amount: formatCurrency(stats.openInvoiceTotal, stats.primaryCurrency),\n }\n )}\n </span>\n </div>\n <ArrowRight className=\"h-4 w-4 shrink-0 text-status-warning-text\" />\n </button>\n )}\n\n {/* Quick actions */}\n <Section\n title={tr('dashboard.quickActions.title', 'Quick actions')}\n description={tr('dashboard.quickActions.description', 'Jump straight into your workflow')}\n >\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {quickActions.map((qa) => (\n <button\n key={qa.path}\n type=\"button\"\n onClick={() => navigateTo(qa.path)}\n className=\"group flex items-center gap-3 rounded-xl border border-border-subtle bg-bg-surface p-4 text-left transition-all hover:-translate-y-0.5 hover:border-border-seam hover:shadow-md\"\n >\n <span className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n {qa.icon}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-medium text-text-primary\">\n {tr(qa.labelKey, qa.fallbackLabel)}\n </span>\n <span className=\"block text-xs text-text-secondary\">\n {tr(qa.descriptionKey, qa.fallbackDescription)}\n </span>\n </span>\n <ArrowRight className=\"h-4 w-4 shrink-0 text-text-secondary transition-transform group-hover:translate-x-0.5\" />\n </button>\n ))}\n </div>\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxPage.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/pages/InboxPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"InboxPage.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/pages/InboxPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAy1BH,wBAAgB,SAAS,gCAgOxB"}
|