// screens-chat.jsx — chat list + thread (branchés sur l'API) // === Helpers === function formatRelativeTime(iso, lang) { if (!iso) return ''; const d = new Date(iso.replace(' ', 'T') + 'Z'); const now = new Date(); const diff = Math.floor((now - d) / 1000); if (diff < 60) return lang === 'ka' ? 'ახლა' : 'maint.'; if (diff < 3600) return Math.floor(diff / 60) + (lang === 'ka' ? ' წ' : ' min'); if (diff < 86400) return Math.floor(diff / 3600) + 'h'; if (diff < 86400 * 7) return Math.floor(diff / 86400) + 'j'; return d.toLocaleDateString(lang === 'ka' ? 'ka-GE' : 'fr-FR'); } function formatTimeShort(iso) { if (!iso) return ''; const d = new Date(iso.replace(' ', 'T') + 'Z'); return d.getHours() + ':' + String(d.getMinutes()).padStart(2, '0'); } // === ChatListScreen === function ChatListScreen({ lang, t, onOpenThread }) { const [conversations, setConversations] = React.useState(null); const [error, setError] = React.useState(null); const [search, setSearch] = React.useState(''); const load = React.useCallback(async () => { if (!window.API_AVAILABLE) { setConversations([]); return; } try { const list = await window.TrustaAPI.listConversations(); setConversations(list); } catch (e) { console.error(e); setError(window.errorMessage(e, lang)); } }, [lang]); React.useEffect(() => { load(); }, [load]); // Refresh quand on revient sur l'écran (toutes les 15s) React.useEffect(() => { const id = setInterval(load, 15000); return () => clearInterval(id); }, [load]); const filtered = (conversations || []).filter(c => !search.trim() || (c.peer_name || '').toLowerCase().includes(search.toLowerCase()) ); return (
setSearch(e.target.value)} placeholder={t('search_placeholder')} style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', color: '#fff', fontFamily: 'Inter', fontSize: 13, }} />
{conversations === null ? (
{lang === 'ka' ? 'იტვირთება...' : 'Chargement...'}
) : error ? (
{error}
) : filtered.length === 0 ? ( ) : filtered.map(c => ( onOpenThread(c)} /> ))}
); } function EmptyChats({ lang }) { return (
💬
{lang === 'ka' ? 'ჯერ ჩატები არ გაქვს' : 'Pas encore de conversation'}
{lang === 'ka' ? 'დაიწყე საუბარი განცხადებიდან "შეტყობინება" ღილაკით' : 'Démarre une conversation depuis une annonce avec "Contacter"'}
); } function ConvItem({ conv, lang, onOpen }) { const me = window.TrustaAuth.getUser(); const myId = me ? me.id : 0; const lastFromMe = conv.last_sender_id === myId; const preview = conv.last_message_body || (lang === 'ka' ? 'საუბარი დაიწყო' : 'Conversation démarrée'); const photoSrc = conv.peer_photo || null; return ( ); } // === ChatThreadScreen === function ChatThreadScreen({ peer, lang, t, onBack, onOpenProfile }) { // peer = conversation object (from list) OR { peer_id, peer_name, peer_photo, peer_verified, listing_id } const [conv, setConv] = React.useState(peer && peer.id ? peer : null); const [messages, setMessages] = React.useState([]); const [draft, setDraft] = React.useState(''); const [sending, setSending] = React.useState(false); const [error, setError] = React.useState(null); const scrollRef = React.useRef(null); const fileRef = React.useRef(null); // Ouverture / création de la conversation si nécessaire React.useEffect(() => { if (conv) return; if (!peer || !peer.peer_id || !window.API_AVAILABLE) return; (async () => { try { const c = await window.TrustaAPI.openConversation(peer.peer_id, peer.listing_id || null); setConv(c); } catch (e) { setError(window.errorMessage(e, lang)); } })(); }, [conv, peer, lang]); // Polling des messages toutes les 5s const lastIdRef = React.useRef(0); const fetchMessages = React.useCallback(async () => { if (!conv || !window.API_AVAILABLE) return; try { const list = await window.TrustaAPI.listMessages(conv.id, lastIdRef.current); if (list.length > 0) { setMessages(prev => { const existing = new Set(prev.map(m => m.id)); const merged = [...prev, ...list.filter(m => !existing.has(m.id))]; if (merged.length) lastIdRef.current = Math.max(...merged.map(m => m.id)); return merged; }); } } catch (e) { console.error(e); } }, [conv]); React.useEffect(() => { if (!conv) return; lastIdRef.current = 0; setMessages([]); fetchMessages(); const id = setInterval(fetchMessages, 5000); return () => clearInterval(id); }, [conv, fetchMessages]); React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages]); const send = async () => { const text = draft.trim(); if (!text || sending || !conv) return; setSending(true); setError(null); try { const msg = await window.TrustaAPI.sendMessage(conv.id, text); setMessages(prev => [...prev, msg]); lastIdRef.current = Math.max(lastIdRef.current, msg.id); setDraft(''); } catch (e) { setError(window.errorMessage(e, lang)); } finally { setSending(false); } }; const sendPhoto = async (file) => { if (!conv || !file || sending) return; setSending(true); setError(null); try { const up = await window.TrustaAPI.uploadListingPhoto(file); const msg = await window.TrustaAPI.sendMessage(conv.id, '', up.url); setMessages(prev => [...prev, msg]); lastIdRef.current = Math.max(lastIdRef.current, msg.id); } catch (e) { setError(window.errorMessage(e, lang)); } finally { setSending(false); if (fileRef.current) fileRef.current.value = ''; } }; // Infos peer pour le header const me = window.TrustaAuth.getUser(); const myId = me ? me.id : 0; const peerName = (conv && conv.peer_name) || peer?.peer_name || (lang === 'ka' ? 'მომხმარებელი' : 'Utilisateur'); const peerPhoto = (conv && conv.peer_photo) || peer?.peer_photo || null; const peerVerified = (conv && conv.peer_verified) || peer?.peer_verified || false; return (
onOpenProfile && onOpenProfile({ id: conv?.peer_id || peer?.peer_id, name: { fr: peerName, ka: peerName }, avatar: peerPhoto, verified: peerVerified })} style={{ display: 'flex', gap: 10, alignItems: 'center', background: 'transparent', border: 'none', cursor: 'pointer', padding: 0 }}>
{peerName}
} /> {/* Trust banner */}
{t('protected_chat')}
{messages.map((m) => { const me = m.sender_id === myId; return (
{m.photo_url && ( )} {m.body && (
{m.body}
)}
{formatTimeShort(m.created_at)}
); })}
{error && (
{error}
)} {/* Composer */}
e.target.files && e.target.files[0] && sendPhoto(e.target.files[0])} />
setDraft(e.target.value)} onKeyDown={e => e.key === 'Enter' && send()} placeholder={t('type_message')} disabled={!conv || sending} style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', color: '#fff', fontFamily: 'Inter', fontSize: 14, }} />
); } window.ChatListScreen = ChatListScreen; window.ChatThreadScreen = ChatThreadScreen;