// ECO-PANNEAU.FR - _react/riverains/_riverains_contact.jsx const { useState, useEffect } = React; window.pano_RiverainContactTab = ({ panneau, themeColor, interactions, settings, refreshData, showToast, isPreview, handleExternalLink }) => { // 1. - SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation du Hook Global Zéro-Dette const { isMounted, safeFetch } = window.pano_useSafeFetch(); // 2. - Routage et modales const { activeDialog, openDialog, closeCurrentLayer } = window.pano_useUrlModal(); const [contactSuccess, setContactSuccess] = useState(false); const [mountTime] = useState(Math.floor(Date.now() / 1000)); // 3. - Composants et Icônes const { PhoneIcon, MessageSquareIcon, Trash2Icon, MailIcon, InfoIcon } = window.pano_getIcons(); const { Button, PublicContactForm, ChatBox, ConfirmModal } = window.pano_getComponents(); // 4. - Contexte du panneau et messages const isPublicContact = panneau.id === 'CONTACT_PUBLIC'; const isSimulated = isPreview || panneau.id === 'demo-panneau'; const panelInteractions = (interactions || []).filter(i => i.panneauId === panneau.id); // 5. - Actions métier const handleChatSend = async (text) => { if (isSimulated) { openDialog('demo_warning', 'chat'); return; } // ZÉRO-TRUST : On se base uniquement sur l'authorType pour retrouver l'email du Riverain let threadEmail = panelInteractions.find(m => m.authorType !== 'Admin' && m.authorType !== 'Client')?.author || localStorage.getItem('pano_riverain_email') || 'Visiteur anonyme'; if (threadEmail !== 'Visiteur anonyme') { localStorage.setItem('pano_riverain_email', threadEmail); } const token = localStorage.getItem('pano_riverain_token_' + threadEmail); const data = { panneauId: panneau.id, detail: text, author: threadEmail, isAlert: 0, hp_time: mountTime, verification_token: token }; // SÉCURITÉ : Utilisation de safeFetch const d = await safeFetch('interactions', { body: data, errorMessage: "Erreur lors de l'envoi" }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { if (refreshData) refreshData(); } else { // ANTI TOKEN-TRAP : Purge du jeton expiré pour débloquer le riverain en cas d'erreur if (token) { localStorage.removeItem('pano_riverain_token_' + threadEmail); } } }; const confirmDeleteThread = async () => { // ZÉRO-TRUST : Utilisation de authorType let threadEmail = panelInteractions.find(m => m.authorType !== 'Admin' && m.authorType !== 'Client')?.author || localStorage.getItem('pano_riverain_email'); const token = localStorage.getItem('pano_riverain_token_' + threadEmail); if (!token || !threadEmail) { if(showToast) showToast("Vous n'êtes pas authentifié pour effectuer cette action.", "error"); closeCurrentLayer(); return; } const data = { thread_id: panneau.id, email: threadEmail, verification_token: token }; // SÉCURITÉ : Utilisation de safeFetch const d = await safeFetch('interactions/delete_thread', { body: data, successMessage: "Votre conversation a été effacée.", errorMessage: "Action non autorisée." }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { if (refreshData) refreshData(); } else { // ANTI TOKEN-TRAP localStorage.removeItem('pano_riverain_token_' + threadEmail); } closeCurrentLayer(); }; // 6. - Préparation des données d'affichage const fakeDemoInteractions = [ { id: 'fake_1', panneauId: panneau.id, author: 'Riverain (Voisin)', authorType: 'Riverain', target: 'Client', detail: "Bonjour, j'habite la maison voisine. Pouvez-vous me dire quand les travaux de démolition vont commencer ?", created_at: new Date(Date.now() - 172800000).toISOString(), resolved: true, isAlert: 0 }, { id: 'fake_2', panneauId: panneau.id, author: 'Responsable', authorType: 'Client', target: 'Riverain', detail: "Bonjour. La démolition est prévue pour la première semaine du mois prochain. Nous ferons le maximum pour limiter les nuisances sonores.", created_at: new Date(Date.now() - 86400000).toISOString(), resolved: true, isAlert: 0 } ]; const effectiveInteractions = isSimulated ? fakeDemoInteractions : panelInteractions; const chatMessages = effectiveInteractions.map(m => ({ ...m, // SÉCURITÉ : authorType détermine le sens d'affichage author: (m.authorType !== 'Admin' && m.authorType !== 'Client') ? (isSimulated ? m.author : 'Riverain') : m.author })); const isChatActive = effectiveInteractions.length > 0; // 7. - Rendu UI return (
{panneau.emergencyPhone && (

Numéro de contact

{panneau.emergencyPhone}

)}

Échanger avec {isPublicContact ? "le service client" : "le responsable"}

{isChatActive && !isSimulated && (
{isSimulated && (
{ e.preventDefault(); e.stopPropagation(); openDialog('demo_warning', 'chat'); }} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); openDialog('demo_warning', 'chat'); }} onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }} title="Messagerie désactivée" >
)} {effectiveInteractions.length === 0 ? ( contactSuccess ? (

Message envoyé !

Votre demande a été transmise au responsable du chantier en toute confidentialité.

Étape suivante

Un e-mail contenant votre lien d'accès sécurisé vient de vous être envoyé. Veuillez utiliser ce lien pour lire la réponse et poursuivre la conversation.

{Button && ( )}
) : ( { setContactSuccess(true); if (refreshData) refreshData(); }} /> ) ) : (
)}
{activeDialog === 'confirm_delete_thread' && ConfirmModal && ( )}
); }; /* EOF ========== [_react/riverains/_riverains_contact.jsx] */