Email verification flow via Resend (Turnstile + click-to-confirm)
Architecture finale :
1. User remplit formulaire + passe Turnstile CAPTCHA → form-handler.js
2. form-handler.js POST au Worker avec action 'requestVerification'
3. Worker valide Turnstile, génère un token UUID, le stocke en KV (TTL 24h)
avec firstname/email/reference_client, puis envoie un email via Resend
avec un lien : confirmation.html?token=XXX
4. User reçoit email, clique 'Confirmer mon email'
5. confirmation.html lit le token de l'URL, POST au Worker avec action
'verifyToken'
6. Worker valide le token, envoie le welcome email via Resend (avec ref +
adresse Paris depuis env var), marque le token comme utilisé
7. confirmation.html affiche 'Inscription confirmée !'
Ainsi : ref + adresse Paris ne sortent JAMAIS avant validation email,
et les bots sont bloqués à l'étape 1 par Turnstile.
Setup Cloudflare requis (côté user) :
- RESEND_API_KEY : clé API Resend (re_...)
- RESEND_FROM : adresse expéditrice ('onboarding@resend.dev' pour test,
ou domain vérifié pour prod)
- SITE_URL : optionnel, défaut https://mva-global-fret.github.io/site-mva-global-fret
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a3a36df811
commit
eb5c4f1cee
@ -129,6 +129,77 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── action: requestVerification ──────────────────────────
|
||||||
|
// Génère un token unique, le stocke en KV avec les infos du contact,
|
||||||
|
// 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.
|
||||||
|
// Anti-bot : Turnstile vérifié d'abord.
|
||||||
|
if (action === 'requestVerification') {
|
||||||
|
if (!body.email) return jsonResponse({ error: 'email requis' }, 400);
|
||||||
|
|
||||||
|
const turnstileOk = await verifyTurnstile(env, body.turnstile_token, request);
|
||||||
|
if (!turnstileOk) {
|
||||||
|
return jsonResponse({ ok: false, error: 'Turnstile validation failed' }, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const verToken = crypto.randomUUID().replace(/-/g, '');
|
||||||
|
const tokenData = {
|
||||||
|
firstname : body.firstname || '',
|
||||||
|
email : body.email.toLowerCase().trim(),
|
||||||
|
reference_client : body.reference_client || '',
|
||||||
|
createdAt : new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!env.WELCOME_KV) {
|
||||||
|
return jsonResponse({ ok: false, error: 'KV not bound' }, 500);
|
||||||
|
}
|
||||||
|
// Token valide 24h
|
||||||
|
await env.WELCOME_KV.put(`verify:${verToken}`, JSON.stringify(tokenData), {
|
||||||
|
expirationTtl: 60 * 60 * 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendVerificationEmail(env, tokenData, verToken);
|
||||||
|
return jsonResponse({ ok: true });
|
||||||
|
} catch (err) {
|
||||||
|
return jsonResponse({ ok: false, error: err.message }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── action: verifyToken ──────────────────────────────────
|
||||||
|
// Appelé par confirmation.html quand l'utilisateur clique sur
|
||||||
|
// le lien dans l'email de validation. Lit le token en KV,
|
||||||
|
// envoie le welcome (avec ref + adresse Paris) via Resend,
|
||||||
|
// puis supprime le token (one-time use).
|
||||||
|
if (action === 'verifyToken') {
|
||||||
|
if (!body.token) return jsonResponse({ error: 'token requis' }, 400);
|
||||||
|
if (!env.WELCOME_KV) return jsonResponse({ ok: false, error: 'KV not bound' }, 500);
|
||||||
|
|
||||||
|
const key = `verify:${body.token}`;
|
||||||
|
const raw = await env.WELCOME_KV.get(key);
|
||||||
|
if (!raw) {
|
||||||
|
return jsonResponse({ ok: false, error: 'Token invalide ou expiré' }, 404);
|
||||||
|
}
|
||||||
|
const tokenData = JSON.parse(raw);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendWelcomeViaResend(env, tokenData);
|
||||||
|
// Marque le token consommé (gardé 7j pour idempotence en cas de
|
||||||
|
// 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({
|
||||||
|
ok: true,
|
||||||
|
firstname : tokenData.firstname,
|
||||||
|
reference_client : tokenData.reference_client,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return jsonResponse({ ok: false, error: err.message }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── action: listSubscriptions (debug : trouver les IDs) ──
|
// ── action: listSubscriptions (debug : trouver les IDs) ──
|
||||||
if (action === 'listSubscriptions') {
|
if (action === 'listSubscriptions') {
|
||||||
// Endpoint legacy email/public/v1 nécessite scope content au lieu de
|
// Endpoint legacy email/public/v1 nécessite scope content au lieu de
|
||||||
@ -354,6 +425,153 @@ async function sendWelcomeEmail(env, params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Resend : envoi d'emails (verification + welcome)
|
||||||
|
// =============================================================
|
||||||
|
// Resend est utilisé pour envoyer les emails car il ne nécessite
|
||||||
|
// pas de template séparé (on construit le HTML directement dans le
|
||||||
|
// Worker). Free tier : 100 emails/jour, 3000/mois.
|
||||||
|
//
|
||||||
|
// Setup requis :
|
||||||
|
// - env.RESEND_API_KEY = clé API Resend (re_...)
|
||||||
|
// - env.RESEND_FROM = adresse expéditrice vérifiée chez Resend
|
||||||
|
// (ex: "MVA Global Fret <noreply@mvaglobalfret.com>")
|
||||||
|
// Pour test : "onboarding@resend.dev"
|
||||||
|
// - env.SITE_URL = base URL du site (ex: "https://mva-global-fret.github.io/site-mva-global-fret")
|
||||||
|
|
||||||
|
const RESEND_API = 'https://api.resend.com/emails';
|
||||||
|
|
||||||
|
async function resendSend(env, { to, subject, html }) {
|
||||||
|
if (!env.RESEND_API_KEY) {
|
||||||
|
throw new Error('RESEND_API_KEY env var not set');
|
||||||
|
}
|
||||||
|
const from = env.RESEND_FROM || 'MVA Global Fret <onboarding@resend.dev>';
|
||||||
|
|
||||||
|
const res = await fetch(RESEND_API, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ from, to: [to], subject, html }),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text();
|
||||||
|
throw new Error(`Resend ${res.status}: ${text}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendVerificationEmail(env, contact, verToken) {
|
||||||
|
const siteUrl = env.SITE_URL || 'https://mva-global-fret.github.io/site-mva-global-fret';
|
||||||
|
const verifyUrl = `${siteUrl}/confirmation.html?token=${verToken}`;
|
||||||
|
const firstname = escapeHtml(contact.firstname || '');
|
||||||
|
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<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="background:#1a1a3e;padding:30px;text-align:center;">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div style="padding:40px;">
|
||||||
|
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname},</p>
|
||||||
|
<p style="color:#333;line-height:1.6;">
|
||||||
|
Merci pour votre inscription chez <strong>MVA Global Fret</strong> !
|
||||||
|
</p>
|
||||||
|
<p style="color:#333;line-height:1.6;">
|
||||||
|
Pour finaliser votre inscription et recevoir votre <strong>numéro de référence client</strong>
|
||||||
|
ainsi que <strong>l'adresse de notre dépôt à Paris</strong>, cliquez sur le bouton ci-dessous :
|
||||||
|
</p>
|
||||||
|
<div style="text-align:center;margin:32px 0;">
|
||||||
|
<a href="${verifyUrl}" style="display:inline-block;background:#c5a55a;color:#1a1a3e;padding:16px 40px;border-radius:50px;text-decoration:none;font-weight:700;font-size:16px;letter-spacing:0.5px;">
|
||||||
|
✓ Confirmer mon email
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p style="color:#666;font-size:13px;line-height:1.6;">
|
||||||
|
Ce lien est valable <strong>24 heures</strong>. Si vous n'êtes pas à l'origine de cette inscription, ignorez simplement cet email.
|
||||||
|
</p>
|
||||||
|
<p style="color:#666;font-size:12px;line-height:1.6;border-top:1px solid #eee;padding-top:18px;margin-top:30px;">
|
||||||
|
Si le bouton ne fonctionne pas, copiez ce lien dans votre navigateur :<br>
|
||||||
|
<span style="color:#c5a55a;word-break:break-all;">${verifyUrl}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a3e;padding:18px;text-align:center;color:#c5a55a;font-size:12px;">
|
||||||
|
© 2026 MVA Global Fret — Tous droits réservés
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
return resendSend(env, {
|
||||||
|
to: contact.email,
|
||||||
|
subject: 'Confirmez votre inscription chez MVA Global Fret',
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendWelcomeViaResend(env, contact) {
|
||||||
|
const firstname = escapeHtml(contact.firstname || '');
|
||||||
|
const ref = escapeHtml(contact.reference_client || '');
|
||||||
|
const parisAddrRaw = env.PARIS_DEPOT_ADDRESS || '';
|
||||||
|
const parisAddr = escapeHtml(parisAddrRaw).replace(/\n/g, '<br>');
|
||||||
|
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<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="background:#1a1a3e;padding:30px;text-align:center;">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div style="padding:40px;">
|
||||||
|
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname},</p>
|
||||||
|
<p style="color:#333;line-height:1.6;">
|
||||||
|
Bienvenu(e) chez <strong>MVA Global Fret</strong> ! Votre email est confirmé,
|
||||||
|
votre inscription est désormais active.
|
||||||
|
</p>
|
||||||
|
<div style="background:#f0ead8;border-left:4px solid #c5a55a;padding:16px 20px;margin:24px 0;border-radius:4px;">
|
||||||
|
<p style="margin:0;color:#1a1a3e;font-size:14px;">Votre numéro de référence client :</p>
|
||||||
|
<p style="margin:8px 0 0;color:#1a1a3e;font-size:22px;font-weight:bold;letter-spacing:2px;">${ref}</p>
|
||||||
|
<p style="margin:6px 0 0;color:#666;font-size:12px;">Conservez ce numéro précieusement.</p>
|
||||||
|
</div>
|
||||||
|
<p style="color:#333;margin-top:28px;"><strong>L'adresse à Paris pour l'envoi de vos colis :</strong></p>
|
||||||
|
<div style="background:#f9f9f9;border:1px solid #ddd;padding:20px 24px;border-radius:6px;margin:12px 0;font-family:monospace;font-size:15px;line-height:1.8;color:#1a1a3e;">
|
||||||
|
${parisAddr}
|
||||||
|
</div>
|
||||||
|
<div style="background:#fff3cd;border:1px solid #ffc107;padding:14px 18px;border-radius:6px;margin:16px 0;">
|
||||||
|
<p style="margin:0;color:#856404;font-size:14px;">
|
||||||
|
📌 <strong>Sur le colis</strong>, indiquez votre numéro de référence <strong>${ref}</strong>
|
||||||
|
juste après le nom du destinataire entre parenthèses.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p style="color:#333;line-height:1.6;">
|
||||||
|
Pour toute question, contactez-nous :<br>
|
||||||
|
📧 mvaglobalfret@gmail.com<br>
|
||||||
|
📞 +33 7 80 97 08 25 (France) — +261 38 49 737 51 (Madagascar)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a3e;padding:18px;text-align:center;color:#c5a55a;font-size:12px;">
|
||||||
|
© 2026 MVA Global Fret — Tous droits réservés
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
return resendSend(env, {
|
||||||
|
to: contact.email,
|
||||||
|
subject: `Bienvenue chez MVA Global Fret — Votre référence ${ref}`,
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s) {
|
||||||
|
return String(s)
|
||||||
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Cloudflare Turnstile : validation anti-bot
|
// Cloudflare Turnstile : validation anti-bot
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|||||||
@ -76,10 +76,10 @@
|
|||||||
<p style="color: var(--text-light); margin-bottom: 32px;" data-i18n="contact.formSubtitle">Remplissez ce formulaire pour recevoir votre numéro de référence client et l'adresse de dépôt à Paris.</p>
|
<p style="color: var(--text-light); margin-bottom: 32px;" data-i18n="contact.formSubtitle">Remplissez ce formulaire pour recevoir votre numéro de référence client et l'adresse de dépôt à Paris.</p>
|
||||||
|
|
||||||
<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-envelope-circle-check" style="font-size: 2rem; color: var(--gold); display: block; margin-bottom: 12px;"></i>
|
||||||
<strong data-i18n="contact.successTitle">Inscription réussie !</strong><br>
|
<strong data-i18n="contact.successTitle">Vérifiez votre boîte mail !</strong><br>
|
||||||
<span data-i18n="contact.successMsg">Merci ! Votre inscription a bien été enregistrée. Un email de bienvenue avec votre numéro de référence client et l'adresse de dépôt à Paris vient de vous être envoyé.</span>
|
<span data-i18n="contact.successMsg">Pour finaliser votre inscription, cliquez sur le lien de confirmation que nous venons de vous envoyer par email. Vous recevrez ensuite votre numéro de référence client et l'adresse de dépôt à Paris.</span>
|
||||||
<p style="margin-top:12px; font-size:0.85rem; color: var(--text-light);" data-i18n="contact.emailSent">📧 Vérifiez votre boîte mail (et vos spams).</p>
|
<p style="margin-top:12px; font-size:0.85rem; color: var(--text-light);" data-i18n="contact.emailSent">📧 Email envoyé — pensez à vérifier vos spams. Le lien expire dans 24h.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- DÉJÀ INSCRIT -->
|
<!-- DÉJÀ INSCRIT -->
|
||||||
|
|||||||
@ -1,110 +1,76 @@
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// MVA Global Fret — Page de confirmation post-double-opt-in
|
// MVA Global Fret — Page de confirmation post-validation email
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Cette page est la cible de redirection après que l'utilisateur
|
// Cette page est la cible du lien dans l'email de validation
|
||||||
// a cliqué sur "Confirmer" dans l'email de validation HubSpot.
|
// (envoyé par Resend après soumission du formulaire).
|
||||||
//
|
//
|
||||||
// HubSpot redirige vers :
|
// URL : https://mva-global-fret.github.io/site-mva-global-fret/confirmation.html?token=XXX
|
||||||
// https://mva-global-fret.github.io/site-mva-global-fret/confirmation.html?email={contact.email}
|
|
||||||
//
|
//
|
||||||
// Étapes :
|
// Étapes :
|
||||||
// 1. Lire l'email depuis l'URL
|
// 1. Lire le token depuis l'URL
|
||||||
// 2. Demander au Cloudflare Worker la fiche du contact (incluant reference_client)
|
// 2. POST au Worker avec action 'verifyToken'
|
||||||
// 3. Envoyer un email de bienvenue via EmailJS contenant la référence
|
// 3. Worker valide le token, envoie le welcome email (avec ref +
|
||||||
// 4. Afficher la référence à l'écran
|
// adresse Paris) via Resend, puis renvoie OK
|
||||||
|
// 4. Page affiche "Inscription confirmée !"
|
||||||
//
|
//
|
||||||
// Si une étape échoue, on affiche un fallback poli (l'inscription
|
// Si le token est invalide / expiré : affichage d'un message d'erreur
|
||||||
// reste valide côté HubSpot, c'est juste l'email qui ne part pas).
|
// avec invitation à contacter le support.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
const WORKER_PROXY_URL = 'https://mva-hubspot-proxy.mvaglobalfret.workers.dev';
|
const WORKER_PROXY_URL = 'https://mva-hubspot-proxy.mvaglobalfret.workers.dev';
|
||||||
const EMAILJS_PUBLIC_KEY = '8KUlaQ7BDVIbkZRyP';
|
|
||||||
const EMAILJS_SERVICE_ID = 'service_aeamo3x';
|
|
||||||
const EMAILJS_TEMPLATE_ID = 'template_s1kr2et';
|
|
||||||
|
|
||||||
// Marqueur localStorage : empêche de relancer l'envoi si l'utilisateur
|
|
||||||
// recharge la page après confirmation (par sécurité ET pour ne pas
|
|
||||||
// renvoyer 3 fois le même email).
|
|
||||||
const STORAGE_KEY_PREFIX = 'mva-confirm-sent-';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (typeof emailjs !== 'undefined') {
|
const token = new URLSearchParams(window.location.search).get('token');
|
||||||
emailjs.init({ publicKey: EMAILJS_PUBLIC_KEY });
|
|
||||||
}
|
|
||||||
|
|
||||||
const email = getEmailFromUrl();
|
if (!token) {
|
||||||
|
showError('Lien de confirmation invalide. Veuillez vérifier votre email ou nous contacter.');
|
||||||
if (!email) {
|
|
||||||
// Pas d'email dans l'URL : on affiche quand même un succès générique
|
|
||||||
// (l'utilisateur a bien confirmé puisqu'il atterrit ici depuis HubSpot)
|
|
||||||
showSuccess(null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contact = await fetchContact(email);
|
|
||||||
|
|
||||||
if (!contact) {
|
|
||||||
// Contact non trouvé via Worker — on affiche succès quand même
|
|
||||||
showSuccess(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ref = contact.reference_client || null;
|
|
||||||
|
|
||||||
// Idempotence : un seul email par confirmation
|
|
||||||
const storageKey = STORAGE_KEY_PREFIX + email.toLowerCase();
|
|
||||||
if (!localStorage.getItem(storageKey)) {
|
|
||||||
await sendWelcomeEmail({
|
|
||||||
firstname: contact.firstname || '',
|
|
||||||
email: email,
|
|
||||||
reference_client: ref || '',
|
|
||||||
});
|
|
||||||
localStorage.setItem(storageKey, String(Date.now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
showSuccess(ref);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[confirmation] flow failed:', err);
|
|
||||||
showError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getEmailFromUrl() {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const raw = params.get('email');
|
|
||||||
if (!raw) return null;
|
|
||||||
const email = raw.trim().toLowerCase();
|
|
||||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchContact(email) {
|
|
||||||
const res = await fetch(WORKER_PROXY_URL, {
|
const res = await fetch(WORKER_PROXY_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email }),
|
body: JSON.stringify({ action: 'verifyToken', token }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error('Worker error: ' + res.status);
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!data.results || data.results.length === 0) return null;
|
|
||||||
return data.results[0].properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendWelcomeEmail(payload) {
|
if (data.ok) {
|
||||||
if (typeof emailjs === 'undefined') return;
|
showSuccess(data.reference_client || null);
|
||||||
await emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, payload);
|
} else {
|
||||||
|
// Token expiré, déjà utilisé, ou inconnu
|
||||||
|
showError(data.error === 'Token invalide ou expiré'
|
||||||
|
? 'Ce lien de confirmation a expiré ou a déjà été utilisé.'
|
||||||
|
: 'Une erreur est survenue lors de la confirmation.');
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[confirmation]', err);
|
||||||
|
showError('Impossible de joindre le serveur. Vérifiez votre connexion et réessayez.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function showSuccess(ref) {
|
function showSuccess(ref) {
|
||||||
document.getElementById('cardLoading').style.display = 'none';
|
const loading = document.getElementById('cardLoading');
|
||||||
document.getElementById('cardSuccess').style.display = '';
|
const success = document.getElementById('cardSuccess');
|
||||||
|
if (loading) loading.style.display = 'none';
|
||||||
|
if (success) {
|
||||||
|
success.style.display = '';
|
||||||
if (ref) {
|
if (ref) {
|
||||||
document.getElementById('refDisplay').textContent = ref;
|
const refDisplay = document.getElementById('refDisplay');
|
||||||
document.getElementById('refBlock').style.display = '';
|
const refBlock = document.getElementById('refBlock');
|
||||||
|
if (refDisplay) refDisplay.textContent = ref;
|
||||||
|
if (refBlock) refBlock.style.display = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError() {
|
function showError(msg) {
|
||||||
document.getElementById('cardLoading').style.display = 'none';
|
const loading = document.getElementById('cardLoading');
|
||||||
document.getElementById('cardError').style.display = '';
|
const error = document.getElementById('cardError');
|
||||||
|
if (loading) loading.style.display = 'none';
|
||||||
|
if (error) {
|
||||||
|
error.style.display = '';
|
||||||
|
const desc = error.querySelector('p');
|
||||||
|
if (desc && msg) desc.textContent = msg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -302,11 +302,10 @@ function showSuccess(refNumber, clientData) {
|
|||||||
if (clientData) sendWelcomeEmail(clientData);
|
if (clientData) sendWelcomeEmail(clientData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── EMAIL DE BIENVENUE ────────────────────────────────────────────────────────
|
// ── EMAIL DE VÉRIFICATION ─────────────────────────────────────────────────────
|
||||||
// Envoyé via le Cloudflare Worker pour que l'adresse Paris ne soit JAMAIS
|
// Le Worker génère un token unique, le stocke en KV, et envoie un email
|
||||||
// présente dans le JS public. Le Worker valide d'abord le token Turnstile
|
// de validation via Resend (avec un lien de confirmation). Le contact ne
|
||||||
// (anti-bot) puis fait le call EmailJS REST avec le PARIS_DEPOT_ADDRESS
|
// reçoit la référence et l'adresse Paris qu'APRÈS avoir cliqué sur le lien.
|
||||||
// depuis ses variables d'environnement.
|
|
||||||
async function sendWelcomeEmail(data) {
|
async function sendWelcomeEmail(data) {
|
||||||
if (!WORKER_PROXY_URL) return;
|
if (!WORKER_PROXY_URL) return;
|
||||||
try {
|
try {
|
||||||
@ -314,7 +313,7 @@ async function sendWelcomeEmail(data) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
action: 'sendWelcomeNow',
|
action: 'requestVerification',
|
||||||
firstname: data.firstname,
|
firstname: data.firstname,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
reference_client: data.reference_client,
|
reference_client: data.reference_client,
|
||||||
@ -322,8 +321,7 @@ async function sendWelcomeEmail(data) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// L'email de bienvenue est un bonus — on ne bloque pas l'inscription si ça échoue
|
console.warn('Verification email failed:', err);
|
||||||
console.warn('Welcome email failed:', err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -150,11 +150,11 @@ const translations = {
|
|||||||
placeholderAdresse: "Adresse complète...",
|
placeholderAdresse: "Adresse complète...",
|
||||||
submitBtn: "S'inscrire",
|
submitBtn: "S'inscrire",
|
||||||
submitLoading: "Envoi en cours...",
|
submitLoading: "Envoi en cours...",
|
||||||
successTitle: "Inscription réussie !",
|
successTitle: "Vérifiez votre boîte mail !",
|
||||||
successMsg: "Merci ! Votre inscription a bien été enregistrée. Un email de bienvenue avec votre numéro de référence client et l'adresse de dépôt à Paris vient de vous être envoyé.",
|
successMsg: "Pour finaliser votre inscription, cliquez sur le lien de confirmation que nous venons de vous envoyer par email. Vous recevrez ensuite votre numéro de référence client et l'adresse de dépôt à Paris.",
|
||||||
refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT",
|
refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT",
|
||||||
successNote: "Conservez ce numéro précieusement — il vous sera utile pour suivre vos colis.",
|
successNote: "Conservez ce numéro précieusement — il vous sera utile pour suivre vos colis.",
|
||||||
emailSent: "📧 Vérifiez votre boîte mail (et vos spams).",
|
emailSent: "📧 Email envoyé — pensez à vérifier vos spams. Le lien expire dans 24h.",
|
||||||
alreadyTitle: "Vous êtes déjà client !",
|
alreadyTitle: "Vous êtes déjà client !",
|
||||||
alreadyMsg: "Votre adresse email est déjà enregistrée dans notre système.",
|
alreadyMsg: "Votre adresse email est déjà enregistrée dans notre système.",
|
||||||
alreadyRefLabel: "VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT",
|
alreadyRefLabel: "VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user