"use client"; // ============================================================ // Inbox — the ONE unified communication surface. It lists everything // IIOS surfaces for you (mentions, needs-reply, system alerts, support // updates, …) AND the mail behind them: click an item tied to a thread // and its conversation opens on the right to read + reply. Compose new // mail from here too. Items come from crm.inbox.*; threads from crm.mail.*. // ============================================================ import { useEffect, useState } from "react"; import { Btn, Icon, PageHead, Pill, useToast } from "./ui"; import { useInboxData, type InboxState, type UiInboxItem } from "@/lib/inbox-api"; import { MailReader, NewMailModal } from "./mail"; const KIND_LABEL: Record = { MAIL: "Mail", MENTION: "Mention", NEEDS_REPLY: "Needs reply", NEEDS_REVIEW: "Needs review", NEEDS_APPROVAL: "Needs approval", SUPPORT_UPDATE: "Support", MEETING_FOLLOWUP: "Meeting", DIGEST: "Digest", SYSTEM_ALERT: "Alert", CRM_OWNER_INTEREST: "Owner", }; const FILTERS: { value: InboxState; label: string }[] = [ { value: "OPEN", label: "Open" }, { value: "SNOOZED", label: "Snoozed" }, { value: "DONE", label: "Done" }, { value: "ARCHIVED", label: "Archived" }, ]; export function Inbox() { const [filter, setFilter] = useState("OPEN"); const inbox = useInboxData(filter); const toast = useToast(); const [selectedId, setSelectedId] = useState(null); const [newOpen, setNewOpen] = useState(false); useEffect(() => { if ((!selectedId || !inbox.items.some((i) => i.id === selectedId)) && inbox.items[0]) setSelectedId(inbox.items[0].id); }, [inbox.items, selectedId]); const selected = inbox.items.find((i) => i.id === selectedId) ?? null; return (
setNewOpen(true)}>New mail} /> {!inbox.live && (
Demo mode — running on mock data. It goes live once the Shell + be-crm are connected.
)}
{FILTERS.map((f) => ( setFilter(f.value)}>{f.label} ))}
{/* Left — the unified item list */} {/* Right — read the mail behind the item, or the item detail */}
{selected ? ( toast.push({ tone: "error", title: "Failed", desc: m })} onDone={() => inbox.transition(selected.id, "DONE")} onSnooze={() => inbox.transition(selected.id, "SNOOZED")} onArchive={() => inbox.transition(selected.id, "ARCHIVED")} /> ) : (

Select an item to read

)}
setNewOpen(false)} onSent={() => { setNewOpen(false); inbox.refetch(); toast.push({ tone: "success", title: "Sent" }); }} onError={(m) => toast.push({ tone: "error", title: "Couldn't send", desc: m })} />
); } function ItemRow({ it, active, onClick }: { it: UiInboxItem; active: boolean; onClick: () => void }) { const isMention = it.kind === "MENTION"; return ( ); } function Detail({ it, onError, onDone, onSnooze, onArchive }: { it: UiInboxItem; onError: (m: string) => void; onDone: () => void; onSnooze: () => void; onArchive: () => void; }) { return ( <> {/* Item actions bar — only for real inbox work-items. Mail isn't an inbox item (no crm.inbox.transition), so it gets read/reply only, no Done/Snooze/Archive. */} {it.state === "OPEN" && it.kind !== "MAIL" && (
Snooze Done Archive
)} {it.threadId ? ( // A message/mail item → open the conversation to read + reply. // key by threadId: the SDK's useQuery only refetches when the ACTION changes, not the // variables — so switching items must remount MailReader to load the new thread's history.
) : ( // A non-threaded item (e.g. a system alert) → show its detail.
{it.title}
{it.summary &&
{it.summary}
}
)} ); }