import React, { useState, useEffect, useCallback, useRef } from 'react' import { createRoot } from 'react-dom/client' import ConversationList from '../../components/messaging/ConversationList' import ConversationThread from '../../components/messaging/ConversationThread' import NewConversationModal from '../../components/messaging/NewConversationModal' // ── helpers ────────────────────────────────────────────────────────────────── function getCsrf() { return document.querySelector('meta[name="csrf-token"]')?.content ?? '' } async function apiFetch(url, options = {}) { const isFormData = options.body instanceof FormData const headers = { 'X-CSRF-TOKEN': getCsrf(), Accept: 'application/json', ...options.headers, } if (!isFormData) { headers['Content-Type'] = 'application/json' } const res = await fetch(url, { headers, ...options, }) if (!res.ok) { const err = await res.json().catch(() => ({})) throw new Error(err.message ?? `HTTP ${res.status}`) } return res.json() } // ── MessagesPage ───────────────────────────────────────────────────────────── function MessagesPage({ userId, username, activeConversationId: initialId }) { const [conversations, setConversations] = useState([]) const [loadingConvs, setLoadingConvs] = useState(true) const [activeId, setActiveId] = useState(initialId ?? null) const [realtimeEnabled, setRealtimeEnabled] = useState(false) const [showNewModal, setShowNewModal] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [searching, setSearching] = useState(false) const pollRef = useRef(null) // ── Load conversations list ──────────────────────────────────────────────── const loadConversations = useCallback(async () => { try { const data = await apiFetch('/api/messages/conversations') setConversations(data.data ?? []) } catch (e) { console.error('Failed to load conversations', e) } finally { setLoadingConvs(false) } }, []) useEffect(() => { loadConversations() apiFetch('/api/messages/settings') .then(data => setRealtimeEnabled(!!data?.realtime_enabled)) .catch(() => setRealtimeEnabled(false)) return () => { if (pollRef.current) clearInterval(pollRef.current) } }, [loadConversations]) useEffect(() => { if (pollRef.current) { clearInterval(pollRef.current) pollRef.current = null } if (realtimeEnabled) { return } pollRef.current = setInterval(loadConversations, 15_000) return () => { if (pollRef.current) clearInterval(pollRef.current) } }, [loadConversations, realtimeEnabled]) const handleSelectConversation = useCallback((id) => { setActiveId(id) history.replaceState(null, '', `/messages/${id}`) }, []) const handleConversationCreated = useCallback((conv) => { setShowNewModal(false) loadConversations() setActiveId(conv.id) history.replaceState(null, '', `/messages/${conv.id}`) }, [loadConversations]) const handleMarkRead = useCallback((conversationId) => { setConversations(prev => prev.map(c => c.id === conversationId ? { ...c, unread_count: 0 } : c) ) }, []) useEffect(() => { let cancelled = false const run = async () => { const q = searchQuery.trim() if (q.length < 2) { setSearchResults([]) setSearching(false) return } setSearching(true) try { const data = await apiFetch(`/api/messages/search?q=${encodeURIComponent(q)}`) if (!cancelled) { setSearchResults(data.data ?? []) } } catch (_) { if (!cancelled) { setSearchResults([]) } } finally { if (!cancelled) { setSearching(false) } } } const timer = setTimeout(run, 250) return () => { cancelled = true clearTimeout(timer) } }, [searchQuery]) const openSearchResult = useCallback((item) => { if (!item?.conversation_id) return setActiveId(item.conversation_id) history.replaceState(null, '', `/messages/${item.conversation_id}?focus=${item.id}`) }, []) const activeConversation = conversations.find(c => c.id === activeId) ?? null return (
{/* ── Left panel: conversation list ─────────────────────────────── */} {/* ── Right panel: thread ───────────────────────────────────────── */}
{activeId ? ( { setActiveId(null); history.replaceState(null, '', '/messages') }} onMarkRead={handleMarkRead} onConversationUpdated={loadConversations} /> ) : (

Select a conversation or start a new one

)}
{showNewModal && ( setShowNewModal(false)} /> )}
) } // ── Mount ──────────────────────────────────────────────────────────────────── const el = document.getElementById('messages-root') if (el) { function parse(key, fallback = null) { try { return JSON.parse(el.dataset[key] ?? 'null') ?? fallback } catch { return fallback } } createRoot(el).render( ) } export default MessagesPage