// ECO-PANNEAU.FR - __react/socle/_socle_messagerie_public.jsx const { useState, useEffect, useRef } = React; // 1. - COMPOSANT PRINCIPAL : FORMULAIRE DE PREMIER CONTACT (OTP) const BasePublicContactForm = ({ panneauId, showToast, onSuccess, isPreview, hideAttachments = false, settings = {}, themeColor = '#059669' }) => { // SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation du Hook Global Zéro-Dette const { isMounted, safeFetch } = window.pano_useSafeFetch(); // 1.1 - Gestion des modales et boîtes de dialogue via l'URL const { activeDialog: routerActiveDialog, openDialog: routerOpenDialog, closeCurrentLayer: routerCloseLayer } = window.pano_useUrlModal(); // 1.2 - Récupération dynamique des composants et icônes const { ArrowUpIcon, LoaderIcon, MessageSquareIcon, FileTextIcon, ImageIcon, PaperclipIcon, MailIcon, XIcon } = window.pano_getIcons(); const { Button, ModerationModal, Modal } = window.pano_getComponents(); // 1.3 - États locaux const [submitting, setSubmitting] = useState(false); const [uploadProgress, setUploadProgress] = useState(''); // Honeypot : Décalage de 10s pour ne pas bloquer l'auto-remplissage rapide du navigateur client const [mountTime] = useState(Math.floor(Date.now() / 1000) - 10); const [email, setEmail] = useState(''); const [hpText, setHpText] = useState(''); const [message, setMessage] = useState(''); const [selectedFiles, setSelectedFiles] = useState([]); const [isDragging, setIsDragging] = useState(false); const [dragCounter, setDragCounter] = useState(0); const textareaRef = useRef(null); // États de vérification OTP const [verificationState, setVerificationState] = useState('idle'); // idle, requesting, verifying, verifying_alert const [verifySession, setVerifySession] = useState(''); const [verifyCode, setVerifyCode] = useState(''); const [targetEmail, setTargetEmail] = useState(''); // États d'alertes gérés par l'URL const [moderationWarning, setModerationWarning] = useState(null); // 2. - CYCLE DE VIE ET EFFETS useEffect(() => { if (routerActiveDialog !== 'verify_email') setVerificationState('idle'); if (routerActiveDialog !== 'moderation_warning') setModerationWarning(null); }, [routerActiveDialog]); // Redimensionnement automatique de la zone de saisie useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = ''; const scrollH = textareaRef.current.scrollHeight; const clientH = textareaRef.current.clientHeight; if (scrollH > clientH) { textareaRef.current.style.height = `${scrollH}px`; } } }, [message]); // 3. - GESTION DES FICHIERS JOINTS const addFiles = (fileList) => { const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']; const files = Array.from(fileList).filter(f => validTypes.includes(f.type)); if (files.length === 0) { if (showToast) showToast("Format de fichier non supporté. Veuillez utiliser un PDF ou une image.", "error"); return; } // Limitation stricte à 1 fichier dans la messagerie (Sécurité Zéro-Trust) if (files.length > 1 || selectedFiles.length >= 1) { if (showToast) showToast("Un seul fichier autorisé par message.", "error"); return; } const file = files[0]; if (file.size > 5 * 1024 * 1024) { if (showToast) showToast("Le fichier dépasse la limite de 5 Mo.", "error"); return; } setSelectedFiles([file]); }; const handleFileChange = (e) => { addFiles(e.target.files); e.target.value = null; }; const handlePaste = (e) => { if (!hideAttachments && e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) addFiles(e.clipboardData.files); }; const handleDragEnter = (e) => { if (hideAttachments) return; e.preventDefault(); e.stopPropagation(); setDragCounter(prev => prev + 1); setIsDragging(true); }; const handleDragLeave = (e) => { if (hideAttachments) return; e.preventDefault(); e.stopPropagation(); setDragCounter(prev => { const nc = prev - 1; if (nc === 0) setIsDragging(false); return nc; }); }; const handleDragOver = (e) => { if (hideAttachments) return; e.preventDefault(); e.stopPropagation(); }; const handleDrop = (e) => { if (hideAttachments) return; e.preventDefault(); e.stopPropagation(); setDragCounter(0); setIsDragging(false); if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) addFiles(e.dataTransfer.files); }; const removeFile = (index) => { const newFiles = [...selectedFiles]; newFiles.splice(index, 1); setSelectedFiles(newFiles); }; // 4. - SÉCURITÉ ET SOUMISSION DU MESSAGE const executeSubmit = async (authorEmail, existingToken, session, code, isPendingControl = false) => { setSubmitting(true); setUploadProgress(''); let finalDetail = message.trim(); if (!hideAttachments) { for (let file of selectedFiles) { if (!isMounted.current) return; // SÉCURITÉ const fileType = file.type.includes('pdf') ? 'pdf' : 'image'; try { const fileId = await window.pano_uploadFile( file, fileType, (msg, percent) => { if (!isMounted.current) return; // SÉCURITÉ if (percent < 100) setUploadProgress(`${percent}%`); else setUploadProgress('Finalisation...'); }, true, false, null, panneauId ); if (!isMounted.current) return; // SÉCURITÉ const actualId = fileId.id || fileId; const actualNumPages = fileId.numPages || 1; finalDetail += `\n[ATTACHMENT:${actualId}:${fileType}:${actualNumPages}]`; } catch(err) { if (!isMounted.current) return; // SÉCURITÉ if (showToast) showToast("Erreur lors de l'envoi de la pièce jointe : " + err.message, 'error'); setSubmitting(false); setUploadProgress(''); return; } } } if (!isMounted.current) return; // SÉCURITÉ // CORRECTION : Déclaration explicite du destinataire pour forcer la notification du serveur const isPublic = panneauId === 'CONTACT_PUBLIC'; const payload = { panneauId, detail: finalDetail, author: authorEmail, authorType: isPublic ? 'Public' : 'Riverain', targetEmail: isPublic ? 'Admin' : 'Client', type: isPublic ? 'support' : 'message', isAlert: isPendingControl ? 2 : 0, hp_text: hpText, hp_time: mountTime, verification_token: existingToken, verify_session: session, verify_code: code }; const d = await safeFetch('interactions', { body: payload, setLoading: setSubmitting, successMessage: "Votre message a été envoyé avec succès.", errorMessage: "Erreur lors de l'envoi" }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { if (d.data && d.data.verification_token) { localStorage.setItem('pano_riverain_token_' + authorEmail, d.data.verification_token); } setMessage(''); setSelectedFiles([]); setVerificationState('idle'); setVerifyCode(''); setUploadProgress(''); if (onSuccess) onSuccess(); } else { if (existingToken) { localStorage.removeItem('pano_riverain_token_' + authorEmail); } setVerificationState('idle'); setUploadProgress(''); } }; const startVerificationFlow = (isPendingControl = false) => { if (!email.trim()) { if (showToast) showToast("Veuillez saisir une adresse e-mail.", "error"); return; } setTargetEmail(email.trim()); const existingToken = localStorage.getItem('pano_riverain_token_' + email.trim()); if (existingToken) { executeSubmit(email.trim(), existingToken, null, null, isPendingControl); return; } setVerificationState('requesting'); safeFetch('interactions/verify_request', { body: { email: email.trim() }, setLoading: setSubmitting, successMessage: "Un code a été envoyé à votre adresse e-mail.", errorMessage: "Veuillez patienter avant de demander un nouveau code." }).then(d => { if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d && d.data && d.data.verify_session) { setVerifySession(d.data.verify_session); setVerificationState(isPendingControl ? 'verifying_alert' : 'verifying'); routerOpenDialog('verify_email'); } else { setVerificationState('idle'); } }); }; const handleInitialSubmit = (e) => { if (e && e.preventDefault) e.preventDefault(); if (isPreview) { if (showToast) showToast("Ceci est un aperçu interactif. La messagerie n'est pas fonctionnelle ici.", "info"); return; } if (window.pano_checkForBlacklist) { const word = window.pano_checkForBlacklist(message, settings); if (word) { setModerationWarning(word); routerOpenDialog('moderation_warning'); return; } } else { if (window.pano_logFallback) window.pano_logFallback("Bouclier sémantique introuvable. Modération ignorée sur BasePublicContactForm."); } if (panneauId === 'CONTACT_PUBLIC') { if (!email.trim()) { if (showToast) showToast("Veuillez saisir une adresse e-mail.", "error"); return; } const existingToken = localStorage.getItem('pano_riverain_token_' + email.trim()); executeSubmit(email.trim(), existingToken, null, null, false); } else { startVerificationFlow(false); } }; // 5. - RENDU UI return (
{isDragging && !hideAttachments && (
Relâchez votre fichier ici Image ou PDF (1 max)
)} {routerActiveDialog === 'moderation_warning' && moderationWarning && ModerationModal && ( { routerCloseLayer(); setTimeout(() => { if (panneauId === 'CONTACT_PUBLIC') { const existingToken = localStorage.getItem('pano_riverain_token_' + email.trim()); executeSubmit(email.trim(), existingToken, null, null, true); } else { startVerificationFlow(true); } }, 100); }} /> )} {routerActiveDialog === 'verify_email' && verificationState.startsWith('verifying') && Modal && ( ( <> )} > { e.preventDefault(); routerCloseLayer(); setTimeout(() => executeSubmit(targetEmail, null, verifySession, verifyCode, verificationState === 'verifying_alert'), 100); }} className="flex flex-col items-center text-center">

Un code à 6 chiffres a été envoyé à {targetEmail}. Veuillez le saisir pour confirmer que cette adresse vous appartient.

setVerifyCode(e.target.value.replace(/\D/g, '').slice(0, 6))} className="w-full border-2 border-slate-200 rounded-xl p-3 text-center text-3xl tracking-[0.25em] font-black outline-none focus:border-emerald-500 mb-2" placeholder="000000" autoFocus />
)} setHpText(e.target.value)} style={{ position: 'absolute', left: '-9999px' }} tabIndex="-1" autoComplete="new-password" /> setEmail(e.target.value)} required placeholder="Votre e-mail (pour recevoir la réponse)" disabled={verificationState === 'requesting' || submitting} className="w-full border-2 rounded-xl p-3 text-sm outline-none transition shrink-0 border-slate-200 focus:border-slate-800 disabled:opacity-50 min-w-0" /> {hideAttachments && selectedFiles.length > 0 && (
{selectedFiles.map((f, i) => (
{f.type.includes('pdf') ? : } {f.name}
))}
)} {!hideAttachments && selectedFiles.length > 0 && (
{selectedFiles.map((f, i) => (
{f.type.includes('pdf') ? : } {f.name}
))}
)}
{!hideAttachments && ( )}
); }; window.pano_PublicContactForm = (props) => { return (
); }; /* EOF ========== [__react/socle/_socle_messagerie_public.jsx] */