Merge pull request #1 from MVA-Global-Fret/migration-brevo
Migration brevo
This commit is contained in:
commit
c66ca36620
@ -130,10 +130,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── action: requestVerification ──────────────────────────
|
// ── action: requestVerification ──────────────────────────
|
||||||
// Génère un token unique, le stocke en KV avec les infos du contact,
|
// Génère un token unique, stocke TOUTES les données du formulaire en KV,
|
||||||
// et envoie un email de validation via Resend (avec un lien de
|
// et envoie un email de validation via Brevo. Le contact n'est créé
|
||||||
// confirmation). Le contact ne reçoit la référence et l'adresse
|
// dans HubSpot QU'APRÈS clic sur le lien de confirmation (anti-spam :
|
||||||
// Paris qu'APRÈS avoir cliqué sur le lien.
|
// les inscriptions non vérifiées ne polluent pas le CRM).
|
||||||
// Anti-bot : Turnstile vérifié d'abord.
|
// Anti-bot : Turnstile vérifié d'abord.
|
||||||
if (action === 'requestVerification') {
|
if (action === 'requestVerification') {
|
||||||
if (!body.email) return jsonResponse({ error: 'email requis' }, 400);
|
if (!body.email) return jsonResponse({ error: 'email requis' }, 400);
|
||||||
@ -146,10 +146,12 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const verToken = crypto.randomUUID().replace(/-/g, '');
|
const verToken = crypto.randomUUID().replace(/-/g, '');
|
||||||
const tokenData = {
|
const tokenData = {
|
||||||
firstname : body.firstname || '',
|
firstname : body.firstname || '',
|
||||||
email : body.email.toLowerCase().trim(),
|
lastname : body.lastname || '',
|
||||||
reference_client : body.reference_client || '',
|
phone : body.phone || '',
|
||||||
createdAt : new Date().toISOString(),
|
email : body.email.toLowerCase().trim(),
|
||||||
|
address : body.address || '',
|
||||||
|
createdAt : new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!env.WELCOME_KV) {
|
if (!env.WELCOME_KV) {
|
||||||
@ -169,9 +171,10 @@ export default {
|
|||||||
|
|
||||||
// ── action: verifyToken ──────────────────────────────────
|
// ── action: verifyToken ──────────────────────────────────
|
||||||
// Appelé par confirmation.html quand l'utilisateur clique sur
|
// Appelé par confirmation.html quand l'utilisateur clique sur
|
||||||
// le lien dans l'email de validation. Lit le token en KV,
|
// le lien dans l'email de validation. C'est ICI que le contact
|
||||||
// envoie le welcome (avec ref + adresse Paris) via Resend,
|
// est CRÉÉ dans HubSpot (avec une référence générée à la volée),
|
||||||
// puis supprime le token (one-time use).
|
// puis le welcome email est envoyé (ref + adresse Paris).
|
||||||
|
// Idempotent : un 2ème clic ne re-crée pas de contact.
|
||||||
if (action === 'verifyToken') {
|
if (action === 'verifyToken') {
|
||||||
if (!body.token) return jsonResponse({ error: 'token requis' }, 400);
|
if (!body.token) return jsonResponse({ error: 'token requis' }, 400);
|
||||||
if (!env.WELCOME_KV) return jsonResponse({ ok: false, error: 'KV not bound' }, 500);
|
if (!env.WELCOME_KV) return jsonResponse({ ok: false, error: 'KV not bound' }, 500);
|
||||||
@ -183,17 +186,67 @@ export default {
|
|||||||
}
|
}
|
||||||
const tokenData = JSON.parse(raw);
|
const tokenData = JSON.parse(raw);
|
||||||
|
|
||||||
try {
|
// Idempotence : si déjà consommé, retourne le résultat précédent
|
||||||
await sendWelcomeViaResend(env, tokenData);
|
// sans recréer le contact ni renvoyer d'email.
|
||||||
// Marque le token consommé (gardé 7j pour idempotence en cas de
|
if (tokenData.used) {
|
||||||
// double clic du lien, mais avec flag "used")
|
|
||||||
await env.WELCOME_KV.put(key, JSON.stringify({ ...tokenData, used: true, usedAt: new Date().toISOString() }), {
|
|
||||||
expirationTtl: 60 * 60 * 24 * 7,
|
|
||||||
});
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
ok: true,
|
ok: true,
|
||||||
firstname : tokenData.firstname,
|
firstname : tokenData.firstname,
|
||||||
reference_client : tokenData.reference_client,
|
reference_client : tokenData.reference_client || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1) Génère la prochaine référence client (MVA-XXX)
|
||||||
|
const refNumber = await getNextRef(token);
|
||||||
|
|
||||||
|
// 2) Crée le contact dans HubSpot avec toutes les propriétés
|
||||||
|
const createRes = await fetch(`${HUBSPOT_API}/crm/v3/objects/contacts`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type' : 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
properties: {
|
||||||
|
firstname : tokenData.firstname,
|
||||||
|
lastname : tokenData.lastname,
|
||||||
|
phone : tokenData.phone,
|
||||||
|
email : tokenData.email,
|
||||||
|
address : tokenData.address,
|
||||||
|
reference_client : refNumber,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!createRes.ok) {
|
||||||
|
const errTxt = await createRes.text();
|
||||||
|
// Si l'email existe déjà (409 conflict), on récupère le contact
|
||||||
|
// existant et on lui envoie quand même son welcome (cas très rare :
|
||||||
|
// doublon créé via une autre source pendant la fenêtre de 24h)
|
||||||
|
if (createRes.status === 409) {
|
||||||
|
throw new Error(`Contact existe déjà dans HubSpot (409). Contactez l'admin.`);
|
||||||
|
}
|
||||||
|
throw new Error(`HubSpot create failed ${createRes.status}: ${errTxt}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Envoie le welcome email avec ref + adresse Paris
|
||||||
|
const welcomeContact = { ...tokenData, reference_client: refNumber };
|
||||||
|
await sendWelcomeViaBrevo(env, welcomeContact);
|
||||||
|
|
||||||
|
// 4) Marque le token consommé (gardé 7j pour idempotence)
|
||||||
|
await env.WELCOME_KV.put(key, JSON.stringify({
|
||||||
|
...tokenData,
|
||||||
|
used : true,
|
||||||
|
usedAt : new Date().toISOString(),
|
||||||
|
reference_client : refNumber,
|
||||||
|
}), {
|
||||||
|
expirationTtl: 60 * 60 * 24 * 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsonResponse({
|
||||||
|
ok: true,
|
||||||
|
firstname : tokenData.firstname,
|
||||||
|
reference_client : refNumber,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return jsonResponse({ ok: false, error: err.message }, 500);
|
return jsonResponse({ ok: false, error: err.message }, 500);
|
||||||
@ -426,38 +479,47 @@ async function sendWelcomeEmail(env, params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Resend : envoi d'emails (verification + welcome)
|
// Brevo (ex-Sendinblue) : envoi d'emails (verification + welcome)
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Resend est utilisé pour envoyer les emails car il ne nécessite
|
// Brevo est utilisé pour l'envoi car il accepte la "single-sender
|
||||||
// pas de template séparé (on construit le HTML directement dans le
|
// verification" : on valide juste une adresse email (mvaglobalfret@gmail.com)
|
||||||
// Worker). Free tier : 100 emails/jour, 3000/mois.
|
// au lieu de devoir vérifier tout un domaine. Free tier : 300 emails/jour.
|
||||||
//
|
//
|
||||||
// Setup requis :
|
// Setup requis :
|
||||||
// - env.RESEND_API_KEY = clé API Resend (re_...)
|
// - env.BREVO_API_KEY = clé API Brevo (xkeysib-...)
|
||||||
// - env.RESEND_FROM = adresse expéditrice vérifiée chez Resend
|
// - env.BREVO_SENDER_EMAIL = adresse expéditrice validée chez Brevo
|
||||||
// (ex: "MVA Global Fret <noreply@mvaglobalfret.com>")
|
// (ex: "mvaglobalfret@gmail.com")
|
||||||
// Pour test : "onboarding@resend.dev"
|
// - env.BREVO_SENDER_NAME = nom affiché à l'expéditeur (ex: "MVA Global Fret")
|
||||||
// - env.SITE_URL = base URL du site (ex: "https://mva-global-fret.github.io/site-mva-global-fret")
|
// - env.SITE_URL = base URL du site (ex: "https://mva-global-fret.github.io/site-mva-global-fret")
|
||||||
|
//
|
||||||
|
// API doc : https://developers.brevo.com/reference/sendtransacemail
|
||||||
|
|
||||||
const RESEND_API = 'https://api.resend.com/emails';
|
const BREVO_API = 'https://api.brevo.com/v3/smtp/email';
|
||||||
|
|
||||||
async function resendSend(env, { to, subject, html }) {
|
async function brevoSend(env, { to, subject, html }) {
|
||||||
if (!env.RESEND_API_KEY) {
|
if (!env.BREVO_API_KEY) {
|
||||||
throw new Error('RESEND_API_KEY env var not set');
|
throw new Error('BREVO_API_KEY env var not set');
|
||||||
}
|
}
|
||||||
const from = env.RESEND_FROM || 'MVA Global Fret <onboarding@resend.dev>';
|
const senderEmail = env.BREVO_SENDER_EMAIL || 'mvaglobalfret@gmail.com';
|
||||||
|
const senderName = env.BREVO_SENDER_NAME || 'MVA Global Fret';
|
||||||
|
|
||||||
const res = await fetch(RESEND_API, {
|
const res = await fetch(BREVO_API, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
'api-key' : env.BREVO_API_KEY,
|
||||||
'Content-Type': 'application/json',
|
'accept' : 'application/json',
|
||||||
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ from, to: [to], subject, html }),
|
body: JSON.stringify({
|
||||||
|
sender : { name: senderName, email: senderEmail },
|
||||||
|
to : [{ email: to }],
|
||||||
|
subject : subject,
|
||||||
|
htmlContent: html,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
throw new Error(`Resend ${res.status}: ${text}`);
|
throw new Error(`Brevo ${res.status}: ${text}`);
|
||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
@ -465,15 +527,25 @@ async function resendSend(env, { to, subject, html }) {
|
|||||||
async function sendVerificationEmail(env, contact, verToken) {
|
async function sendVerificationEmail(env, contact, verToken) {
|
||||||
const siteUrl = env.SITE_URL || 'https://mva-global-fret.github.io/site-mva-global-fret';
|
const siteUrl = env.SITE_URL || 'https://mva-global-fret.github.io/site-mva-global-fret';
|
||||||
const verifyUrl = `${siteUrl}/confirmation.html?token=${verToken}`;
|
const verifyUrl = `${siteUrl}/confirmation.html?token=${verToken}`;
|
||||||
|
const logoUrl = `${siteUrl}/PNG%20MVA%20GLOBAL%20FRET.png`;
|
||||||
const firstname = escapeHtml(contact.firstname || '');
|
const firstname = escapeHtml(contact.firstname || '');
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f5f5f5;">
|
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f5f5f5;">
|
||||||
<div style="max-width:600px;margin:0 auto;background:#fff;">
|
<div style="max-width:600px;margin:0 auto;background:#fff;">
|
||||||
<div style="background:#1a1a3e;padding:30px;text-align:center;">
|
<div style="background:#1a1a3e;padding:24px 30px;">
|
||||||
<div style="color:#c5a55a;font-size:24px;font-weight:700;letter-spacing:2px;">MVA GLOBAL FRET</div>
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="width:100%;border-collapse:collapse;">
|
||||||
<div style="color:#fff;font-size:13px;margin-top:6px;">Fret Aérien Paris — Antananarivo</div>
|
<tr>
|
||||||
|
<td style="width:80px;vertical-align:middle;">
|
||||||
|
<img src="${logoUrl}" alt="MVA Global Fret" style="display:block;width:70px;height:auto;border:0;">
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align:middle;text-align:center;padding-right:80px;">
|
||||||
|
<div style="color:#c5a55a;font-size:24px;font-weight:700;letter-spacing:2px;">MVA GLOBAL FRET</div>
|
||||||
|
<div style="color:#fff;font-size:13px;margin-top:6px;">Fret Aérien Paris — Antananarivo</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:40px;">
|
<div style="padding:40px;">
|
||||||
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname},</p>
|
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname},</p>
|
||||||
@ -504,26 +576,50 @@ async function sendVerificationEmail(env, contact, verToken) {
|
|||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
return resendSend(env, {
|
return brevoSend(env, {
|
||||||
to: contact.email,
|
to: contact.email,
|
||||||
subject: 'Confirmez votre inscription chez MVA Global Fret',
|
subject: 'Confirmez votre inscription chez MVA Global Fret',
|
||||||
html,
|
html,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendWelcomeViaResend(env, contact) {
|
async function sendWelcomeViaBrevo(env, contact) {
|
||||||
|
const siteUrl = env.SITE_URL || 'https://mva-global-fret.github.io/site-mva-global-fret';
|
||||||
|
const logoUrl = `${siteUrl}/PNG%20MVA%20GLOBAL%20FRET.png`;
|
||||||
const firstname = escapeHtml(contact.firstname || '');
|
const firstname = escapeHtml(contact.firstname || '');
|
||||||
const ref = escapeHtml(contact.reference_client || '');
|
const ref = escapeHtml(contact.reference_client || '');
|
||||||
const parisAddrRaw = env.PARIS_DEPOT_ADDRESS || '';
|
const refRaw = contact.reference_client || '';
|
||||||
|
|
||||||
|
// Format adresse Paris : la 1ère ligne (nom du destinataire) reçoit
|
||||||
|
// automatiquement la référence client entre parenthèses, comme ça
|
||||||
|
// le client a directement la bonne forme à recopier sur son colis.
|
||||||
|
// Support aussi un placeholder {{ref}} si présent dans l'env var.
|
||||||
|
let parisAddrRaw = env.PARIS_DEPOT_ADDRESS || '';
|
||||||
|
if (parisAddrRaw.includes('{{ref}}')) {
|
||||||
|
parisAddrRaw = parisAddrRaw.replace(/\{\{ref\}\}/g, refRaw);
|
||||||
|
} else if (refRaw && parisAddrRaw) {
|
||||||
|
const lines = parisAddrRaw.split('\n');
|
||||||
|
lines[0] = `${lines[0]} (${refRaw})`;
|
||||||
|
parisAddrRaw = lines.join('\n');
|
||||||
|
}
|
||||||
const parisAddr = escapeHtml(parisAddrRaw).replace(/\n/g, '<br>');
|
const parisAddr = escapeHtml(parisAddrRaw).replace(/\n/g, '<br>');
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f5f5f5;">
|
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f5f5f5;">
|
||||||
<div style="max-width:600px;margin:0 auto;background:#fff;">
|
<div style="max-width:600px;margin:0 auto;background:#fff;">
|
||||||
<div style="background:#1a1a3e;padding:30px;text-align:center;">
|
<div style="background:#1a1a3e;padding:24px 30px;">
|
||||||
<div style="color:#c5a55a;font-size:24px;font-weight:700;letter-spacing:2px;">MVA GLOBAL FRET</div>
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="width:100%;border-collapse:collapse;">
|
||||||
<div style="color:#fff;font-size:13px;margin-top:6px;">Fret Aérien Paris — Antananarivo</div>
|
<tr>
|
||||||
|
<td style="width:80px;vertical-align:middle;">
|
||||||
|
<img src="${logoUrl}" alt="MVA Global Fret" style="display:block;width:70px;height:auto;border:0;">
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align:middle;text-align:center;padding-right:80px;">
|
||||||
|
<div style="color:#c5a55a;font-size:24px;font-weight:700;letter-spacing:2px;">MVA GLOBAL FRET</div>
|
||||||
|
<div style="color:#fff;font-size:13px;margin-top:6px;">Fret Aérien Paris — Antananarivo</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:40px;">
|
<div style="padding:40px;">
|
||||||
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname},</p>
|
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname},</p>
|
||||||
@ -559,7 +655,7 @@ async function sendWelcomeViaResend(env, contact) {
|
|||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
return resendSend(env, {
|
return brevoSend(env, {
|
||||||
to: contact.email,
|
to: contact.email,
|
||||||
subject: `Bienvenue chez MVA Global Fret — Votre référence ${ref}`,
|
subject: `Bienvenue chez MVA Global Fret — Votre référence ${ref}`,
|
||||||
html,
|
html,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// MVA Global Fret — Page de confirmation post-validation email
|
// MVA Global Fret — Page de confirmation post-validation email
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Cette page est la cible du lien dans l'email de validation
|
// Cette page est la cible du lien dans l'email de validation
|
||||||
// (envoyé par Resend après soumission du formulaire).
|
// (envoyé par Brevo après soumission du formulaire).
|
||||||
//
|
//
|
||||||
// URL : https://mva-global-fret.github.io/site-mva-global-fret/confirmation.html?token=XXX
|
// URL : https://mva-global-fret.github.io/site-mva-global-fret/confirmation.html?token=XXX
|
||||||
//
|
//
|
||||||
@ -10,7 +10,7 @@
|
|||||||
// 1. Lire le token depuis l'URL
|
// 1. Lire le token depuis l'URL
|
||||||
// 2. POST au Worker avec action 'verifyToken'
|
// 2. POST au Worker avec action 'verifyToken'
|
||||||
// 3. Worker valide le token, envoie le welcome email (avec ref +
|
// 3. Worker valide le token, envoie le welcome email (avec ref +
|
||||||
// adresse Paris) via Resend, puis renvoie OK
|
// adresse Paris) via Brevo, puis renvoie OK
|
||||||
// 4. Page affiche "Inscription confirmée !"
|
// 4. Page affiche "Inscription confirmée !"
|
||||||
//
|
//
|
||||||
// Si le token est invalide / expiré : affichage d'un message d'erreur
|
// Si le token est invalide / expiré : affichage d'un message d'erreur
|
||||||
|
|||||||
@ -98,40 +98,50 @@ function setupContactForm(form) {
|
|||||||
const email = form.email.value.trim();
|
const email = form.email.value.trim();
|
||||||
|
|
||||||
// ── VÉRIFICATION DOUBLON ──────────────────────────────────────────────────
|
// ── VÉRIFICATION DOUBLON ──────────────────────────────────────────────────
|
||||||
// Si le client existe déjà, on affiche uniquement un message informatif.
|
// Vérifie HubSpot. Comme les contacts ne sont créés QU'APRÈS confirmation
|
||||||
// On ne soumet RIEN à HubSpot — la référence existante n'est JAMAIS modifiée.
|
// email, ce check ne retourne que les vrais clients déjà inscrits (pas
|
||||||
|
// les inscriptions en attente de confirmation).
|
||||||
const existing = await checkExistingContact(email);
|
const existing = await checkExistingContact(email);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
showAlreadyRegistered(existing);
|
showAlreadyRegistered(existing);
|
||||||
return; // ← arrêt complet, aucune soumission
|
return;
|
||||||
}
|
}
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Nouveau client : génération du numéro de référence unique
|
|
||||||
const refNumber = await 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: email,
|
email: email,
|
||||||
address: form.address.value.trim(),
|
address: form.address.value.trim(),
|
||||||
reference_client: refNumber,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const results = await Promise.allSettled([
|
// ── ENVOI VERS LE WORKER ──────────────────────────────────────────────────
|
||||||
submitToHubSpot(data),
|
// Le Worker stocke les données en KV (24h), envoie un email de validation
|
||||||
submitToFormspree(data),
|
// via Brevo. Le contact n'est créé dans HubSpot QUE quand l'utilisateur
|
||||||
]);
|
// clique sur le lien de confirmation (anti-pollution du CRM).
|
||||||
|
let ok = false;
|
||||||
const hubspotOk = results[0].status === 'fulfilled';
|
try {
|
||||||
const formspreeOk = results[1].status === 'fulfilled';
|
const res = await fetch(WORKER_PROXY_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'requestVerification',
|
||||||
|
...data,
|
||||||
|
turnstile_token: window.turnstileToken || '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const result = await res.json().catch(() => ({}));
|
||||||
|
ok = res.ok && result.ok === true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[requestVerification]', err);
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
if (hubspotOk || formspreeOk) {
|
if (ok) {
|
||||||
showSuccess(refNumber, data);
|
showSuccess(null, data);
|
||||||
} else {
|
} else {
|
||||||
showError();
|
showError();
|
||||||
}
|
}
|
||||||
@ -285,7 +295,10 @@ function setLoading(isLoading) {
|
|||||||
form?.classList.toggle('form-loading', isLoading);
|
form?.classList.toggle('form-loading', isLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSuccess(refNumber, clientData) {
|
function showSuccess(_refNumber, _clientData) {
|
||||||
|
// L'envoi de l'email de validation est déjà fait dans setupContactForm
|
||||||
|
// via l'appel Worker requestVerification — on n'a plus rien à faire ici
|
||||||
|
// sauf afficher la confirmation à l'écran.
|
||||||
const successEl = document.getElementById('formSuccess');
|
const successEl = document.getElementById('formSuccess');
|
||||||
const form = document.getElementById('contactForm');
|
const form = document.getElementById('contactForm');
|
||||||
if (successEl) {
|
if (successEl) {
|
||||||
@ -293,36 +306,6 @@ function showSuccess(refNumber, clientData) {
|
|||||||
successEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
successEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
}
|
}
|
||||||
if (form) form.style.display = 'none';
|
if (form) form.style.display = 'none';
|
||||||
|
|
||||||
// Envoi de l'email de bienvenue (avec la référence + l'adresse Paris)
|
|
||||||
// directement après inscription. Les bots qui soumettent avec un faux
|
|
||||||
// email ne reçoivent rien (boîte inexistante = bounce). Pour bloquer
|
|
||||||
// les bots qui utilisent de vrais emails, voir la protection honeypot
|
|
||||||
// dans validateForm() + le rate limit côté worker.
|
|
||||||
if (clientData) sendWelcomeEmail(clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── EMAIL DE VÉRIFICATION ─────────────────────────────────────────────────────
|
|
||||||
// Le Worker génère un token unique, le stocke en KV, et envoie un email
|
|
||||||
// de validation via Resend (avec un lien de confirmation). Le contact ne
|
|
||||||
// reçoit la référence et l'adresse Paris qu'APRÈS avoir cliqué sur le lien.
|
|
||||||
async function sendWelcomeEmail(data) {
|
|
||||||
if (!WORKER_PROXY_URL) return;
|
|
||||||
try {
|
|
||||||
await fetch(WORKER_PROXY_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
action: 'requestVerification',
|
|
||||||
firstname: data.firstname,
|
|
||||||
email: data.email,
|
|
||||||
reference_client: data.reference_client,
|
|
||||||
turnstile_token: window.turnstileToken || '',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Verification email failed:', err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── EMAIL "RAVIS DE TE REVOIR" (client déjà inscrit) ──────────────────────────
|
// ── EMAIL "RAVIS DE TE REVOIR" (client déjà inscrit) ──────────────────────────
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user