// ============================================ // MVA Global Fret — Form Handler // HubSpot Portal ID : 148163754 // HubSpot Form GUID : 1d9b75c9-8b60-4966-aa18-4bf503452e9a // ============================================ const HUBSPOT_PORTAL_ID = '148163754'; const HUBSPOT_FORM_GUID = '1d9b75c9-8b60-4966-aa18-4bf503452e9a'; const FORMSPREE_ID = 'mojrvokp'; // ── PROXY CLOUDFLARE WORKER ─────────────────────────────────────────────────── // URL du Worker qui proxifie l'API HubSpot CRM (contourne le CORS). // Après déploiement du Worker (voir cloudflare-worker/hubspot-proxy.js), // remplacer la chaîne vide par l'URL obtenue, ex : // 'https://mva-hubspot-proxy.moncompte.workers.dev' // Tant que cette constante est vide, la vérification doublon est désactivée // (le formulaire s'envoie normalement — aucun blocage). const WORKER_PROXY_URL = 'https://mva-hubspot-proxy.mvaglobalfret.workers.dev'; document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('contactForm'); if (form) setupContactForm(form); }); // Génération basée sur le timestamp : impossible d'avoir deux fois le même numéro function generateRefNumber() { const year = new Date().getFullYear(); // Timestamp milliseconde → 6 derniers chiffres, toujours entre 100000 et 999999 const ts = (Date.now() % 900000) + 100000; return `MVA-${year}-${ts}`; } // Vérifie si l'email existe déjà dans HubSpot via le proxy Cloudflare Worker. // Retourne les propriétés du contact existant, ou null si nouveau client / proxy non configuré. async function checkExistingContact(email) { // Si le proxy n'est pas encore déployé, on laisse passer sans bloquer if (!WORKER_PROXY_URL) return null; try { const res = await fetch(WORKER_PROXY_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email.toLowerCase().trim() }), }); if (!res.ok) return null; const data = await res.json(); return data.total > 0 ? data.results[0].properties : null; } catch { // Erreur réseau ou Worker indisponible : on laisse passer return null; } } function setupContactForm(form) { form.addEventListener('submit', async (e) => { e.preventDefault(); if (!validateForm(form)) return; setLoading(true); const email = form.email.value.trim(); // ── VÉRIFICATION DOUBLON ────────────────────────────────────────────────── // Si le client existe déjà, on affiche uniquement un message informatif. // On ne soumet RIEN à HubSpot — la référence existante n'est JAMAIS modifiée. const existing = await checkExistingContact(email); if (existing) { setLoading(false); showAlreadyRegistered(existing); return; // ← arrêt complet, aucune soumission } // ───────────────────────────────────────────────────────────────────────── // Nouveau client : génération du numéro de référence unique const refNumber = generateRefNumber(); const data = { firstname: form.firstname.value.trim(), lastname: form.lastname.value.trim(), phone: form.phone.value.trim(), email: email, address: form.address.value.trim(), reference_client: refNumber, }; const results = await Promise.allSettled([ submitToHubSpot(data), submitToFormspree(data), ]); const hubspotOk = results[0].status === 'fulfilled'; const formspreeOk = results[1].status === 'fulfilled'; setLoading(false); if (hubspotOk || formspreeOk) { showSuccess(refNumber); } else { showError(); } }); } // ── SOUMISSION HUBSPOT ──────────────────────────────────────────────────────── async function submitToHubSpot(data) { const payload = { fields: [ { name: 'firstname', value: data.firstname }, { name: 'lastname', value: data.lastname }, { name: 'phone', value: data.phone }, { name: 'email', value: data.email }, { name: 'address', value: data.address }, { name: 'reference_client', value: data.reference_client }, ], context: { pageUri: window.location.href, pageName: document.title, }, }; const res = await fetch( `https://api.hsforms.com/submissions/v3/integration/submit/${HUBSPOT_PORTAL_ID}/${HUBSPOT_FORM_GUID}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), } ); if (!res.ok) throw new Error(`HubSpot error: ${res.status}`); return res.json(); } // ── SOUMISSION FORMSPREE (email de backup) ──────────────────────────────────── async function submitToFormspree(data) { if (FORMSPREE_ID === 'YOUR_FORMSPREE_ID') return; const res = await fetch(`https://formspree.io/f/${FORMSPREE_ID}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ nom: data.lastname, prenom: data.firstname, telephone: data.phone, email: data.email, adresse_livraison: data.address, reference_client: data.reference_client, }), }); if (!res.ok) throw new Error(`Formspree error: ${res.status}`); return res.json(); } // ── NOTIFICATION DOUBLON (email interne seulement, sans toucher aux données) ── async function notifyDuplicateViaFormspree(contact) { if (FORMSPREE_ID === 'YOUR_FORMSPREE_ID') return; try { await fetch(`https://formspree.io/f/${FORMSPREE_ID}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ _subject: `[MVA] Tentative double inscription — ${contact.firstname || ''} ${contact.lastname || ''}`, message: `Le client ${contact.firstname || ''} ${contact.lastname || ''} (${contact.email}) a tenté de s'inscrire à nouveau. Référence existante : ${contact.reference_client || 'non définie'}.`, }), }); } catch { /* Ne pas bloquer l'interface si la notification échoue */ } } // ── VALIDATION ──────────────────────────────────────────────────────────────── function validateForm(form) { let valid = true; const lang = localStorage.getItem('mva-lang') || 'fr'; const t = translations?.[lang]?.contact || {}; const requiredMsg = t.required || 'Ce champ est obligatoire'; const invalidEmail = t.invalidEmail || 'Adresse email invalide'; const invalidPhone = t.invalidPhone || 'Numéro de téléphone invalide'; const fields = ['firstname', 'lastname', 'phone', 'email', 'address']; fields.forEach(name => clearError(name)); clearError('cgv'); fields.forEach(name => { const el = form[name]; if (!el.value.trim()) { showFieldError(name, requiredMsg); valid = false; } }); const cgvBox = form['cgv']; if (cgvBox && !cgvBox.checked) { showFieldError('cgv', t.cgvRequired || 'Vous devez accepter les Conditions Générales de Vente.'); valid = false; } if (form.email.value.trim() && !isValidEmail(form.email.value.trim())) { showFieldError('email', invalidEmail); valid = false; } if (form.phone.value.trim() && !isValidPhone(form.phone.value.trim())) { showFieldError('phone', invalidPhone); valid = false; } return valid; } function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } function isValidPhone(phone) { return /^[+\d][\d\s\-().]{6,20}$/.test(phone); } // ── AFFICHAGE ───────────────────────────────────────────────────────────────── function showFieldError(name, msg) { const el = document.getElementById(`error-${name}`); const input = document.getElementById(name) || document.querySelector(`[name="${name}"]`); if (el) el.textContent = msg; if (input) input.classList.add('error'); } function clearError(name) { const el = document.getElementById(`error-${name}`); const input = document.getElementById(name) || document.querySelector(`[name="${name}"]`); if (el) el.textContent = ''; if (input) input.classList.remove('error'); } function setLoading(isLoading) { const btn = document.getElementById('submitBtn'); const txt = document.getElementById('submitText'); const form = document.getElementById('contactForm'); const lang = localStorage.getItem('mva-lang') || 'fr'; const t = translations?.[lang]?.contact || {}; if (!btn) return; btn.disabled = isLoading; if (txt) { txt.textContent = isLoading ? (t.submitLoading || 'Envoi en cours...') : (t.submitBtn || "S'inscrire"); } form?.classList.toggle('form-loading', isLoading); } function showSuccess(refNumber) { const successEl = document.getElementById('formSuccess'); const form = document.getElementById('contactForm'); const refDisplay = document.getElementById('refNumberDisplay'); if (refDisplay && refNumber) refDisplay.textContent = refNumber; if (successEl) { successEl.classList.add('show'); successEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } if (form) form.style.display = 'none'; } // Affiche le message "déjà client" — ne modifie AUCUNE donnée HubSpot function showAlreadyRegistered(contact) { const lang = localStorage.getItem('mva-lang') || 'fr'; const t = translations?.[lang]?.contact || {}; const alreadyEl = document.getElementById('alreadyRegistered'); const form = document.getElementById('contactForm'); const refDisplay = document.getElementById('existingRefDisplay'); // Affiche la référence existante si disponible, sinon message de contact if (refDisplay) { refDisplay.textContent = contact.reference_client ? contact.reference_client : (t.alreadyRefUnknown || 'Contactez-nous pour retrouver votre référence.'); } if (alreadyEl) { alreadyEl.classList.add('show'); alreadyEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } if (form) form.style.display = 'none'; // Envoi d'une notification interne à MVA (sans modifier les données du client) notifyDuplicateViaFormspree(contact); } function showError() { const errEl = document.getElementById('formErrorGlobal'); const lang = localStorage.getItem('mva-lang') || 'fr'; const t = translations?.[lang]?.contact || {}; if (errEl) { errEl.style.display = 'block'; errEl.textContent = t.errorMsg || 'Une erreur est survenue. Veuillez réessayer ou nous contacter directement.'; } }