/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Composant : Formulaire de premier contact (Riverains et Public) avec OTP * ========================================================================= */ const { useState, useEffect, useRef } = React; const BasePublicContactForm = ({ panneauId, showToast, onSuccess, isPreview, hideAttachments = false, settings = {}, themeColor = '#059669' }) => { // FACTORISATION : Utilisation du Hook global de gestion des modales par URL const { activeDialog: routerActiveDialog, openDialog: routerOpenDialog, closeCurrentLayer: routerCloseLayer } = window.useUrlModal(); // FACTORISATION : Récupération sécurisée et propre des icônes const { ArrowUpIcon, LoaderIcon, MessageSquareIcon, FileTextIcon, ImageIcon, PaperclipIcon, MailIcon, XIcon } = window.getIcons(); const Button = window.Button || (() => null); const ModerationModal = window.ModerationModal || (() => null); const [submitting, setSubmitting] = useState(false); const [mountTime] = useState(Math.floor(Date.now() / 1000)); const [selectedFiles, setSelectedFiles] = useState([]); const [message, setMessage] = useState(''); const [isDragging, setIsDragging] = useState(false); const [dragCounter, setDragCounter] = useState(0); const textareaRef = useRef(null); // États de vérification const [verificationState, setVerificationState] = useState('idle'); // idle, requesting, verifying, verifying_alert const [verifySession, setVerifySession] = useState(''); const [verifyCode, setVerifyCode] = useState(''); const [targetEmail, setTargetEmail] = useState(''); // États gérés par l'URL const [moderationWarning, setModerationWarning] = useState(null); useEffect(() => { if (routerActiveDialog !== 'verify_email') setVerificationState('idle'); if (routerActiveDialog !== 'moderation_warning') setModerationWarning(null); }, [routerActiveDialog]); 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]); 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) return; if (selectedFiles.length + files.length > 20) { if (showToast) showToast("Maximum 20 fichiers autorisés.", "error"); return; } const validFiles = []; for (let f of files) { if (f.size > 5 * 1024 * 1024) { if (showToast) showToast("Un ou plusieurs fichiers dépassent 5 Mo.", "error"); return; } validFiles.push(f); } setSelectedFiles(prev => [...prev, ...validFiles]); }; 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); }; const executeSubmit = async (authorEmail, existingToken, session, code, isPendingControl = false) => { setSubmitting(true); let finalDetail = message.trim(); if (!hideAttachments) { for (let file of selectedFiles) { const fileType = file.type.includes('pdf') ? 'pdf' : 'image'; try { const fileId = await window.uploadFile(file, fileType, null, true, false, null, panneauId); finalDetail += `\n[ATTACHMENT:${fileId}:${fileType}]`; } catch(err) { if (showToast) showToast("Erreur lors de l'upload : " + err.message, 'error'); setSubmitting(false); return; } } } const payload = { panneauId, detail: finalDetail, author: authorEmail, isAlert: isPendingControl ? 2 : 0, hp_text: document.querySelector('input[name="hp_text"]')?.value || '', hp_time: mountTime, verification_token: existingToken, verify_session: session, verify_code: code }; const d = await window.apiFetch('interactions', { body: payload, setLoading: setSubmitting, errorMessage: "Erreur lors de l'envoi" }); if (d) { if (d.data && d.data.verification_token) { localStorage.setItem('eco_riverain_token_' + authorEmail, d.data.verification_token); } setMessage(''); setSelectedFiles([]); setVerificationState('idle'); setVerifyCode(''); if (onSuccess) onSuccess(); } else { setVerificationState('idle'); } }; const startVerificationFlow = (isPendingControl = false) => { const email = document.querySelector('input[name="email"]')?.value || ''; if (!email) return; setTargetEmail(email); const existingToken = localStorage.getItem('eco_riverain_token_' + email); if (existingToken) { executeSubmit(email, existingToken, null, null, isPendingControl); return; } setVerificationState('requesting'); window.apiFetch('interactions/verify_request', { body: { email }, successMessage: "Un code a été envoyé à votre adresse e-mail.", errorMessage: "Veuillez patienter avant de demander un nouveau code." }).then(d => { 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; } const word = window.checkForBlacklist ? window.checkForBlacklist(message, settings) : null; if (word) { setModerationWarning(word); routerOpenDialog('moderation_warning'); return; } // BYPASS: Si c'est le formulaire public du site, on envoie directement (le Honeypot protège déjà). if (panneauId === 'CONTACT_PUBLIC') { const email = document.querySelector('input[name="email"]')?.value || ''; executeSubmit(email, null, null, null, false); } else { startVerificationFlow(false); } }; return (
); }; window.PublicContactForm = (props) => { return (