@burdenoff/microfe-subscriberbot 2026.1023.2 → 2026.1029.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.js +5 -5
- package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
- package/dist/subscriberbot/components/PostureOptions.d.ts +10 -0
- package/dist/subscriberbot/components/PostureOptions.d.ts.map +1 -0
- package/dist/subscriberbot/components/PostureOptions.js +20 -0
- package/dist/subscriberbot/components/PostureOptions.js.map +1 -0
- package/dist/subscriberbot/components/index.d.ts +2 -0
- package/dist/subscriberbot/components/index.d.ts.map +1 -1
- package/dist/subscriberbot/components/index.js +1 -0
- 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 +2 -1
- package/dist/subscriberbot/hooks/useAutonomousAgentMode.d.ts +15 -0
- package/dist/subscriberbot/hooks/useAutonomousAgentMode.d.ts.map +1 -0
- package/dist/subscriberbot/hooks/useAutonomousAgentMode.js +13 -0
- package/dist/subscriberbot/hooks/useAutonomousAgentMode.js.map +1 -0
- package/dist/subscriberbot/pages/AssetVaultPage.js +16 -16
- package/dist/subscriberbot/pages/DashboardPage.js +23 -23
- package/dist/subscriberbot/pages/DashboardPage.js.map +1 -1
- package/dist/subscriberbot/pages/InboxPage.js +62 -62
- package/dist/subscriberbot/pages/InsightsPage.js +14 -14
- package/dist/subscriberbot/pages/InvoicesPage.js +66 -66
- package/dist/subscriberbot/pages/WorkflowsPage.js +20 -20
- package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js +184 -182
- 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 +77 -79
- 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 +134 -133
- package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +64 -64
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js +3 -3
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js +18 -18
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +76 -76
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js +6 -6
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js +7 -7
- package/package.json +1 -1
|
@@ -4,13 +4,13 @@ import { MetricsCard as n } from "../components/MetricsCard.js";
|
|
|
4
4
|
import { formatCurrency as r, formatEnumLabel as i, formatRelativeTime as a } from "../utils/formatters.js";
|
|
5
5
|
import { StatusBadge as o } from "../components/StatusBadge.js";
|
|
6
6
|
import { EmptyState as s } from "../components/EmptyState.js";
|
|
7
|
-
import {
|
|
7
|
+
import { useTr as c } from "../hooks/useTr.js";
|
|
8
|
+
import { ErrorView as ee, LoadingView as l, Section as u } from "../components/StateViews.js";
|
|
8
9
|
import { Donut as d, Legend as f, Timeline as p } from "../components/viz.js";
|
|
9
10
|
import "../components/index.js";
|
|
10
11
|
import { useInboxMessages as m, useInvoices as h, useRelationships as g, useRenewalEvents as _ } from "../hooks/useSubscriberBotApi.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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";
|
|
12
|
+
import { useMemo as v } from "react";
|
|
13
|
+
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";
|
|
14
14
|
import { jsx as k, jsxs as A } from "react/jsx-runtime";
|
|
15
15
|
import { GlassCard as j, PagePurpose as M } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/subscriberbot/pages/DashboardPage.tsx
|
|
@@ -25,7 +25,7 @@ var N = {
|
|
|
25
25
|
USAGE_BASED: 1
|
|
26
26
|
}, P = 1e3 * 60 * 60 * 24;
|
|
27
27
|
function F() {
|
|
28
|
-
let { navigateTo: F, currentUser: I } = e(), L =
|
|
28
|
+
let { navigateTo: F, currentUser: I } = e(), L = c(), R = g({ pagination: {
|
|
29
29
|
limit: 100,
|
|
30
30
|
offset: 0
|
|
31
31
|
} }), z = h({ pagination: {
|
|
@@ -34,10 +34,10 @@ function F() {
|
|
|
34
34
|
} }), B = m({ pagination: {
|
|
35
35
|
limit: 50,
|
|
36
36
|
offset: 0
|
|
37
|
-
} }), V = _({ upcomingOnly: !0 }), H =
|
|
37
|
+
} }), V = _({ upcomingOnly: !0 }), H = v(() => R.data?.items ?? [], [R.data]), U = v(() => z.data?.items ?? [], [z.data]), W = v(() => B.data?.items ?? [], [B.data]), G = v(() => V.data ?? [], [V.data]), K = v(() => {
|
|
38
38
|
let e = /* @__PURE__ */ new Map();
|
|
39
39
|
return H.forEach((t) => e.set(t.id, t)), e;
|
|
40
|
-
}, [H]), q =
|
|
40
|
+
}, [H]), q = v(() => {
|
|
41
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).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;
|
|
42
42
|
return {
|
|
43
43
|
activeCount: e.length,
|
|
@@ -53,13 +53,13 @@ function F() {
|
|
|
53
53
|
H,
|
|
54
54
|
W,
|
|
55
55
|
U
|
|
56
|
-
]), J =
|
|
56
|
+
]), J = v(() => {
|
|
57
57
|
let e = /* @__PURE__ */ new Map();
|
|
58
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]) => ({
|
|
59
59
|
label: i(e),
|
|
60
60
|
value: t
|
|
61
61
|
}));
|
|
62
|
-
}, [H]), Y =
|
|
62
|
+
}, [H]), Y = v(() => {
|
|
63
63
|
let e = Date.now(), t = G.map((t) => {
|
|
64
64
|
let n = K.get(t.relationshipId), r = new Date(t.occursAt).getTime();
|
|
65
65
|
return {
|
|
@@ -92,12 +92,12 @@ function F() {
|
|
|
92
92
|
G,
|
|
93
93
|
H,
|
|
94
94
|
K
|
|
95
|
-
]), X =
|
|
95
|
+
]), X = v(() => Y.slice(0, 6).map((e) => ({
|
|
96
96
|
title: e.title,
|
|
97
97
|
sub: a(e.when),
|
|
98
98
|
tone: e.soon ? "warning" : "default",
|
|
99
99
|
meta: e.amount == null ? void 0 : r(e.amount, e.currency ?? q.primaryCurrency, { maximumFractionDigits: 0 })
|
|
100
|
-
})), [Y, q.primaryCurrency]), Z =
|
|
100
|
+
})), [Y, q.primaryCurrency]), Z = v(() => W.filter((e) => e.actionRequired), [W]), Q = R.isLoading || z.isLoading || B.isLoading || V.isLoading, te = R.isError && z.isError && B.isError && V.isError;
|
|
101
101
|
if (Q) return /* @__PURE__ */ k(t, {
|
|
102
102
|
title: L("dashboard.title", "Overview"),
|
|
103
103
|
description: L("dashboard.description", "Your recurring digital relationships at a glance"),
|
|
@@ -106,7 +106,7 @@ function F() {
|
|
|
106
106
|
if (te) return /* @__PURE__ */ k(t, {
|
|
107
107
|
title: L("dashboard.title", "Overview"),
|
|
108
108
|
description: L("dashboard.description", "Your recurring digital relationships at a glance"),
|
|
109
|
-
children: /* @__PURE__ */ k(
|
|
109
|
+
children: /* @__PURE__ */ k(ee, {
|
|
110
110
|
message: L("dashboard.error", "We couldn't reach the Subscriber Bot service. Please try again."),
|
|
111
111
|
onRetry: () => {
|
|
112
112
|
R.refetch(), z.refetch(), B.refetch(), V.refetch();
|
|
@@ -119,7 +119,7 @@ function F() {
|
|
|
119
119
|
descriptionKey: "dashboard.quickActions.addRelationship.description",
|
|
120
120
|
fallbackLabel: "Add a relationship",
|
|
121
121
|
fallbackDescription: "Track a new subscription",
|
|
122
|
-
icon: /* @__PURE__ */ k(
|
|
122
|
+
icon: /* @__PURE__ */ k(T, { className: "h-5 w-5" }),
|
|
123
123
|
path: "/relationships/new"
|
|
124
124
|
},
|
|
125
125
|
{
|
|
@@ -135,7 +135,7 @@ function F() {
|
|
|
135
135
|
descriptionKey: "dashboard.quickActions.openInbox.description",
|
|
136
136
|
fallbackLabel: "Open inbox",
|
|
137
137
|
fallbackDescription: "Merged communications",
|
|
138
|
-
icon: /* @__PURE__ */ k(
|
|
138
|
+
icon: /* @__PURE__ */ k(w, { className: "h-5 w-5" }),
|
|
139
139
|
path: "/inbox"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
@@ -143,7 +143,7 @@ function F() {
|
|
|
143
143
|
descriptionKey: "dashboard.quickActions.configureAgents.description",
|
|
144
144
|
fallbackLabel: "Configure agents",
|
|
145
145
|
fallbackDescription: "Automate your portfolio",
|
|
146
|
-
icon: /* @__PURE__ */ k(
|
|
146
|
+
icon: /* @__PURE__ */ k(S, { className: "h-5 w-5" }),
|
|
147
147
|
path: "/agents"
|
|
148
148
|
}
|
|
149
149
|
];
|
|
@@ -165,17 +165,17 @@ function F() {
|
|
|
165
165
|
label: L("dashboard.metrics.recurringSpend", "Recurring spend / month")
|
|
166
166
|
}),
|
|
167
167
|
/* @__PURE__ */ k(n, {
|
|
168
|
-
icon: /* @__PURE__ */ k(
|
|
168
|
+
icon: /* @__PURE__ */ k(T, {}),
|
|
169
169
|
value: q.activeCount,
|
|
170
170
|
label: L("dashboard.metrics.activeRelationships", "Active relationships")
|
|
171
171
|
}),
|
|
172
172
|
/* @__PURE__ */ k(n, {
|
|
173
|
-
icon: /* @__PURE__ */ k(
|
|
173
|
+
icon: /* @__PURE__ */ k(C, {}),
|
|
174
174
|
value: Y.length,
|
|
175
175
|
label: L("dashboard.metrics.upcomingRenewals", "Upcoming renewals")
|
|
176
176
|
}),
|
|
177
177
|
/* @__PURE__ */ k(n, {
|
|
178
|
-
icon: /* @__PURE__ */ k(
|
|
178
|
+
icon: /* @__PURE__ */ k(y, {}),
|
|
179
179
|
value: q.actionRequired,
|
|
180
180
|
label: L("dashboard.metrics.needsAction", "Need your action"),
|
|
181
181
|
trend: q.actionRequired > 0 ? "down" : void 0
|
|
@@ -197,7 +197,7 @@ function F() {
|
|
|
197
197
|
children: [
|
|
198
198
|
L("common.viewAll", "View all"),
|
|
199
199
|
" ",
|
|
200
|
-
/* @__PURE__ */ k(
|
|
200
|
+
/* @__PURE__ */ k(x, { className: "h-3.5 w-3.5" })
|
|
201
201
|
]
|
|
202
202
|
}),
|
|
203
203
|
children: /* @__PURE__ */ k(j, {
|
|
@@ -261,7 +261,7 @@ function F() {
|
|
|
261
261
|
className: "flex items-center gap-3 px-2 py-4",
|
|
262
262
|
children: [/* @__PURE__ */ k("span", {
|
|
263
263
|
className: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-status-success-bg-subtle text-status-success-text",
|
|
264
|
-
children: /* @__PURE__ */ k(
|
|
264
|
+
children: /* @__PURE__ */ k(E, { className: "h-5 w-5" })
|
|
265
265
|
}), /* @__PURE__ */ A("div", {
|
|
266
266
|
className: "min-w-0",
|
|
267
267
|
children: [/* @__PURE__ */ k("p", {
|
|
@@ -303,14 +303,14 @@ function F() {
|
|
|
303
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",
|
|
304
304
|
children: [/* @__PURE__ */ A("div", {
|
|
305
305
|
className: "flex items-center gap-3",
|
|
306
|
-
children: [/* @__PURE__ */ k(
|
|
306
|
+
children: [/* @__PURE__ */ k(y, { className: "h-5 w-5 shrink-0 text-status-warning-text" }), /* @__PURE__ */ k("span", {
|
|
307
307
|
className: "text-sm font-medium text-status-warning-text",
|
|
308
308
|
children: L("dashboard.openInvoices.label", "{{count}} open invoice · {{amount}} outstanding", {
|
|
309
309
|
count: q.openInvoiceCount,
|
|
310
310
|
amount: r(q.openInvoiceTotal, q.primaryCurrency)
|
|
311
311
|
})
|
|
312
312
|
})]
|
|
313
|
-
}), /* @__PURE__ */ k(
|
|
313
|
+
}), /* @__PURE__ */ k(b, { className: "h-4 w-4 shrink-0 text-status-warning-text" })]
|
|
314
314
|
}),
|
|
315
315
|
/* @__PURE__ */ k(u, {
|
|
316
316
|
title: L("dashboard.quickActions.title", "Quick actions"),
|
|
@@ -336,7 +336,7 @@ function F() {
|
|
|
336
336
|
children: L(e.descriptionKey, e.fallbackDescription)
|
|
337
337
|
})]
|
|
338
338
|
}),
|
|
339
|
-
/* @__PURE__ */ k(
|
|
339
|
+
/* @__PURE__ */ k(b, { className: "h-4 w-4 shrink-0 text-text-secondary transition-transform group-hover:translate-x-0.5" })
|
|
340
340
|
]
|
|
341
341
|
}, e.path))
|
|
342
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 { 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 // BOFF-8660 — KPI must match the Universal Inbox \"need action\" counter\n // and the on-page Needs-attention list, both of which count by\n // `actionRequired` alone (read state is irrelevant). The previous\n // `&& !m.isRead` clause made the tile show 0 the moment the user read\n // the only action-required message, contradicting the surfaces below.\n const actionRequired = messages.filter((m) => m.actionRequired).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 actionRequired,\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.actionRequired}\n label={tr('dashboard.metrics.needsAction', 'Need your action')}\n trend={stats.actionRequired > 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
|
+
{"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 // BOFF-8660 — KPI must match the Universal Inbox \"need action\" counter\n // and the on-page Needs-attention list, both of which count by\n // `actionRequired` alone (read state is irrelevant). The previous\n // `&& !m.isRead` clause made the tile show 0 the moment the user read\n // the only action-required message, contradicting the surfaces below.\n const actionRequired = messages.filter((m) => m.actionRequired).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 actionRequired,\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.actionRequired}\n label={tr('dashboard.metrics.needsAction', 'Need your action')}\n trend={stats.actionRequired > 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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"}
|