import { Bell, ChevronDown, ChevronLeft, ChevronRight, CircleHelp, Filter, LayoutGrid, List, Mail, RefreshCw, Search, Settings, Sun, Tag, Users, Wallet, } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { getEmail, listEmails, permanentlyDeleteEmail, restoreEmail, setEmailRead, setEmailStar, trashEmail, } from "../api"; import AgAgentPanel from "./AgAgentPanel"; import AgentSpark from "./AgentSpark"; import CommandPalette from "./CommandPalette"; import { useAgentInsight } from "../hooks/useAgentInsight"; import { useMailboxMaturity } from "../hooks/useMailboxMaturity"; import MailDetail from "./MailDetail"; import MailRow from "./MailRow"; import MailboxEmptyState from "./MailboxEmptyState"; import MailboxSelectionBar from "./MailboxSelectionBar"; import StickyNotesPanel from "./StickyNotesPanel"; import { categoryCounts, emailCategory, inboxIntelligence, labelCounts, matchesLens, } from "../utils/inboxSignals"; const PAGE_SIZE = 25; const CATEGORY_TABS = [ { name: "Primary", icon: LayoutGrid }, { name: "Updates", icon: Bell }, { name: "Promotions", icon: Tag }, { name: "Social", icon: Users }, { name: "Finance", icon: Wallet }, { name: "All Mail", icon: Mail }, ]; function resultPage(result) { if (Array.isArray(result)) return { items: result, nextCursor: null }; return { items: result.items || result.emails || result.results || [], nextCursor: result.next_cursor || result.nextCursor || null, }; } export default function MailboxView({ mailbox, title, refreshKey = 0, account, lens = "all", agentNav = null, onLogout, onOpenSettings, onCompose, onStats, }) { const token = sessionStorage.getItem("agmail_access_token"); const [emails, setEmails] = useState([]); const [nextCursor, setNextCursor] = useState(null); const [cursor, setCursor] = useState(null); const [cursorHistory, setCursorHistory] = useState([]); const [selected, setSelected] = useState(null); const [checked, setChecked] = useState({}); const [hidden, setHidden] = useState({}); const [detail, setDetail] = useState(null); const [status, setStatus] = useState({ loading: true, error: "" }); const [detailStatus, setDetailStatus] = useState({ loading: false, error: "", busy: false }); const [filtersOpen, setFiltersOpen] = useState(false); const [category, setCategory] = useState("Primary"); const [agentFocus, setAgentFocus] = useState(""); const { insight, runInsight } = useAgentInsight(); const [commandOpen, setCommandOpen] = useState(false); const [filters, setFilters] = useState({ query: "", read: "all", sender: "", dateFrom: "", dateTo: "", }); const load = useCallback(async () => { setStatus({ loading: true, error: "" }); try { const result = await listEmails( { mailbox, limit: PAGE_SIZE, cursor, q: filters.query.trim(), is_read: filters.read === "all" ? undefined : filters.read === "read", sender: filters.sender.trim(), date_from: filters.dateFrom || undefined, date_to: filters.dateTo || undefined, }, token, ); const page = resultPage(result); setEmails(page.items); setNextCursor(page.nextCursor); setStatus({ loading: false, error: "" }); } catch (error) { setStatus({ loading: false, error: error.message }); } }, [cursor, filters, mailbox, token]); useEffect(() => { const timeout = window.setTimeout(load, filters.query ? 250 : 0); return () => window.clearTimeout(timeout); }, [load, refreshKey]); useEffect(() => { function openPalette(event) { const target = event.target; const typing = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target?.isContentEditable; if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault(); setCommandOpen(true); return; } if (event.key === "/" && !typing && !commandOpen) { event.preventDefault(); setCommandOpen(true); } } window.addEventListener("keydown", openPalette); return () => window.removeEventListener("keydown", openPalette); }, [commandOpen]); const visibleEmails = useMemo( () => emails.filter((email) => !hidden[email.id] && matchesLens(email, lens)), [emails, hidden, lens], ); const listedEmails = useMemo(() => { if (mailbox !== "inbox" || category === "All Mail") return visibleEmails; return visibleEmails.filter((email) => emailCategory(email) === category); }, [category, mailbox, visibleEmails]); const counts = useMemo(() => categoryCounts(visibleEmails), [visibleEmails]); const selectedIds = useMemo( () => Object.keys(checked).filter((id) => checked[id]), [checked], ); const selectedCount = selectedIds.length; const hasFilters = filters.read !== "all" || Boolean(filters.sender.trim()) || Boolean(filters.dateFrom) || Boolean(filters.dateTo); const hasSearch = Boolean(filters.query.trim()); const { isBrandNew } = useMailboxMaturity(account, emails, status.loading); const isEmpty = !status.loading && !status.error && listedEmails.length === 0; useEffect(() => { if (!onStats) return; const intel = inboxIntelligence(emails); onStats({ unread: emails.filter((email) => email.is_read === false).length, starred: emails.filter((email) => email.is_starred).length, important: intel.important, drafts: 0, actionItems: intel.actionItems, labels: labelCounts(emails), }); }, [emails, onStats]); function changeFilter(name, value) { setFilters((current) => ({ ...current, [name]: value })); setCursor(null); setCursorHistory([]); } async function openEmail(email) { setSelected(email.id); setDetail(email); setDetailStatus({ loading: true, error: "", busy: false }); if (email.is_read === false) { setEmails((current) => current.map((item) => item.id === email.id ? { ...item, is_read: true } : item), ); setDetail((current) => ({ ...current, is_read: true })); setEmailRead(email.id, true, token).catch(() => { setEmails((current) => current.map((item) => item.id === email.id ? { ...item, is_read: false } : item), ); setDetail((current) => current?.id === email.id ? { ...current, is_read: false } : current, ); }); } try { const completeEmail = await getEmail(email.id, token); setDetail((current) => ({ ...current, ...completeEmail, is_read: current?.is_read ?? completeEmail.is_read })); setDetailStatus({ loading: false, error: "", busy: false }); } catch (error) { setDetailStatus({ loading: false, error: error.message, busy: false }); } } async function toggleStar(email, isStarred) { setEmails((current) => current.map((item) => item.id === email.id ? { ...item, is_starred: isStarred } : item), ); if (detail?.id === email.id) { setDetail((current) => ({ ...current, is_starred: isStarred })); } try { await setEmailStar(email.id, isStarred, token); } catch { setEmails((current) => current.map((item) => item.id === email.id ? { ...item, is_starred: !isStarred } : item), ); if (detail?.id === email.id) { setDetail((current) => ({ ...current, is_starred: !isStarred })); } } } async function markUnread(email) { setEmails((current) => current.map((item) => item.id === email.id ? { ...item, is_read: false } : item), ); try { await setEmailRead(email.id, false, token); } catch { setEmails((current) => current.map((item) => item.id === email.id ? { ...item, is_read: email.is_read } : item), ); } } async function removeEmail(email) { setEmails((current) => current.filter((item) => item.id !== email.id)); if (detail?.id === email.id) { setSelected(null); setDetail(null); } try { await trashEmail(email.id, token); } catch (error) { setAgentFocus(error.message); load(); } } function hideForSession(email, message) { setHidden((current) => ({ ...current, [email.id]: true })); if (detail?.id === email.id) { setSelected(null); setDetail(null); } setAgentFocus(message); } function summarize(email) { const subject = email.subject?.trim() || "(No subject)"; const snippet = email.snippet || email.preview || email.body_text || "No preview available."; setAgentFocus(""); runInsight({ title: subject, summary: snippet, details: [ `From ${email.sender?.name || email.sender?.email || "unknown sender"}`, email.is_read === false ? "Marked unread — likely needs attention" : "Already read", ], actions: ["Draft a reply", "Extract action items", "Snooze for later"], }); } function summarizeSelection() { const items = listedEmails.filter((email) => checked[email.id]); if (!items.length) return; setAgentFocus(""); runInsight({ title: `${items.length} selected messages`, summary: items.length === 1 ? (items[0].snippet || items[0].preview || "No preview available.") : `${items.length} conversations span ${items.filter((email) => email.is_read === false).length} unread threads.`, details: items.slice(0, 3).map((email) => email.subject || "(No subject)"), actions: ["Draft batch replies", "Archive completed threads", "Flag for follow-up"], }); } function runBulkAction(action) { const items = listedEmails.filter((email) => checked[email.id]); if (!items.length) return; if (action === "archive" || action === "snooze") { items.forEach((email) => hideForSession( email, action === "archive" ? "Archived in this view until you refresh." : "Snoozed in this view until you refresh.", )); } if (action === "delete" || action === "spam") { items.forEach((email) => removeEmail(email)); } if (action === "unread") { items.forEach((email) => markUnread(email)); } if (action === "move" || action === "label" || action === "more") { setAgentFocus(`${action === "move" ? "Move" : action === "label" ? "Label" : "More"} for ${items.length} selected messages is coming soon.`); } setChecked({}); } function toggleSelectAll() { if (selectedCount === listedEmails.length) { setChecked({}); return; } setChecked(listedEmails.reduce((map, email) => ({ ...map, [email.id]: true }), {})); } function extractActions(email) { const text = email.body_text || email.snippet || email.preview || ""; setAgentFocus(""); runInsight({ title: email.subject || "Message", summary: text ? "Scanned the message for commitments, requests, and deadlines." : "No plain-text content to extract actions from.", details: text ? ["Reply requested or implied", "Review attachments if any", "Check calendar mentions"] : ["Open the full message to extract richer actions"], actions: ["Draft a reply", "Add to action items", "Set a reminder"], }); } function summarizeThread(email) { setAgentFocus(""); runInsight({ title: email.subject || "Thread", summary: email.snippet || email.preview || "Thread context loaded from the latest message.", details: ["Prior messages in thread considered", "Open questions highlighted"], actions: ["Draft a reply", "Summarize for your team", "Extract next steps"], }); } function analyzeAttachment(email) { setAgentFocus(""); runInsight({ title: email.subject || "Attachments", summary: "Reviewing files attached to this conversation.", details: ["File types identified", "Key entities extracted from filenames"], actions: ["Summarize document", "Find related messages", "Save to notes"], }); } function runQuickAction(action) { const intel = inboxIntelligence(listedEmails); if (action === "summarize") { setAgentFocus(""); runInsight({ title: `${category} inbox`, summary: listedEmails.length ? `${listedEmails.length} messages loaded. ${intel.actionItems} look like they need a reply.` : "Nothing in this view to summarize yet.", details: [ `${intel.meetings} mention meetings`, `${intel.invoices} look like bills`, `${intel.unread} unread overall`, ], actions: ["Show action items", "Draft replies", "Find attachments"], }); return; } if (action === "draft") { const first = listedEmails.find((email) => email.is_read === false) || listedEmails[0]; setAgentFocus(""); runInsight({ title: first?.subject || "Draft replies", summary: first ? `Ready to draft a reply to "${first.subject || "the latest message"}".` : "No message is waiting for a reply.", details: first ? ["Tone: professional and concise", "Context pulled from inbox signals"] : [], actions: ["Open compose", "Suggest three reply options", "Improve existing draft"], }); return; } if (action === "unread") { changeFilter("read", "unread"); setFiltersOpen(true); setAgentFocus(`${intel.unread} unread messages in this mailbox.`); return; } if (action === "attachments" || action === "actions" || action === "important" || action === "actionItems" || action === "meetings" || action === "invoices") { const labels = { attachments: "attachments", actions: "action items", actionItems: "action items", important: "important mail", meetings: "meetings", invoices: "invoices", }; setCategory(action === "invoices" ? "Finance" : action === "important" ? "Primary" : category); setAgentFocus(`Showing signals for ${labels[action] || action} from the messages already loaded.`); } } function handleCommand(action, query = "") { if (action === "search") { if (query.trim()) changeFilter("query", query.trim()); window.setTimeout(() => document.getElementById("workspace-search")?.focus(), 0); return; } if (action === "from") { setFiltersOpen(true); if (query.trim()) changeFilter("sender", query.trim()); setAgentFocus("Find emails from a specific person — use the sender filter."); return; } runQuickAction(action); } useEffect(() => { if (!agentNav || status.loading) return; if (agentNav === "integrations") { setAgentFocus("Connect CRM, calendar, and billing tools from Integrations."); return; } if (agentNav === "ask" || agentNav === "summaries") { runQuickAction("summarize"); return; } if (agentNav === "actions") { runQuickAction("actions"); } }, [agentNav, status.loading, listedEmails.length, category]); async function toggleRead(isRead) { if (!detail) return; setDetailStatus((current) => ({ ...current, busy: true, error: "" })); try { await setEmailRead(detail.id, isRead, token); setDetail((current) => ({ ...current, is_read: isRead })); setEmails((current) => current.map((item) => item.id === detail.id ? { ...item, is_read: isRead } : item), ); setDetailStatus((current) => ({ ...current, busy: false })); } catch (error) { setDetailStatus((current) => ({ ...current, busy: false, error: error.message })); } } async function mutateSelected(action) { if (!detail) return; setDetailStatus((current) => ({ ...current, busy: true, error: "" })); try { await action(detail.id, token); setEmails((current) => current.filter((item) => item.id !== detail.id)); setSelected(null); setDetail(null); setDetailStatus({ loading: false, error: "", busy: false }); } catch (error) { setDetailStatus((current) => ({ ...current, busy: false, error: error.message })); } } function deletePermanently() { if (window.confirm("Permanently delete this message? This cannot be undone.")) { mutateSelected(permanentlyDeleteEmail); } } function nextPage() { if (!nextCursor) return; setCursorHistory((history) => [...history, cursor]); setCursor(nextCursor); setSelected(null); setDetail(null); } function previousPage() { if (!cursorHistory.length) return; const previous = cursorHistory[cursorHistory.length - 1]; setCursorHistory((history) => history.slice(0, -1)); setCursor(previous); setSelected(null); setDetail(null); } const initials = `${account?.first_name?.[0] || ""}${account?.last_name?.[0] || ""}`.toUpperCase(); const rangeStart = listedEmails.length ? cursorHistory.length * PAGE_SIZE + 1 : 0; const rangeEnd = cursorHistory.length * PAGE_SIZE + listedEmails.length; return (
{selectedCount > 0 && ( runBulkAction("archive")} onSpam={() => runBulkAction("spam")} onDelete={() => runBulkAction("delete")} onMarkUnread={() => runBulkAction("unread")} onSnooze={() => runBulkAction("snooze")} onMove={() => runBulkAction("move")} onLabel={() => runBulkAction("label")} onMore={() => runBulkAction("more")} onSummarize={summarizeSelection} /> )} {mailbox === "inbox" && selectedCount === 0 && (
{CATEGORY_TABS.map(({ name, icon: Icon }) => ( ))}
)} {mailbox !== "inbox" && (
)} {filtersOpen && (
)}
{status.loading &&
Loading messages…
} {status.error && (

{status.error}

)} {isEmpty && ( )} {!status.loading && !status.error && listedEmails.map((email) => ( setChecked((current) => ({ ...current, [item.id]: !current[item.id] }))} onTrash={removeEmail} onMarkUnread={markUnread} onArchive={(item) => hideForSession(item, "Archived in this view until you refresh.")} onSnooze={(item) => hideForSession(item, "Snoozed in this view until you refresh.")} onSummarize={summarize} /> ))}
{!status.loading && (
{listedEmails.length === 0 ? "0 messages" : `${rangeStart}–${rangeEnd}${nextCursor ? "" : ` · ${rangeEnd} loaded`}`}
{listedEmails.length > 0 && ( <> )}
)} { setSelected(null); setDetail(null); }} onToggleRead={toggleRead} onTrash={() => mutateSelected(trashEmail)} onRestore={() => mutateSelected(restoreEmail)} onDelete={deletePermanently} onExtractActions={() => detail && extractActions(detail)} onSummarizeThread={() => detail && summarizeThread(detail)} onAnalyzeAttachment={() => detail && analyzeAttachment(detail)} />
setCommandOpen(false)} onCommand={handleCommand} />
); }