diff --git a/js/form-handler.js b/js/form-handler.js
index 2093ae0..92d5200 100644
--- a/js/form-handler.js
+++ b/js/form-handler.js
@@ -6,7 +6,8 @@
const HUBSPOT_PORTAL_ID = '148163754';
const HUBSPOT_FORM_GUID = '1d9b75c9-8b60-4966-aa18-4bf503452e9a';
-// Create a free account at formspree.io and replace this ID to enable email backup
+// Clé de service HubSpot — lecture seule des contacts (crm.objects.contacts.read)
+const HUBSPOT_SERVICE_KEY = 'pat-eu1-e3c92146-bb17-45fe-8d77-0c665fc4df3b';
const FORMSPREE_ID = 'mojrvokp';
document.addEventListener('DOMContentLoaded', () => {
@@ -14,10 +15,44 @@ document.addEventListener('DOMContentLoaded', () => {
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();
- const rand = Math.floor(1000 + Math.random() * 9000); // 4 digits
- return `MVA-${year}-${rand}`;
+ // 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 (lecture seule — ne modifie rien)
+async function checkExistingContact(email) {
+ try {
+ const res = await fetch(
+ 'https://api.hubapi.com/crm/v3/objects/contacts/search',
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${HUBSPOT_SERVICE_KEY}`
+ },
+ body: JSON.stringify({
+ filterGroups: [{
+ filters: [{
+ propertyName: 'email',
+ operator: 'EQ',
+ value: email
+ }]
+ }],
+ properties: ['firstname', 'lastname', 'email', 'reference_client']
+ })
+ }
+ );
+ if (!res.ok) return null;
+ const data = await res.json();
+ return data.total > 0 ? data.results[0].properties : null;
+ } catch {
+ // En cas d'erreur réseau : on laisse passer (ne pas bloquer l'inscription)
+ return null;
+ }
}
function setupContactForm(form) {
@@ -27,14 +62,28 @@ function setupContactForm(form) {
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: form.email.value.trim(),
- address: form.address.value.trim(),
+ lastname: form.lastname.value.trim(),
+ phone: form.phone.value.trim(),
+ email: email,
+ address: form.address.value.trim(),
reference_client: refNumber,
};
@@ -43,7 +92,7 @@ function setupContactForm(form) {
submitToFormspree(data),
]);
- const hubspotOk = results[0].status === 'fulfilled';
+ const hubspotOk = results[0].status === 'fulfilled';
const formspreeOk = results[1].status === 'fulfilled';
setLoading(false);
@@ -56,18 +105,19 @@ function setupContactForm(form) {
});
}
+// ── 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 },
+ { 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,
+ pageUri: window.location.href,
pageName: document.title,
},
};
@@ -85,6 +135,7 @@ async function submitToHubSpot(data) {
return res.json();
}
+// ── SOUMISSION FORMSPREE (email de backup) ────────────────────────────────────
async function submitToFormspree(data) {
if (FORMSPREE_ID === 'YOUR_FORMSPREE_ID') return;
@@ -92,12 +143,12 @@ async function submitToFormspree(data) {
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,
+ nom: data.lastname,
+ prenom: data.firstname,
+ telephone: data.phone,
+ email: data.email,
adresse_livraison: data.address,
- reference_client: data.reference_client,
+ reference_client: data.reference_client,
}),
});
@@ -105,12 +156,28 @@ async function submitToFormspree(data) {
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 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';
@@ -153,40 +220,41 @@ function isValidPhone(phone) {
return /^[+\d][\d\s\-().]{6,20}$/.test(phone);
}
+// ── AFFICHAGE ─────────────────────────────────────────────────────────────────
function showFieldError(name, msg) {
- const el = document.getElementById(`error-${name}`);
+ const el = document.getElementById(`error-${name}`);
const input = document.getElementById(name) || document.querySelector(`[name="${name}"]`);
- if (el) el.textContent = msg;
+ if (el) el.textContent = msg;
if (input) input.classList.add('error');
}
function clearError(name) {
- const el = document.getElementById(`error-${name}`);
+ const el = document.getElementById(`error-${name}`);
const input = document.getElementById(name) || document.querySelector(`[name="${name}"]`);
- if (el) el.textContent = '';
+ if (el) el.textContent = '';
if (input) input.classList.remove('error');
}
function setLoading(isLoading) {
- const btn = document.getElementById('submitBtn');
- const txt = document.getElementById('submitText');
+ 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 || {};
+ 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");
+ : (t.submitBtn || "S'inscrire");
}
form?.classList.toggle('form-loading', isLoading);
}
function showSuccess(refNumber) {
- const successEl = document.getElementById('formSuccess');
- const form = document.getElementById('contactForm');
+ const successEl = document.getElementById('formSuccess');
+ const form = document.getElementById('contactForm');
const refDisplay = document.getElementById('refNumberDisplay');
if (refDisplay && refNumber) refDisplay.textContent = refNumber;
if (successEl) {
@@ -196,12 +264,39 @@ function showSuccess(refNumber) {
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 || {};
+ 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.';
+ errEl.textContent = t.errorMsg || 'Une erreur est survenue. Veuillez réessayer ou nous contacter directement.';
}
}
diff --git a/js/translations.js b/js/translations.js
index 9595744..92b5f1a 100644
--- a/js/translations.js
+++ b/js/translations.js
@@ -114,6 +114,11 @@ const translations = {
successMsg: "Merci ! Votre inscription a bien été enregistrée.",
refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT",
successNote: "Conservez ce numéro — il vous sera demandé lors de chaque envoi.",
+ alreadyTitle: "Vous êtes déjà client !",
+ alreadyMsg: "Votre adresse email est déjà enregistrée dans notre système.",
+ alreadyRefLabel: "VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT",
+ alreadyRefUnknown: "Contactez-nous pour retrouver votre référence.",
+ alreadyContact: "Pour toute question, contactez-nous directement.",
errorMsg: "Une erreur est survenue. Veuillez réessayer ou nous contacter directement.",
required: "Ce champ est obligatoire",
invalidEmail: "Adresse email invalide",
@@ -320,6 +325,11 @@ const translations = {
successMsg: "Thank you! Your registration has been recorded.",
refLabel: "YOUR CLIENT REFERENCE NUMBER",
successNote: "Keep this number — you will need it for every shipment.",
+ alreadyTitle: "You are already a client!",
+ alreadyMsg: "Your email address is already registered in our system.",
+ alreadyRefLabel: "YOUR EXISTING REFERENCE NUMBER",
+ alreadyRefUnknown: "Contact us to retrieve your reference number.",
+ alreadyContact: "For any questions, please contact us directly.",
errorMsg: "An error occurred. Please try again or contact us directly.",
required: "This field is required",
invalidEmail: "Invalid email address",
@@ -526,6 +536,11 @@ const translations = {
successMsg: "Misaotra! Voaray tsara ny fisoratana anarana.",
refLabel: "NY LAHARANAO MPANJIFA",
successNote: "Tehirizo ity laharana ity — ilaina amin'ny fandefasana tsirairay.",
+ alreadyTitle: "Efa mpanjifa ianao!",
+ alreadyMsg: "Efa voasoratra ao amin'ny rafitra ny adiresy mailaka.",
+ alreadyRefLabel: "NY LAHARANAO EFA MISY",
+ alreadyRefUnknown: "Mifandraisa aminay hahazoana ny laharanao.",
+ alreadyContact: "Ho an'ny fanontaniana rehetra, mifandraisa aminay mivantana.",
errorMsg: "Nisy olana nitranga. Andramo indray na mifandraisa mivantana aminay.",
required: "Tsy maintsy fenoina ity saha ity",
invalidEmail: "Tsy mety ny adiresy mailaka",