@burdenoff/microfe-subscriberbot 2026.827.1 → 2026.1014.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.
Files changed (56) hide show
  1. package/dist/subscriberbot/admin/AdminDashboardPage.d.ts.map +1 -1
  2. package/dist/subscriberbot/admin/AdminDashboardPage.js +80 -83
  3. package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
  4. package/dist/subscriberbot/hooks/index.d.ts +1 -0
  5. package/dist/subscriberbot/hooks/index.d.ts.map +1 -1
  6. package/dist/subscriberbot/hooks/index.js +1 -0
  7. package/dist/subscriberbot/hooks/useTr.d.ts +17 -0
  8. package/dist/subscriberbot/hooks/useTr.d.ts.map +1 -0
  9. package/dist/subscriberbot/hooks/useTr.js +14 -0
  10. package/dist/subscriberbot/hooks/useTr.js.map +1 -0
  11. package/dist/subscriberbot/pages/AssetVaultPage.d.ts.map +1 -1
  12. package/dist/subscriberbot/pages/AssetVaultPage.js +111 -109
  13. package/dist/subscriberbot/pages/AssetVaultPage.js.map +1 -1
  14. package/dist/subscriberbot/pages/DashboardPage.d.ts.map +1 -1
  15. package/dist/subscriberbot/pages/DashboardPage.js +113 -105
  16. package/dist/subscriberbot/pages/DashboardPage.js.map +1 -1
  17. package/dist/subscriberbot/pages/InboxPage.d.ts.map +1 -1
  18. package/dist/subscriberbot/pages/InboxPage.js +404 -369
  19. package/dist/subscriberbot/pages/InboxPage.js.map +1 -1
  20. package/dist/subscriberbot/pages/InsightsPage.d.ts.map +1 -1
  21. package/dist/subscriberbot/pages/InsightsPage.js +94 -93
  22. package/dist/subscriberbot/pages/InsightsPage.js.map +1 -1
  23. package/dist/subscriberbot/pages/InvoicesPage.d.ts.map +1 -1
  24. package/dist/subscriberbot/pages/InvoicesPage.js +360 -366
  25. package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
  26. package/dist/subscriberbot/pages/WorkflowsPage.d.ts.map +1 -1
  27. package/dist/subscriberbot/pages/WorkflowsPage.js +209 -193
  28. package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -1
  29. package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts.map +1 -1
  30. package/dist/subscriberbot/pages/agents/AgentDetailPage.js +268 -236
  31. package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -1
  32. package/dist/subscriberbot/pages/agents/AgentNewPage.d.ts.map +1 -1
  33. package/dist/subscriberbot/pages/agents/AgentNewPage.js +96 -76
  34. package/dist/subscriberbot/pages/agents/AgentNewPage.js.map +1 -1
  35. package/dist/subscriberbot/pages/agents/AgentsListPage.d.ts.map +1 -1
  36. package/dist/subscriberbot/pages/agents/AgentsListPage.js +145 -161
  37. package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
  38. package/dist/subscriberbot/pages/providers/ProviderDetailPage.d.ts.map +1 -1
  39. package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +514 -441
  40. package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
  41. package/dist/subscriberbot/pages/providers/ProviderNewPage.d.ts.map +1 -1
  42. package/dist/subscriberbot/pages/providers/ProviderNewPage.js +167 -129
  43. package/dist/subscriberbot/pages/providers/ProviderNewPage.js.map +1 -1
  44. package/dist/subscriberbot/pages/providers/ProvidersListPage.d.ts.map +1 -1
  45. package/dist/subscriberbot/pages/providers/ProvidersListPage.js +171 -218
  46. package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
  47. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -1
  48. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +340 -279
  49. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
  50. package/dist/subscriberbot/pages/relationships/RelationshipNewPage.d.ts.map +1 -1
  51. package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js +185 -166
  52. package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -1
  53. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.d.ts.map +1 -1
  54. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js +153 -182
  55. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -1
  56. 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 { useMemo as v } from "react";
12
- import { AlertTriangle as y, ArrowRight as b, ArrowUpRight as x, Bot as S, CalendarClock as C, Inbox as w, Repeat2 as T, ShieldCheck as E, Sparkles as D, Wallet as O } from "lucide-react";
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
- } }), R = h({ pagination: {
31
+ } }), z = h({ pagination: {
31
32
  limit: 50,
32
33
  offset: 0
33
- } }), z = m({ pagination: {
34
+ } }), B = m({ pagination: {
34
35
  limit: 50,
35
36
  offset: 0
36
- } }), B = _({ upcomingOnly: !0 }), V = v(() => L.data?.items ?? [], [L.data]), H = v(() => R.data?.items ?? [], [R.data]), U = v(() => z.data?.items ?? [], [z.data]), W = v(() => B.data ?? [], [B.data]), G = v(() => {
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 V.forEach((t) => e.set(t.id, t)), e;
39
- }, [V]), K = v(() => {
40
- let e = V.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 ?? V[0]?.currency ?? "USD", r = U.filter((e) => e.actionRequired && !e.isRead).length, i = H.filter((e) => e.status === "ISSUED" || e.status === "OVERDUE" || e.status === "FAILED"), a = i.reduce((e, t) => e + t.total, 0), o = V.filter((e) => e.status === "PAST_DUE" || e.status === "LAPSED").length;
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: V.length,
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
- V,
53
- U,
54
- H
55
- ]), q = v(() => {
53
+ H,
54
+ W,
55
+ U
56
+ ]), J = y(() => {
56
57
  let e = /* @__PURE__ */ new Map();
57
- return V.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
+ 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
- }, [V]), J = v(() => {
62
- let e = Date.now(), t = W.map((t) => {
63
- let n = G.get(t.relationshipId), r = new Date(t.occursAt).getTime();
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()) : V.filter((t) => {
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
- W,
92
- V,
93
- G
94
- ]), Y = v(() => J.slice(0, 6).map((e) => ({
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 ?? K.primaryCurrency, { maximumFractionDigits: 0 })
99
- })), [J, K.primaryCurrency]), X = v(() => U.filter((e) => e.actionRequired), [U]), Z = L.isLoading || R.isLoading || z.isLoading || B.isLoading, Q = L.isError && R.isError && z.isError && B.isError;
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
- L.refetch(), R.refetch(), z.refetch(), B.refetch();
112
+ R.refetch(), z.refetch(), B.refetch(), V.refetch();
112
113
  }
113
114
  })
114
115
  });
115
- let $ = I?.firstName || I?.name?.split(" ")[0], ee = [
116
+ let $ = I?.firstName || I?.name?.split(" ")[0], ne = [
116
117
  {
117
- label: "Add a relationship",
118
- description: "Track a new subscription",
119
- icon: /* @__PURE__ */ k(T, { className: "h-5 w-5" }),
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
- label: "Discover providers",
124
- description: "Browse the registry",
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
- label: "Open inbox",
130
- description: "Merged communications",
131
- icon: /* @__PURE__ */ k(w, { className: "h-5 w-5" }),
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
- label: "Configure agents",
136
- description: "Automate your portfolio",
137
- icon: /* @__PURE__ */ k(S, { className: "h-5 w-5" }),
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: $ ? `Welcome back, ${$}` : "Overview",
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(K.monthlySpend, K.primaryCurrency, { maximumFractionDigits: 0 }),
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(T, {}),
160
- value: K.activeCount,
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(C, {}),
165
- value: J.length,
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(y, {}),
170
- value: K.unreadActionRequired,
171
- label: "Need your action",
172
- trend: K.unreadActionRequired > 0 ? "down" : void 0
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: ["View all ", /* @__PURE__ */ k(x, { className: "h-3.5 w-3.5" })]
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: Y.length === 0 ? /* @__PURE__ */ k(s, {
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: Y })
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: q.length === 0 ? /* @__PURE__ */ k(s, {
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: q,
235
+ data: J,
223
236
  size: 148,
224
237
  thickness: 20,
225
- centerLabel: String(K.totalCount),
226
- centerSub: K.totalCount === 1 ? "relationship" : "relationships",
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: q,
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: X.length === 0 ? /* @__PURE__ */ A("div", {
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(E, { className: "h-5 w-5" })
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: X.slice(0, 5).map((e) => /* @__PURE__ */ k("li", { children: /* @__PURE__ */ A("button", {
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
- K.openInvoiceCount > 0 && /* @__PURE__ */ A("button", {
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(y, { className: "h-5 w-5 shrink-0 text-status-warning-text" }), /* @__PURE__ */ A("span", {
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
- K.openInvoiceCount,
297
- " open invoice",
298
- K.openInvoiceCount === 1 ? "" : "s",
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(b, { className: "h-4 w-4 shrink-0 text-status-warning-text" })]
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: ee.map((e) => /* @__PURE__ */ A("button", {
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.label
333
+ children: L(e.labelKey, e.fallbackLabel)
326
334
  }), /* @__PURE__ */ k("span", {
327
335
  className: "block text-xs text-text-secondary",
328
- children: e.description
336
+ children: L(e.descriptionKey, e.fallbackDescription)
329
337
  })]
330
338
  }),
331
- /* @__PURE__ */ k(b, { className: "h-4 w-4 shrink-0 text-text-secondary transition-transform group-hover:translate-x-0.5" })
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;AAwwBH,wBAAgB,SAAS,gCAkNxB"}
1
+ {"version":3,"file":"InboxPage.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/pages/InboxPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAy1BH,wBAAgB,SAAS,gCAgOxB"}