Add on-blur email validation to every newsletter signup form
Extends the checkout pattern (inline red error text, refocus on submit if invalid) to all four newsletter-signup entry points. Two of them (WeeklyImpulsesHero's inline hero form on /newsletter, and /challenge's EmailCapture) turned out to be completely non-functional before this too — same static-markup-with-no-onSubmit issue as Newsletter.tsx/NewsletterModal.tsx had, just missed in the previous pass since they're separate components sharing only the visual pattern, not the code. Consolidated the shared email+consent+submit state (previously duplicated per-component) into useNewsletterSignup.ts, and pulled the plain email-format regex (previously duplicated in CheckoutContent.tsx and the subscribe route) into lib/email.ts as a single source of truth. /challenge's EmailCapture is now its own client component (app/challenge/components/EmailCapture.tsx) since its parent page is an async Server Component and can't hold form state itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState, type FormEvent, type ReactNode } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Reveal } from "./Reveal";
|
||||
import { useNewsletterSignup } from "../lib/useNewsletterSignup";
|
||||
|
||||
// Same lock icon + copy as /challenge's and /newsletter's EmailCapture —
|
||||
// unified across all newsletter-signup forms instead of each having its
|
||||
@@ -33,33 +34,8 @@ export function Newsletter({
|
||||
title = <>Starte mit einer Woche voller Klarheit<span className="text-brand">.</span></>,
|
||||
description = "Melde dich zum Newsletter an und erhalte die 7-Tage-Challenge, mit der du durch mehr Struktur weniger Stress spürst.",
|
||||
}: NewsletterProps = {}) {
|
||||
const [email, setEmail] = useState("");
|
||||
const [consent, setConsent] = useState(false);
|
||||
const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
setStatus("submitting");
|
||||
setError("");
|
||||
try {
|
||||
const res = await fetch("/api/newsletter/subscribe", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, consent, source: "newsletter-page" }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
setError(data.reason || "Anmeldung ist fehlgeschlagen. Bitte versuche es später erneut.");
|
||||
setStatus("error");
|
||||
return;
|
||||
}
|
||||
setStatus("success");
|
||||
} catch {
|
||||
setError("Anmeldung ist fehlgeschlagen. Bitte versuche es später erneut.");
|
||||
setStatus("error");
|
||||
}
|
||||
}
|
||||
const { email, emailError, consent, setConsent, status, error, emailRef, handleEmailChange, handleEmailBlur, handleSubmit } =
|
||||
useNewsletterSignup("newsletter-page");
|
||||
|
||||
return (
|
||||
<section className="py-16 w-full">
|
||||
@@ -112,12 +88,17 @@ export function Newsletter({
|
||||
{/* Input + submit button — stacked below md, side by side from md+ */}
|
||||
<div className="flex flex-col md:flex-row gap-4 items-stretch w-full">
|
||||
<input
|
||||
ref={emailRef}
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onChange={(e) => handleEmailChange(e.target.value)}
|
||||
onBlur={(e) => handleEmailBlur(e.target.value)}
|
||||
placeholder="Deine E-Mail-Adresse"
|
||||
className="flex-1 min-w-0 bg-bg-white border border-border rounded-sm px-6 py-3 text-body text-text-muted font-normal outline-none focus:border-brand transition-colors"
|
||||
aria-invalid={Boolean(emailError)}
|
||||
className={`flex-1 min-w-0 bg-bg-white border rounded-sm px-6 py-3 text-body text-text-muted font-normal outline-none transition-colors ${
|
||||
emailError ? "border-red-600 focus:border-red-600" : "border-border focus:border-brand"
|
||||
}`}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -127,6 +108,9 @@ export function Newsletter({
|
||||
{status === "submitting" ? "Wird gesendet…" : "Jetzt anmelden"}
|
||||
</button>
|
||||
</div>
|
||||
{emailError && (
|
||||
<p className="text-label text-red-600 font-normal -mt-2">{emailError}</p>
|
||||
)}
|
||||
|
||||
{/* Consent checkbox — required since this signup's legal
|
||||
basis is consent (email marketing), not the "Ich achte
|
||||
|
||||
Reference in New Issue
Block a user