Unicité référence client + détection doublon email
- generateRefNumber() basé sur timestamp : références jamais identiques - checkExistingContact() : lecture HubSpot via clé de service (read-only) - Si email déjà connu : affiche message 'déjà client' + référence existante, AUCUNE soumission envoyée (référence existante jamais modifiée) - Notification interne Formspree si tentative double inscription - Traductions FR/EN/MG pour les nouveaux messages
This commit is contained in:
parent
64f5401112
commit
fa4b49e1b8
14
contact.html
14
contact.html
@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
<div class="form-success" id="formSuccess" role="alert">
|
<div class="form-success" id="formSuccess" role="alert">
|
||||||
<i class="fa-solid fa-circle-check" style="font-size: 2rem; color: var(--green); display: block; margin-bottom: 12px;"></i>
|
<i class="fa-solid fa-circle-check" style="font-size: 2rem; color: var(--green); display: block; margin-bottom: 12px;"></i>
|
||||||
<strong data-i18n="contact.successTitle">Inscription envoyée !</strong><br>
|
<strong data-i18n="contact.successTitle">Inscription réussie !</strong><br>
|
||||||
<span data-i18n="contact.successMsg">Merci ! Votre inscription a bien été enregistrée.</span>
|
<span data-i18n="contact.successMsg">Merci ! Votre inscription a bien été enregistrée.</span>
|
||||||
<div id="refNumberBlock" style="margin-top: 16px; padding: 14px 20px; background: var(--navy); border-radius: 8px; border: 2px solid var(--gold);">
|
<div id="refNumberBlock" style="margin-top: 16px; padding: 14px 20px; background: var(--navy); border-radius: 8px; border: 2px solid var(--gold);">
|
||||||
<p style="margin:0 0 4px; font-size:0.85rem; color: var(--gold); font-weight:600;" data-i18n="contact.refLabel">VOTRE NUMÉRO DE RÉFÉRENCE CLIENT</p>
|
<p style="margin:0 0 4px; font-size:0.85rem; color: var(--gold); font-weight:600;" data-i18n="contact.refLabel">VOTRE NUMÉRO DE RÉFÉRENCE CLIENT</p>
|
||||||
@ -85,6 +85,18 @@
|
|||||||
<p style="margin-top:12px; font-size:0.9rem; color: var(--text-light);" data-i18n="contact.successNote">Conservez ce numéro — il vous sera demandé lors de chaque envoi.</p>
|
<p style="margin-top:12px; font-size:0.9rem; color: var(--text-light);" data-i18n="contact.successNote">Conservez ce numéro — il vous sera demandé lors de chaque envoi.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DÉJÀ INSCRIT -->
|
||||||
|
<div class="form-success" id="alreadyRegistered" role="alert" style="border-color: var(--gold); background: rgba(197,165,90,0.08);">
|
||||||
|
<i class="fa-solid fa-circle-info" style="font-size: 2rem; color: var(--gold); display: block; margin-bottom: 12px;"></i>
|
||||||
|
<strong data-i18n="contact.alreadyTitle">Vous êtes déjà client !</strong><br>
|
||||||
|
<span data-i18n="contact.alreadyMsg">Votre adresse email est déjà enregistrée dans notre système.</span>
|
||||||
|
<div id="existingRefBlock" style="margin-top: 16px; padding: 14px 20px; background: var(--navy); border-radius: 8px; border: 2px solid var(--gold);">
|
||||||
|
<p style="margin:0 0 4px; font-size:0.85rem; color: var(--gold); font-weight:600;" data-i18n="contact.alreadyRefLabel">VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT</p>
|
||||||
|
<p id="existingRefDisplay" style="margin:0; font-size:1.6rem; font-weight:700; color:#fff; letter-spacing:2px;"></p>
|
||||||
|
</div>
|
||||||
|
<p style="margin-top:12px; font-size:0.9rem; color: var(--text-light);" data-i18n="contact.alreadyContact">Pour toute question, contactez-nous directement.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form id="contactForm" novalidate>
|
<form id="contactForm" novalidate>
|
||||||
<div class="grid-2" style="gap: 16px;">
|
<div class="grid-2" style="gap: 16px;">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
const HUBSPOT_PORTAL_ID = '148163754';
|
const HUBSPOT_PORTAL_ID = '148163754';
|
||||||
const HUBSPOT_FORM_GUID = '1d9b75c9-8b60-4966-aa18-4bf503452e9a';
|
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';
|
const FORMSPREE_ID = 'mojrvokp';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
@ -14,10 +15,44 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (form) setupContactForm(form);
|
if (form) setupContactForm(form);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Génération basée sur le timestamp : impossible d'avoir deux fois le même numéro
|
||||||
function generateRefNumber() {
|
function generateRefNumber() {
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
const rand = Math.floor(1000 + Math.random() * 9000); // 4 digits
|
// Timestamp milliseconde → 6 derniers chiffres, toujours entre 100000 et 999999
|
||||||
return `MVA-${year}-${rand}`;
|
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) {
|
function setupContactForm(form) {
|
||||||
@ -27,13 +62,27 @@ function setupContactForm(form) {
|
|||||||
|
|
||||||
setLoading(true);
|
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 refNumber = generateRefNumber();
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
firstname: form.firstname.value.trim(),
|
firstname: form.firstname.value.trim(),
|
||||||
lastname: form.lastname.value.trim(),
|
lastname: form.lastname.value.trim(),
|
||||||
phone: form.phone.value.trim(),
|
phone: form.phone.value.trim(),
|
||||||
email: form.email.value.trim(),
|
email: email,
|
||||||
address: form.address.value.trim(),
|
address: form.address.value.trim(),
|
||||||
reference_client: refNumber,
|
reference_client: refNumber,
|
||||||
};
|
};
|
||||||
@ -56,6 +105,7 @@ function setupContactForm(form) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── SOUMISSION HUBSPOT ────────────────────────────────────────────────────────
|
||||||
async function submitToHubSpot(data) {
|
async function submitToHubSpot(data) {
|
||||||
const payload = {
|
const payload = {
|
||||||
fields: [
|
fields: [
|
||||||
@ -85,6 +135,7 @@ async function submitToHubSpot(data) {
|
|||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── SOUMISSION FORMSPREE (email de backup) ────────────────────────────────────
|
||||||
async function submitToFormspree(data) {
|
async function submitToFormspree(data) {
|
||||||
if (FORMSPREE_ID === 'YOUR_FORMSPREE_ID') return;
|
if (FORMSPREE_ID === 'YOUR_FORMSPREE_ID') return;
|
||||||
|
|
||||||
@ -105,6 +156,22 @@ async function submitToFormspree(data) {
|
|||||||
return res.json();
|
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) {
|
function validateForm(form) {
|
||||||
let valid = true;
|
let valid = true;
|
||||||
const lang = localStorage.getItem('mva-lang') || 'fr';
|
const lang = localStorage.getItem('mva-lang') || 'fr';
|
||||||
@ -153,6 +220,7 @@ function isValidPhone(phone) {
|
|||||||
return /^[+\d][\d\s\-().]{6,20}$/.test(phone);
|
return /^[+\d][\d\s\-().]{6,20}$/.test(phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── AFFICHAGE ─────────────────────────────────────────────────────────────────
|
||||||
function showFieldError(name, msg) {
|
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}"]`);
|
const input = document.getElementById(name) || document.querySelector(`[name="${name}"]`);
|
||||||
@ -196,6 +264,33 @@ function showSuccess(refNumber) {
|
|||||||
if (form) form.style.display = 'none';
|
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() {
|
function showError() {
|
||||||
const errEl = document.getElementById('formErrorGlobal');
|
const errEl = document.getElementById('formErrorGlobal');
|
||||||
const lang = localStorage.getItem('mva-lang') || 'fr';
|
const lang = localStorage.getItem('mva-lang') || 'fr';
|
||||||
|
|||||||
@ -114,6 +114,11 @@ const translations = {
|
|||||||
successMsg: "Merci ! Votre inscription a bien été enregistrée.",
|
successMsg: "Merci ! Votre inscription a bien été enregistrée.",
|
||||||
refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT",
|
refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT",
|
||||||
successNote: "Conservez ce numéro — il vous sera demandé lors de chaque envoi.",
|
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.",
|
errorMsg: "Une erreur est survenue. Veuillez réessayer ou nous contacter directement.",
|
||||||
required: "Ce champ est obligatoire",
|
required: "Ce champ est obligatoire",
|
||||||
invalidEmail: "Adresse email invalide",
|
invalidEmail: "Adresse email invalide",
|
||||||
@ -320,6 +325,11 @@ const translations = {
|
|||||||
successMsg: "Thank you! Your registration has been recorded.",
|
successMsg: "Thank you! Your registration has been recorded.",
|
||||||
refLabel: "YOUR CLIENT REFERENCE NUMBER",
|
refLabel: "YOUR CLIENT REFERENCE NUMBER",
|
||||||
successNote: "Keep this number — you will need it for every shipment.",
|
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.",
|
errorMsg: "An error occurred. Please try again or contact us directly.",
|
||||||
required: "This field is required",
|
required: "This field is required",
|
||||||
invalidEmail: "Invalid email address",
|
invalidEmail: "Invalid email address",
|
||||||
@ -526,6 +536,11 @@ const translations = {
|
|||||||
successMsg: "Misaotra! Voaray tsara ny fisoratana anarana.",
|
successMsg: "Misaotra! Voaray tsara ny fisoratana anarana.",
|
||||||
refLabel: "NY LAHARANAO MPANJIFA",
|
refLabel: "NY LAHARANAO MPANJIFA",
|
||||||
successNote: "Tehirizo ity laharana ity — ilaina amin'ny fandefasana tsirairay.",
|
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.",
|
errorMsg: "Nisy olana nitranga. Andramo indray na mifandraisa mivantana aminay.",
|
||||||
required: "Tsy maintsy fenoina ity saha ity",
|
required: "Tsy maintsy fenoina ity saha ity",
|
||||||
invalidEmail: "Tsy mety ny adiresy mailaka",
|
invalidEmail: "Tsy mety ny adiresy mailaka",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user