@burdenoff/microfe-subscriberbot 2026.706.1 → 2026.706.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/dist/subscriberbot/components/Modal.js +51 -0
  2. package/dist/subscriberbot/components/Modal.js.map +1 -0
  3. package/dist/subscriberbot/hooks/useSubscriberBotApi.js +358 -163
  4. package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
  5. package/dist/subscriberbot/pages/InboxPage.js +339 -66
  6. package/dist/subscriberbot/pages/InboxPage.js.map +1 -1
  7. package/dist/subscriberbot/pages/InvoicesPage.js +496 -73
  8. package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
  9. package/dist/subscriberbot/pages/WorkflowsPage.js +136 -150
  10. package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -1
  11. package/dist/subscriberbot/pages/agents/AgentDetailPage.js +197 -58
  12. package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -1
  13. package/dist/subscriberbot/pages/agents/AgentsListPage.js +2 -0
  14. package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
  15. package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +538 -71
  16. package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
  17. package/dist/subscriberbot/pages/providers/ProvidersListPage.js +2 -0
  18. package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
  19. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +664 -126
  20. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
  21. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js +18 -16
  22. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -1
  23. package/package.json +1 -1
@@ -1,17 +1,18 @@
1
1
  import { useSubscriberBot as e } from "../context/SubscriberBotContext.js";
2
2
  import { cn as t } from "../utils/cn.js";
3
3
  import { PageLayout as n } from "../components/PageLayout.js";
4
- import { formatRelativeTime as r } from "../utils/formatters.js";
5
- import { StatusBadge as i } from "../components/StatusBadge.js";
6
- import { EmptyState as a } from "../components/EmptyState.js";
7
- import { ErrorView as o, LoadingView as s } from "../components/StateViews.js";
8
- import { useInboxMessages as c, useMarkMessageRead as l } from "../hooks/useSubscriberBotApi.js";
9
- import { useState as u } from "react";
10
- import { AlertTriangle as d, Inbox as f, Mail as p, MailOpen as m } from "lucide-react";
11
- import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
12
- import { PagePurpose as v } from "@burdenoff/fe-libs/ui";
4
+ import { formatEnumLabel as r, formatRelativeTime as i } from "../utils/formatters.js";
5
+ import { StatusBadge as a } from "../components/StatusBadge.js";
6
+ import { EmptyState as o } from "../components/EmptyState.js";
7
+ import { ErrorView as s, LoadingView as c, Section as l } from "../components/StateViews.js";
8
+ import { useChannels as u, useCreateChannel as d, useInboxMessages as f, useIngestMessage as p, useMarkMessageRead as m, useVerifyChannel as h } from "../hooks/useSubscriberBotApi.js";
9
+ import { Modal as g } from "../components/Modal.js";
10
+ import { useState as _ } from "react";
11
+ import { AlertTriangle as v, BadgeCheck as y, Inbox as b, Mail as x, MailOpen as S, Plus as C, Radio as w, Send as T } from "lucide-react";
12
+ import { Fragment as E, jsx as D, jsxs as O } from "react/jsx-runtime";
13
+ import { PagePurpose as k } from "@burdenoff/fe-libs/ui";
13
14
  //#region src/subscriberbot/pages/InboxPage.tsx
14
- var y = 25, b = [
15
+ var A = 25, j = "w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]", M = "mb-1 block text-xs font-medium text-text-secondary", N = [
15
16
  {
16
17
  value: "",
17
18
  label: "All categories"
@@ -44,97 +45,368 @@ var y = 25, b = [
44
45
  value: "OTHER",
45
46
  label: "Other"
46
47
  }
48
+ ], P = [
49
+ "BILLING",
50
+ "RENEWAL",
51
+ "REQUIRED_ACTION",
52
+ "SECURITY_ALERT",
53
+ "PRODUCT_UPDATE",
54
+ "MARKETING",
55
+ "OTHER"
56
+ ], F = [
57
+ "EMAIL",
58
+ "SMS",
59
+ "SLACK",
60
+ "DISCORD",
61
+ "WHATSAPP",
62
+ "PUSH",
63
+ "WEBHOOK"
47
64
  ];
48
- function x() {
49
- let { navigateTo: x } = e(), [S, C] = u(0), [w, T] = u(""), [E, D] = u(!1), { data: O, isLoading: k, isError: A, refetch: j, isFetching: M } = c({
50
- category: w || void 0,
51
- unreadOnly: E || void 0,
65
+ function I({ onClose: e }) {
66
+ let t = d(), [n, r] = _("EMAIL"), [i, a] = _(""), [o, s] = _(""), [c, l] = _(!0), [u, f] = _(null);
67
+ return /* @__PURE__ */ D(g, {
68
+ title: "Add channel",
69
+ onClose: e,
70
+ maxWidthClassName: "max-w-xl",
71
+ children: /* @__PURE__ */ O("form", {
72
+ onSubmit: (r) => {
73
+ if (r.preventDefault(), f(null), !i.trim()) {
74
+ f("An address is required.");
75
+ return;
76
+ }
77
+ t.mutate({
78
+ type: n,
79
+ address: i.trim(),
80
+ ingestEnabled: c,
81
+ ...o.trim() ? { label: o.trim() } : {}
82
+ }, {
83
+ onSuccess: () => e(),
84
+ onError: (e) => f(e instanceof Error ? e.message : "Could not add channel.")
85
+ });
86
+ },
87
+ className: "space-y-4",
88
+ children: [
89
+ u && /* @__PURE__ */ D("div", {
90
+ className: "rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text",
91
+ children: u
92
+ }),
93
+ /* @__PURE__ */ O("div", {
94
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
95
+ children: [/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("label", {
96
+ htmlFor: "ch-type",
97
+ className: M,
98
+ children: "Type"
99
+ }), /* @__PURE__ */ D("select", {
100
+ id: "ch-type",
101
+ value: n,
102
+ onChange: (e) => r(e.target.value),
103
+ className: j,
104
+ children: F.map((e) => /* @__PURE__ */ D("option", {
105
+ value: e,
106
+ children: e.replace(/_/g, " ")
107
+ }, e))
108
+ })] }), /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("label", {
109
+ htmlFor: "ch-label",
110
+ className: M,
111
+ children: "Label"
112
+ }), /* @__PURE__ */ D("input", {
113
+ id: "ch-label",
114
+ type: "text",
115
+ value: o,
116
+ onChange: (e) => s(e.target.value),
117
+ placeholder: "e.g. Billing inbox",
118
+ className: j
119
+ })] })]
120
+ }),
121
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ O("label", {
122
+ htmlFor: "ch-address",
123
+ className: M,
124
+ children: ["Address ", /* @__PURE__ */ D("span", {
125
+ className: "text-status-error-text",
126
+ children: "*"
127
+ })]
128
+ }), /* @__PURE__ */ D("input", {
129
+ id: "ch-address",
130
+ type: "text",
131
+ value: i,
132
+ onChange: (e) => a(e.target.value),
133
+ placeholder: "e.g. billing@acme.com, +1…, a webhook URL",
134
+ className: j,
135
+ required: !0
136
+ })] }),
137
+ /* @__PURE__ */ O("label", {
138
+ className: "flex items-center gap-2 text-sm text-text-primary",
139
+ children: [/* @__PURE__ */ D("input", {
140
+ type: "checkbox",
141
+ checked: c,
142
+ onChange: (e) => l(e.target.checked),
143
+ className: "size-4 rounded border-border-subtle"
144
+ }), "Ingest messages from this channel"]
145
+ }),
146
+ /* @__PURE__ */ O("div", {
147
+ className: "flex justify-end gap-3",
148
+ children: [/* @__PURE__ */ D("button", {
149
+ type: "button",
150
+ onClick: e,
151
+ className: "rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
152
+ children: "Cancel"
153
+ }), /* @__PURE__ */ D("button", {
154
+ type: "submit",
155
+ disabled: t.isPending,
156
+ className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50",
157
+ children: t.isPending ? "Adding…" : "Add channel"
158
+ })]
159
+ })
160
+ ]
161
+ })
162
+ });
163
+ }
164
+ function L({ channel: e, onClose: t }) {
165
+ let n = p(), [r, i] = _(""), [a, o] = _(""), [s, c] = _("OTHER"), [l, u] = _(!1), [d, f] = _(null);
166
+ return /* @__PURE__ */ D(g, {
167
+ title: "Log test message",
168
+ description: `Simulate an ingested message from ${e.label || e.address}.`,
169
+ onClose: t,
170
+ children: /* @__PURE__ */ O("form", {
171
+ onSubmit: (i) => {
172
+ i.preventDefault(), f(null), n.mutate({
173
+ channelId: e.id,
174
+ receivedAt: (/* @__PURE__ */ new Date()).toISOString(),
175
+ category: s,
176
+ actionRequired: l,
177
+ ...r.trim() ? { subject: r.trim() } : {},
178
+ ...a.trim() ? { preview: a.trim() } : {}
179
+ }, {
180
+ onSuccess: () => t(),
181
+ onError: (e) => f(e instanceof Error ? e.message : "Could not log the message.")
182
+ });
183
+ },
184
+ className: "space-y-4",
185
+ children: [
186
+ d && /* @__PURE__ */ D("div", {
187
+ className: "rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text",
188
+ children: d
189
+ }),
190
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("label", {
191
+ htmlFor: "msg-subject",
192
+ className: M,
193
+ children: "Subject"
194
+ }), /* @__PURE__ */ D("input", {
195
+ id: "msg-subject",
196
+ type: "text",
197
+ value: r,
198
+ onChange: (e) => i(e.target.value),
199
+ placeholder: "e.g. Your plan renews in 3 days",
200
+ className: j
201
+ })] }),
202
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("label", {
203
+ htmlFor: "msg-preview",
204
+ className: M,
205
+ children: "Preview"
206
+ }), /* @__PURE__ */ D("textarea", {
207
+ id: "msg-preview",
208
+ value: a,
209
+ onChange: (e) => o(e.target.value),
210
+ rows: 2,
211
+ className: j
212
+ })] }),
213
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("label", {
214
+ htmlFor: "msg-category",
215
+ className: M,
216
+ children: "Category"
217
+ }), /* @__PURE__ */ D("select", {
218
+ id: "msg-category",
219
+ value: s,
220
+ onChange: (e) => c(e.target.value),
221
+ className: j,
222
+ children: P.map((e) => /* @__PURE__ */ D("option", {
223
+ value: e,
224
+ children: e.replace(/_/g, " ")
225
+ }, e))
226
+ })] }),
227
+ /* @__PURE__ */ O("label", {
228
+ className: "flex items-center gap-2 text-sm text-text-primary",
229
+ children: [/* @__PURE__ */ D("input", {
230
+ type: "checkbox",
231
+ checked: l,
232
+ onChange: (e) => u(e.target.checked),
233
+ className: "size-4 rounded border-border-subtle"
234
+ }), "Requires action"]
235
+ }),
236
+ /* @__PURE__ */ O("div", {
237
+ className: "flex justify-end gap-3",
238
+ children: [/* @__PURE__ */ D("button", {
239
+ type: "button",
240
+ onClick: t,
241
+ className: "rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
242
+ children: "Cancel"
243
+ }), /* @__PURE__ */ D("button", {
244
+ type: "submit",
245
+ disabled: n.isPending,
246
+ className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50",
247
+ children: n.isPending ? "Logging…" : "Log message"
248
+ })]
249
+ })
250
+ ]
251
+ })
252
+ });
253
+ }
254
+ function R() {
255
+ let { data: e, isLoading: t, isError: n, refetch: i } = u(), d = h(), [f, p] = _(!1), [m, g] = _(null), v = e ?? [];
256
+ return /* @__PURE__ */ O(l, {
257
+ title: "Ingest channels",
258
+ description: "The sources the Universal Inbox pulls subscription communication from",
259
+ action: /* @__PURE__ */ O("button", {
260
+ type: "button",
261
+ onClick: () => p(!0),
262
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken",
263
+ children: [/* @__PURE__ */ D(C, { className: "h-3.5 w-3.5" }), " Add channel"]
264
+ }),
265
+ children: [
266
+ t ? /* @__PURE__ */ D(c, { label: "Loading channels…" }) : n ? /* @__PURE__ */ D(s, {
267
+ message: "Could not load channels.",
268
+ onRetry: () => void i()
269
+ }) : v.length === 0 ? /* @__PURE__ */ D(o, {
270
+ illustration: "empty-data",
271
+ icon: /* @__PURE__ */ D(w, { className: "h-12 w-12" }),
272
+ title: "No channels yet",
273
+ description: "Add an email, SMS, Slack or webhook channel to start ingesting subscription communication."
274
+ }) : /* @__PURE__ */ D("div", {
275
+ className: "divide-y divide-border-subtle rounded-lg border border-border-subtle bg-bg-surface",
276
+ children: v.map((e) => /* @__PURE__ */ O("div", {
277
+ className: "flex items-center justify-between gap-3 p-3",
278
+ children: [/* @__PURE__ */ O("div", {
279
+ className: "flex min-w-0 items-center gap-2",
280
+ children: [/* @__PURE__ */ D(w, { className: "h-4 w-4 shrink-0 text-text-secondary" }), /* @__PURE__ */ O("div", {
281
+ className: "min-w-0",
282
+ children: [/* @__PURE__ */ D("p", {
283
+ className: "truncate text-sm font-medium text-text-primary",
284
+ children: e.label || e.address
285
+ }), /* @__PURE__ */ O("p", {
286
+ className: "text-xs text-text-secondary",
287
+ children: [r(e.type), e.label ? ` · ${e.address}` : ""]
288
+ })]
289
+ })]
290
+ }), /* @__PURE__ */ O("div", {
291
+ className: "flex shrink-0 items-center gap-2",
292
+ children: [
293
+ /* @__PURE__ */ D(a, { status: e.status }),
294
+ e.status === "PENDING" && /* @__PURE__ */ O("button", {
295
+ type: "button",
296
+ disabled: d.isPending,
297
+ onClick: () => d.mutate(e.id),
298
+ className: "inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50",
299
+ children: [/* @__PURE__ */ D(y, { className: "h-3.5 w-3.5" }), " Verify"]
300
+ }),
301
+ e.status === "VERIFIED" && e.ingestEnabled && /* @__PURE__ */ O("button", {
302
+ type: "button",
303
+ onClick: () => g(e),
304
+ className: "inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken",
305
+ children: [/* @__PURE__ */ D(T, { className: "h-3.5 w-3.5" }), " Log test message"]
306
+ })
307
+ ]
308
+ })]
309
+ }, e.id))
310
+ }),
311
+ f && /* @__PURE__ */ D(I, { onClose: () => p(!1) }),
312
+ m && /* @__PURE__ */ D(L, {
313
+ channel: m,
314
+ onClose: () => g(null)
315
+ })
316
+ ]
317
+ });
318
+ }
319
+ function z() {
320
+ let { navigateTo: r } = e(), [l, u] = _(0), [d, p] = _(""), [h, g] = _(!1), { data: y, isLoading: C, isError: w, refetch: T, isFetching: j } = f({
321
+ category: d || void 0,
322
+ unreadOnly: h || void 0,
52
323
  pagination: {
53
- limit: y,
54
- offset: S * y
324
+ limit: A,
325
+ offset: l * A
55
326
  }
56
- }), N = l(), P = O?.items ?? [], F = O?.pageInfo.hasMore ?? !1;
57
- return /* @__PURE__ */ _(n, {
327
+ }), M = m(), P = y?.items ?? [], F = y?.pageInfo.hasMore ?? !1;
328
+ return /* @__PURE__ */ O(n, {
58
329
  title: "Universal Inbox",
59
330
  description: "Every subscription communication, merged and categorized",
60
331
  children: [
61
- /* @__PURE__ */ g(v, {
332
+ /* @__PURE__ */ D(k, {
62
333
  className: "mb-4",
63
334
  children: "Subscription communication is noisy and scattered. The Universal Inbox merges every channel a provider uses and uses AI to categorize each message so you never miss a renewal, required action or security notice."
64
335
  }),
65
- /* @__PURE__ */ _("div", {
336
+ /* @__PURE__ */ O("div", {
66
337
  className: "flex flex-wrap items-center gap-3",
67
- children: [/* @__PURE__ */ g("select", {
68
- value: w,
338
+ children: [/* @__PURE__ */ D("select", {
339
+ "aria-label": "Filter by category",
340
+ value: d,
69
341
  onChange: (e) => {
70
- T(e.target.value), C(0);
342
+ p(e.target.value), u(0);
71
343
  },
72
344
  className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
73
- children: b.map((e) => /* @__PURE__ */ g("option", {
345
+ children: N.map((e) => /* @__PURE__ */ D("option", {
74
346
  value: e.value,
75
347
  children: e.label
76
348
  }, e.value))
77
- }), /* @__PURE__ */ _("label", {
349
+ }), /* @__PURE__ */ O("label", {
78
350
  className: "flex items-center gap-2 text-sm text-text-primary",
79
- children: [/* @__PURE__ */ g("input", {
351
+ children: [/* @__PURE__ */ D("input", {
80
352
  type: "checkbox",
81
- checked: E,
353
+ checked: h,
82
354
  onChange: (e) => {
83
- D(e.target.checked), C(0);
355
+ g(e.target.checked), u(0);
84
356
  },
85
357
  className: "size-4 rounded border-border-subtle"
86
358
  }), "Unread only"]
87
359
  })]
88
360
  }),
89
- k ? /* @__PURE__ */ g(s, { label: "Loading inbox…" }) : A ? /* @__PURE__ */ g(o, {
361
+ C ? /* @__PURE__ */ D(c, { label: "Loading inbox…" }) : w ? /* @__PURE__ */ D(s, {
90
362
  message: "Could not load the inbox.",
91
- onRetry: () => void j()
92
- }) : P.length === 0 ? /* @__PURE__ */ g("div", {
363
+ onRetry: () => void T()
364
+ }) : P.length === 0 ? /* @__PURE__ */ D("div", {
93
365
  className: "mt-6",
94
- children: /* @__PURE__ */ g(a, {
366
+ children: /* @__PURE__ */ D(o, {
95
367
  illustration: "empty-inbox",
96
- icon: /* @__PURE__ */ g(f, { className: "h-12 w-12" }),
368
+ icon: /* @__PURE__ */ D(b, { className: "h-12 w-12" }),
97
369
  title: "Inbox is empty",
98
- description: E || w ? "No messages match these filters." : "Merged subscription communications will appear here once channels start ingesting."
370
+ description: h || d ? "No messages match these filters." : "Merged subscription communications will appear here once channels start ingesting."
99
371
  })
100
- }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("div", {
372
+ }) : /* @__PURE__ */ O(E, { children: [/* @__PURE__ */ D("div", {
101
373
  className: "mt-6 divide-y divide-border-subtle rounded-lg border border-border-subtle bg-bg-surface",
102
- children: P.map((e) => /* @__PURE__ */ _("div", {
374
+ children: P.map((e) => /* @__PURE__ */ O("div", {
103
375
  className: t("flex items-start gap-3 p-4", !e.isRead && "bg-accent-wash"),
104
376
  children: [
105
- /* @__PURE__ */ g("span", {
377
+ /* @__PURE__ */ D("span", {
106
378
  className: "mt-0.5 shrink-0 text-text-secondary",
107
- children: e.isRead ? /* @__PURE__ */ g(m, { className: "h-4 w-4" }) : /* @__PURE__ */ g(p, { className: "h-4 w-4 text-text-link" })
379
+ children: e.isRead ? /* @__PURE__ */ D(S, { className: "h-4 w-4" }) : /* @__PURE__ */ D(x, { className: "h-4 w-4 text-text-link" })
108
380
  }),
109
- /* @__PURE__ */ _("div", {
381
+ /* @__PURE__ */ O("div", {
110
382
  className: "min-w-0 flex-1",
111
383
  children: [
112
- /* @__PURE__ */ _("div", {
384
+ /* @__PURE__ */ O("div", {
113
385
  className: "flex items-center gap-2",
114
- children: [/* @__PURE__ */ g("p", {
386
+ children: [/* @__PURE__ */ D("p", {
115
387
  className: t("truncate text-sm", e.isRead ? "font-normal text-text-primary" : "font-semibold text-text-primary"),
116
388
  children: e.subject || "(no subject)"
117
- }), e.actionRequired && /* @__PURE__ */ _("span", {
389
+ }), e.actionRequired && /* @__PURE__ */ O("span", {
118
390
  title: "Action required",
119
391
  className: "inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-warning-text",
120
- children: [/* @__PURE__ */ g(d, { className: "h-3 w-3" }), " Action"]
392
+ children: [/* @__PURE__ */ D(v, { className: "h-3 w-3" }), " Action"]
121
393
  })]
122
394
  }),
123
- e.preview && /* @__PURE__ */ g("p", {
395
+ e.preview && /* @__PURE__ */ D("p", {
124
396
  className: "mt-0.5 line-clamp-2 text-xs text-text-secondary",
125
397
  children: e.preview
126
398
  }),
127
- /* @__PURE__ */ _("div", {
399
+ /* @__PURE__ */ O("div", {
128
400
  className: "mt-1.5 flex flex-wrap items-center gap-2",
129
401
  children: [
130
- /* @__PURE__ */ g(i, { status: e.category }),
131
- /* @__PURE__ */ g("span", {
402
+ /* @__PURE__ */ D(a, { status: e.category }),
403
+ /* @__PURE__ */ D("span", {
132
404
  className: "text-xs text-text-secondary",
133
- children: r(e.receivedAt)
405
+ children: i(e.receivedAt)
134
406
  }),
135
- e.relationshipId && /* @__PURE__ */ g("button", {
407
+ e.relationshipId && /* @__PURE__ */ D("button", {
136
408
  type: "button",
137
- onClick: () => x(`/relationships/${e.relationshipId}`),
409
+ onClick: () => r(`/relationships/${e.relationshipId}`),
138
410
  className: "text-xs font-medium text-text-link hover:underline",
139
411
  children: "View relationship"
140
412
  })
@@ -142,45 +414,46 @@ function x() {
142
414
  })
143
415
  ]
144
416
  }),
145
- !e.isRead && /* @__PURE__ */ g("button", {
417
+ !e.isRead && /* @__PURE__ */ D("button", {
146
418
  type: "button",
147
- disabled: N.isPending,
148
- onClick: () => N.mutate(e.id),
419
+ disabled: M.isPending,
420
+ onClick: () => M.mutate(e.id),
149
421
  className: "shrink-0 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50",
150
422
  children: "Mark read"
151
423
  })
152
424
  ]
153
425
  }, e.id))
154
- }), /* @__PURE__ */ _("div", {
426
+ }), /* @__PURE__ */ O("div", {
155
427
  className: "mt-4 flex items-center justify-between",
156
- children: [/* @__PURE__ */ _("p", {
428
+ children: [/* @__PURE__ */ O("p", {
157
429
  className: "text-xs text-text-secondary",
158
430
  children: [
159
431
  "Page ",
160
- S + 1,
161
- M ? " · refreshing…" : ""
432
+ l + 1,
433
+ j ? " · refreshing…" : ""
162
434
  ]
163
- }), /* @__PURE__ */ _("div", {
435
+ }), /* @__PURE__ */ O("div", {
164
436
  className: "flex gap-2",
165
- children: [/* @__PURE__ */ g("button", {
437
+ children: [/* @__PURE__ */ D("button", {
166
438
  type: "button",
167
- disabled: S === 0,
168
- onClick: () => C((e) => Math.max(0, e - 1)),
169
- className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", S === 0 ? "cursor-not-allowed text-text-secondary opacity-50" : "text-text-primary hover:bg-bg-sunken"),
439
+ disabled: l === 0,
440
+ onClick: () => u((e) => Math.max(0, e - 1)),
441
+ className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", l === 0 ? "cursor-not-allowed text-text-secondary opacity-50" : "text-text-primary hover:bg-bg-sunken"),
170
442
  children: "Previous"
171
- }), /* @__PURE__ */ g("button", {
443
+ }), /* @__PURE__ */ D("button", {
172
444
  type: "button",
173
445
  disabled: !F,
174
- onClick: () => C((e) => e + 1),
446
+ onClick: () => u((e) => e + 1),
175
447
  className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", F ? "text-text-primary hover:bg-bg-sunken" : "cursor-not-allowed text-text-secondary opacity-50"),
176
448
  children: "Next"
177
449
  })]
178
450
  })]
179
- })] })
451
+ })] }),
452
+ /* @__PURE__ */ D(R, {})
180
453
  ]
181
454
  });
182
455
  }
183
456
  //#endregion
184
- export { x as InboxPage };
457
+ export { z as InboxPage };
185
458
 
186
459
  //# sourceMappingURL=InboxPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InboxPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InboxPage.tsx"],"sourcesContent":["/**\n * Universal Inbox\n * @module subscriberbot/pages\n *\n * Every merged subscription communication in one timeline, AI-categorized.\n * Filter by category / unread, mark messages read. Reads\n * `subListInboxMessages` (a SubInboxMessageConnection); writes\n * `subMarkMessageRead`.\n */\n\nimport { useState } from 'react';\nimport { Mail, MailOpen, AlertTriangle, Inbox as InboxIcon } from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingView, ErrorView } from '../components/StateViews';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport { useInboxMessages, useMarkMessageRead } from '../hooks/useSubscriberBotApi';\nimport { cn } from '../utils/cn';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { SubMessageCategory } from '../types';\n\nconst PAGE_SIZE = 25;\n\nconst CATEGORY_OPTIONS: Array<{ value: '' | SubMessageCategory; label: string }> = [\n { value: '', label: 'All categories' },\n { value: 'BILLING', label: 'Billing' },\n { value: 'RENEWAL', label: 'Renewals' },\n { value: 'REQUIRED_ACTION', label: 'Required actions' },\n { value: 'SECURITY_ALERT', label: 'Security alerts' },\n { value: 'PRODUCT_UPDATE', label: 'Product updates' },\n { value: 'MARKETING', label: 'Marketing' },\n { value: 'OTHER', label: 'Other' },\n];\n\nexport function InboxPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [category, setCategory] = useState<'' | SubMessageCategory>('');\n const [unreadOnly, setUnreadOnly] = useState(false);\n\n const { data, isLoading, isError, refetch, isFetching } = useInboxMessages({\n category: category || undefined,\n unreadOnly: unreadOnly || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markRead = useMarkMessageRead();\n\n const items = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Universal Inbox\"\n description=\"Every subscription communication, merged and categorized\"\n >\n <PagePurpose className=\"mb-4\">\n Subscription communication is noisy and scattered. The Universal Inbox merges every channel\n a provider uses and uses AI to categorize each message so you never miss a renewal, required\n action or security notice.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n value={category}\n onChange={(e) => {\n setCategory(e.target.value as '' | SubMessageCategory);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {CATEGORY_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={unreadOnly}\n onChange={(e) => {\n setUnreadOnly(e.target.checked);\n setPage(0);\n }}\n className=\"size-4 rounded border-border-subtle\"\n />\n Unread only\n </label>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading inbox…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load the inbox.\" onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-inbox\"\n icon={<InboxIcon className=\"h-12 w-12\" />}\n title=\"Inbox is empty\"\n description={\n unreadOnly || category\n ? 'No messages match these filters.'\n : 'Merged subscription communications will appear here once channels start ingesting.'\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 divide-y divide-border-subtle rounded-lg border border-border-subtle bg-bg-surface\">\n {items.map((m) => (\n <div\n key={m.id}\n className={cn('flex items-start gap-3 p-4', !m.isRead && 'bg-accent-wash')}\n >\n <span className=\"mt-0.5 shrink-0 text-text-secondary\">\n {m.isRead ? (\n <MailOpen className=\"h-4 w-4\" />\n ) : (\n <Mail className=\"h-4 w-4 text-text-link\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <p\n className={cn(\n 'truncate text-sm',\n m.isRead\n ? 'font-normal text-text-primary'\n : 'font-semibold text-text-primary'\n )}\n >\n {m.subject || '(no subject)'}\n </p>\n {m.actionRequired && (\n <span\n title=\"Action required\"\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3 w-3\" /> Action\n </span>\n )}\n </div>\n {m.preview && (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">{m.preview}</p>\n )}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={m.category} />\n <span className=\"text-xs text-text-secondary\">\n {formatRelativeTime(m.receivedAt)}\n </span>\n {m.relationshipId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${m.relationshipId}`)}\n className=\"text-xs font-medium text-text-link hover:underline\"\n >\n View relationship\n </button>\n )}\n </div>\n </div>\n {!m.isRead && (\n <button\n type=\"button\"\n disabled={markRead.isPending}\n onClick={() => markRead.mutate(m.id)}\n className=\"shrink-0 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n Mark read\n </button>\n )}\n </div>\n ))}\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Page {page + 1}\n {isFetching ? ' · refreshing…' : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Previous\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Next\n </button>\n </div>\n </div>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"InboxPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InboxPage.tsx"],"sourcesContent":["/**\n * Universal Inbox\n * @module subscriberbot/pages\n *\n * Every merged subscription communication in one timeline, AI-categorized,\n * plus management of the ingest channels that feed it. Filter by category /\n * unread, mark messages read, add & verify channels, and log a test message.\n * Reads `subListInboxMessages` / `subListChannels`; writes `subMarkMessageRead`,\n * `subCreateChannel`, `subVerifyChannel`, `subIngestMessage`.\n */\n\nimport { useState } from 'react';\nimport {\n Mail,\n MailOpen,\n AlertTriangle,\n Inbox as InboxIcon,\n Plus,\n Radio,\n BadgeCheck,\n Send,\n} from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Modal } from '../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useInboxMessages,\n useMarkMessageRead,\n useChannels,\n useCreateChannel,\n useVerifyChannel,\n useIngestMessage,\n} from '../hooks/useSubscriberBotApi';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel, formatRelativeTime } from '../utils/formatters';\nimport type { SubChannelType, SubCommunicationChannel, SubMessageCategory } from '../types';\n\nconst PAGE_SIZE = 25;\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst CATEGORY_OPTIONS: Array<{ value: '' | SubMessageCategory; label: string }> = [\n { value: '', label: 'All categories' },\n { value: 'BILLING', label: 'Billing' },\n { value: 'RENEWAL', label: 'Renewals' },\n { value: 'REQUIRED_ACTION', label: 'Required actions' },\n { value: 'SECURITY_ALERT', label: 'Security alerts' },\n { value: 'PRODUCT_UPDATE', label: 'Product updates' },\n { value: 'MARKETING', label: 'Marketing' },\n { value: 'OTHER', label: 'Other' },\n];\n\nconst MESSAGE_CATEGORIES: SubMessageCategory[] = [\n 'BILLING',\n 'RENEWAL',\n 'REQUIRED_ACTION',\n 'SECURITY_ALERT',\n 'PRODUCT_UPDATE',\n 'MARKETING',\n 'OTHER',\n];\n\nconst CHANNEL_TYPES: SubChannelType[] = [\n 'EMAIL',\n 'SMS',\n 'SLACK',\n 'DISCORD',\n 'WHATSAPP',\n 'PUSH',\n 'WEBHOOK',\n];\n\n// ---------------------------------------------------------------------------\n// Add channel modal → subCreateChannel\n// ---------------------------------------------------------------------------\n\nfunction AddChannelModal({ onClose }: { onClose: () => void }) {\n const create = useCreateChannel();\n const [type, setType] = useState<SubChannelType>('EMAIL');\n const [address, setAddress] = useState('');\n const [label, setLabel] = useState('');\n const [ingestEnabled, setIngestEnabled] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!address.trim()) {\n setFormError('An address is required.');\n return;\n }\n create.mutate(\n {\n type,\n address: address.trim(),\n ingestEnabled,\n ...(label.trim() ? { label: label.trim() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add channel.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add channel\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ch-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"ch-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubChannelType)}\n className={inputCls}\n >\n {CHANNEL_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ch-label\" className={labelCls}>\n Label\n </label>\n <input\n id=\"ch-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder=\"e.g. Billing inbox\"\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"ch-address\" className={labelCls}>\n Address <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"ch-address\"\n type=\"text\"\n value={address}\n onChange={(e) => setAddress(e.target.value)}\n placeholder=\"e.g. billing@acme.com, +1…, a webhook URL\"\n className={inputCls}\n required\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={ingestEnabled}\n onChange={(e) => setIngestEnabled(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Ingest messages from this channel\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add channel'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Log test message modal → subIngestMessage\n// ---------------------------------------------------------------------------\n\nfunction LogMessageModal({\n channel,\n onClose,\n}: {\n channel: SubCommunicationChannel;\n onClose: () => void;\n}) {\n const ingest = useIngestMessage();\n const [subject, setSubject] = useState('');\n const [preview, setPreview] = useState('');\n const [category, setCategory] = useState<SubMessageCategory>('OTHER');\n const [actionRequired, setActionRequired] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n ingest.mutate(\n {\n channelId: channel.id,\n receivedAt: new Date().toISOString(),\n category,\n actionRequired,\n ...(subject.trim() ? { subject: subject.trim() } : {}),\n ...(preview.trim() ? { preview: preview.trim() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not log the message.'),\n }\n );\n };\n\n return (\n <Modal\n title=\"Log test message\"\n description={`Simulate an ingested message from ${channel.label || channel.address}.`}\n onClose={onClose}\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"msg-subject\" className={labelCls}>\n Subject\n </label>\n <input\n id=\"msg-subject\"\n type=\"text\"\n value={subject}\n onChange={(e) => setSubject(e.target.value)}\n placeholder=\"e.g. Your plan renews in 3 days\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"msg-preview\" className={labelCls}>\n Preview\n </label>\n <textarea\n id=\"msg-preview\"\n value={preview}\n onChange={(e) => setPreview(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"msg-category\" className={labelCls}>\n Category\n </label>\n <select\n id=\"msg-category\"\n value={category}\n onChange={(e) => setCategory(e.target.value as SubMessageCategory)}\n className={inputCls}\n >\n {MESSAGE_CATEGORIES.map((c) => (\n <option key={c} value={c}>\n {c.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={actionRequired}\n onChange={(e) => setActionRequired(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Requires action\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={ingest.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {ingest.isPending ? 'Logging…' : 'Log message'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Channels management panel\n// ---------------------------------------------------------------------------\n\nfunction ChannelsSection() {\n const { data, isLoading, isError, refetch } = useChannels();\n const verify = useVerifyChannel();\n const [showAdd, setShowAdd] = useState(false);\n const [logChannel, setLogChannel] = useState<SubCommunicationChannel | null>(null);\n\n const channels = data ?? [];\n\n return (\n <Section\n title=\"Ingest channels\"\n description=\"The sources the Universal Inbox pulls subscription communication from\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAdd(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add channel\n </button>\n }\n >\n {isLoading ? (\n <LoadingView label=\"Loading channels…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load channels.\" onRetry={() => void refetch()} />\n ) : channels.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Radio className=\"h-12 w-12\" />}\n title=\"No channels yet\"\n description=\"Add an email, SMS, Slack or webhook channel to start ingesting subscription communication.\"\n />\n ) : (\n <div className=\"divide-y divide-border-subtle rounded-lg border border-border-subtle bg-bg-surface\">\n {channels.map((ch) => (\n <div key={ch.id} className=\"flex items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Radio className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {ch.label || ch.address}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatEnumLabel(ch.type)}\n {ch.label ? ` · ${ch.address}` : ''}\n </p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <StatusBadge status={ch.status} />\n {ch.status === 'PENDING' && (\n <button\n type=\"button\"\n disabled={verify.isPending}\n onClick={() => verify.mutate(ch.id)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <BadgeCheck className=\"h-3.5 w-3.5\" /> Verify\n </button>\n )}\n {ch.status === 'VERIFIED' && ch.ingestEnabled && (\n <button\n type=\"button\"\n onClick={() => setLogChannel(ch)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Send className=\"h-3.5 w-3.5\" /> Log test message\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {showAdd && <AddChannelModal onClose={() => setShowAdd(false)} />}\n {logChannel && <LogMessageModal channel={logChannel} onClose={() => setLogChannel(null)} />}\n </Section>\n );\n}\n\nexport function InboxPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [category, setCategory] = useState<'' | SubMessageCategory>('');\n const [unreadOnly, setUnreadOnly] = useState(false);\n\n const { data, isLoading, isError, refetch, isFetching } = useInboxMessages({\n category: category || undefined,\n unreadOnly: unreadOnly || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markRead = useMarkMessageRead();\n\n const items = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Universal Inbox\"\n description=\"Every subscription communication, merged and categorized\"\n >\n <PagePurpose className=\"mb-4\">\n Subscription communication is noisy and scattered. The Universal Inbox merges every channel\n a provider uses and uses AI to categorize each message so you never miss a renewal, required\n action or security notice.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by category\"\n value={category}\n onChange={(e) => {\n setCategory(e.target.value as '' | SubMessageCategory);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {CATEGORY_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={unreadOnly}\n onChange={(e) => {\n setUnreadOnly(e.target.checked);\n setPage(0);\n }}\n className=\"size-4 rounded border-border-subtle\"\n />\n Unread only\n </label>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading inbox…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load the inbox.\" onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-inbox\"\n icon={<InboxIcon className=\"h-12 w-12\" />}\n title=\"Inbox is empty\"\n description={\n unreadOnly || category\n ? 'No messages match these filters.'\n : 'Merged subscription communications will appear here once channels start ingesting.'\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 divide-y divide-border-subtle rounded-lg border border-border-subtle bg-bg-surface\">\n {items.map((m) => (\n <div\n key={m.id}\n className={cn('flex items-start gap-3 p-4', !m.isRead && 'bg-accent-wash')}\n >\n <span className=\"mt-0.5 shrink-0 text-text-secondary\">\n {m.isRead ? (\n <MailOpen className=\"h-4 w-4\" />\n ) : (\n <Mail className=\"h-4 w-4 text-text-link\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <p\n className={cn(\n 'truncate text-sm',\n m.isRead\n ? 'font-normal text-text-primary'\n : 'font-semibold text-text-primary'\n )}\n >\n {m.subject || '(no subject)'}\n </p>\n {m.actionRequired && (\n <span\n title=\"Action required\"\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3 w-3\" /> Action\n </span>\n )}\n </div>\n {m.preview && (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">{m.preview}</p>\n )}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={m.category} />\n <span className=\"text-xs text-text-secondary\">\n {formatRelativeTime(m.receivedAt)}\n </span>\n {m.relationshipId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${m.relationshipId}`)}\n className=\"text-xs font-medium text-text-link hover:underline\"\n >\n View relationship\n </button>\n )}\n </div>\n </div>\n {!m.isRead && (\n <button\n type=\"button\"\n disabled={markRead.isPending}\n onClick={() => markRead.mutate(m.id)}\n className=\"shrink-0 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n Mark read\n </button>\n )}\n </div>\n ))}\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Page {page + 1}\n {isFetching ? ' · refreshing…' : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Previous\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Next\n </button>\n </div>\n </div>\n </>\n )}\n\n <ChannelsSection />\n </PageLayout>\n );\n}\n"],"mappings":"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"}