"use client"; import { useState } from "react"; import Link from "next/link"; import SenderAvatar from "@/components/SenderAvatar"; import EmailBody from "@/components/EmailBody"; import PinButton from "@/components/PinButton"; import CalendarEventCard, { CalendarEventData, CalendarResponse } from "@/components/CalendarEventCard"; import AttachmentList from "@/components/AttachmentList"; import MailIcon from "@/components/MailIcon"; import MarkUnreadButton from "@/components/MarkUnreadButton"; import { Email } from "@/lib/types"; import { formatAddressList, formatFullDate } from "@/lib/format"; import NotSpamButton from "@/components/NotSpamButton"; import { visibleAttachments } from "@/lib/attachments"; import Popover from "@/components/Popover"; // --------------------------------------------------------------------------- // Body resolution (mirrors email/[id]/page.tsx logic) // --------------------------------------------------------------------------- function resolveBody(email: Email): { body: string; type: "html" | "text" } | null { if (email.htmlBody?.length > 0) { const part = email.htmlBody[0]; if (part.partId && email.bodyValues?.[part.partId]) { return { body: email.bodyValues[part.partId].value, type: "html" }; } } if (email.textBody?.length > 0) { const part = email.textBody[0]; if (part.partId && email.bodyValues?.[part.partId]) { const raw = email.bodyValues[part.partId].value; // Skip text/plain parts that are actually raw HTML source if (!/^\s* void; expanded: boolean; onToggle: () => void; spamMailboxId?: string; inboxMailboxId?: string; } function EmailStackItem({ email, calendarEvent, persistedResponse, onResponseSent, expanded, onToggle, spamMailboxId, inboxMailboxId, }: ItemProps) { const isUnread = !email.keywords?.["$seen"]; const resolved = resolveBody(email); const isSpam = !!(spamMailboxId && email.mailboxIds[spamMailboxId]); const downloadableAttachments = visibleAttachments(email.attachments); const hasMultipleRecipients = (email.to?.length ?? 0) + (email.cc?.length ?? 0) > 1; return (
{/* Header — always visible, tap to expand/collapse */} {/* Expanded content */} {expanded && (
{/* To / Cc metadata */}
{email.to && email.to.length > 0 && (
To {formatAddressList(email.to)}
)} {email.cc && email.cc.length > 0 && (
Cc {formatAddressList(email.cc)}
)}
{/* Action buttons */}
{hasMultipleRecipients ? "Reply all" : "Reply"} {hasMultipleRecipients && ( Reply )} {isSpam && inboxMailboxId && ( )} More } triggerClassName="flex min-h-10 items-center gap-1 rounded-md border border-stone-200 px-3 text-xs text-stone-500 transition-colors hover:bg-stone-100 hover:text-stone-900 dark:border-stone-700 dark:text-stone-400 dark:hover:bg-stone-700 dark:hover:text-stone-100" contentClassName="min-w-36 overflow-hidden rounded-lg border border-stone-200 bg-white py-1 text-sm shadow-lg dark:border-stone-700 dark:bg-stone-900" > Forward Print
{/* Calendar invite */} {calendarEvent && (
)} {/* Body */} {resolved ? ( ) : (

No content.

)} {/* Attachments */} {downloadableAttachments.length > 0 && (
)}
)}
); } // --------------------------------------------------------------------------- // Thread view — the full stacked list // --------------------------------------------------------------------------- interface Props { emails: Email[]; // sorted oldest → newest calendarEvents: (CalendarEventData | null)[]; spamMailboxId?: string; inboxMailboxId?: string; } export default function ThreadView({ emails, calendarEvents, spamMailboxId, inboxMailboxId }: Props) { // Start with the most recent email expanded const lastId = emails[emails.length - 1]?.id; const [expandedIds, setExpandedIds] = useState>( () => new Set(lastId ? [lastId] : []) ); // Persists RSVP responses across collapse/expand so CalendarEventCard // doesn't lose its confirmed state when it unmounts and remounts. const [rsvpResponses, setRsvpResponses] = useState>({}); function toggle(id: string) { setExpandedIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); } return (
{emails.map((email, i) => ( setRsvpResponses((prev) => ({ ...prev, [email.id]: r }))} expanded={expandedIds.has(email.id)} onToggle={() => toggle(email.id)} spamMailboxId={spamMailboxId} inboxMailboxId={inboxMailboxId} /> ))}
); }