diff --git a/site/config/routes/download-white-paper.php b/site/config/routes/download-white-paper.php index 53564af..a1f713c 100644 --- a/site/config/routes/download-white-paper.php +++ b/site/config/routes/download-white-paper.php @@ -49,10 +49,43 @@ function parseContact(array $body): array return compact('firstName', 'lastName', 'email', 'company', 'role'); } -function checkEmailDeliverability(string $email): bool +function checkEmailDeliverability(string $email, string $apiKey): bool { - // Plug in an external email validation API here (e.g. Hunter, ZeroBounce, AbstractAPI) - return true; + if ($apiKey === '') { + error_log('[WP] Emailable: no api_key configured, skipping email check'); + return true; + } + + $url = 'https://api.emailable.com/v1/verify?' . http_build_query([ + 'email' => $email, + 'api_key' => $apiKey, + ]); + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPGET => true, + CURLOPT_TIMEOUT => 8, + CURLOPT_SSL_VERIFYPEER => true, + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + if ($response === false || $httpCode !== 200) { + error_log('[WP] Emailable check failed (HTTP ' . $httpCode . '): ' . $curlError); + return true; // fail open + } + + $data = json_decode($response, true); + $state = $data['state'] ?? 'unknown'; + + error_log('[WP] Emailable response for ' . $email . ': state=' . $state . ' | raw=' . $response); + + return $state !== 'undeliverable'; } function upsertKirbyContact(\Kirby\Cms\Page $page, array $contact): void @@ -179,7 +212,8 @@ return [ $contact = parseContact($body); - if (!checkEmailDeliverability($contact['email'])) { + $emailableApiKey = kirby()->option('emailable_whitepaper', [])['api_key'] ?? ''; + if (!checkEmailDeliverability($contact['email'], $emailableApiKey)) { wpReject(422, 'Undeliverable email'); } diff --git a/src/App.svelte b/src/App.svelte index 79e5ea9..53f4708 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -196,6 +196,8 @@ const handleKeydown = (e) => { if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return + const tag = document.activeElement?.tagName + if (tag === 'INPUT' || tag === 'TEXTAREA' || document.activeElement?.isContentEditable) return // Si on est sur une sous-page (ex: /livres-blancs/slug), ne pas changer de slide const activePath = slides.active?.path ?? '' diff --git a/src/components/WhitePaperDialog.svelte b/src/components/WhitePaperDialog.svelte index 8170832..22d35cf 100644 --- a/src/components/WhitePaperDialog.svelte +++ b/src/components/WhitePaperDialog.svelte @@ -14,8 +14,9 @@ let email = $state('') let consent = $state(false) let honeypot = $state('') - let submitting = $state(false) - let status = $state(null) // null | 'success' | 'error' + let submitting = $state(false) + let status = $state(null) // null | 'success' | 'error' + let emailInvalid = $state(false) let isEmailValid = $derived.by(() => { const re = /^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$/gm @@ -66,6 +67,7 @@ if (!consent || !uri) return submitting = true status = null + emailInvalid = false try { const prefix = locale.current === 'en' ? '/en' : '' const res = await fetch(`${prefix}/${uri}/download`, { @@ -74,8 +76,11 @@ body: JSON.stringify({ firstName, lastName, company, role, email, _hp: honeypot }) }) const result = await res.json() - if (result.fileUrl) { - window.open(result.fileUrl, '_blank') + if (res.status === 422 && result.error === 'Undeliverable email') { + emailInvalid = true + return + } + if (result.success) { status = 'success' } else { status = 'error' @@ -188,13 +193,19 @@ emailInvalid = false} /> + {#if emailInvalid} + + {/if} @@ -344,6 +355,26 @@ color: rgba(255, 255, 255, 0.4); } + .wp-dialog__input--invalid { + border-color: #ff6b6b; + animation: shake 0.4s ease; + } + + .wp-dialog__email-error { + font-family: "Danzza", sans-serif; + font-size: var(--font-size-paragraph-small); + color: #ff6b6b; + margin-top: -0.25rem; + } + + @keyframes shake { + 0%, 100% { transform: translateX(0); } + 20% { transform: translateX(-6px); } + 40% { transform: translateX(6px); } + 60% { transform: translateX(-4px); } + 80% { transform: translateX(3px); } + } + .wp-dialog__hp { position: absolute; left: -9999px; diff --git a/src/views/WhitePaper.svelte b/src/views/WhitePaper.svelte index 6059c29..9cecd81 100644 --- a/src/views/WhitePaper.svelte +++ b/src/views/WhitePaper.svelte @@ -10,8 +10,9 @@ let role = $state('') let email = $state('') let consent = $state(false) - let submitting = $state(false) - let status = $state(null) // null | 'success' | 'error' + let submitting = $state(false) + let status = $state(null) // null | 'success' | 'error' + let emailInvalid = $state(false) let showForm = $state(false) let honeypot = $state('') @@ -29,6 +30,7 @@ if (!consent) return submitting = true status = null + emailInvalid = false try { const prefix = locale.current === 'en' ? '/en' : '' const res = await fetch(`${prefix}/${data.uri}/download`, { @@ -37,8 +39,11 @@ body: JSON.stringify({ firstName, lastName, company, role, email, _hp: honeypot }) }) const result = await res.json() - if (result.fileUrl) { - window.open(result.fileUrl, '_blank') + if (res.status === 422 && result.error === 'Undeliverable email') { + emailInvalid = true + return + } + if (result.success) { status = 'success' } else { status = 'error' @@ -96,7 +101,19 @@ - + emailInvalid = false} + /> + {#if emailInvalid} + + {/if}