@handled-ai/design-system 0.18.58 → 0.19.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/components/badge.d.ts +1 -1
  2. package/dist/components/button.d.ts +1 -1
  3. package/dist/components/case-panel-activity-timeline.d.ts +2 -0
  4. package/dist/components/case-panel-activity-timeline.js +22 -1
  5. package/dist/components/case-panel-activity-timeline.js.map +1 -1
  6. package/dist/components/comment-composer.d.ts +29 -0
  7. package/dist/components/comment-composer.js +102 -0
  8. package/dist/components/comment-composer.js.map +1 -0
  9. package/dist/components/conversation-panel.d.ts +95 -0
  10. package/dist/components/conversation-panel.js +636 -0
  11. package/dist/components/conversation-panel.js.map +1 -0
  12. package/dist/components/detail-view.js +1 -1
  13. package/dist/components/detail-view.js.map +1 -1
  14. package/dist/components/owner-chips.d.ts +59 -0
  15. package/dist/components/owner-chips.js +256 -0
  16. package/dist/components/owner-chips.js.map +1 -0
  17. package/dist/components/pill.d.ts +1 -1
  18. package/dist/components/score-why-chips.d.ts +1 -1
  19. package/dist/components/signal-priority-popover.d.ts +1 -1
  20. package/dist/components/signal-priority-popover.js +16 -7
  21. package/dist/components/signal-priority-popover.js.map +1 -1
  22. package/dist/components/tabs.d.ts +1 -1
  23. package/dist/components/timeline-activity.d.ts +7 -0
  24. package/dist/components/timeline-activity.js +22 -1
  25. package/dist/components/timeline-activity.js.map +1 -1
  26. package/dist/components/virtualized-data-table.js +4 -4
  27. package/dist/components/virtualized-data-table.js.map +1 -1
  28. package/dist/index.d.ts +4 -1
  29. package/dist/index.js +3 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/internal/safe-html.d.ts +11 -0
  32. package/dist/internal/safe-html.js +222 -0
  33. package/dist/internal/safe-html.js.map +1 -0
  34. package/dist/prototype/index.d.ts +1 -1
  35. package/dist/prototype/prototype-accounts-view.d.ts +1 -1
  36. package/dist/prototype/prototype-admin-view.d.ts +1 -1
  37. package/dist/prototype/prototype-config.d.ts +1 -1
  38. package/dist/prototype/prototype-inbox-view.d.ts +1 -1
  39. package/dist/prototype/prototype-inbox-view.js +2 -0
  40. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  41. package/dist/prototype/prototype-insights-view.d.ts +1 -1
  42. package/dist/prototype/prototype-shell.d.ts +1 -1
  43. package/dist/{signal-priority-popover-QJngMAj7.d.ts → signal-priority-popover-CZitE9xq.d.ts} +11 -2
  44. package/package.json +1 -1
  45. package/src/components/__tests__/comment-composer.test.tsx +57 -0
  46. package/src/components/__tests__/conversation-panel.test.tsx +157 -0
  47. package/src/components/__tests__/owner-chips.test.tsx +100 -0
  48. package/src/components/__tests__/signal-priority-popover.test.tsx +41 -4
  49. package/src/components/__tests__/timeline-activity.test.tsx +55 -0
  50. package/src/components/__tests__/virtualized-data-table-resize.test.tsx +18 -0
  51. package/src/components/case-panel-activity-timeline.tsx +20 -0
  52. package/src/components/comment-composer.tsx +119 -0
  53. package/src/components/conversation-panel.tsx +790 -0
  54. package/src/components/detail-view.tsx +3 -1
  55. package/src/components/owner-chips.tsx +335 -0
  56. package/src/components/signal-priority-popover.tsx +19 -6
  57. package/src/components/timeline-activity.tsx +37 -3
  58. package/src/components/virtualized-data-table.tsx +4 -4
  59. package/src/index.ts +4 -1
  60. package/src/internal/__tests__/safe-html.test.ts +53 -0
  61. package/src/internal/safe-html.ts +284 -0
  62. package/src/prototype/__tests__/detail-view-score-why.test.tsx +34 -0
  63. package/src/prototype/prototype-config.ts +5 -1
  64. package/src/prototype/prototype-inbox-view.tsx +2 -0
@@ -0,0 +1,636 @@
1
+ "use client"
2
+
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __defProps = Object.defineProperties;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
24
+ import * as React from "react";
25
+ import {
26
+ ChevronDown,
27
+ ChevronUp,
28
+ CornerUpLeft,
29
+ CheckCheck,
30
+ MailOpen,
31
+ Reply,
32
+ ReplyAll,
33
+ Eye,
34
+ Send,
35
+ Lock,
36
+ Pause,
37
+ GitMerge,
38
+ Check,
39
+ X
40
+ } from "lucide-react";
41
+ import { cn } from "../lib/utils.js";
42
+ import { getInitials } from "../lib/user-display.js";
43
+ import { BRAND_ICONS } from "../lib/icons.js";
44
+ import { htmlToTextSnippet, sanitizeHtml } from "../internal/safe-html.js";
45
+ import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
46
+ import { Button } from "./button.js";
47
+ import { Switch } from "./switch.js";
48
+ import { Textarea } from "./textarea.js";
49
+ import { RichTextToolbar } from "./rich-text-toolbar.js";
50
+ import {
51
+ Dialog,
52
+ DialogContent,
53
+ DialogHeader,
54
+ DialogTitle,
55
+ DialogDescription,
56
+ DialogFooter
57
+ } from "./dialog.js";
58
+ const PROSE = cn(
59
+ "text-sm leading-[1.62] text-foreground/90 break-words",
60
+ "[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0",
61
+ "[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline",
62
+ "[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5",
63
+ "[&_img]:max-w-full [&_img]:h-auto"
64
+ );
65
+ function escapeHtml(s) {
66
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
67
+ }
68
+ function textToHtml(text) {
69
+ return text.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean).map((p) => `<p>${escapeHtml(p).replace(/\n/g, "<br>")}</p>`).join("");
70
+ }
71
+ function GmailMark({ size = 14 }) {
72
+ return (
73
+ // eslint-disable-next-line @next/next/no-img-element
74
+ /* @__PURE__ */ jsx(
75
+ "img",
76
+ {
77
+ src: BRAND_ICONS.gmail.icon,
78
+ alt: "Gmail",
79
+ width: size,
80
+ height: size,
81
+ style: { width: size, height: size, objectFit: "contain", display: "block" }
82
+ }
83
+ )
84
+ );
85
+ }
86
+ function PersonAvatar({ person, size = "sm" }) {
87
+ return /* @__PURE__ */ jsxs(Avatar, { size, children: [
88
+ person.avatarUrl ? /* @__PURE__ */ jsx(AvatarImage, { src: person.avatarUrl, alt: person.name }) : null,
89
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-muted text-muted-foreground text-[10px] font-medium uppercase", children: getInitials({ name: person.name, email: person.email }) })
90
+ ] });
91
+ }
92
+ function firstName(name) {
93
+ return name.split(" ")[0] || name;
94
+ }
95
+ const STATUS_PILL = {
96
+ responded: { label: "New reply", cls: "bg-status-active-bg text-status-active-fg border-status-active-border" },
97
+ awaiting: { label: "Awaiting", cls: "bg-status-pending-bg text-status-pending-fg border-status-pending-border" },
98
+ viewing: { label: "Viewing", cls: "bg-muted text-muted-foreground border-border" }
99
+ };
100
+ const STATUS_DOT = {
101
+ responded: "bg-status-active-fg",
102
+ awaiting: "bg-status-pending-fg",
103
+ viewing: "bg-muted-foreground/50"
104
+ };
105
+ function effectiveStatus(t) {
106
+ return t.canReply === false ? "viewing" : t.status;
107
+ }
108
+ function MessageView({
109
+ message,
110
+ expanded,
111
+ onToggle,
112
+ me
113
+ }) {
114
+ var _a, _b, _c;
115
+ const [quoteOpen, setQuoteOpen] = React.useState(false);
116
+ const snippet = (_b = (_a = message.body) == null ? void 0 : _a.split("\n").find(Boolean)) != null ? _b : message.bodyHtml ? htmlToTextSnippet(message.bodyHtml, 140) : "";
117
+ if (!expanded) {
118
+ return /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ "data-slot": "conv-message-collapsed",
123
+ onClick: onToggle,
124
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-muted/40",
125
+ children: [
126
+ /* @__PURE__ */ jsx(PersonAvatar, { person: message.from }),
127
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground min-w-0 flex-1 truncate text-[13px]", children: [
128
+ /* @__PURE__ */ jsx("b", { className: "text-foreground", children: firstName(message.from.name) }),
129
+ " \xB7 ",
130
+ snippet
131
+ ] }),
132
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 shrink-0 text-xs", children: (_c = message.ago) != null ? _c : message.date }),
133
+ /* @__PURE__ */ jsx(ChevronDown, { size: 13, className: "text-muted-foreground shrink-0" })
134
+ ]
135
+ }
136
+ );
137
+ }
138
+ const toLabel = me && message.to.email === me.email ? "me" : firstName(message.to.name);
139
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-message", className: "rounded-md border border-border bg-background", children: [
140
+ /* @__PURE__ */ jsxs(
141
+ "button",
142
+ {
143
+ type: "button",
144
+ onClick: onToggle,
145
+ className: "flex w-full items-start gap-2 px-3 py-2 text-left",
146
+ children: [
147
+ /* @__PURE__ */ jsx(PersonAvatar, { person: message.from, size: "default" }),
148
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
149
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-wrap items-baseline gap-x-1.5", children: [
150
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold", children: message.from.name }),
151
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/60 truncate text-xs", children: [
152
+ "<",
153
+ message.from.email,
154
+ ">"
155
+ ] })
156
+ ] }),
157
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground block text-xs", children: [
158
+ "to ",
159
+ /* @__PURE__ */ jsx("b", { children: toLabel })
160
+ ] })
161
+ ] }),
162
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
163
+ message.receipt ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground inline-flex items-center gap-1 text-[11px]", children: [
164
+ message.receipt.kind === "new" ? /* @__PURE__ */ jsx(CornerUpLeft, { size: 11 }) : message.receipt.kind === "read" ? /* @__PURE__ */ jsx(CheckCheck, { size: 11 }) : /* @__PURE__ */ jsx(MailOpen, { size: 11 }),
165
+ message.receipt.label
166
+ ] }) : null,
167
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 text-xs", children: message.date }),
168
+ /* @__PURE__ */ jsx(ChevronUp, { size: 13, className: "text-muted-foreground" })
169
+ ] })
170
+ ]
171
+ }
172
+ ),
173
+ /* @__PURE__ */ jsxs("div", { className: "px-3 pb-3", children: [
174
+ message.bodyHtml ? /* @__PURE__ */ jsx("div", { className: PROSE, dangerouslySetInnerHTML: { __html: sanitizeHtml(message.bodyHtml) } }) : /* @__PURE__ */ jsx("div", { className: cn(PROSE, "whitespace-pre-line"), children: message.body }),
175
+ message.quoted ? /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
176
+ /* @__PURE__ */ jsx(
177
+ "button",
178
+ {
179
+ type: "button",
180
+ onClick: () => setQuoteOpen((v) => !v),
181
+ className: "text-muted-foreground hover:bg-muted rounded px-1.5 text-xs leading-5",
182
+ title: quoteOpen ? "Hide quoted text" : "Show quoted text",
183
+ children: "\u2022\u2022\u2022"
184
+ }
185
+ ),
186
+ quoteOpen ? /* @__PURE__ */ jsxs("div", { className: "border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]", children: [
187
+ /* @__PURE__ */ jsx("p", { className: "mb-1", dangerouslySetInnerHTML: { __html: sanitizeHtml(message.quoted.attr) } }),
188
+ /* @__PURE__ */ jsx("div", { className: PROSE, dangerouslySetInnerHTML: { __html: sanitizeHtml(message.quoted.html) } })
189
+ ] }) : null
190
+ ] }) : null
191
+ ] })
192
+ ] });
193
+ }
194
+ function ReplyComposer({
195
+ thread,
196
+ me,
197
+ replyAll,
198
+ tenantName,
199
+ onClose,
200
+ onSend,
201
+ onDraft
202
+ }) {
203
+ var _a, _b;
204
+ const [body, setBody] = React.useState((_a = thread.draft) != null ? _a : "");
205
+ const [sig, setSig] = React.useState(true);
206
+ const [preview, setPreview] = React.useState(false);
207
+ const [sending, setSending] = React.useState(false);
208
+ const [sendError, setSendError] = React.useState(null);
209
+ const ccList = replyAll ? (_b = thread.cc) != null ? _b : [] : [];
210
+ const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`;
211
+ const previewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : "");
212
+ const handleSend = async () => {
213
+ setSending(true);
214
+ setSendError(null);
215
+ try {
216
+ await onSend(body, sig);
217
+ setPreview(false);
218
+ } catch (error) {
219
+ setSendError(error instanceof Error ? error.message : "Could not send this reply. Please try again.");
220
+ } finally {
221
+ setSending(false);
222
+ }
223
+ };
224
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-reply", className: "border-border bg-muted/20 rounded-md border p-3", children: [
225
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2", children: [
226
+ me ? /* @__PURE__ */ jsx(PersonAvatar, { person: me }) : null,
227
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-[13px] font-medium", children: replyAll ? /* @__PURE__ */ jsxs(Fragment, { children: [
228
+ "Reply all",
229
+ " ",
230
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground font-normal", children: [
231
+ "\xB7 ",
232
+ 1 + ccList.length,
233
+ " recipients"
234
+ ] })
235
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
236
+ "Reply to ",
237
+ /* @__PURE__ */ jsx("b", { children: firstName(thread.contact.name) })
238
+ ] }) }),
239
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground inline-flex items-center gap-1 text-[11px]", children: [
240
+ /* @__PURE__ */ jsx(GitMerge, { size: 11 }),
241
+ " Same thread"
242
+ ] }),
243
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, title: "Discard reply", className: "text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { size: 15 }) })
244
+ ] }),
245
+ /* @__PURE__ */ jsxs("div", { className: "border-border mb-2 space-y-1 border-b pb-2 text-[13px]", children: [
246
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
247
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground w-12 shrink-0 text-[11px] font-medium", children: "To" }),
248
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: thread.contact.name }),
249
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 truncate text-xs", children: thread.contact.email })
250
+ ] }),
251
+ replyAll && ccList.length ? /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
252
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground w-12 shrink-0 text-[11px] font-medium", children: "Cc" }),
253
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: ccList.map((c) => c.name).join(", ") })
254
+ ] }) : null,
255
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
256
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground w-12 shrink-0 text-[11px] font-medium", children: "Subject" }),
257
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: subject }),
258
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/60 ml-auto inline-flex items-center gap-1 text-[11px]", children: [
259
+ /* @__PURE__ */ jsx(Lock, { size: 10 }),
260
+ " on thread"
261
+ ] })
262
+ ] })
263
+ ] }),
264
+ /* @__PURE__ */ jsx(
265
+ Textarea,
266
+ {
267
+ value: body,
268
+ onChange: (e) => setBody(e.target.value),
269
+ placeholder: "Write your reply\u2026",
270
+ className: "min-h-28 resize-y text-sm",
271
+ onKeyDown: (e) => {
272
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
273
+ e.preventDefault();
274
+ setPreview(true);
275
+ }
276
+ }
277
+ }
278
+ ),
279
+ sig && thread.signature ? /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground mt-2 whitespace-pre-line border-l-2 border-border pl-3 text-[13px]", children: [
280
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 mr-1", children: "--" }),
281
+ thread.signature
282
+ ] }) : null,
283
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
284
+ /* @__PURE__ */ jsx(RichTextToolbar, {}),
285
+ /* @__PURE__ */ jsxs("label", { className: "text-muted-foreground ml-auto inline-flex cursor-pointer items-center gap-1.5 text-[12px]", children: [
286
+ /* @__PURE__ */ jsx(Switch, { checked: sig, onCheckedChange: setSig, "aria-label": "Toggle signature" }),
287
+ "Signature"
288
+ ] }),
289
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: sending, onClick: () => setPreview(true), children: [
290
+ /* @__PURE__ */ jsx(Eye, { size: 14 }),
291
+ " Preview"
292
+ ] }),
293
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", disabled: sending, onClick: () => setPreview(true), children: [
294
+ /* @__PURE__ */ jsx(Send, { size: 14 }),
295
+ " Send"
296
+ ] })
297
+ ] }),
298
+ /* @__PURE__ */ jsx(Dialog, { open: preview, onOpenChange: (open) => {
299
+ if (!sending) setPreview(open);
300
+ }, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-xl", children: [
301
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
302
+ /* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-1.5 text-[15px]", children: [
303
+ /* @__PURE__ */ jsx(Eye, { size: 15 }),
304
+ " Preview: this is exactly what sends"
305
+ ] }),
306
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
307
+ "Stays on the ",
308
+ subject.replace(/^Re:\s*/i, ""),
309
+ " thread. Gmail keeps it threaded."
310
+ ] })
311
+ ] }),
312
+ /* @__PURE__ */ jsxs("div", { className: "border-border space-y-1 rounded-md border p-3 text-[13px]", children: [
313
+ /* @__PURE__ */ jsxs("div", { children: [
314
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "To " }),
315
+ /* @__PURE__ */ jsx("b", { children: thread.contact.name }),
316
+ " ",
317
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/60", children: [
318
+ "<",
319
+ thread.contact.email,
320
+ ">"
321
+ ] })
322
+ ] }),
323
+ replyAll && ccList.length ? /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground", children: [
324
+ "Cc ",
325
+ ccList.map((c) => c.name).join(", ")
326
+ ] }) : null,
327
+ /* @__PURE__ */ jsxs("div", { children: [
328
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Subject " }),
329
+ subject
330
+ ] })
331
+ ] }),
332
+ /* @__PURE__ */ jsx("div", { className: cn(PROSE, "max-h-72 overflow-auto"), dangerouslySetInnerHTML: { __html: previewHtml } }),
333
+ sendError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive text-sm", children: sendError }) : null,
334
+ /* @__PURE__ */ jsxs(DialogFooter, { className: "sm:justify-between", children: [
335
+ /* @__PURE__ */ jsxs(
336
+ "button",
337
+ {
338
+ type: "button",
339
+ disabled: sending,
340
+ onClick: () => {
341
+ setPreview(false);
342
+ onDraft(body, sig);
343
+ },
344
+ className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50",
345
+ children: [
346
+ /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
347
+ " Open draft in Gmail"
348
+ ]
349
+ }
350
+ ),
351
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
352
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: sending, onClick: () => setPreview(false), children: "Keep editing" }),
353
+ /* @__PURE__ */ jsxs(
354
+ Button,
355
+ {
356
+ type: "button",
357
+ size: "sm",
358
+ disabled: sending,
359
+ onClick: handleSend,
360
+ children: [
361
+ /* @__PURE__ */ jsx(Send, { size: 14 }),
362
+ " ",
363
+ sending ? "Sending..." : "Send now"
364
+ ]
365
+ }
366
+ )
367
+ ] })
368
+ ] })
369
+ ] }) }),
370
+ tenantName ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground/70 mt-2 text-[11px]", children: "Sends via Gmail \xB7 playbooks stay stopped." }) : null
371
+ ] });
372
+ }
373
+ function ThreadBody({
374
+ thread,
375
+ me,
376
+ tenantName,
377
+ onSendReply,
378
+ onCreateGmailDraft,
379
+ onOpenInGmail
380
+ }) {
381
+ const canReply = thread.canReply !== false;
382
+ const hasCc = !!(thread.cc && thread.cc.length);
383
+ const [mode, setMode] = React.useState("idle");
384
+ const [replyAll, setReplyAll] = React.useState(false);
385
+ const [expanded, setExpanded] = React.useState(() => {
386
+ const o = {};
387
+ thread.messages.forEach((m, i) => {
388
+ o[m.id] = i === thread.messages.length - 1;
389
+ });
390
+ return o;
391
+ });
392
+ const toggle = (id) => setExpanded((e) => __spreadProps(__spreadValues({}, e), { [id]: !e[id] }));
393
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-thread-body", className: "space-y-2", children: [
394
+ canReply && thread.paused ? /* @__PURE__ */ jsxs("div", { className: "border-status-pending-border bg-status-pending-bg text-status-pending-fg flex items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
395
+ /* @__PURE__ */ jsx(Pause, { size: 13, className: "mt-0.5 shrink-0" }),
396
+ /* @__PURE__ */ jsxs("span", { children: [
397
+ /* @__PURE__ */ jsx("b", { children: "Follow-up actions stopped." }),
398
+ " Your ",
399
+ thread.paused.playbook,
400
+ " next steps won\u2019t send automatically while this conversation is live. Continue it in ",
401
+ tenantName != null ? tenantName : "the app",
402
+ " or Gmail."
403
+ ] })
404
+ ] }) : null,
405
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: thread.messages.map((m) => /* @__PURE__ */ jsx(MessageView, { message: m, expanded: !!expanded[m.id], onToggle: () => toggle(m.id), me }, m.id)) }),
406
+ !canReply ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
407
+ /* @__PURE__ */ jsx(Eye, { size: 14, className: "mt-0.5 shrink-0" }),
408
+ /* @__PURE__ */ jsxs("span", { children: [
409
+ /* @__PURE__ */ jsx("b", { children: "Viewing only." }),
410
+ " You\u2019re not a participant on this thread, so replying is disabled here."
411
+ ] })
412
+ ] }) : null,
413
+ canReply && mode === "idle" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
414
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: () => {
415
+ setReplyAll(false);
416
+ setMode("replying");
417
+ }, children: [
418
+ /* @__PURE__ */ jsx(Reply, { size: 15 }),
419
+ " Reply"
420
+ ] }),
421
+ hasCc ? /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
422
+ setReplyAll(true);
423
+ setMode("replying");
424
+ }, children: [
425
+ /* @__PURE__ */ jsx(ReplyAll, { size: 14 }),
426
+ " Reply all"
427
+ ] }) : null,
428
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenInGmail == null ? void 0 : onOpenInGmail(thread.threadId), children: [
429
+ /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
430
+ " Open in Gmail"
431
+ ] }),
432
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 ml-auto inline-flex items-center gap-1 text-[11px]", children: [
433
+ /* @__PURE__ */ jsx(GitMerge, { size: 12 }),
434
+ " Stays on this thread"
435
+ ] })
436
+ ] }) : null,
437
+ canReply && mode === "replying" ? /* @__PURE__ */ jsx(
438
+ ReplyComposer,
439
+ {
440
+ thread,
441
+ me,
442
+ replyAll,
443
+ tenantName,
444
+ onClose: () => setMode("idle"),
445
+ onSend: async (body, includeSignature) => {
446
+ await (onSendReply == null ? void 0 : onSendReply({ threadId: thread.threadId, body, includeSignature, replyAll }));
447
+ setMode("sent");
448
+ },
449
+ onDraft: (body, includeSignature) => {
450
+ onCreateGmailDraft == null ? void 0 : onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll });
451
+ setMode("draft");
452
+ }
453
+ }
454
+ ) : null,
455
+ canReply && mode === "sent" ? /* @__PURE__ */ jsxs("div", { className: "border-status-active-border bg-status-active-bg flex items-center gap-2 rounded-md border p-3 text-[13px]", children: [
456
+ /* @__PURE__ */ jsx(Check, { size: 16, className: "text-status-active-fg shrink-0" }),
457
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
458
+ /* @__PURE__ */ jsx("b", { children: replyAll ? "Reply all sent" : "Reply sent" }),
459
+ " \xB7 added to the thread. Delivered to",
460
+ " ",
461
+ /* @__PURE__ */ jsx("b", { children: thread.contact.name }),
462
+ ". This action stays ",
463
+ /* @__PURE__ */ jsx("b", { children: "Pending" }),
464
+ "; playbooks remain stopped."
465
+ ] }),
466
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setMode("idle"), children: "Done" })
467
+ ] }) : null,
468
+ canReply && mode === "draft" ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-muted/30 flex items-center gap-2 rounded-md border p-3 text-[13px]", children: [
469
+ /* @__PURE__ */ jsx(GmailMark, { size: 16 }),
470
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
471
+ /* @__PURE__ */ jsx("b", { children: "Draft saved to Gmail." }),
472
+ " Waiting on the ",
473
+ /* @__PURE__ */ jsxs("b", { children: [
474
+ "Re: ",
475
+ thread.subject
476
+ ] }),
477
+ " thread; open it there to finish. Nothing was sent."
478
+ ] }),
479
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenInGmail == null ? void 0 : onOpenInGmail(thread.threadId), children: [
480
+ /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
481
+ " Open in Gmail"
482
+ ] }),
483
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setMode("idle"), children: "Done" })
484
+ ] }) : null
485
+ ] });
486
+ }
487
+ function ThreadRow({
488
+ thread,
489
+ open,
490
+ onToggleOpen,
491
+ me,
492
+ tenantName,
493
+ onSendReply,
494
+ onCreateGmailDraft,
495
+ onOpenInGmail
496
+ }) {
497
+ var _a, _b;
498
+ const status = effectiveStatus(thread);
499
+ const last = thread.messages[thread.messages.length - 1];
500
+ const who = (last == null ? void 0 : last.direction) === "inbound" ? firstName(last.from.name) : "You";
501
+ const lastSnippet = (_b = (_a = last == null ? void 0 : last.body) == null ? void 0 : _a.split("\n").find(Boolean)) != null ? _b : (last == null ? void 0 : last.bodyHtml) ? htmlToTextSnippet(last.bodyHtml, 120) : "";
502
+ const pill = STATUS_PILL[status];
503
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-thread", "data-open": open ? "true" : void 0, className: "border-border border-b last:border-b-0", children: [
504
+ /* @__PURE__ */ jsxs(
505
+ "button",
506
+ {
507
+ type: "button",
508
+ onClick: onToggleOpen,
509
+ "aria-expanded": open,
510
+ className: "flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30",
511
+ children: [
512
+ /* @__PURE__ */ jsx("span", { className: cn("size-2 shrink-0 rounded-full", STATUS_DOT[status]), "aria-hidden": true }),
513
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
514
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
515
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-semibold", children: thread.subject }),
516
+ /* @__PURE__ */ jsx("span", { className: cn("shrink-0 rounded border px-1.5 text-[10px] font-medium leading-4", pill.cls), children: pill.label })
517
+ ] }),
518
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [
519
+ /* @__PURE__ */ jsx("b", { className: "text-foreground/80", children: thread.contact.name }),
520
+ " \xB7 ",
521
+ who,
522
+ ": ",
523
+ lastSnippet
524
+ ] })
525
+ ] }),
526
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 shrink-0 text-xs", children: thread.lastWhen }),
527
+ open ? /* @__PURE__ */ jsx(ChevronUp, { size: 15, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 15, className: "text-muted-foreground shrink-0" })
528
+ ]
529
+ }
530
+ ),
531
+ open ? /* @__PURE__ */ jsx("div", { className: "px-3 pb-3", children: /* @__PURE__ */ jsx(
532
+ ThreadBody,
533
+ {
534
+ thread,
535
+ me,
536
+ tenantName,
537
+ onSendReply,
538
+ onCreateGmailDraft,
539
+ onOpenInGmail
540
+ }
541
+ ) }) : null
542
+ ] });
543
+ }
544
+ function ConversationPanel({
545
+ threads,
546
+ me,
547
+ tenantName,
548
+ onSendReply,
549
+ onCreateGmailDraft,
550
+ onOpenInGmail,
551
+ defaultOpenThreadId,
552
+ className
553
+ }) {
554
+ const responded = threads.filter((t) => t.status === "responded" && t.canReply !== false).length;
555
+ const awaiting = threads.filter((t) => effectiveStatus(t) === "awaiting").length;
556
+ const anyPaused = threads.some((t) => t.paused);
557
+ const [hubOpen, setHubOpen] = React.useState(true);
558
+ const [openId, setOpenId] = React.useState(() => {
559
+ if (defaultOpenThreadId) return defaultOpenThreadId;
560
+ const firstResponded = threads.find((t) => t.status === "responded" && t.canReply !== false);
561
+ return firstResponded ? firstResponded.threadId : null;
562
+ });
563
+ if (!threads.length) return null;
564
+ const badge = responded > 0 ? { label: "Email response detected", dot: "bg-status-active-fg", ring: "bg-status-active-fg/30" } : awaiting > 0 ? { label: "Awaiting response", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" } : { label: "Conversations", dot: "bg-muted-foreground/50", ring: "bg-muted-foreground/20" };
565
+ const headTitle = responded > 0 ? `${responded} ${responded === 1 ? "reply needs" : "replies need"} your response` : awaiting > 0 ? `Awaiting response on ${awaiting} ${awaiting === 1 ? "thread" : "threads"}` : `${threads.length} email ${threads.length === 1 ? "thread" : "threads"}`;
566
+ return /* @__PURE__ */ jsxs(
567
+ "section",
568
+ {
569
+ "data-slot": "conversation-panel",
570
+ "data-responded": responded > 0 ? "true" : void 0,
571
+ className: cn("border-border bg-background overflow-hidden rounded-xl border", className),
572
+ children: [
573
+ /* @__PURE__ */ jsxs(
574
+ "button",
575
+ {
576
+ type: "button",
577
+ onClick: () => setHubOpen((v) => !v),
578
+ "aria-expanded": hubOpen,
579
+ className: "flex w-full items-center gap-3 px-3 py-2.5 text-left",
580
+ children: [
581
+ /* @__PURE__ */ jsxs(
582
+ "span",
583
+ {
584
+ "data-slot": "conversation-badge",
585
+ className: cn(
586
+ "inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
587
+ responded > 0 ? "bg-status-active-bg text-status-active-fg" : awaiting > 0 ? "bg-status-pending-bg text-status-pending-fg" : "bg-muted text-muted-foreground"
588
+ ),
589
+ children: [
590
+ /* @__PURE__ */ jsxs("span", { className: "relative inline-flex size-2", children: [
591
+ /* @__PURE__ */ jsx("span", { className: cn("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", badge.ring) }),
592
+ /* @__PURE__ */ jsx("span", { className: cn("relative inline-flex size-2 rounded-full", badge.dot) })
593
+ ] }),
594
+ badge.label
595
+ ]
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
599
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-semibold", children: headTitle }),
600
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [
601
+ threads.length,
602
+ " ",
603
+ threads.length === 1 ? "thread" : "threads",
604
+ " on this action",
605
+ anyPaused ? /* @__PURE__ */ jsxs(Fragment, { children: [
606
+ " \xB7 ",
607
+ /* @__PURE__ */ jsx("b", { children: "playbook stopped" })
608
+ ] }) : null
609
+ ] })
610
+ ] }),
611
+ hubOpen ? /* @__PURE__ */ jsx(ChevronUp, { size: 16, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 16, className: "text-muted-foreground shrink-0" })
612
+ ]
613
+ }
614
+ ),
615
+ hubOpen ? /* @__PURE__ */ jsx("div", { className: "border-border border-t", children: threads.map((t) => /* @__PURE__ */ jsx(
616
+ ThreadRow,
617
+ {
618
+ thread: t,
619
+ open: openId === t.threadId,
620
+ onToggleOpen: () => setOpenId((cur) => cur === t.threadId ? null : t.threadId),
621
+ me,
622
+ tenantName,
623
+ onSendReply,
624
+ onCreateGmailDraft,
625
+ onOpenInGmail
626
+ },
627
+ t.threadId
628
+ )) }) : null
629
+ ]
630
+ }
631
+ );
632
+ }
633
+ export {
634
+ ConversationPanel
635
+ };
636
+ //# sourceMappingURL=conversation-panel.js.map