@handled-ai/design-system 0.20.4 → 0.20.5
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/components/conversation-panel.d.ts +9 -1
- package/dist/components/conversation-panel.js +64 -18
- package/dist/components/conversation-panel.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +48 -0
- package/src/components/conversation-panel.tsx +91 -19
|
@@ -63,8 +63,16 @@ interface ConversationThread {
|
|
|
63
63
|
paused?: {
|
|
64
64
|
playbook: string;
|
|
65
65
|
} | null;
|
|
66
|
-
/** false => operator
|
|
66
|
+
/** false => operator cannot reply or create drafts from this thread. */
|
|
67
67
|
canReply?: boolean;
|
|
68
|
+
/** Explains why reply and draft creation are disabled. */
|
|
69
|
+
replyDisabledReason?: string;
|
|
70
|
+
/** Existing Gmail draft or thread URL. Rendered as a new-tab link when present. */
|
|
71
|
+
openInGmailUrl?: string | null;
|
|
72
|
+
/** Forces the Open in Gmail action into a disabled state. */
|
|
73
|
+
openInGmailDisabled?: boolean;
|
|
74
|
+
/** Tooltip/read-only copy for a disabled Open in Gmail action. */
|
|
75
|
+
openInGmailDisabledReason?: string | null;
|
|
68
76
|
messages: ConvMessage[];
|
|
69
77
|
/** Prefilled reply draft body. */
|
|
70
78
|
draft?: string;
|
|
@@ -350,6 +350,47 @@ const STATUS_DOT = {
|
|
|
350
350
|
function effectiveStatus(t) {
|
|
351
351
|
return t.canReply === false ? "viewing" : t.status;
|
|
352
352
|
}
|
|
353
|
+
function disabledOpenInGmailReason(thread) {
|
|
354
|
+
var _a, _b;
|
|
355
|
+
return ((_a = thread.openInGmailDisabledReason) == null ? void 0 : _a.trim()) || ((_b = thread.replyDisabledReason) == null ? void 0 : _b.trim()) || "Gmail access is not available for this thread.";
|
|
356
|
+
}
|
|
357
|
+
function canOpenInGmail(thread, onOpenInGmail) {
|
|
358
|
+
return thread.openInGmailDisabled !== true && Boolean(thread.openInGmailUrl || onOpenInGmail);
|
|
359
|
+
}
|
|
360
|
+
function OpenInGmailButton({
|
|
361
|
+
thread,
|
|
362
|
+
onOpenInGmail,
|
|
363
|
+
label = "Open in Gmail"
|
|
364
|
+
}) {
|
|
365
|
+
const hasConfiguredAction = Boolean(thread.openInGmailUrl || onOpenInGmail || thread.openInGmailDisabled || thread.openInGmailDisabledReason);
|
|
366
|
+
if (!hasConfiguredAction) return null;
|
|
367
|
+
const disabled = !canOpenInGmail(thread, onOpenInGmail);
|
|
368
|
+
const disabledReason = disabled ? disabledOpenInGmailReason(thread) : void 0;
|
|
369
|
+
if (!disabled && thread.openInGmailUrl) {
|
|
370
|
+
return /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", asChild: true, children: /* @__PURE__ */ jsxs("a", { href: thread.openInGmailUrl, target: "_blank", rel: "noopener noreferrer", children: [
|
|
371
|
+
/* @__PURE__ */ jsx(GmailMark, { size: 14 }),
|
|
372
|
+
" ",
|
|
373
|
+
label
|
|
374
|
+
] }) });
|
|
375
|
+
}
|
|
376
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex", title: disabledReason, children: /* @__PURE__ */ jsxs(
|
|
377
|
+
Button,
|
|
378
|
+
{
|
|
379
|
+
type: "button",
|
|
380
|
+
variant: "ghost",
|
|
381
|
+
size: "sm",
|
|
382
|
+
disabled,
|
|
383
|
+
"aria-disabled": disabled || void 0,
|
|
384
|
+
"aria-label": disabledReason ? `${label}: ${disabledReason}` : label,
|
|
385
|
+
onClick: disabled ? void 0 : () => onOpenInGmail == null ? void 0 : onOpenInGmail(thread.threadId),
|
|
386
|
+
children: [
|
|
387
|
+
/* @__PURE__ */ jsx(GmailMark, { size: 14 }),
|
|
388
|
+
" ",
|
|
389
|
+
label
|
|
390
|
+
]
|
|
391
|
+
}
|
|
392
|
+
) });
|
|
393
|
+
}
|
|
353
394
|
function MessageView({
|
|
354
395
|
message,
|
|
355
396
|
expanded,
|
|
@@ -462,7 +503,8 @@ function ReplyComposer({
|
|
|
462
503
|
onClose,
|
|
463
504
|
onSend,
|
|
464
505
|
onDraft,
|
|
465
|
-
onPreviewReply
|
|
506
|
+
onPreviewReply,
|
|
507
|
+
draftDisabledReason
|
|
466
508
|
}) {
|
|
467
509
|
var _a, _b, _c, _d;
|
|
468
510
|
const [body, setBody] = React.useState((_a = thread.draft) != null ? _a : "");
|
|
@@ -473,6 +515,7 @@ function ReplyComposer({
|
|
|
473
515
|
const [sendError, setSendError] = React.useState(null);
|
|
474
516
|
const ccList = replyAll ? (_b = thread.cc) != null ? _b : [] : [];
|
|
475
517
|
const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`;
|
|
518
|
+
const draftDisabled = Boolean(draftDisabledReason);
|
|
476
519
|
const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : "");
|
|
477
520
|
const openPreview = async () => {
|
|
478
521
|
var _a2, _b2;
|
|
@@ -516,6 +559,7 @@ function ReplyComposer({
|
|
|
516
559
|
}
|
|
517
560
|
};
|
|
518
561
|
const handleDraft = async () => {
|
|
562
|
+
if (draftDisabled) return;
|
|
519
563
|
setSending(true);
|
|
520
564
|
setSendError(null);
|
|
521
565
|
try {
|
|
@@ -654,19 +698,20 @@ function ReplyComposer({
|
|
|
654
698
|
),
|
|
655
699
|
sendError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive text-sm", children: sendError }) : null,
|
|
656
700
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "sm:justify-between", children: [
|
|
657
|
-
/* @__PURE__ */ jsxs(
|
|
701
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex", title: draftDisabledReason != null ? draftDisabledReason : void 0, children: /* @__PURE__ */ jsxs(
|
|
658
702
|
"button",
|
|
659
703
|
{
|
|
660
704
|
type: "button",
|
|
661
|
-
disabled: sending || previewState.loading,
|
|
705
|
+
disabled: sending || previewState.loading || draftDisabled,
|
|
662
706
|
onClick: handleDraft,
|
|
707
|
+
"aria-label": draftDisabledReason ? `Open draft in Gmail: ${draftDisabledReason}` : "Open draft in Gmail",
|
|
663
708
|
className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50",
|
|
664
709
|
children: [
|
|
665
710
|
/* @__PURE__ */ jsx(GmailMark, { size: 14 }),
|
|
666
711
|
" Open draft in Gmail"
|
|
667
712
|
]
|
|
668
713
|
}
|
|
669
|
-
),
|
|
714
|
+
) }),
|
|
670
715
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
671
716
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: sending, onClick: () => {
|
|
672
717
|
setPreview(false);
|
|
@@ -701,7 +746,10 @@ function ThreadBody({
|
|
|
701
746
|
onPreviewReply,
|
|
702
747
|
onOpenInGmail
|
|
703
748
|
}) {
|
|
749
|
+
var _a;
|
|
704
750
|
const canReply = thread.canReply !== false;
|
|
751
|
+
const replyDisabledReason = ((_a = thread.replyDisabledReason) == null ? void 0 : _a.trim()) || "You are not a participant on this thread, so replying is disabled here.";
|
|
752
|
+
const draftDisabledReason = onCreateGmailDraft ? null : "Gmail draft creation is not available for this thread.";
|
|
705
753
|
const hasCc = !!(thread.cc && thread.cc.length);
|
|
706
754
|
const [mode, setMode] = React.useState("idle");
|
|
707
755
|
const [replyAll, setReplyAll] = React.useState(false);
|
|
@@ -726,12 +774,14 @@ function ThreadBody({
|
|
|
726
774
|
] })
|
|
727
775
|
] }) : null,
|
|
728
776
|
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: thread.messages.map((m) => /* @__PURE__ */ jsx(MessageView, { message: m, expanded: !!expanded[m.id], onToggle: () => toggle(m.id), me }, m.id)) }),
|
|
729
|
-
!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: [
|
|
777
|
+
!canReply ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-muted/30 text-muted-foreground flex flex-wrap items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
|
|
730
778
|
/* @__PURE__ */ jsx(Eye, { size: 14, className: "mt-0.5 shrink-0" }),
|
|
731
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
779
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
732
780
|
/* @__PURE__ */ jsx("b", { children: "Viewing only." }),
|
|
733
|
-
"
|
|
734
|
-
|
|
781
|
+
" ",
|
|
782
|
+
replyDisabledReason
|
|
783
|
+
] }),
|
|
784
|
+
/* @__PURE__ */ jsx(OpenInGmailButton, { thread, onOpenInGmail })
|
|
735
785
|
] }) : null,
|
|
736
786
|
canReply && mode === "idle" ? /* @__PURE__ */ jsxs("div", { "data-slot": "conv-action-row", className: "border-border/70 mt-1 flex flex-wrap items-center gap-x-3 gap-y-2 border-t pt-3", children: [
|
|
737
787
|
/* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: () => {
|
|
@@ -748,10 +798,7 @@ function ThreadBody({
|
|
|
748
798
|
/* @__PURE__ */ jsx(ReplyAll, { size: 14 }),
|
|
749
799
|
" Reply all"
|
|
750
800
|
] }) : null,
|
|
751
|
-
/* @__PURE__ */
|
|
752
|
-
/* @__PURE__ */ jsx(GmailMark, { size: 14 }),
|
|
753
|
-
" Open in Gmail"
|
|
754
|
-
] }),
|
|
801
|
+
/* @__PURE__ */ jsx(OpenInGmailButton, { thread, onOpenInGmail }),
|
|
755
802
|
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]", children: [
|
|
756
803
|
/* @__PURE__ */ jsx(GitMerge, { size: 13 }),
|
|
757
804
|
" Stays on this thread"
|
|
@@ -771,9 +818,11 @@ function ThreadBody({
|
|
|
771
818
|
setMode("sent");
|
|
772
819
|
},
|
|
773
820
|
onDraft: async (body, includeSignature) => {
|
|
774
|
-
|
|
821
|
+
if (!onCreateGmailDraft) return;
|
|
822
|
+
await onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll });
|
|
775
823
|
setMode("draft");
|
|
776
|
-
}
|
|
824
|
+
},
|
|
825
|
+
draftDisabledReason
|
|
777
826
|
}
|
|
778
827
|
) : null,
|
|
779
828
|
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: [
|
|
@@ -800,10 +849,7 @@ function ThreadBody({
|
|
|
800
849
|
] }),
|
|
801
850
|
" thread; open it there to finish. Nothing was sent."
|
|
802
851
|
] }),
|
|
803
|
-
/* @__PURE__ */
|
|
804
|
-
/* @__PURE__ */ jsx(GmailMark, { size: 14 }),
|
|
805
|
-
" Open in Gmail"
|
|
806
|
-
] }),
|
|
852
|
+
/* @__PURE__ */ jsx(OpenInGmailButton, { thread, onOpenInGmail }),
|
|
807
853
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setMode("idle"), children: "Done" })
|
|
808
854
|
] }) : null
|
|
809
855
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/conversation-panel.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * conversation-panel.tsx — in-case email-thread reader + reply, for the case\n * panel (\"Email response detected\" hub).\n *\n * v1 scope (WIT-853 / WIT-802): INLINE thread reading + reply only.\n * - A collapsible hub header with a pulse badge whose color reflects state:\n * responded (a reply needs you) / awaiting (sent, waiting) / viewing (read-only).\n * - A list of thread rows; clicking one opens a Gmail-style reader inline,\n * right under the row, with collapsible messages + quoted-history toggle.\n * - Reply / Reply-all composer with a signature toggle and two send paths:\n * Preview→Send (in-app) and \"Open draft in Gmail\" (deep link).\n * - A \"playbook stopped\" banner when the customer's reply halted the sequence,\n * and a read-only notice when the operator is not a thread participant.\n *\n * The bottom-right floating dock / side-by-side compare is intentionally OUT of\n * scope here and tracked separately (WIT-855).\n *\n * Presentational: all data + side effects come from the consumer. Email bodies\n * (`bodyHtml`, `quoted.html`) are sanitized before rendering.\n */\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n CornerUpLeft,\n CheckCheck,\n MailOpen,\n FilePenLine,\n Reply,\n ReplyAll,\n Eye,\n Send,\n Lock,\n Pause,\n GitMerge,\n Check,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { BRAND_ICONS } from \"../lib/icons\"\nimport { htmlToTextSnippet, sanitizeHtml } from \"../internal/safe-html\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Switch } from \"./switch\"\nimport { Textarea } from \"./textarea\"\nimport { RichTextToolbar } from \"./rich-text-toolbar\"\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n} from \"./dialog\"\n\n/* ── Types ───────────────────────────────────────────────────────────────── */\n\nexport interface ConvParticipant {\n name: string\n email: string\n avatarUrl?: string | null\n role?: string\n}\n\nexport interface ConvMessage {\n id: string\n direction: \"inbound\" | \"outbound\"\n from: ConvParticipant\n to: ConvParticipant\n /** Absolute timestamp label, e.g. \"Jun 1, 2026, 9:12 AM\". */\n date: string\n /** Relative label, e.g. \"2 days ago\". */\n ago?: string\n receipt?: { kind: \"new\" | \"read\" | \"opened\" | \"sent\" | \"draft\"; label: string }\n /** HTML body (preferred). Sanitized by the component before rendering. */\n bodyHtml?: string\n /** Plain-text fallback when `bodyHtml` is absent. */\n body?: string\n /** Quoted prior message, collapsed behind a toggle. Sanitized before rendering. */\n quoted?: { attr: string; html: string }\n}\n\nexport type ConvStatus = \"responded\" | \"awaiting\" | \"viewing\" | \"draft\"\n\nexport interface ConversationThread {\n threadId: string\n subject: string\n status: ConvStatus\n /** Relative label for the most recent activity. */\n lastWhen?: string\n contact: ConvParticipant\n cc?: ConvParticipant[]\n /** Set when this thread's reply halted a playbook (terminal). */\n paused?: { playbook: string } | null\n /** false => operator is not a participant; reply disabled (read-only). */\n canReply?: boolean\n messages: ConvMessage[]\n /** Prefilled reply draft body. */\n draft?: string\n /** Signature text appended to replies (plain text). */\n signature?: string\n}\n\nexport interface ConversationReplyPayload {\n threadId: string\n body: string\n includeSignature: boolean\n replyAll: boolean\n}\n\n/**\n * Result of a server-side reply preview. `htmlBody` is the exact HTML the server\n * prepared for this reply (sanitized by the component before render).\n * `confirmationToken`, when present, identifies the prepared confirmation so a\n * consumer can reuse it for the final send instead of re-preparing the message.\n */\nexport interface ConversationReplyPreview {\n htmlBody: string\n confirmationToken?: string\n}\n\nexport interface ConversationPanelProps {\n threads: ConversationThread[]\n /** Current operator: drives \"to me\" + the reply avatar. */\n me?: ConvParticipant\n /** Deployment brand, used in the paused-playbook copy. */\n tenantName?: string\n onSendReply?: (payload: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (payload: ConversationReplyPayload) => void | Promise<void>\n /**\n * Server-side preview contract. When provided, the reply preview requests the\n * exact send HTML from the server, the component sanitizes it before render,\n * and retains any returned `confirmationToken` in preview state so a consumer\n * can reuse it for the final send. When omitted, the composer falls back to a\n * clearly labeled local draft preview only.\n */\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n /** Inline-open this thread initially (defaults to the first responded one). */\n defaultOpenThreadId?: string\n className?: string\n}\n\n/* ── Shared helpers ──────────────────────────────────────────────────────── */\n\n/** Gmail-like reading-pane typography (mirrors timeline-activity's email mode). */\nconst PROSE = cn(\n \"text-sm leading-[1.62] text-foreground/90 break-words\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_img]:max-w-full [&_img]:h-auto\"\n)\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n}\n\n/** Plain-text -> simple paragraph HTML for the Preview / sent-message body. */\nfunction textToHtml(text: string): string {\n return text\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter(Boolean)\n .map((p) => `<p>${escapeHtml(p).replace(/\\n/g, \"<br>\")}</p>`)\n .join(\"\")\n}\n\ntype CollapsedHtmlMessage = { bodyHtml: string; detailsHtml: string }\ntype CollapsedTextMessage = { bodyText: string; detailsText: string }\ntype MessageSegment = { html?: string; text?: string; visibleText: string }\n\nconst SPLITTABLE_BLOCK_TAGS = new Set([\"p\", \"div\"])\nconst WHOLE_BLOCK_TAGS = new Set([\"blockquote\", \"table\", \"ul\", \"ol\", \"hr\"])\nconst BLOCK_TAGS = new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS])\nconst BR_TAG_RE = /<br\\s*\\/?>/gi\nconst HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\\b[^>]*>/i\n\nconst SIGNATURE_DELIMITER_RE = /^--\\s*$/\nconst SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i\nconst DETAILS_START_RE = /^(?:confidentiality notice\\b|this message and any attachments\\b|this email and any attachments\\b|the information contained in this message\\b|this communication may contain\\b|unsubscribe\\b|manage your preferences\\b)/i\nconst CONTACT_DETAIL_RE = /(?:@|https?:\\/\\/|www\\.|\\+?\\d[\\d\\s().-]{6,}\\d|\\b(?:ceo|cfo|cto|coo|founder|co-founder|director|manager|vp|vice president|president|head of|sales|marketing|operations|account|customer success|success|support|engineer|consultant|partner|principal|advisor|associate)\\b|\\b(?:inc|llc|ltd|corp|corporation|company|co\\.)\\b)/i\n\nfunction decodeHtmlText(value: string): string {\n const withoutTags = value\n .replace(BR_TAG_RE, \"\\n\")\n .replace(/<\\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n\n if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\")\n textarea.innerHTML = withoutTags\n return textarea.value\n }\n\n return withoutTags\n .replace(/ /gi, \" \")\n .replace(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, '\"')\n .replace(/'|'/gi, \"'\")\n}\n\nfunction htmlToVisibleText(html: string): string {\n return decodeHtmlText(html).replace(/\\u00a0/g, \" \").replace(/[ \\t]+/g, \" \").trim()\n}\n\nfunction serializeNode(node: Node): string {\n const host = document.createElement(\"div\")\n host.appendChild(node.cloneNode(true))\n return host.innerHTML\n}\n\nfunction wrapHtmlLike(element: Element, innerHtml: string): string {\n const clone = element.cloneNode(false) as HTMLElement\n clone.innerHTML = innerHtml\n return clone.outerHTML\n}\n\nfunction hasDirectBr(nodes: readonly Node[]): boolean {\n return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\")\n}\n\nfunction hasDirectBlockChild(element: Element): boolean {\n return Array.from(element.children).some((child) => {\n const tagName = child.tagName.toLowerCase()\n return tagName !== \"br\" && BLOCK_TAGS.has(tagName)\n })\n}\n\nfunction makeHtmlSegment(html: string): MessageSegment | null {\n const visibleText = htmlToVisibleText(html)\n if (!html.trim() || (!visibleText && !/<(?:img|hr)\\b/i.test(html))) return null\n return { html, visibleText }\n}\n\nfunction splitInlineNodes(nodes: readonly Node[], wrapper?: Element): MessageSegment[] {\n const containsBr = hasDirectBr(nodes)\n const chunks: string[][] = [[]]\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\") {\n chunks.push([])\n return\n }\n\n chunks[chunks.length - 1]?.push(serializeNode(node))\n })\n\n return chunks\n .map((chunk) => chunk.join(\"\"))\n .map((innerHtml) => {\n if (wrapper) return wrapHtmlLike(wrapper, innerHtml)\n return containsBr ? `<div>${innerHtml}</div>` : innerHtml\n })\n .map(makeHtmlSegment)\n .filter((segment): segment is MessageSegment => Boolean(segment))\n}\n\nfunction splitElementSegment(element: Element): MessageSegment[] {\n const tagName = element.tagName.toLowerCase()\n\n if (tagName === \"div\" && hasDirectBlockChild(element)) {\n const childSegments = splitHtmlNodes(Array.from(element.childNodes))\n return childSegments.length ? childSegments : [makeHtmlSegment(element.outerHTML)].filter(Boolean) as MessageSegment[]\n }\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {\n return splitInlineNodes(Array.from(element.childNodes), element)\n }\n\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n}\n\nfunction splitHtmlNodes(nodes: readonly Node[]): MessageSegment[] {\n const segments: MessageSegment[] = []\n let inlineNodes: Node[] = []\n\n const flushInline = () => {\n if (!inlineNodes.length) return\n segments.push(...splitInlineNodes(inlineNodes))\n inlineNodes = []\n }\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n if (BLOCK_TAGS.has(tagName)) {\n flushInline()\n segments.push(...splitElementSegment(element))\n return\n }\n }\n\n inlineNodes.push(node)\n })\n\n flushInline()\n return segments\n}\n\nfunction findMatchingCloseTag(html: string, tagName: string, openTagEnd: number): number {\n if (tagName === \"hr\") return openTagEnd + 1\n\n const tagPattern = new RegExp(`</?${tagName}\\\\b[^>]*>`, \"gi\")\n tagPattern.lastIndex = openTagEnd + 1\n let depth = 1\n let match: RegExpExecArray | null\n\n while ((match = tagPattern.exec(html)) !== null) {\n const rawTag = match[0]\n if (/^<\\//.test(rawTag)) depth -= 1\n else if (!/\\/\\s*>$/.test(rawTag)) depth += 1\n if (depth === 0) return tagPattern.lastIndex\n }\n\n return html.length\n}\n\nfunction splitHtmlSegmentsFallback(html: string): MessageSegment[] {\n const segments: MessageSegment[] = []\n let cursor = 0\n\n const pushInline = (inlineHtml: string) => {\n const chunks = inlineHtml.split(BR_TAG_RE)\n const hadBr = chunks.length > 1\n chunks.forEach((chunk) => {\n const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk)\n if (segment) segments.push(segment)\n })\n }\n\n while (cursor < html.length) {\n const rest = html.slice(cursor)\n const match = HTML_BLOCK_START_RE.exec(rest)\n if (!match || match.index === undefined) {\n pushInline(rest)\n break\n }\n\n if (match.index > 0) pushInline(rest.slice(0, match.index))\n\n const tagStart = cursor + match.index\n const rawOpen = match[0]\n const tagName = match[1].toLowerCase()\n const openTagEnd = tagStart + rawOpen.length - 1\n const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd)\n const blockHtml = html.slice(tagStart, segmentEnd)\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {\n const openTag = rawOpen\n const closeTag = `</${tagName}>`\n const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"), \"\").replace(new RegExp(`${closeTag}$`, \"i\"), \"\")\n inner.split(BR_TAG_RE).forEach((chunk) => {\n const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`)\n if (segment) segments.push(segment)\n })\n } else {\n const segment = makeHtmlSegment(blockHtml)\n if (segment) segments.push(segment)\n }\n\n cursor = segmentEnd\n }\n\n return segments\n}\n\nfunction segmentHtmlMessage(html: string): MessageSegment[] {\n if (typeof document === \"undefined\" || typeof Node === \"undefined\") {\n return splitHtmlSegmentsFallback(html)\n }\n\n const template = document.createElement(\"template\")\n template.innerHTML = html\n return splitHtmlNodes(Array.from(template.content.childNodes))\n}\n\nfunction segmentTextMessage(text: string): MessageSegment[] {\n const lines = text.match(/[^\\n]*(?:\\n|$)/g) ?? []\n return lines\n .filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === \"\"))\n .map((line) => ({ text: line, visibleText: line.replace(/\\u00a0/g, \" \").trim() }))\n}\n\nfunction firstVisibleLine(text: string): string {\n return text.replace(/\\u00a0/g, \" \").trimStart().split(/\\r?\\n/).find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction isLikelySenderNameLine(line: string): boolean {\n if (!line || line.length > 60) return false\n if (/[,@:;!?]|https?:\\/\\/|www\\.|\\d/.test(line)) return false\n\n const words = line.split(/\\s+/).filter(Boolean)\n if (words.length < 1 || words.length > 4) return false\n\n return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word))\n}\n\nfunction nextVisibleSegmentText(segments: MessageSegment[], fromIndex: number): string {\n for (let index = fromIndex + 1; index < segments.length; index += 1) {\n const text = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (text) return text\n }\n return \"\"\n}\n\nfunction isFooterBoundary(segments: MessageSegment[], index: number): boolean {\n const line = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (!line) return false\n if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line)) return true\n\n const nextText = nextVisibleSegmentText(segments, index)\n if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)))\n\n return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText)\n}\n\nfunction splitFooterSegments(segments: MessageSegment[]): { bodySegments: MessageSegment[]; detailsSegments: MessageSegment[] } {\n const visibleIndexes = segments\n .map((segment, index) => (segment.visibleText ? index : -1))\n .filter((index) => index >= 0)\n const visibleCount = visibleIndexes.length\n if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] }\n\n const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8)\n const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount)\n\n for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {\n const index = visibleIndexes[ordinal]\n if (ordinal > 0 && isFooterBoundary(segments, index)) {\n return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) }\n }\n }\n\n return { bodySegments: segments, detailsSegments: [] }\n}\n\nfunction splitMessageHtml(rawHtml: string): CollapsedHtmlMessage {\n const sanitizedHtml = sanitizeHtml(rawHtml)\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml))\n\n if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: \"\" }\n\n return {\n bodyHtml: bodySegments.map((segment) => segment.html ?? \"\").join(\"\"),\n detailsHtml: detailsSegments.map((segment) => segment.html ?? \"\").join(\"\"),\n }\n}\n\nfunction splitMessageText(text: string): CollapsedTextMessage {\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(text))\n\n if (!detailsSegments.length) return { bodyText: text, detailsText: \"\" }\n\n return {\n bodyText: bodySegments.map((segment) => segment.text ?? \"\").join(\"\"),\n detailsText: detailsSegments.map((segment) => segment.text ?? \"\").join(\"\"),\n }\n}\n\nfunction messageBodySnippet(message: Pick<ConvMessage, \"body\" | \"bodyHtml\">, maxLength = 140): string {\n if (message.bodyHtml) {\n return htmlToTextSnippet(splitMessageHtml(message.bodyHtml).bodyHtml, maxLength)\n }\n\n return splitMessageText(message.body ?? \"\").bodyText.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction GmailMark({ size = 14 }: { size?: number }) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={BRAND_ICONS.gmail.icon}\n alt=\"Gmail\"\n width={size}\n height={size}\n style={{ width: size, height: size, objectFit: \"contain\", display: \"block\" }}\n />\n )\n}\n\nfunction PersonAvatar({ person, size = \"sm\" }: { person: ConvParticipant; size?: \"sm\" | \"default\" }) {\n return (\n <Avatar size={size}>\n {person.avatarUrl ? <AvatarImage src={person.avatarUrl} alt={person.name} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: person.name, email: person.email })}\n </AvatarFallback>\n </Avatar>\n )\n}\n\nfunction firstName(name: string): string {\n return name.split(\" \")[0] || name\n}\n\nconst STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {\n responded: { label: \"New reply\", cls: \"bg-status-active-bg text-status-active-fg border-status-active-border\" },\n draft: { label: \"Draft\", cls: \"bg-background text-foreground/80 border-border\" },\n awaiting: { label: \"Awaiting\", cls: \"bg-status-pending-bg text-status-pending-fg border-status-pending-border\" },\n viewing: { label: \"Viewing\", cls: \"bg-muted text-muted-foreground border-border\" },\n}\n\nconst STATUS_DOT: Record<ConvStatus, string> = {\n responded: \"bg-status-active-fg\",\n draft: \"bg-status-pending-fg\",\n awaiting: \"bg-status-pending-fg\",\n viewing: \"bg-muted-foreground/50\",\n}\n\nfunction effectiveStatus(t: ConversationThread): ConvStatus {\n return t.canReply === false ? \"viewing\" : t.status\n}\n\n/* ── One message (collapsible) ──────────────────────────────────────────── */\n\nfunction MessageView({\n message,\n expanded,\n onToggle,\n me,\n}: {\n message: ConvMessage\n expanded: boolean\n onToggle: () => void\n me?: ConvParticipant\n}) {\n const [quoteOpen, setQuoteOpen] = React.useState(false)\n const [detailsOpen, setDetailsOpen] = React.useState(false)\n\n if (!expanded) {\n const snippet = messageBodySnippet(message, 140)\n\n return (\n <button\n type=\"button\"\n data-slot=\"conv-message-collapsed\"\n onClick={onToggle}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-muted/40\"\n >\n <PersonAvatar person={message.from} />\n <span className=\"text-muted-foreground min-w-0 flex-1 truncate text-[13px]\">\n <b className=\"text-foreground\">{firstName(message.from.name)}</b> · {snippet}\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{message.ago ?? message.date}</span>\n <ChevronDown size={13} className=\"text-muted-foreground shrink-0\" />\n </button>\n )\n }\n\n const toLabel = me && message.to.email === me.email ? \"me\" : firstName(message.to.name)\n const htmlParts = message.bodyHtml ? splitMessageHtml(message.bodyHtml) : null\n const textParts = message.bodyHtml ? null : splitMessageText(message.body ?? \"\")\n const hasDetails = Boolean(htmlParts?.detailsHtml || textParts?.detailsText)\n\n return (\n <div data-slot=\"conv-message\" className=\"rounded-md border border-border bg-background\">\n <button\n type=\"button\"\n onClick={onToggle}\n className=\"flex w-full items-start gap-2 px-3 py-2 text-left\"\n >\n <PersonAvatar person={message.from} size=\"default\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex flex-wrap items-baseline gap-x-1.5\">\n <span className=\"text-[13px] font-semibold\">{message.from.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\"><{message.from.email}></span>\n </span>\n <span className=\"text-muted-foreground block text-xs\">\n to <b>{toLabel}</b>\n </span>\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {message.receipt ? (\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n {message.receipt.kind === \"new\" ? (\n <CornerUpLeft size={11} />\n ) : message.receipt.kind === \"read\" ? (\n <CheckCheck size={11} />\n ) : message.receipt.kind === \"draft\" ? (\n <FilePenLine size={11} />\n ) : (\n <MailOpen size={11} />\n )}\n {message.receipt.label}\n </span>\n ) : null}\n <span className=\"text-muted-foreground/60 text-xs\">{message.date}</span>\n <ChevronUp size={13} className=\"text-muted-foreground\" />\n </span>\n </button>\n\n <div className=\"px-3 pb-2.5\">\n {htmlParts ? (\n <div data-slot=\"conv-message-body\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.bodyHtml }} />\n ) : (\n <div data-slot=\"conv-message-body\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.bodyText}</div>\n )}\n\n {hasDetails ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setDetailsOpen((v) => !v)}\n className=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n aria-expanded={detailsOpen}\n >\n {detailsOpen ? \"Hide signature/details\" : \"Show signature/details\"}\n </button>\n {detailsOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n {htmlParts ? (\n <div data-slot=\"conv-message-details\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.detailsHtml }} />\n ) : (\n <div data-slot=\"conv-message-details\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.detailsText}</div>\n )}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {message.quoted ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setQuoteOpen((v) => !v)}\n className=\"text-muted-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n title={quoteOpen ? \"Hide quoted text\" : \"Show quoted text\"}\n >\n •••\n </button>\n {quoteOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n <p className=\"mb-1\" dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.attr) }} />\n <div className={PROSE} dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.html) }} />\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\n/* ── Reply composer ─────────────────────────────────────────────────────── */\n\ntype PreviewState = {\n loading: boolean\n /** Sanitized HTML ready to render. */\n html: string | null\n /** Confirmation token returned by the server preview, retained for reuse on send. */\n confirmationToken: string | null\n error: string | null\n /** True when `html` came from the local fallback rather than the server. */\n local: boolean\n}\n\nconst IDLE_PREVIEW: PreviewState = { loading: false, html: null, confirmationToken: null, error: null, local: false }\n\nfunction ReplyComposer({\n thread,\n me,\n replyAll,\n tenantName,\n onClose,\n onSend,\n onDraft,\n onPreviewReply,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n replyAll: boolean\n tenantName?: string\n onClose: () => void\n onSend: (body: string, includeSignature: boolean) => void | Promise<void>\n onDraft: (body: string, includeSignature: boolean) => void | Promise<void>\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n}) {\n const [body, setBody] = React.useState(thread.draft ?? \"\")\n const [sig, setSig] = React.useState(true)\n const [preview, setPreview] = React.useState(false)\n const [previewState, setPreviewState] = React.useState<PreviewState>(IDLE_PREVIEW)\n const [sending, setSending] = React.useState(false)\n const [sendError, setSendError] = React.useState<string | null>(null)\n const ccList = replyAll ? thread.cc ?? [] : []\n const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`\n\n const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : \"\")\n\n const openPreview = async () => {\n setPreview(true)\n setSendError(null)\n\n if (!onPreviewReply) {\n // No server preview contract: render a sanitized local draft preview only.\n setPreviewState({ loading: false, html: sanitizeHtml(localPreviewHtml), confirmationToken: null, error: null, local: true })\n return\n }\n\n setPreviewState({ loading: true, html: null, confirmationToken: null, error: null, local: false })\n try {\n const result = await onPreviewReply({ threadId: thread.threadId, body, includeSignature: sig, replyAll })\n setPreviewState({\n loading: false,\n html: sanitizeHtml(result.htmlBody ?? \"\"),\n confirmationToken: result.confirmationToken ?? null,\n error: null,\n local: false,\n })\n } catch (error) {\n setPreviewState({\n loading: false,\n html: null,\n confirmationToken: null,\n error: error instanceof Error ? error.message : \"Could not load the preview. Please try again.\",\n local: false,\n })\n }\n }\n\n const handleSend = async () => {\n setSending(true)\n setSendError(null)\n try {\n await onSend(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not send this reply. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n const handleDraft = async () => {\n setSending(true)\n setSendError(null)\n try {\n await onDraft(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not create the Gmail draft. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n return (\n <div data-slot=\"conv-reply\" className=\"border-border bg-muted/20 rounded-md border p-3\">\n <div className=\"mb-2 flex items-center gap-2\">\n {me ? <PersonAvatar person={me} /> : null}\n <span className=\"flex-1 text-[13px] font-medium\">\n {replyAll ? (\n <>\n Reply all{\" \"}\n <span className=\"text-muted-foreground font-normal\">· {1 + ccList.length} recipients</span>\n </>\n ) : (\n <>\n Reply to <b>{firstName(thread.contact.name)}</b>\n </>\n )}\n </span>\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n <GitMerge size={11} /> Same thread\n </span>\n <button type=\"button\" onClick={onClose} title=\"Discard reply\" className=\"text-muted-foreground hover:text-foreground\">\n <X size={15} />\n </button>\n </div>\n\n <div className=\"border-border mb-2 space-y-1 border-b pb-2 text-[13px]\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">To</span>\n <span className=\"font-medium\">{thread.contact.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\">{thread.contact.email}</span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"flex items-start gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Cc</span>\n <span className=\"text-muted-foreground text-xs\">\n {ccList.map((c) => c.name).join(\", \")}\n </span>\n </div>\n ) : null}\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Subject</span>\n <span className=\"truncate\">{subject}</span>\n <span className=\"text-muted-foreground/60 ml-auto inline-flex items-center gap-1 text-[11px]\">\n <Lock size={10} /> on thread\n </span>\n </div>\n </div>\n\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Write your reply…\"\n className=\"min-h-28 resize-y text-sm\"\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n void openPreview()\n }\n }}\n />\n\n {sig && thread.signature ? (\n <div className=\"text-muted-foreground mt-2 whitespace-pre-line border-l-2 border-border pl-3 text-[13px]\">\n <span className=\"text-muted-foreground/60 mr-1\">--</span>\n {thread.signature}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <RichTextToolbar />\n <label className=\"text-muted-foreground ml-auto inline-flex cursor-pointer items-center gap-1.5 text-[12px]\">\n <Switch checked={sig} onCheckedChange={setSig} aria-label=\"Toggle signature\" />\n Signature\n </label>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Eye size={14} /> Preview\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Send size={14} /> Send\n </Button>\n </div>\n\n <Dialog open={preview} onOpenChange={(open) => { if (!sending) { setPreview(open); if (!open) setPreviewState(IDLE_PREVIEW) } }}>\n <DialogContent className=\"max-w-xl\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-1.5 text-[15px]\">\n <Eye size={15} /> {previewState.local ? \"Local draft preview\" : \"Reply preview\"}\n </DialogTitle>\n <DialogDescription>\n {previewState.local\n ? \"Local draft preview only — the server prepares the exact message on send.\"\n : <>Stays on the {subject.replace(/^Re:\\s*/i, \"\")} thread. Gmail keeps it threaded.</>}\n </DialogDescription>\n </DialogHeader>\n <div className=\"border-border space-y-1 rounded-md border p-3 text-[13px]\">\n <div>\n <span className=\"text-muted-foreground\">To </span>\n <b>{thread.contact.name}</b>{\" \"}\n <span className=\"text-muted-foreground/60\"><{thread.contact.email}></span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"text-muted-foreground\">Cc {ccList.map((c) => c.name).join(\", \")}</div>\n ) : null}\n <div>\n <span className=\"text-muted-foreground\">Subject </span>\n {subject}\n </div>\n </div>\n {previewState.loading ? (\n <div data-slot=\"conv-preview-loading\" role=\"status\" className=\"text-muted-foreground flex items-center gap-2 px-1 py-6 text-[13px]\">\n <span className=\"border-muted-foreground/40 border-t-foreground size-4 animate-spin rounded-full border-2\" aria-hidden />\n Loading preview…\n </div>\n ) : previewState.error ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {previewState.error}\n </p>\n ) : (\n <div\n data-slot=\"conv-preview-body\"\n data-confirmation-token={previewState.confirmationToken ?? undefined}\n className={cn(PROSE, \"max-h-72 overflow-auto\")}\n dangerouslySetInnerHTML={{ __html: previewState.html ?? \"\" }}\n />\n )}\n {sendError ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {sendError}\n </p>\n ) : null}\n <DialogFooter className=\"sm:justify-between\">\n <button\n type=\"button\"\n disabled={sending || previewState.loading}\n onClick={handleDraft}\n className=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50\"\n >\n <GmailMark size={14} /> Open draft in Gmail\n </button>\n <span className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => { setPreview(false); setPreviewState(IDLE_PREVIEW) }}>\n Keep editing\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={sending || previewState.loading}\n onClick={handleSend}\n >\n <Send size={14} /> {sending ? \"Sending...\" : \"Send now\"}\n </Button>\n </span>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {tenantName ? (\n <p className=\"text-muted-foreground/70 mt-2 text-[11px]\">Sends via Gmail · playbooks stay stopped.</p>\n ) : null}\n </div>\n )\n}\n\n/* ── Thread body (messages + footer/composer/done states) ───────────────── */\n\ntype ThreadMode = \"idle\" | \"replying\" | \"sent\" | \"draft\"\n\nfunction ThreadBody({\n thread,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n tenantName?: string\n onSendReply?: (p: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (p: ConversationReplyPayload) => void | Promise<void>\n onPreviewReply?: (p: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n}) {\n const canReply = thread.canReply !== false\n const hasCc = !!(thread.cc && thread.cc.length)\n const [mode, setMode] = React.useState<ThreadMode>(\"idle\")\n const [replyAll, setReplyAll] = React.useState(false)\n const [expanded, setExpanded] = React.useState<Record<string, boolean>>(() => {\n const o: Record<string, boolean> = {}\n thread.messages.forEach((m, i) => {\n o[m.id] = i === thread.messages.length - 1\n })\n return o\n })\n\n const toggle = (id: string) => setExpanded((e) => ({ ...e, [id]: !e[id] }))\n\n return (\n <div data-slot=\"conv-thread-body\" className=\"space-y-2\">\n {canReply && thread.paused ? (\n <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]\">\n <Pause size={13} className=\"mt-0.5 shrink-0\" />\n <span>\n <b>Follow-up actions stopped.</b> Your {thread.paused.playbook} next steps won’t send\n automatically while this conversation is live. Continue it in {tenantName ?? \"the app\"} or Gmail.\n </span>\n </div>\n ) : null}\n\n <div className=\"space-y-1\">\n {thread.messages.map((m) => (\n <MessageView key={m.id} message={m} expanded={!!expanded[m.id]} onToggle={() => toggle(m.id)} me={me} />\n ))}\n </div>\n\n {!canReply ? (\n <div className=\"border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]\">\n <Eye size={14} className=\"mt-0.5 shrink-0\" />\n <span>\n <b>Viewing only.</b> You’re not a participant on this thread, so replying is disabled here.\n </span>\n </div>\n ) : null}\n\n {canReply && mode === \"idle\" ? (\n <div data-slot=\"conv-action-row\" className=\"border-border/70 mt-1 flex flex-wrap items-center gap-x-3 gap-y-2 border-t pt-3\">\n <Button type=\"button\" size=\"sm\" onClick={() => { setReplyAll(false); setMode(\"replying\") }}>\n <Reply size={15} /> Reply\n </Button>\n {hasCc ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setReplyAll(true); setMode(\"replying\") }}>\n <ReplyAll size={14} /> Reply all\n </Button>\n ) : null}\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onOpenInGmail?.(thread.threadId)}>\n <GmailMark size={14} /> Open in Gmail\n </Button>\n <span className=\"text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]\">\n <GitMerge size={13} /> Stays on this thread\n </span>\n </div>\n ) : null}\n\n {canReply && mode === \"replying\" ? (\n <ReplyComposer\n thread={thread}\n me={me}\n replyAll={replyAll}\n tenantName={tenantName}\n onPreviewReply={onPreviewReply}\n onClose={() => setMode(\"idle\")}\n onSend={async (body, includeSignature) => {\n await onSendReply?.({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"sent\")\n }}\n onDraft={async (body, includeSignature) => {\n await onCreateGmailDraft?.({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"draft\")\n }}\n />\n ) : null}\n\n {canReply && mode === \"sent\" ? (\n <div className=\"border-status-active-border bg-status-active-bg flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <Check size={16} className=\"text-status-active-fg shrink-0\" />\n <span className=\"flex-1\">\n <b>{replyAll ? \"Reply all sent\" : \"Reply sent\"}</b> · added to the thread. Delivered to{\" \"}\n <b>{thread.contact.name}</b>. This action stays <b>Pending</b>; playbooks remain stopped.\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n\n {canReply && mode === \"draft\" ? (\n <div className=\"border-border bg-muted/30 flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <GmailMark size={16} />\n <span className=\"flex-1\">\n <b>Draft saved to Gmail.</b> Waiting on the <b>Re: {thread.subject}</b> thread; open it there to finish. Nothing was sent.\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onOpenInGmail?.(thread.threadId)}>\n <GmailMark size={14} /> Open in Gmail\n </Button>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── A thread row + its inline reader ───────────────────────────────────── */\n\nfunction ThreadRow({\n thread,\n open,\n onToggleOpen,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n open: boolean\n onToggleOpen: () => void\n} & Pick<ConversationPanelProps, \"me\" | \"tenantName\" | \"onSendReply\" | \"onCreateGmailDraft\" | \"onPreviewReply\" | \"onOpenInGmail\">) {\n const status = effectiveStatus(thread)\n const last = thread.messages[thread.messages.length - 1]\n const who = last?.direction === \"inbound\" ? firstName(last.from.name) : \"You\"\n const lastSnippet = last ? messageBodySnippet(last, 120) : \"\"\n const pill = STATUS_PILL[status]\n\n return (\n <div data-slot=\"conv-thread\" data-open={open ? \"true\" : undefined} className=\"border-border border-b last:border-b-0\">\n <button\n type=\"button\"\n onClick={onToggleOpen}\n aria-expanded={open}\n className=\"flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30\"\n >\n <span className={cn(\"size-2 shrink-0 rounded-full\", STATUS_DOT[status])} aria-hidden />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-semibold\">{thread.subject}</span>\n <span className={cn(\"shrink-0 rounded-md border px-1.5 py-px text-[10px] font-medium leading-4\", pill.cls)}>\n {pill.label}\n </span>\n </span>\n <span className=\"text-muted-foreground block truncate text-xs\">\n <b className=\"text-foreground/80\">{thread.contact.name}</b> · {who}: {lastSnippet}\n </span>\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{thread.lastWhen}</span>\n {open ? (\n <ChevronUp size={15} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={15} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {open ? (\n <div className=\"px-3 pb-3\">\n <ThreadBody\n thread={thread}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── The hub ─────────────────────────────────────────────────────────────── */\n\nfunction ConversationPanel({\n threads,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n defaultOpenThreadId,\n className,\n}: ConversationPanelProps) {\n const responded = threads.filter((t) => t.status === \"responded\" && t.canReply !== false).length\n const draft = threads.filter((t) => effectiveStatus(t) === \"draft\").length\n const awaiting = threads.filter((t) => effectiveStatus(t) === \"awaiting\").length\n const anyPaused = threads.some((t) => t.paused)\n\n const [hubOpen, setHubOpen] = React.useState(true)\n const [openId, setOpenId] = React.useState<string | null>(() => {\n if (defaultOpenThreadId) return defaultOpenThreadId\n const firstActionable = threads.find((t) => [\"responded\", \"draft\"].includes(t.status) && t.canReply !== false)\n return firstActionable ? firstActionable.threadId : null\n })\n\n if (!threads.length) return null\n\n // Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.\n const badge =\n responded > 0\n ? { label: \"Email response detected\", dot: \"bg-status-active-fg\", ring: \"bg-status-active-fg/30\" }\n : draft > 0\n ? { label: \"Draft ready\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : awaiting > 0\n ? { label: \"Awaiting response\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : { label: \"Conversations\", dot: \"bg-muted-foreground/50\", ring: \"bg-muted-foreground/20\" }\n\n const headTitle =\n responded > 0\n ? `${responded} ${responded === 1 ? \"reply needs\" : \"replies need\"} your response`\n : draft > 0\n ? `Draft ready on ${draft} ${draft === 1 ? \"thread\" : \"threads\"}`\n : awaiting > 0\n ? `Awaiting response on ${awaiting} ${awaiting === 1 ? \"thread\" : \"threads\"}`\n : `${threads.length} email ${threads.length === 1 ? \"thread\" : \"threads\"}`\n\n return (\n <section\n data-slot=\"conversation-panel\"\n data-responded={responded > 0 ? \"true\" : undefined}\n className={cn(\"border-border bg-background overflow-hidden rounded-xl border\", className)}\n >\n <button\n type=\"button\"\n onClick={() => setHubOpen((v) => !v)}\n aria-expanded={hubOpen}\n className=\"flex w-full items-center gap-3 px-3 py-2.5 text-left\"\n >\n <span\n data-slot=\"conversation-badge\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold\",\n responded > 0\n ? \"bg-status-active-bg text-status-active-fg\"\n : draft > 0 || awaiting > 0\n ? \"bg-status-pending-bg text-status-pending-fg\"\n : \"bg-muted text-muted-foreground\"\n )}\n >\n <span className=\"relative inline-flex size-2\">\n <span className={cn(\"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75\", badge.ring)} />\n <span className={cn(\"relative inline-flex size-2 rounded-full\", badge.dot)} />\n </span>\n {badge.label}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold leading-tight\">{headTitle}</span>\n <span className=\"text-muted-foreground mt-0.5 block truncate text-xs\">\n {threads.length} {threads.length === 1 ? \"thread\" : \"threads\"} on this action\n {anyPaused ? <> · <b>playbook stopped</b></> : null}\n </span>\n </span>\n {hubOpen ? (\n <ChevronUp size={16} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={16} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {hubOpen ? (\n <div className=\"border-border border-t\">\n {threads.map((t) => (\n <ThreadRow\n key={t.threadId}\n thread={t}\n open={openId === t.threadId}\n onToggleOpen={() => setOpenId((cur) => (cur === t.threadId ? null : t.threadId))}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n ))}\n </div>\n ) : null}\n </section>\n )\n}\n\nexport { ConversationPanel }\n"],"mappings":"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","names":["_a","_b"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/conversation-panel.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * conversation-panel.tsx — in-case email-thread reader + reply, for the case\n * panel (\"Email response detected\" hub).\n *\n * v1 scope (WIT-853 / WIT-802): INLINE thread reading + reply only.\n * - A collapsible hub header with a pulse badge whose color reflects state:\n * responded (a reply needs you) / awaiting (sent, waiting) / viewing (read-only).\n * - A list of thread rows; clicking one opens a Gmail-style reader inline,\n * right under the row, with collapsible messages + quoted-history toggle.\n * - Reply / Reply-all composer with a signature toggle and two send paths:\n * Preview→Send (in-app) and \"Open draft in Gmail\" (deep link).\n * - A \"playbook stopped\" banner when the customer's reply halted the sequence,\n * and a read-only notice when the operator is not a thread participant.\n *\n * The bottom-right floating dock / side-by-side compare is intentionally OUT of\n * scope here and tracked separately (WIT-855).\n *\n * Presentational: all data + side effects come from the consumer. Email bodies\n * (`bodyHtml`, `quoted.html`) are sanitized before rendering.\n */\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n CornerUpLeft,\n CheckCheck,\n MailOpen,\n FilePenLine,\n Reply,\n ReplyAll,\n Eye,\n Send,\n Lock,\n Pause,\n GitMerge,\n Check,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { BRAND_ICONS } from \"../lib/icons\"\nimport { htmlToTextSnippet, sanitizeHtml } from \"../internal/safe-html\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Switch } from \"./switch\"\nimport { Textarea } from \"./textarea\"\nimport { RichTextToolbar } from \"./rich-text-toolbar\"\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n} from \"./dialog\"\n\n/* ── Types ───────────────────────────────────────────────────────────────── */\n\nexport interface ConvParticipant {\n name: string\n email: string\n avatarUrl?: string | null\n role?: string\n}\n\nexport interface ConvMessage {\n id: string\n direction: \"inbound\" | \"outbound\"\n from: ConvParticipant\n to: ConvParticipant\n /** Absolute timestamp label, e.g. \"Jun 1, 2026, 9:12 AM\". */\n date: string\n /** Relative label, e.g. \"2 days ago\". */\n ago?: string\n receipt?: { kind: \"new\" | \"read\" | \"opened\" | \"sent\" | \"draft\"; label: string }\n /** HTML body (preferred). Sanitized by the component before rendering. */\n bodyHtml?: string\n /** Plain-text fallback when `bodyHtml` is absent. */\n body?: string\n /** Quoted prior message, collapsed behind a toggle. Sanitized before rendering. */\n quoted?: { attr: string; html: string }\n}\n\nexport type ConvStatus = \"responded\" | \"awaiting\" | \"viewing\" | \"draft\"\n\nexport interface ConversationThread {\n threadId: string\n subject: string\n status: ConvStatus\n /** Relative label for the most recent activity. */\n lastWhen?: string\n contact: ConvParticipant\n cc?: ConvParticipant[]\n /** Set when this thread's reply halted a playbook (terminal). */\n paused?: { playbook: string } | null\n /** false => operator cannot reply or create drafts from this thread. */\n canReply?: boolean\n /** Explains why reply and draft creation are disabled. */\n replyDisabledReason?: string\n /** Existing Gmail draft or thread URL. Rendered as a new-tab link when present. */\n openInGmailUrl?: string | null\n /** Forces the Open in Gmail action into a disabled state. */\n openInGmailDisabled?: boolean\n /** Tooltip/read-only copy for a disabled Open in Gmail action. */\n openInGmailDisabledReason?: string | null\n messages: ConvMessage[]\n /** Prefilled reply draft body. */\n draft?: string\n /** Signature text appended to replies (plain text). */\n signature?: string\n}\n\nexport interface ConversationReplyPayload {\n threadId: string\n body: string\n includeSignature: boolean\n replyAll: boolean\n}\n\n/**\n * Result of a server-side reply preview. `htmlBody` is the exact HTML the server\n * prepared for this reply (sanitized by the component before render).\n * `confirmationToken`, when present, identifies the prepared confirmation so a\n * consumer can reuse it for the final send instead of re-preparing the message.\n */\nexport interface ConversationReplyPreview {\n htmlBody: string\n confirmationToken?: string\n}\n\nexport interface ConversationPanelProps {\n threads: ConversationThread[]\n /** Current operator: drives \"to me\" + the reply avatar. */\n me?: ConvParticipant\n /** Deployment brand, used in the paused-playbook copy. */\n tenantName?: string\n onSendReply?: (payload: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (payload: ConversationReplyPayload) => void | Promise<void>\n /**\n * Server-side preview contract. When provided, the reply preview requests the\n * exact send HTML from the server, the component sanitizes it before render,\n * and retains any returned `confirmationToken` in preview state so a consumer\n * can reuse it for the final send. When omitted, the composer falls back to a\n * clearly labeled local draft preview only.\n */\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n /** Inline-open this thread initially (defaults to the first responded one). */\n defaultOpenThreadId?: string\n className?: string\n}\n\n/* ── Shared helpers ──────────────────────────────────────────────────────── */\n\n/** Gmail-like reading-pane typography (mirrors timeline-activity's email mode). */\nconst PROSE = cn(\n \"text-sm leading-[1.62] text-foreground/90 break-words\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_img]:max-w-full [&_img]:h-auto\"\n)\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n}\n\n/** Plain-text -> simple paragraph HTML for the Preview / sent-message body. */\nfunction textToHtml(text: string): string {\n return text\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter(Boolean)\n .map((p) => `<p>${escapeHtml(p).replace(/\\n/g, \"<br>\")}</p>`)\n .join(\"\")\n}\n\ntype CollapsedHtmlMessage = { bodyHtml: string; detailsHtml: string }\ntype CollapsedTextMessage = { bodyText: string; detailsText: string }\ntype MessageSegment = { html?: string; text?: string; visibleText: string }\n\nconst SPLITTABLE_BLOCK_TAGS = new Set([\"p\", \"div\"])\nconst WHOLE_BLOCK_TAGS = new Set([\"blockquote\", \"table\", \"ul\", \"ol\", \"hr\"])\nconst BLOCK_TAGS = new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS])\nconst BR_TAG_RE = /<br\\s*\\/?>/gi\nconst HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\\b[^>]*>/i\n\nconst SIGNATURE_DELIMITER_RE = /^--\\s*$/\nconst SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i\nconst DETAILS_START_RE = /^(?:confidentiality notice\\b|this message and any attachments\\b|this email and any attachments\\b|the information contained in this message\\b|this communication may contain\\b|unsubscribe\\b|manage your preferences\\b)/i\nconst CONTACT_DETAIL_RE = /(?:@|https?:\\/\\/|www\\.|\\+?\\d[\\d\\s().-]{6,}\\d|\\b(?:ceo|cfo|cto|coo|founder|co-founder|director|manager|vp|vice president|president|head of|sales|marketing|operations|account|customer success|success|support|engineer|consultant|partner|principal|advisor|associate)\\b|\\b(?:inc|llc|ltd|corp|corporation|company|co\\.)\\b)/i\n\nfunction decodeHtmlText(value: string): string {\n const withoutTags = value\n .replace(BR_TAG_RE, \"\\n\")\n .replace(/<\\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n\n if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\")\n textarea.innerHTML = withoutTags\n return textarea.value\n }\n\n return withoutTags\n .replace(/ /gi, \" \")\n .replace(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, '\"')\n .replace(/'|'/gi, \"'\")\n}\n\nfunction htmlToVisibleText(html: string): string {\n return decodeHtmlText(html).replace(/\\u00a0/g, \" \").replace(/[ \\t]+/g, \" \").trim()\n}\n\nfunction serializeNode(node: Node): string {\n const host = document.createElement(\"div\")\n host.appendChild(node.cloneNode(true))\n return host.innerHTML\n}\n\nfunction wrapHtmlLike(element: Element, innerHtml: string): string {\n const clone = element.cloneNode(false) as HTMLElement\n clone.innerHTML = innerHtml\n return clone.outerHTML\n}\n\nfunction hasDirectBr(nodes: readonly Node[]): boolean {\n return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\")\n}\n\nfunction hasDirectBlockChild(element: Element): boolean {\n return Array.from(element.children).some((child) => {\n const tagName = child.tagName.toLowerCase()\n return tagName !== \"br\" && BLOCK_TAGS.has(tagName)\n })\n}\n\nfunction makeHtmlSegment(html: string): MessageSegment | null {\n const visibleText = htmlToVisibleText(html)\n if (!html.trim() || (!visibleText && !/<(?:img|hr)\\b/i.test(html))) return null\n return { html, visibleText }\n}\n\nfunction splitInlineNodes(nodes: readonly Node[], wrapper?: Element): MessageSegment[] {\n const containsBr = hasDirectBr(nodes)\n const chunks: string[][] = [[]]\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\") {\n chunks.push([])\n return\n }\n\n chunks[chunks.length - 1]?.push(serializeNode(node))\n })\n\n return chunks\n .map((chunk) => chunk.join(\"\"))\n .map((innerHtml) => {\n if (wrapper) return wrapHtmlLike(wrapper, innerHtml)\n return containsBr ? `<div>${innerHtml}</div>` : innerHtml\n })\n .map(makeHtmlSegment)\n .filter((segment): segment is MessageSegment => Boolean(segment))\n}\n\nfunction splitElementSegment(element: Element): MessageSegment[] {\n const tagName = element.tagName.toLowerCase()\n\n if (tagName === \"div\" && hasDirectBlockChild(element)) {\n const childSegments = splitHtmlNodes(Array.from(element.childNodes))\n return childSegments.length ? childSegments : [makeHtmlSegment(element.outerHTML)].filter(Boolean) as MessageSegment[]\n }\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {\n return splitInlineNodes(Array.from(element.childNodes), element)\n }\n\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n}\n\nfunction splitHtmlNodes(nodes: readonly Node[]): MessageSegment[] {\n const segments: MessageSegment[] = []\n let inlineNodes: Node[] = []\n\n const flushInline = () => {\n if (!inlineNodes.length) return\n segments.push(...splitInlineNodes(inlineNodes))\n inlineNodes = []\n }\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n if (BLOCK_TAGS.has(tagName)) {\n flushInline()\n segments.push(...splitElementSegment(element))\n return\n }\n }\n\n inlineNodes.push(node)\n })\n\n flushInline()\n return segments\n}\n\nfunction findMatchingCloseTag(html: string, tagName: string, openTagEnd: number): number {\n if (tagName === \"hr\") return openTagEnd + 1\n\n const tagPattern = new RegExp(`</?${tagName}\\\\b[^>]*>`, \"gi\")\n tagPattern.lastIndex = openTagEnd + 1\n let depth = 1\n let match: RegExpExecArray | null\n\n while ((match = tagPattern.exec(html)) !== null) {\n const rawTag = match[0]\n if (/^<\\//.test(rawTag)) depth -= 1\n else if (!/\\/\\s*>$/.test(rawTag)) depth += 1\n if (depth === 0) return tagPattern.lastIndex\n }\n\n return html.length\n}\n\nfunction splitHtmlSegmentsFallback(html: string): MessageSegment[] {\n const segments: MessageSegment[] = []\n let cursor = 0\n\n const pushInline = (inlineHtml: string) => {\n const chunks = inlineHtml.split(BR_TAG_RE)\n const hadBr = chunks.length > 1\n chunks.forEach((chunk) => {\n const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk)\n if (segment) segments.push(segment)\n })\n }\n\n while (cursor < html.length) {\n const rest = html.slice(cursor)\n const match = HTML_BLOCK_START_RE.exec(rest)\n if (!match || match.index === undefined) {\n pushInline(rest)\n break\n }\n\n if (match.index > 0) pushInline(rest.slice(0, match.index))\n\n const tagStart = cursor + match.index\n const rawOpen = match[0]\n const tagName = match[1].toLowerCase()\n const openTagEnd = tagStart + rawOpen.length - 1\n const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd)\n const blockHtml = html.slice(tagStart, segmentEnd)\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {\n const openTag = rawOpen\n const closeTag = `</${tagName}>`\n const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"), \"\").replace(new RegExp(`${closeTag}$`, \"i\"), \"\")\n inner.split(BR_TAG_RE).forEach((chunk) => {\n const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`)\n if (segment) segments.push(segment)\n })\n } else {\n const segment = makeHtmlSegment(blockHtml)\n if (segment) segments.push(segment)\n }\n\n cursor = segmentEnd\n }\n\n return segments\n}\n\nfunction segmentHtmlMessage(html: string): MessageSegment[] {\n if (typeof document === \"undefined\" || typeof Node === \"undefined\") {\n return splitHtmlSegmentsFallback(html)\n }\n\n const template = document.createElement(\"template\")\n template.innerHTML = html\n return splitHtmlNodes(Array.from(template.content.childNodes))\n}\n\nfunction segmentTextMessage(text: string): MessageSegment[] {\n const lines = text.match(/[^\\n]*(?:\\n|$)/g) ?? []\n return lines\n .filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === \"\"))\n .map((line) => ({ text: line, visibleText: line.replace(/\\u00a0/g, \" \").trim() }))\n}\n\nfunction firstVisibleLine(text: string): string {\n return text.replace(/\\u00a0/g, \" \").trimStart().split(/\\r?\\n/).find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction isLikelySenderNameLine(line: string): boolean {\n if (!line || line.length > 60) return false\n if (/[,@:;!?]|https?:\\/\\/|www\\.|\\d/.test(line)) return false\n\n const words = line.split(/\\s+/).filter(Boolean)\n if (words.length < 1 || words.length > 4) return false\n\n return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word))\n}\n\nfunction nextVisibleSegmentText(segments: MessageSegment[], fromIndex: number): string {\n for (let index = fromIndex + 1; index < segments.length; index += 1) {\n const text = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (text) return text\n }\n return \"\"\n}\n\nfunction isFooterBoundary(segments: MessageSegment[], index: number): boolean {\n const line = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (!line) return false\n if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line)) return true\n\n const nextText = nextVisibleSegmentText(segments, index)\n if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)))\n\n return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText)\n}\n\nfunction splitFooterSegments(segments: MessageSegment[]): { bodySegments: MessageSegment[]; detailsSegments: MessageSegment[] } {\n const visibleIndexes = segments\n .map((segment, index) => (segment.visibleText ? index : -1))\n .filter((index) => index >= 0)\n const visibleCount = visibleIndexes.length\n if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] }\n\n const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8)\n const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount)\n\n for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {\n const index = visibleIndexes[ordinal]\n if (ordinal > 0 && isFooterBoundary(segments, index)) {\n return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) }\n }\n }\n\n return { bodySegments: segments, detailsSegments: [] }\n}\n\nfunction splitMessageHtml(rawHtml: string): CollapsedHtmlMessage {\n const sanitizedHtml = sanitizeHtml(rawHtml)\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml))\n\n if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: \"\" }\n\n return {\n bodyHtml: bodySegments.map((segment) => segment.html ?? \"\").join(\"\"),\n detailsHtml: detailsSegments.map((segment) => segment.html ?? \"\").join(\"\"),\n }\n}\n\nfunction splitMessageText(text: string): CollapsedTextMessage {\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(text))\n\n if (!detailsSegments.length) return { bodyText: text, detailsText: \"\" }\n\n return {\n bodyText: bodySegments.map((segment) => segment.text ?? \"\").join(\"\"),\n detailsText: detailsSegments.map((segment) => segment.text ?? \"\").join(\"\"),\n }\n}\n\nfunction messageBodySnippet(message: Pick<ConvMessage, \"body\" | \"bodyHtml\">, maxLength = 140): string {\n if (message.bodyHtml) {\n return htmlToTextSnippet(splitMessageHtml(message.bodyHtml).bodyHtml, maxLength)\n }\n\n return splitMessageText(message.body ?? \"\").bodyText.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction GmailMark({ size = 14 }: { size?: number }) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={BRAND_ICONS.gmail.icon}\n alt=\"Gmail\"\n width={size}\n height={size}\n style={{ width: size, height: size, objectFit: \"contain\", display: \"block\" }}\n />\n )\n}\n\nfunction PersonAvatar({ person, size = \"sm\" }: { person: ConvParticipant; size?: \"sm\" | \"default\" }) {\n return (\n <Avatar size={size}>\n {person.avatarUrl ? <AvatarImage src={person.avatarUrl} alt={person.name} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: person.name, email: person.email })}\n </AvatarFallback>\n </Avatar>\n )\n}\n\nfunction firstName(name: string): string {\n return name.split(\" \")[0] || name\n}\n\nconst STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {\n responded: { label: \"New reply\", cls: \"bg-status-active-bg text-status-active-fg border-status-active-border\" },\n draft: { label: \"Draft\", cls: \"bg-background text-foreground/80 border-border\" },\n awaiting: { label: \"Awaiting\", cls: \"bg-status-pending-bg text-status-pending-fg border-status-pending-border\" },\n viewing: { label: \"Viewing\", cls: \"bg-muted text-muted-foreground border-border\" },\n}\n\nconst STATUS_DOT: Record<ConvStatus, string> = {\n responded: \"bg-status-active-fg\",\n draft: \"bg-status-pending-fg\",\n awaiting: \"bg-status-pending-fg\",\n viewing: \"bg-muted-foreground/50\",\n}\n\nfunction effectiveStatus(t: ConversationThread): ConvStatus {\n return t.canReply === false ? \"viewing\" : t.status\n}\n\nfunction disabledOpenInGmailReason(thread: ConversationThread): string {\n return (\n thread.openInGmailDisabledReason?.trim() ||\n thread.replyDisabledReason?.trim() ||\n \"Gmail access is not available for this thread.\"\n )\n}\n\nfunction canOpenInGmail(thread: ConversationThread, onOpenInGmail?: (threadId: string) => void): boolean {\n return thread.openInGmailDisabled !== true && Boolean(thread.openInGmailUrl || onOpenInGmail)\n}\n\nfunction OpenInGmailButton({\n thread,\n onOpenInGmail,\n label = \"Open in Gmail\",\n}: {\n thread: ConversationThread\n onOpenInGmail?: (threadId: string) => void\n label?: string\n}) {\n const hasConfiguredAction = Boolean(thread.openInGmailUrl || onOpenInGmail || thread.openInGmailDisabled || thread.openInGmailDisabledReason)\n if (!hasConfiguredAction) return null\n\n const disabled = !canOpenInGmail(thread, onOpenInGmail)\n const disabledReason = disabled\n ? disabledOpenInGmailReason(thread)\n : undefined\n\n if (!disabled && thread.openInGmailUrl) {\n return (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" asChild>\n <a href={thread.openInGmailUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n <GmailMark size={14} /> {label}\n </a>\n </Button>\n )\n }\n\n return (\n <span className=\"inline-flex\" title={disabledReason}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={disabled}\n aria-disabled={disabled || undefined}\n aria-label={disabledReason ? `${label}: ${disabledReason}` : label}\n onClick={disabled ? undefined : () => onOpenInGmail?.(thread.threadId)}\n >\n <GmailMark size={14} /> {label}\n </Button>\n </span>\n )\n}\n\n/* ── One message (collapsible) ──────────────────────────────────────────── */\n\nfunction MessageView({\n message,\n expanded,\n onToggle,\n me,\n}: {\n message: ConvMessage\n expanded: boolean\n onToggle: () => void\n me?: ConvParticipant\n}) {\n const [quoteOpen, setQuoteOpen] = React.useState(false)\n const [detailsOpen, setDetailsOpen] = React.useState(false)\n\n if (!expanded) {\n const snippet = messageBodySnippet(message, 140)\n\n return (\n <button\n type=\"button\"\n data-slot=\"conv-message-collapsed\"\n onClick={onToggle}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-muted/40\"\n >\n <PersonAvatar person={message.from} />\n <span className=\"text-muted-foreground min-w-0 flex-1 truncate text-[13px]\">\n <b className=\"text-foreground\">{firstName(message.from.name)}</b> · {snippet}\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{message.ago ?? message.date}</span>\n <ChevronDown size={13} className=\"text-muted-foreground shrink-0\" />\n </button>\n )\n }\n\n const toLabel = me && message.to.email === me.email ? \"me\" : firstName(message.to.name)\n const htmlParts = message.bodyHtml ? splitMessageHtml(message.bodyHtml) : null\n const textParts = message.bodyHtml ? null : splitMessageText(message.body ?? \"\")\n const hasDetails = Boolean(htmlParts?.detailsHtml || textParts?.detailsText)\n\n return (\n <div data-slot=\"conv-message\" className=\"rounded-md border border-border bg-background\">\n <button\n type=\"button\"\n onClick={onToggle}\n className=\"flex w-full items-start gap-2 px-3 py-2 text-left\"\n >\n <PersonAvatar person={message.from} size=\"default\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex flex-wrap items-baseline gap-x-1.5\">\n <span className=\"text-[13px] font-semibold\">{message.from.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\"><{message.from.email}></span>\n </span>\n <span className=\"text-muted-foreground block text-xs\">\n to <b>{toLabel}</b>\n </span>\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {message.receipt ? (\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n {message.receipt.kind === \"new\" ? (\n <CornerUpLeft size={11} />\n ) : message.receipt.kind === \"read\" ? (\n <CheckCheck size={11} />\n ) : message.receipt.kind === \"draft\" ? (\n <FilePenLine size={11} />\n ) : (\n <MailOpen size={11} />\n )}\n {message.receipt.label}\n </span>\n ) : null}\n <span className=\"text-muted-foreground/60 text-xs\">{message.date}</span>\n <ChevronUp size={13} className=\"text-muted-foreground\" />\n </span>\n </button>\n\n <div className=\"px-3 pb-2.5\">\n {htmlParts ? (\n <div data-slot=\"conv-message-body\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.bodyHtml }} />\n ) : (\n <div data-slot=\"conv-message-body\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.bodyText}</div>\n )}\n\n {hasDetails ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setDetailsOpen((v) => !v)}\n className=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n aria-expanded={detailsOpen}\n >\n {detailsOpen ? \"Hide signature/details\" : \"Show signature/details\"}\n </button>\n {detailsOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n {htmlParts ? (\n <div data-slot=\"conv-message-details\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.detailsHtml }} />\n ) : (\n <div data-slot=\"conv-message-details\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.detailsText}</div>\n )}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {message.quoted ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setQuoteOpen((v) => !v)}\n className=\"text-muted-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n title={quoteOpen ? \"Hide quoted text\" : \"Show quoted text\"}\n >\n •••\n </button>\n {quoteOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n <p className=\"mb-1\" dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.attr) }} />\n <div className={PROSE} dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.html) }} />\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\n/* ── Reply composer ─────────────────────────────────────────────────────── */\n\ntype PreviewState = {\n loading: boolean\n /** Sanitized HTML ready to render. */\n html: string | null\n /** Confirmation token returned by the server preview, retained for reuse on send. */\n confirmationToken: string | null\n error: string | null\n /** True when `html` came from the local fallback rather than the server. */\n local: boolean\n}\n\nconst IDLE_PREVIEW: PreviewState = { loading: false, html: null, confirmationToken: null, error: null, local: false }\n\nfunction ReplyComposer({\n thread,\n me,\n replyAll,\n tenantName,\n onClose,\n onSend,\n onDraft,\n onPreviewReply,\n draftDisabledReason,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n replyAll: boolean\n tenantName?: string\n onClose: () => void\n onSend: (body: string, includeSignature: boolean) => void | Promise<void>\n onDraft: (body: string, includeSignature: boolean) => void | Promise<void>\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n draftDisabledReason?: string | null\n}) {\n const [body, setBody] = React.useState(thread.draft ?? \"\")\n const [sig, setSig] = React.useState(true)\n const [preview, setPreview] = React.useState(false)\n const [previewState, setPreviewState] = React.useState<PreviewState>(IDLE_PREVIEW)\n const [sending, setSending] = React.useState(false)\n const [sendError, setSendError] = React.useState<string | null>(null)\n const ccList = replyAll ? thread.cc ?? [] : []\n const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`\n const draftDisabled = Boolean(draftDisabledReason)\n\n const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : \"\")\n\n const openPreview = async () => {\n setPreview(true)\n setSendError(null)\n\n if (!onPreviewReply) {\n // No server preview contract: render a sanitized local draft preview only.\n setPreviewState({ loading: false, html: sanitizeHtml(localPreviewHtml), confirmationToken: null, error: null, local: true })\n return\n }\n\n setPreviewState({ loading: true, html: null, confirmationToken: null, error: null, local: false })\n try {\n const result = await onPreviewReply({ threadId: thread.threadId, body, includeSignature: sig, replyAll })\n setPreviewState({\n loading: false,\n html: sanitizeHtml(result.htmlBody ?? \"\"),\n confirmationToken: result.confirmationToken ?? null,\n error: null,\n local: false,\n })\n } catch (error) {\n setPreviewState({\n loading: false,\n html: null,\n confirmationToken: null,\n error: error instanceof Error ? error.message : \"Could not load the preview. Please try again.\",\n local: false,\n })\n }\n }\n\n const handleSend = async () => {\n setSending(true)\n setSendError(null)\n try {\n await onSend(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not send this reply. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n const handleDraft = async () => {\n if (draftDisabled) return\n setSending(true)\n setSendError(null)\n try {\n await onDraft(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not create the Gmail draft. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n return (\n <div data-slot=\"conv-reply\" className=\"border-border bg-muted/20 rounded-md border p-3\">\n <div className=\"mb-2 flex items-center gap-2\">\n {me ? <PersonAvatar person={me} /> : null}\n <span className=\"flex-1 text-[13px] font-medium\">\n {replyAll ? (\n <>\n Reply all{\" \"}\n <span className=\"text-muted-foreground font-normal\">· {1 + ccList.length} recipients</span>\n </>\n ) : (\n <>\n Reply to <b>{firstName(thread.contact.name)}</b>\n </>\n )}\n </span>\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n <GitMerge size={11} /> Same thread\n </span>\n <button type=\"button\" onClick={onClose} title=\"Discard reply\" className=\"text-muted-foreground hover:text-foreground\">\n <X size={15} />\n </button>\n </div>\n\n <div className=\"border-border mb-2 space-y-1 border-b pb-2 text-[13px]\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">To</span>\n <span className=\"font-medium\">{thread.contact.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\">{thread.contact.email}</span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"flex items-start gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Cc</span>\n <span className=\"text-muted-foreground text-xs\">\n {ccList.map((c) => c.name).join(\", \")}\n </span>\n </div>\n ) : null}\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Subject</span>\n <span className=\"truncate\">{subject}</span>\n <span className=\"text-muted-foreground/60 ml-auto inline-flex items-center gap-1 text-[11px]\">\n <Lock size={10} /> on thread\n </span>\n </div>\n </div>\n\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Write your reply…\"\n className=\"min-h-28 resize-y text-sm\"\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n void openPreview()\n }\n }}\n />\n\n {sig && thread.signature ? (\n <div className=\"text-muted-foreground mt-2 whitespace-pre-line border-l-2 border-border pl-3 text-[13px]\">\n <span className=\"text-muted-foreground/60 mr-1\">--</span>\n {thread.signature}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <RichTextToolbar />\n <label className=\"text-muted-foreground ml-auto inline-flex cursor-pointer items-center gap-1.5 text-[12px]\">\n <Switch checked={sig} onCheckedChange={setSig} aria-label=\"Toggle signature\" />\n Signature\n </label>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Eye size={14} /> Preview\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Send size={14} /> Send\n </Button>\n </div>\n\n <Dialog open={preview} onOpenChange={(open) => { if (!sending) { setPreview(open); if (!open) setPreviewState(IDLE_PREVIEW) } }}>\n <DialogContent className=\"max-w-xl\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-1.5 text-[15px]\">\n <Eye size={15} /> {previewState.local ? \"Local draft preview\" : \"Reply preview\"}\n </DialogTitle>\n <DialogDescription>\n {previewState.local\n ? \"Local draft preview only — the server prepares the exact message on send.\"\n : <>Stays on the {subject.replace(/^Re:\\s*/i, \"\")} thread. Gmail keeps it threaded.</>}\n </DialogDescription>\n </DialogHeader>\n <div className=\"border-border space-y-1 rounded-md border p-3 text-[13px]\">\n <div>\n <span className=\"text-muted-foreground\">To </span>\n <b>{thread.contact.name}</b>{\" \"}\n <span className=\"text-muted-foreground/60\"><{thread.contact.email}></span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"text-muted-foreground\">Cc {ccList.map((c) => c.name).join(\", \")}</div>\n ) : null}\n <div>\n <span className=\"text-muted-foreground\">Subject </span>\n {subject}\n </div>\n </div>\n {previewState.loading ? (\n <div data-slot=\"conv-preview-loading\" role=\"status\" className=\"text-muted-foreground flex items-center gap-2 px-1 py-6 text-[13px]\">\n <span className=\"border-muted-foreground/40 border-t-foreground size-4 animate-spin rounded-full border-2\" aria-hidden />\n Loading preview…\n </div>\n ) : previewState.error ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {previewState.error}\n </p>\n ) : (\n <div\n data-slot=\"conv-preview-body\"\n data-confirmation-token={previewState.confirmationToken ?? undefined}\n className={cn(PROSE, \"max-h-72 overflow-auto\")}\n dangerouslySetInnerHTML={{ __html: previewState.html ?? \"\" }}\n />\n )}\n {sendError ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {sendError}\n </p>\n ) : null}\n <DialogFooter className=\"sm:justify-between\">\n <span className=\"inline-flex\" title={draftDisabledReason ?? undefined}>\n <button\n type=\"button\"\n disabled={sending || previewState.loading || draftDisabled}\n onClick={handleDraft}\n aria-label={draftDisabledReason ? `Open draft in Gmail: ${draftDisabledReason}` : \"Open draft in Gmail\"}\n className=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50\"\n >\n <GmailMark size={14} /> Open draft in Gmail\n </button>\n </span>\n <span className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => { setPreview(false); setPreviewState(IDLE_PREVIEW) }}>\n Keep editing\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={sending || previewState.loading}\n onClick={handleSend}\n >\n <Send size={14} /> {sending ? \"Sending...\" : \"Send now\"}\n </Button>\n </span>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {tenantName ? (\n <p className=\"text-muted-foreground/70 mt-2 text-[11px]\">Sends via Gmail · playbooks stay stopped.</p>\n ) : null}\n </div>\n )\n}\n\n/* ── Thread body (messages + footer/composer/done states) ───────────────── */\n\ntype ThreadMode = \"idle\" | \"replying\" | \"sent\" | \"draft\"\n\nfunction ThreadBody({\n thread,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n tenantName?: string\n onSendReply?: (p: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (p: ConversationReplyPayload) => void | Promise<void>\n onPreviewReply?: (p: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n}) {\n const canReply = thread.canReply !== false\n const replyDisabledReason = thread.replyDisabledReason?.trim() || \"You are not a participant on this thread, so replying is disabled here.\"\n const draftDisabledReason = onCreateGmailDraft ? null : \"Gmail draft creation is not available for this thread.\"\n const hasCc = !!(thread.cc && thread.cc.length)\n const [mode, setMode] = React.useState<ThreadMode>(\"idle\")\n const [replyAll, setReplyAll] = React.useState(false)\n const [expanded, setExpanded] = React.useState<Record<string, boolean>>(() => {\n const o: Record<string, boolean> = {}\n thread.messages.forEach((m, i) => {\n o[m.id] = i === thread.messages.length - 1\n })\n return o\n })\n\n const toggle = (id: string) => setExpanded((e) => ({ ...e, [id]: !e[id] }))\n\n return (\n <div data-slot=\"conv-thread-body\" className=\"space-y-2\">\n {canReply && thread.paused ? (\n <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]\">\n <Pause size={13} className=\"mt-0.5 shrink-0\" />\n <span>\n <b>Follow-up actions stopped.</b> Your {thread.paused.playbook} next steps won’t send\n automatically while this conversation is live. Continue it in {tenantName ?? \"the app\"} or Gmail.\n </span>\n </div>\n ) : null}\n\n <div className=\"space-y-1\">\n {thread.messages.map((m) => (\n <MessageView key={m.id} message={m} expanded={!!expanded[m.id]} onToggle={() => toggle(m.id)} me={me} />\n ))}\n </div>\n\n {!canReply ? (\n <div className=\"border-border bg-muted/30 text-muted-foreground flex flex-wrap items-start gap-2 rounded-md border p-2.5 text-[12px]\">\n <Eye size={14} className=\"mt-0.5 shrink-0\" />\n <span className=\"min-w-0 flex-1\">\n <b>Viewing only.</b> {replyDisabledReason}\n </span>\n <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />\n </div>\n ) : null}\n\n {canReply && mode === \"idle\" ? (\n <div data-slot=\"conv-action-row\" className=\"border-border/70 mt-1 flex flex-wrap items-center gap-x-3 gap-y-2 border-t pt-3\">\n <Button type=\"button\" size=\"sm\" onClick={() => { setReplyAll(false); setMode(\"replying\") }}>\n <Reply size={15} /> Reply\n </Button>\n {hasCc ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setReplyAll(true); setMode(\"replying\") }}>\n <ReplyAll size={14} /> Reply all\n </Button>\n ) : null}\n <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />\n <span className=\"text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]\">\n <GitMerge size={13} /> Stays on this thread\n </span>\n </div>\n ) : null}\n\n {canReply && mode === \"replying\" ? (\n <ReplyComposer\n thread={thread}\n me={me}\n replyAll={replyAll}\n tenantName={tenantName}\n onPreviewReply={onPreviewReply}\n onClose={() => setMode(\"idle\")}\n onSend={async (body, includeSignature) => {\n await onSendReply?.({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"sent\")\n }}\n onDraft={async (body, includeSignature) => {\n if (!onCreateGmailDraft) return\n await onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"draft\")\n }}\n draftDisabledReason={draftDisabledReason}\n />\n ) : null}\n\n {canReply && mode === \"sent\" ? (\n <div className=\"border-status-active-border bg-status-active-bg flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <Check size={16} className=\"text-status-active-fg shrink-0\" />\n <span className=\"flex-1\">\n <b>{replyAll ? \"Reply all sent\" : \"Reply sent\"}</b> · added to the thread. Delivered to{\" \"}\n <b>{thread.contact.name}</b>. This action stays <b>Pending</b>; playbooks remain stopped.\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n\n {canReply && mode === \"draft\" ? (\n <div className=\"border-border bg-muted/30 flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <GmailMark size={16} />\n <span className=\"flex-1\">\n <b>Draft saved to Gmail.</b> Waiting on the <b>Re: {thread.subject}</b> thread; open it there to finish. Nothing was sent.\n </span>\n <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── A thread row + its inline reader ───────────────────────────────────── */\n\nfunction ThreadRow({\n thread,\n open,\n onToggleOpen,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n open: boolean\n onToggleOpen: () => void\n} & Pick<ConversationPanelProps, \"me\" | \"tenantName\" | \"onSendReply\" | \"onCreateGmailDraft\" | \"onPreviewReply\" | \"onOpenInGmail\">) {\n const status = effectiveStatus(thread)\n const last = thread.messages[thread.messages.length - 1]\n const who = last?.direction === \"inbound\" ? firstName(last.from.name) : \"You\"\n const lastSnippet = last ? messageBodySnippet(last, 120) : \"\"\n const pill = STATUS_PILL[status]\n\n return (\n <div data-slot=\"conv-thread\" data-open={open ? \"true\" : undefined} className=\"border-border border-b last:border-b-0\">\n <button\n type=\"button\"\n onClick={onToggleOpen}\n aria-expanded={open}\n className=\"flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30\"\n >\n <span className={cn(\"size-2 shrink-0 rounded-full\", STATUS_DOT[status])} aria-hidden />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-semibold\">{thread.subject}</span>\n <span className={cn(\"shrink-0 rounded-md border px-1.5 py-px text-[10px] font-medium leading-4\", pill.cls)}>\n {pill.label}\n </span>\n </span>\n <span className=\"text-muted-foreground block truncate text-xs\">\n <b className=\"text-foreground/80\">{thread.contact.name}</b> · {who}: {lastSnippet}\n </span>\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{thread.lastWhen}</span>\n {open ? (\n <ChevronUp size={15} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={15} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {open ? (\n <div className=\"px-3 pb-3\">\n <ThreadBody\n thread={thread}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── The hub ─────────────────────────────────────────────────────────────── */\n\nfunction ConversationPanel({\n threads,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n defaultOpenThreadId,\n className,\n}: ConversationPanelProps) {\n const responded = threads.filter((t) => t.status === \"responded\" && t.canReply !== false).length\n const draft = threads.filter((t) => effectiveStatus(t) === \"draft\").length\n const awaiting = threads.filter((t) => effectiveStatus(t) === \"awaiting\").length\n const anyPaused = threads.some((t) => t.paused)\n\n const [hubOpen, setHubOpen] = React.useState(true)\n const [openId, setOpenId] = React.useState<string | null>(() => {\n if (defaultOpenThreadId) return defaultOpenThreadId\n const firstActionable = threads.find((t) => [\"responded\", \"draft\"].includes(t.status) && t.canReply !== false)\n return firstActionable ? firstActionable.threadId : null\n })\n\n if (!threads.length) return null\n\n // Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.\n const badge =\n responded > 0\n ? { label: \"Email response detected\", dot: \"bg-status-active-fg\", ring: \"bg-status-active-fg/30\" }\n : draft > 0\n ? { label: \"Draft ready\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : awaiting > 0\n ? { label: \"Awaiting response\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : { label: \"Conversations\", dot: \"bg-muted-foreground/50\", ring: \"bg-muted-foreground/20\" }\n\n const headTitle =\n responded > 0\n ? `${responded} ${responded === 1 ? \"reply needs\" : \"replies need\"} your response`\n : draft > 0\n ? `Draft ready on ${draft} ${draft === 1 ? \"thread\" : \"threads\"}`\n : awaiting > 0\n ? `Awaiting response on ${awaiting} ${awaiting === 1 ? \"thread\" : \"threads\"}`\n : `${threads.length} email ${threads.length === 1 ? \"thread\" : \"threads\"}`\n\n return (\n <section\n data-slot=\"conversation-panel\"\n data-responded={responded > 0 ? \"true\" : undefined}\n className={cn(\"border-border bg-background overflow-hidden rounded-xl border\", className)}\n >\n <button\n type=\"button\"\n onClick={() => setHubOpen((v) => !v)}\n aria-expanded={hubOpen}\n className=\"flex w-full items-center gap-3 px-3 py-2.5 text-left\"\n >\n <span\n data-slot=\"conversation-badge\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold\",\n responded > 0\n ? \"bg-status-active-bg text-status-active-fg\"\n : draft > 0 || awaiting > 0\n ? \"bg-status-pending-bg text-status-pending-fg\"\n : \"bg-muted text-muted-foreground\"\n )}\n >\n <span className=\"relative inline-flex size-2\">\n <span className={cn(\"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75\", badge.ring)} />\n <span className={cn(\"relative inline-flex size-2 rounded-full\", badge.dot)} />\n </span>\n {badge.label}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold leading-tight\">{headTitle}</span>\n <span className=\"text-muted-foreground mt-0.5 block truncate text-xs\">\n {threads.length} {threads.length === 1 ? \"thread\" : \"threads\"} on this action\n {anyPaused ? <> · <b>playbook stopped</b></> : null}\n </span>\n </span>\n {hubOpen ? (\n <ChevronUp size={16} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={16} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {hubOpen ? (\n <div className=\"border-border border-t\">\n {threads.map((t) => (\n <ThreadRow\n key={t.threadId}\n thread={t}\n open={openId === t.threadId}\n onToggleOpen={() => setOpenId((cur) => (cur === t.threadId ? null : t.threadId))}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n ))}\n </div>\n ) : null}\n </section>\n )\n}\n\nexport { ConversationPanel }\n"],"mappings":"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","names":["_a","_b"]}
|
package/package.json
CHANGED
|
@@ -86,6 +86,54 @@ describe("ConversationPanel", () => {
|
|
|
86
86
|
expect(screen.getByText("Draft copy")).toBeDefined();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
|
|
90
|
+
it("uses the custom read-only reason and disables Open in Gmail when access is unavailable", () => {
|
|
91
|
+
const onOpenInGmail = vi.fn();
|
|
92
|
+
|
|
93
|
+
render(
|
|
94
|
+
<ConversationPanel
|
|
95
|
+
threads={[
|
|
96
|
+
thread({
|
|
97
|
+
canReply: false,
|
|
98
|
+
replyDisabledReason: "Only the case owner can open Gmail drafts for this case.",
|
|
99
|
+
openInGmailDisabled: true,
|
|
100
|
+
openInGmailDisabledReason: "Only the case owner can open this draft in Gmail.",
|
|
101
|
+
}),
|
|
102
|
+
]}
|
|
103
|
+
me={me}
|
|
104
|
+
onOpenInGmail={onOpenInGmail}
|
|
105
|
+
defaultOpenThreadId="t1"
|
|
106
|
+
/>,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(screen.getByText(/Only the case owner can open Gmail drafts for this case\./)).toBeDefined();
|
|
110
|
+
const button = screen.getByRole("button", {
|
|
111
|
+
name: "Open in Gmail: Only the case owner can open this draft in Gmail.",
|
|
112
|
+
});
|
|
113
|
+
expect(button).toHaveProperty("disabled", true);
|
|
114
|
+
|
|
115
|
+
fireEvent.click(button);
|
|
116
|
+
expect(onOpenInGmail).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("renders Open in Gmail as a new-tab link when a URL is provided", () => {
|
|
120
|
+
render(
|
|
121
|
+
<ConversationPanel
|
|
122
|
+
threads={[
|
|
123
|
+
thread({
|
|
124
|
+
openInGmailUrl: "https://mail.google.com/mail/?authuser=dana%40handled.ai#drafts/msg-1",
|
|
125
|
+
}),
|
|
126
|
+
]}
|
|
127
|
+
me={me}
|
|
128
|
+
/>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const link = screen.getByRole("link", { name: /Open in Gmail/ });
|
|
132
|
+
expect(link.getAttribute("href")).toBe("https://mail.google.com/mail/?authuser=dana%40handled.ai#drafts/msg-1");
|
|
133
|
+
expect(link.getAttribute("target")).toBe("_blank");
|
|
134
|
+
expect(link.getAttribute("rel")).toBe("noopener noreferrer");
|
|
135
|
+
});
|
|
136
|
+
|
|
89
137
|
it("auto-opens the first responded thread and renders its newest message as HTML", () => {
|
|
90
138
|
const { container } = render(<ConversationPanel threads={[thread()]} me={me} />);
|
|
91
139
|
// newest (inbound) message expanded -> its anchor is in the DOM
|
|
@@ -97,8 +97,16 @@ export interface ConversationThread {
|
|
|
97
97
|
cc?: ConvParticipant[]
|
|
98
98
|
/** Set when this thread's reply halted a playbook (terminal). */
|
|
99
99
|
paused?: { playbook: string } | null
|
|
100
|
-
/** false => operator
|
|
100
|
+
/** false => operator cannot reply or create drafts from this thread. */
|
|
101
101
|
canReply?: boolean
|
|
102
|
+
/** Explains why reply and draft creation are disabled. */
|
|
103
|
+
replyDisabledReason?: string
|
|
104
|
+
/** Existing Gmail draft or thread URL. Rendered as a new-tab link when present. */
|
|
105
|
+
openInGmailUrl?: string | null
|
|
106
|
+
/** Forces the Open in Gmail action into a disabled state. */
|
|
107
|
+
openInGmailDisabled?: boolean
|
|
108
|
+
/** Tooltip/read-only copy for a disabled Open in Gmail action. */
|
|
109
|
+
openInGmailDisabledReason?: string | null
|
|
102
110
|
messages: ConvMessage[]
|
|
103
111
|
/** Prefilled reply draft body. */
|
|
104
112
|
draft?: string
|
|
@@ -521,6 +529,62 @@ function effectiveStatus(t: ConversationThread): ConvStatus {
|
|
|
521
529
|
return t.canReply === false ? "viewing" : t.status
|
|
522
530
|
}
|
|
523
531
|
|
|
532
|
+
function disabledOpenInGmailReason(thread: ConversationThread): string {
|
|
533
|
+
return (
|
|
534
|
+
thread.openInGmailDisabledReason?.trim() ||
|
|
535
|
+
thread.replyDisabledReason?.trim() ||
|
|
536
|
+
"Gmail access is not available for this thread."
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function canOpenInGmail(thread: ConversationThread, onOpenInGmail?: (threadId: string) => void): boolean {
|
|
541
|
+
return thread.openInGmailDisabled !== true && Boolean(thread.openInGmailUrl || onOpenInGmail)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function OpenInGmailButton({
|
|
545
|
+
thread,
|
|
546
|
+
onOpenInGmail,
|
|
547
|
+
label = "Open in Gmail",
|
|
548
|
+
}: {
|
|
549
|
+
thread: ConversationThread
|
|
550
|
+
onOpenInGmail?: (threadId: string) => void
|
|
551
|
+
label?: string
|
|
552
|
+
}) {
|
|
553
|
+
const hasConfiguredAction = Boolean(thread.openInGmailUrl || onOpenInGmail || thread.openInGmailDisabled || thread.openInGmailDisabledReason)
|
|
554
|
+
if (!hasConfiguredAction) return null
|
|
555
|
+
|
|
556
|
+
const disabled = !canOpenInGmail(thread, onOpenInGmail)
|
|
557
|
+
const disabledReason = disabled
|
|
558
|
+
? disabledOpenInGmailReason(thread)
|
|
559
|
+
: undefined
|
|
560
|
+
|
|
561
|
+
if (!disabled && thread.openInGmailUrl) {
|
|
562
|
+
return (
|
|
563
|
+
<Button type="button" variant="ghost" size="sm" asChild>
|
|
564
|
+
<a href={thread.openInGmailUrl} target="_blank" rel="noopener noreferrer">
|
|
565
|
+
<GmailMark size={14} /> {label}
|
|
566
|
+
</a>
|
|
567
|
+
</Button>
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return (
|
|
572
|
+
<span className="inline-flex" title={disabledReason}>
|
|
573
|
+
<Button
|
|
574
|
+
type="button"
|
|
575
|
+
variant="ghost"
|
|
576
|
+
size="sm"
|
|
577
|
+
disabled={disabled}
|
|
578
|
+
aria-disabled={disabled || undefined}
|
|
579
|
+
aria-label={disabledReason ? `${label}: ${disabledReason}` : label}
|
|
580
|
+
onClick={disabled ? undefined : () => onOpenInGmail?.(thread.threadId)}
|
|
581
|
+
>
|
|
582
|
+
<GmailMark size={14} /> {label}
|
|
583
|
+
</Button>
|
|
584
|
+
</span>
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
|
|
524
588
|
/* ── One message (collapsible) ──────────────────────────────────────────── */
|
|
525
589
|
|
|
526
590
|
function MessageView({
|
|
@@ -675,6 +739,7 @@ function ReplyComposer({
|
|
|
675
739
|
onSend,
|
|
676
740
|
onDraft,
|
|
677
741
|
onPreviewReply,
|
|
742
|
+
draftDisabledReason,
|
|
678
743
|
}: {
|
|
679
744
|
thread: ConversationThread
|
|
680
745
|
me?: ConvParticipant
|
|
@@ -684,6 +749,7 @@ function ReplyComposer({
|
|
|
684
749
|
onSend: (body: string, includeSignature: boolean) => void | Promise<void>
|
|
685
750
|
onDraft: (body: string, includeSignature: boolean) => void | Promise<void>
|
|
686
751
|
onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>
|
|
752
|
+
draftDisabledReason?: string | null
|
|
687
753
|
}) {
|
|
688
754
|
const [body, setBody] = React.useState(thread.draft ?? "")
|
|
689
755
|
const [sig, setSig] = React.useState(true)
|
|
@@ -693,6 +759,7 @@ function ReplyComposer({
|
|
|
693
759
|
const [sendError, setSendError] = React.useState<string | null>(null)
|
|
694
760
|
const ccList = replyAll ? thread.cc ?? [] : []
|
|
695
761
|
const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`
|
|
762
|
+
const draftDisabled = Boolean(draftDisabledReason)
|
|
696
763
|
|
|
697
764
|
const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : "")
|
|
698
765
|
|
|
@@ -742,6 +809,7 @@ function ReplyComposer({
|
|
|
742
809
|
}
|
|
743
810
|
|
|
744
811
|
const handleDraft = async () => {
|
|
812
|
+
if (draftDisabled) return
|
|
745
813
|
setSending(true)
|
|
746
814
|
setSendError(null)
|
|
747
815
|
try {
|
|
@@ -885,14 +953,17 @@ function ReplyComposer({
|
|
|
885
953
|
</p>
|
|
886
954
|
) : null}
|
|
887
955
|
<DialogFooter className="sm:justify-between">
|
|
888
|
-
<
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
956
|
+
<span className="inline-flex" title={draftDisabledReason ?? undefined}>
|
|
957
|
+
<button
|
|
958
|
+
type="button"
|
|
959
|
+
disabled={sending || previewState.loading || draftDisabled}
|
|
960
|
+
onClick={handleDraft}
|
|
961
|
+
aria-label={draftDisabledReason ? `Open draft in Gmail: ${draftDisabledReason}` : "Open draft in Gmail"}
|
|
962
|
+
className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50"
|
|
963
|
+
>
|
|
964
|
+
<GmailMark size={14} /> Open draft in Gmail
|
|
965
|
+
</button>
|
|
966
|
+
</span>
|
|
896
967
|
<span className="flex items-center gap-2">
|
|
897
968
|
<Button type="button" variant="outline" size="sm" disabled={sending} onClick={() => { setPreview(false); setPreviewState(IDLE_PREVIEW) }}>
|
|
898
969
|
Keep editing
|
|
@@ -939,6 +1010,8 @@ function ThreadBody({
|
|
|
939
1010
|
onOpenInGmail?: (threadId: string) => void
|
|
940
1011
|
}) {
|
|
941
1012
|
const canReply = thread.canReply !== false
|
|
1013
|
+
const replyDisabledReason = thread.replyDisabledReason?.trim() || "You are not a participant on this thread, so replying is disabled here."
|
|
1014
|
+
const draftDisabledReason = onCreateGmailDraft ? null : "Gmail draft creation is not available for this thread."
|
|
942
1015
|
const hasCc = !!(thread.cc && thread.cc.length)
|
|
943
1016
|
const [mode, setMode] = React.useState<ThreadMode>("idle")
|
|
944
1017
|
const [replyAll, setReplyAll] = React.useState(false)
|
|
@@ -971,11 +1044,12 @@ function ThreadBody({
|
|
|
971
1044
|
</div>
|
|
972
1045
|
|
|
973
1046
|
{!canReply ? (
|
|
974
|
-
<div className="border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]">
|
|
1047
|
+
<div className="border-border bg-muted/30 text-muted-foreground flex flex-wrap items-start gap-2 rounded-md border p-2.5 text-[12px]">
|
|
975
1048
|
<Eye size={14} className="mt-0.5 shrink-0" />
|
|
976
|
-
<span>
|
|
977
|
-
<b>Viewing only.</b>
|
|
1049
|
+
<span className="min-w-0 flex-1">
|
|
1050
|
+
<b>Viewing only.</b> {replyDisabledReason}
|
|
978
1051
|
</span>
|
|
1052
|
+
<OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />
|
|
979
1053
|
</div>
|
|
980
1054
|
) : null}
|
|
981
1055
|
|
|
@@ -989,9 +1063,7 @@ function ThreadBody({
|
|
|
989
1063
|
<ReplyAll size={14} /> Reply all
|
|
990
1064
|
</Button>
|
|
991
1065
|
) : null}
|
|
992
|
-
<
|
|
993
|
-
<GmailMark size={14} /> Open in Gmail
|
|
994
|
-
</Button>
|
|
1066
|
+
<OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />
|
|
995
1067
|
<span className="text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]">
|
|
996
1068
|
<GitMerge size={13} /> Stays on this thread
|
|
997
1069
|
</span>
|
|
@@ -1011,9 +1083,11 @@ function ThreadBody({
|
|
|
1011
1083
|
setMode("sent")
|
|
1012
1084
|
}}
|
|
1013
1085
|
onDraft={async (body, includeSignature) => {
|
|
1014
|
-
|
|
1086
|
+
if (!onCreateGmailDraft) return
|
|
1087
|
+
await onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll })
|
|
1015
1088
|
setMode("draft")
|
|
1016
1089
|
}}
|
|
1090
|
+
draftDisabledReason={draftDisabledReason}
|
|
1017
1091
|
/>
|
|
1018
1092
|
) : null}
|
|
1019
1093
|
|
|
@@ -1036,9 +1110,7 @@ function ThreadBody({
|
|
|
1036
1110
|
<span className="flex-1">
|
|
1037
1111
|
<b>Draft saved to Gmail.</b> Waiting on the <b>Re: {thread.subject}</b> thread; open it there to finish. Nothing was sent.
|
|
1038
1112
|
</span>
|
|
1039
|
-
<
|
|
1040
|
-
<GmailMark size={14} /> Open in Gmail
|
|
1041
|
-
</Button>
|
|
1113
|
+
<OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />
|
|
1042
1114
|
<Button type="button" variant="ghost" size="sm" onClick={() => setMode("idle")}>
|
|
1043
1115
|
Done
|
|
1044
1116
|
</Button>
|