Merge branch 'feat/v11-i' into feat/page-cerveau-v1
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
# Kit (ex-ConvertKit) - newsletter infolettre
|
||||
KIT_API_SECRET_V4=kit_xxx
|
||||
|
||||
# Behold.so feed IDs (voir docs/BEHOLD-SETUP.md)
|
||||
# 1) Inscris-toi sur https://behold.so/dashboard
|
||||
# 2) Connecte les 2 comptes Insta (@aep.politique + @julesneny)
|
||||
|
||||
70
src/components/astro/Footer.astro
Normal file
70
src/components/astro/Footer.astro
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
// Footer.astro - CTA infolettre Kit + nav footer
|
||||
---
|
||||
<footer class="border-t border-neutral-200 px-6 py-8 text-sm bg-white">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<h3 class="font-semibold mb-1" style="font-family: 'Courier New', Courier, monospace;">
|
||||
S'abonner a la lettre
|
||||
</h3>
|
||||
<p class="text-neutral-600 text-xs mb-3">
|
||||
1-2 emails par mois - pas de spam - desinscription en 1 clic.
|
||||
</p>
|
||||
<form id="subscribe-form" class="flex gap-2 max-w-md">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder="ton@email.fr"
|
||||
class="flex-1 px-3 py-2 border border-neutral-300 rounded-lg text-sm focus:outline-none focus:border-neutral-900"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-neutral-900 text-white rounded-lg text-sm hover:bg-neutral-700 transition-colors"
|
||||
>
|
||||
s'abonner
|
||||
</button>
|
||||
</form>
|
||||
<p id="subscribe-msg" class="mt-2 text-xs text-neutral-500 min-h-[1rem]"></p>
|
||||
<nav class="mt-6 flex flex-wrap gap-4 text-xs text-neutral-500">
|
||||
<a href="/manifeste" class="hover:text-neutral-900">Manifeste</a>
|
||||
<a href="/a-propos" class="hover:text-neutral-900">A propos</a>
|
||||
<a href="/mentions-legales" class="hover:text-neutral-900">Mentions legales</a>
|
||||
<a href="https://www.instagram.com/aep.politique/" target="_blank" rel="noopener" class="hover:text-neutral-900">@aep.politique</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const form = document.getElementById('subscribe-form') as HTMLFormElement | null;
|
||||
const msg = document.getElementById('subscribe-msg') as HTMLParagraphElement | null;
|
||||
|
||||
form?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
if (!msg || !form) return;
|
||||
|
||||
const emailInput = form.elements.namedItem('email') as HTMLInputElement;
|
||||
const email = emailInput?.value?.trim() ?? '';
|
||||
if (!email) return;
|
||||
|
||||
msg.textContent = 'envoi...';
|
||||
|
||||
try {
|
||||
const r = await fetch('/api/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.ok) {
|
||||
msg.textContent = data.already
|
||||
? 'tu es deja abonne - a tres vite.'
|
||||
: 'merci ! check ta boite mail (parfois spam).';
|
||||
form.reset();
|
||||
} else {
|
||||
msg.textContent = `souci : ${data.error || 'reessaie plus tard'}`;
|
||||
}
|
||||
} catch {
|
||||
msg.textContent = 'erreur reseau - reessaie plus tard';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import Footer from '../components/astro/Footer.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
@@ -29,5 +30,6 @@ const {
|
||||
</head>
|
||||
<body class="m-0 bg-white text-neutral-900 antialiased">
|
||||
<slot />
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
71
src/pages/api/subscribe.ts
Normal file
71
src/pages/api/subscribe.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
export const prerender = false
|
||||
|
||||
const KIT_API_BASE = 'https://api.kit.com/v4'
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
const KIT_API_KEY = import.meta.env.KIT_API_SECRET_V4
|
||||
if (!KIT_API_KEY) {
|
||||
return new Response(JSON.stringify({ error: 'config_missing' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
let body: { email: string; first_name?: string }
|
||||
try {
|
||||
body = await request.json()
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ error: 'invalid_json' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
const email = (body.email || '').trim().toLowerCase()
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
return new Response(JSON.stringify({ error: 'invalid_email' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const upstream = await fetch(`${KIT_API_BASE}/subscribers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Kit-Api-Key': KIT_API_KEY,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email_address: email,
|
||||
first_name: body.first_name || undefined,
|
||||
state: 'active',
|
||||
}),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
})
|
||||
|
||||
if (upstream.ok) {
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
if (upstream.status === 422) {
|
||||
return new Response(JSON.stringify({ ok: true, already: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'kit_failed', status: upstream.status }),
|
||||
{ status: 502, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
} catch (e) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'upstream_failed', detail: (e as Error).message }),
|
||||
{ status: 502, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user