Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei: Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich (Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung, PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma- Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, type FormEvent } from "react";
|
|
import { Container, SectionHeading, Button } from "@/components/ui";
|
|
import { ScrollReveal } from "@/components/scroll-reveal";
|
|
import { TurnstileWidget } from "@/components/turnstile-widget";
|
|
import { useI18n } from "@/i18n/i18n-provider";
|
|
|
|
type Status = "idle" | "loading" | "success" | "error";
|
|
|
|
const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
|
|
|
|
export function NewsletterSignup() {
|
|
const { t } = useI18n();
|
|
const [email, setEmail] = useState("");
|
|
const [status, setStatus] = useState<Status>("idle");
|
|
const [turnstileToken, setTurnstileToken] = useState("");
|
|
|
|
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
setStatus("loading");
|
|
try {
|
|
const response = await fetch("/api/newsletter", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ email, turnstileToken }),
|
|
});
|
|
if (!response.ok) throw new Error("request failed");
|
|
setStatus("success");
|
|
setEmail("");
|
|
} catch {
|
|
setStatus("error");
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section className="py-24">
|
|
<Container className="max-w-2xl text-center">
|
|
<ScrollReveal>
|
|
<SectionHeading
|
|
eyebrow={t.home.newsletter.eyebrow}
|
|
title={t.home.newsletter.title}
|
|
align="center"
|
|
className="mx-auto"
|
|
/>
|
|
<p className="mt-4 text-ink/70">{t.home.newsletter.description}</p>
|
|
|
|
{status === "success" ? (
|
|
<p className="mt-8 text-forest">{t.home.newsletter.successMessage}</p>
|
|
) : (
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className="mt-8 flex flex-col sm:flex-row gap-3 max-w-lg mx-auto"
|
|
>
|
|
<input
|
|
type="email"
|
|
required
|
|
placeholder={t.home.newsletter.emailPlaceholder}
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
className="min-w-0 flex-1 border border-ink/20 bg-background px-4 py-3 text-sm focus:outline-none focus:border-forest"
|
|
/>
|
|
<Button type="submit" disabled={status === "loading"} className="flex-shrink-0 whitespace-nowrap">
|
|
{status === "loading" ? t.home.newsletter.subscribing : t.home.newsletter.subscribe}
|
|
</Button>
|
|
{TURNSTILE_SITE_KEY ? (
|
|
<div className="w-full flex justify-center">
|
|
<TurnstileWidget siteKey={TURNSTILE_SITE_KEY} onVerify={setTurnstileToken} />
|
|
</div>
|
|
) : null}
|
|
</form>
|
|
)}
|
|
{status === "error" ? (
|
|
<p className="mt-4 text-sm text-red-700">{t.home.newsletter.errorMessage}</p>
|
|
) : null}
|
|
|
|
<p className="mt-6 text-xs text-ink/65">
|
|
{t.home.newsletter.consentBeforeLink}
|
|
<a href="/datenschutz" className="underline">
|
|
{t.home.newsletter.privacyLinkLabel}
|
|
</a>
|
|
{t.home.newsletter.consentAfterLink}
|
|
</p>
|
|
</ScrollReveal>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|