/* global React, Icon, Avatar, ChannelIcon, ChannelDot, StageChip */ const { useState, useEffect, useMemo } = React; const CHANNEL_TYPE_TO_KIND = { LINKEDIN: 'linkedin', WHATSAPP: 'whatsapp', INSTAGRAM: 'instagram', GOOGLE_OAUTH: 'email', GOOGLE: 'email', OUTLOOK: 'email', IMAP: 'email', }; function relativeTime(iso) { if (!iso) return ''; const d = new Date(iso); const diff = (Date.now() - d.getTime()) / 1000; if (diff < 60) return 'now'; if (diff < 3600) return `${Math.round(diff / 60)}m`; if (diff < 86400) return `${Math.round(diff / 3600)}h`; if (diff < 86400 * 7) return `${Math.round(diff / 86400)}d`; return d.toLocaleDateString(); } function colorFromString(s) { const palette = ['#22c55e', '#3b82f6', '#a855f7', '#f97316', '#eab308', '#ec4899', '#06b6d4', '#84cc16']; let h = 0; for (let i = 0; i < (s || '').length; i++) h = ((h << 5) - h) + s.charCodeAt(i); return palette[Math.abs(h) % palette.length]; } function api(path, opts = {}) { return fetch(path, { credentials: 'include', ...opts }).then(async (r) => { if (r.status === 401) { window.location.href = '/login.html?next=' + encodeURIComponent(window.location.pathname); return null; } if (!r.ok) throw new Error(`${r.status} ${r.statusText}`); return r.json(); }); } function Inbox({ goto }) { const [threads, setThreads] = useState(null); const [activeId, setActiveId] = useState(null); const [filter, setFilter] = useState('all'); const [activeMessages, setActiveMessages] = useState([]); const [composer, setComposer] = useState(''); const [sending, setSending] = useState(false); const [sendError, setSendError] = useState(null); const [error, setError] = useState(null); const reloadThreads = () => api('/api/messages/threads?limit=100').then((d) => { if (!d) return; setThreads(d.items || []); if ((d.items || []).length > 0 && !activeId) setActiveId(d.items[0].id); }); useEffect(() => { reloadThreads().catch((e) => setError(e.message)); }, []); const reloadActiveMessages = (threadKey) => { if (!threadKey) { setActiveMessages([]); return Promise.resolve(); } return api(`/api/messages?thread_id=${encodeURIComponent(threadKey)}&limit=200`) .then((d) => d && setActiveMessages(d.items || [])) .catch(() => setActiveMessages([])); }; useEffect(() => { if (!activeId || !threads) return; const active = threads.find((t) => t.id === activeId); if (!active || !active.thread_id) { setActiveMessages([]); return; } reloadActiveMessages(active.thread_id); }, [activeId, threads]); const handleSend = async () => { const active = threads?.find((t) => t.id === activeId); if (!active || !active.thread_id || !composer.trim()) return; setSending(true); setSendError(null); try { const res = await fetch('/api/messages/send', { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ thread_id: active.thread_id, text: composer.trim() }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail || `${res.status} ${res.statusText}`); } setComposer(''); await reloadActiveMessages(active.thread_id); await reloadThreads(); } catch (e) { setSendError(e.message || 'Send failed'); } finally { setSending(false); } }; const filtered = useMemo(() => { if (!threads) return []; if (filter === 'all') return threads; return threads.filter((t) => CHANNEL_TYPE_TO_KIND[t.channel_type] === filter); }, [threads, filter]); const active = threads ? threads.find((t) => t.id === activeId) : null; const activeKind = active ? CHANNEL_TYPE_TO_KIND[active.channel_type] || 'email' : null; return (