"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}
/>
))}
);
}