popup-subscription into main #22
|
|
@ -4268,6 +4268,19 @@
|
||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@prisma/config/node_modules/magicast": {
|
||||||
|
"version": "0.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
|
||||||
|
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.25.4",
|
||||||
|
"@babel/types": "^7.25.4",
|
||||||
|
"source-map-js": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@prisma/config/node_modules/readdirp": {
|
"node_modules/@prisma/config/node_modules/readdirp": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,14 @@ model Contact {
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([email, createdAt])
|
@@index([email, createdAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
model NewsletterSubscriber {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
email String
|
||||||
|
locale String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@unique([email, locale])
|
||||||
|
@@index([email])
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { Icon } from "@iconify/vue";
|
||||||
|
import { createTranslator } from "../i18n";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
locale: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tl = createTranslator(props.locale);
|
||||||
|
|
||||||
|
const modalRef = ref(null);
|
||||||
|
const email = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const submitted = ref(false);
|
||||||
|
|
||||||
|
const STORAGE_KEY = "newsletter_popup_shown";
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
modalRef.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/newsletter/subscribe", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ email: email.value, locale: props.locale }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error();
|
||||||
|
submitted.value = true;
|
||||||
|
|
||||||
|
setTimeout(() => closeModal(), 1500);
|
||||||
|
} catch {
|
||||||
|
// silently ignore — popup is non-critical
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (localStorage.getItem(STORAGE_KEY)) return;
|
||||||
|
|
||||||
|
localStorage.setItem(STORAGE_KEY, "1");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
modalRef.value?.showModal();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<dialog ref="modalRef" class="modal modal-bottom sm:modal-middle p-8">
|
||||||
|
<div class="modal-box bg-[#EBE6D2] text-[#22523F] rounded-none p-8 max-w-md">
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="closeModal"
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
>
|
||||||
|
<Icon icon="ph:x" class="text-2xl" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<template v-if="!submitted">
|
||||||
|
<h3 class="text-xl font-bold lg:text-2xl font-secondary text-center uppercase mb-4">
|
||||||
|
{{ tl("newsletter.popup.title") }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p class="text-base font-primary text-center mb-6">
|
||||||
|
{{ tl("newsletter.popup.text") }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form @submit="handleSubmit" class="flex flex-col gap-3 items-center">
|
||||||
|
<label class="input rounded-none bg-white w-full">
|
||||||
|
<Icon icon="ph:envelope" class="text-xl text-[#22523F]" />
|
||||||
|
<input
|
||||||
|
v-model="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
:placeholder="tl('newsletter.popup.placeholder')"
|
||||||
|
class="w-full rounded-none"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="loading"
|
||||||
|
class="btn w-full mt-2 bg-[#22523F] text-[#EBE6D2] border-0 hover:bg-[#1a3f30] hover:text-[#EBE6D2] uppercase rounded-none"
|
||||||
|
>
|
||||||
|
{{ loading ? tl("newsletter.popup.sending") : tl("newsletter.popup.button") }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex flex-col items-center gap-4 py-4">
|
||||||
|
<Icon icon="ph:check-circle" class="text-5xl text-[#22523F]" />
|
||||||
|
<p class="text-lg font-bold font-secondary text-center uppercase">
|
||||||
|
{{ tl("newsletter.popup.success") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button @click="closeModal">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
|
@ -203,6 +203,14 @@
|
||||||
"footer.form.name": "Name and Lastname",
|
"footer.form.name": "Name and Lastname",
|
||||||
"footer.form.mesagge": "Write a message",
|
"footer.form.mesagge": "Write a message",
|
||||||
"footer.form.button": "Send",
|
"footer.form.button": "Send",
|
||||||
"footer.reserved": "©2026 All Rights Reserved. Kingdom of Peace and Justice Center"
|
"footer.reserved": "©2026 All Rights Reserved. Kingdom of Peace and Justice Center",
|
||||||
|
|
||||||
|
"newsletter.popup.title": "Stay informed",
|
||||||
|
"newsletter.popup.text": "Subscribe to receive the latest news and updates from the Kingdom of Peace and Justice Center.",
|
||||||
|
"newsletter.popup.placeholder": "Your email address",
|
||||||
|
"newsletter.popup.button": "Subscribe",
|
||||||
|
"newsletter.popup.sending": "Sending...",
|
||||||
|
"newsletter.popup.success": "Thank you for subscribing!",
|
||||||
|
"newsletter.popup.close": "Close"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -208,5 +208,13 @@
|
||||||
"footer.form.name": "Nombre y Apellido",
|
"footer.form.name": "Nombre y Apellido",
|
||||||
"footer.form.mesagge": "Escriba su mensaje",
|
"footer.form.mesagge": "Escriba su mensaje",
|
||||||
"footer.form.button": "Enviar",
|
"footer.form.button": "Enviar",
|
||||||
"footer.reserved": "©2026. Todos los Derechos Reservados. Centro del Reino de Paz y Justicia"
|
"footer.reserved": "©2026. Todos los Derechos Reservados. Centro del Reino de Paz y Justicia",
|
||||||
|
|
||||||
|
"newsletter.popup.title": "Mantente informado",
|
||||||
|
"newsletter.popup.text": "Suscríbete para recibir las últimas noticias y novedades del Centro del Reino de Paz y Justicia.",
|
||||||
|
"newsletter.popup.placeholder": "Tu correo electrónico",
|
||||||
|
"newsletter.popup.button": "Suscribirme",
|
||||||
|
"newsletter.popup.sending": "Enviando...",
|
||||||
|
"newsletter.popup.success": "¡Gracias por suscribirte!",
|
||||||
|
"newsletter.popup.close": "Cerrar"
|
||||||
}
|
}
|
||||||
|
|
@ -4,5 +4,12 @@
|
||||||
"nav.about": "À propos",
|
"nav.about": "À propos",
|
||||||
"nav.news": "Nouvelles",
|
"nav.news": "Nouvelles",
|
||||||
"nav.programs": "Programmes",
|
"nav.programs": "Programmes",
|
||||||
"nav.contact": "Contactez"
|
"nav.contact": "Contactez",
|
||||||
|
"newsletter.popup.title": "Restez informé",
|
||||||
|
"newsletter.popup.text": "Abonnez-vous pour recevoir les dernières nouvelles et mises à jour du Centre du Royaume de Paix et de Justice.",
|
||||||
|
"newsletter.popup.placeholder": "Votre adresse e-mail",
|
||||||
|
"newsletter.popup.button": "S'abonner",
|
||||||
|
"newsletter.popup.sending": "Envoi en cours...",
|
||||||
|
"newsletter.popup.success": "Merci pour votre abonnement !",
|
||||||
|
"newsletter.popup.close": "Fermer"
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,14 @@
|
||||||
"footer.form.name": "שם מלא",
|
"footer.form.name": "שם מלא",
|
||||||
"footer.form.mesagge": "כתיבת הודעה",
|
"footer.form.mesagge": "כתיבת הודעה",
|
||||||
"footer.form.button": "שליחה",
|
"footer.form.button": "שליחה",
|
||||||
"footer.reserved": "כל הזכויות שמורות 2026 © Centro del Reino de Paz y Justicia"
|
"footer.reserved": "כל הזכויות שמורות 2026 © Centro del Reino de Paz y Justicia",
|
||||||
|
"newsletter.popup.title": "הישארו מעודכנים",
|
||||||
|
"newsletter.popup.text": "הירשמו לקבלת החדשות והעדכונים האחרונים ממרכז הממלכה לשלום וצדק.",
|
||||||
|
"newsletter.popup.placeholder": "כתובת הדוא\"ל שלכם",
|
||||||
|
"newsletter.popup.button": "הרשמה",
|
||||||
|
"newsletter.popup.sending": "שולח...",
|
||||||
|
"newsletter.popup.success": "תודה שנרשמתם!",
|
||||||
|
"newsletter.popup.close": "סגור"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,5 +4,12 @@
|
||||||
"nav.about": "Somos",
|
"nav.about": "Somos",
|
||||||
"nav.news": "Noticias",
|
"nav.news": "Noticias",
|
||||||
"nav.programs": "Programas",
|
"nav.programs": "Programas",
|
||||||
"nav.contact": "Contacto"
|
"nav.contact": "Contacto",
|
||||||
|
"newsletter.popup.title": "Fique informado",
|
||||||
|
"newsletter.popup.text": "Inscreva-se para receber as últimas notícias e atualizações do Centro do Reino da Paz e da Justiça.",
|
||||||
|
"newsletter.popup.placeholder": "Seu endereço de e-mail",
|
||||||
|
"newsletter.popup.button": "Inscrever-se",
|
||||||
|
"newsletter.popup.sending": "Enviando...",
|
||||||
|
"newsletter.popup.success": "Obrigado por se inscrever!",
|
||||||
|
"newsletter.popup.close": "Fechar"
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,14 @@
|
||||||
"footer.form.name": "Имя и фамилия",
|
"footer.form.name": "Имя и фамилия",
|
||||||
"footer.form.mesagge": "Напишите свое сообщение",
|
"footer.form.mesagge": "Напишите свое сообщение",
|
||||||
"footer.form.button": "Отправить",
|
"footer.form.button": "Отправить",
|
||||||
"footer.reserved": "© 2026. Все права защищены. Центр Царства мира и справедливости"
|
"footer.reserved": "© 2026. Все права защищены. Центр Царства мира и справедливости",
|
||||||
|
"newsletter.popup.title": "Будьте в курсе",
|
||||||
|
"newsletter.popup.text": "Подпишитесь, чтобы получать последние новости и обновления от Центра Царства мира и справедливости.",
|
||||||
|
"newsletter.popup.placeholder": "Ваш адрес электронной почты",
|
||||||
|
"newsletter.popup.button": "Подписаться",
|
||||||
|
"newsletter.popup.sending": "Отправка...",
|
||||||
|
"newsletter.popup.success": "Спасибо за подписку!",
|
||||||
|
"newsletter.popup.close": "Закрыть"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -208,5 +208,12 @@
|
||||||
"footer.form.name": "Amazina yombi",
|
"footer.form.name": "Amazina yombi",
|
||||||
"footer.form.mesagge": "Andika ubutumwa bwawe",
|
"footer.form.mesagge": "Andika ubutumwa bwawe",
|
||||||
"footer.form.button": "Ohereza",
|
"footer.form.button": "Ohereza",
|
||||||
"footer.reserved": "© 2026. Uburenganzira bwose bufitwe na Centro del Reino de Paz y Justicia(Ihuriro ry’Ubwami bw’Amahoro n’Ubutabera)"
|
"footer.reserved": "© 2026. Uburenganzira bwose bufitwe na Centro del Reino de Paz y Justicia(Ihuriro ry’Ubwami bw’Amahoro n’Ubutabera)",
|
||||||
|
"newsletter.popup.title": "Komeza uhabwe amakuru",
|
||||||
|
"newsletter.popup.text": "Iyandikishe kugira ngo ubone amakuru mashya n’amakuru y’Ihuriro ry’Ubwami bw’Amahoro n’Ubutabera.",
|
||||||
|
"newsletter.popup.placeholder": "Aderesi yawe ya imeyili",
|
||||||
|
"newsletter.popup.button": "Iyandikishe",
|
||||||
|
"newsletter.popup.sending": "Kohereza...",
|
||||||
|
"newsletter.popup.success": "Urakoze kwiyandikisha!",
|
||||||
|
"newsletter.popup.close": "Funga"
|
||||||
}
|
}
|
||||||
|
|
@ -4,5 +4,12 @@
|
||||||
"nav.about": "Somos",
|
"nav.about": "Somos",
|
||||||
"nav.news": "Noticias",
|
"nav.news": "Noticias",
|
||||||
"nav.programs": "Programas",
|
"nav.programs": "Programas",
|
||||||
"nav.contact": "Contacto"
|
"nav.contact": "Contacto",
|
||||||
|
"newsletter.popup.title": "Будьте в курсі",
|
||||||
|
"newsletter.popup.text": "Підпишіться, щоб отримувати останні новини та оновлення від Центру Царства Миру та Справедливості.",
|
||||||
|
"newsletter.popup.placeholder": "Ваша електронна адреса",
|
||||||
|
"newsletter.popup.button": "Підписатися",
|
||||||
|
"newsletter.popup.sending": "Надсилання...",
|
||||||
|
"newsletter.popup.success": "Дякуємо за підписку!",
|
||||||
|
"newsletter.popup.close": "Закрити"
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,8 @@ import "@fontsource/poppins/400.css";
|
||||||
import "@fontsource/poppins/500.css";
|
import "@fontsource/poppins/500.css";
|
||||||
import "@fontsource/poppins/700.css";
|
import "@fontsource/poppins/700.css";
|
||||||
import "@fontsource-variable/kameron";
|
import "@fontsource-variable/kameron";
|
||||||
import ShareSticky from "../components/ShareSticky.vue";
|
import ShareSticky from "../components/ShareSticky.vue";
|
||||||
|
import NewsletterPopup from "../components/NewsletterPopup.vue";
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
|
@ -38,6 +39,7 @@ const direction = currentLocale === 'he' ? 'rtl' : 'ltr';
|
||||||
{Astro.url.pathname.includes('/news/') && (
|
{Astro.url.pathname.includes('/news/') && (
|
||||||
<ShareSticky client:only url={Astro.url.href} />
|
<ShareSticky client:only url={Astro.url.href} />
|
||||||
)}
|
)}
|
||||||
|
<NewsletterPopup client:only locale={currentLocale} />
|
||||||
<slot />
|
<slot />
|
||||||
<Footer />
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import { prisma } from "../lib/prisma";
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const email = body.email?.toString().trim().toLowerCase();
|
||||||
|
const locale = body.locale?.toString().trim() || "";
|
||||||
|
|
||||||
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
|
return new Response(JSON.stringify({ error: "Email inválido" }), {
|
||||||
|
status: 400,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.newsletterSubscriber.findUnique({
|
||||||
|
where: { email_locale: { email, locale } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
await prisma.newsletterSubscriber.create({ data: { email, locale } });
|
||||||
|
}else{
|
||||||
|
console.log(`Email ${email} já está inscrito para a locale ${locale}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ ok: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return new Response(JSON.stringify({ error: "Error interno" }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue