diff --git a/README.md b/README.md index 2decacf..f92db2b 100644 --- a/README.md +++ b/README.md @@ -1175,21 +1175,35 @@ unsynced. — a contact's `listIds` on Brevo is only populated once double opt-in actually confirms, never for a merely-requested one, so its presence is a reliable signal. If already subscribed: skips the resend entirely and - returns `{ ok: true, alreadySubscribed: true }` instead, which the UI - (`useNewsletterSignup.ts`) surfaces as a different message ("Diese - E-Mail-Adresse ist schon für unseren Newsletter angemeldet.") instead of - the normal "check your inbox" copy. The check fails open (any error → - proceed to the normal signup flow) — it's a UX nicety, never a reason to - block a real signup. + returns `{ ok: true, alreadySubscribed: true }` instead. The check fails + open (any error → proceed to the normal signup flow) — it's a UX + nicety, never a reason to block a real signup. **Routed through the + existing error state, not a success variant** — per explicit feedback, + swapping the whole form out for a bare message (the real-success + treatment) felt wrong for "you're already signed up, nothing to do"; the + form stays visible with a small red note below it instead, exactly like + every other inline validation error (`useNewsletterSignup.ts` sets + `status: "error"`, `error: "Diese E-Mail-Adresse ist schon für unseren + Newsletter angemeldet."` — no new UI needed in any of the 4 forms, they + already render `{status === "error" &&
{error}
}`). - **`app/lib/useNewsletterSignup.ts`** — the shared email/consent/submit state + on-blur validation + refocus-on-invalid-submit behind all four forms (same "state of the art, simple" input-quality bar as checkout's own fields). Each form keeps its own markup/visual style (`Newsletter`'s panel layout, `/challenge`'s hardcoded-hex-color palette, etc.) — only - the logic is shared, not a one-size-fits-all component. The success - message text itself also now lives here (`successMessage`), not - hardcoded 4 times per form — one of two variants depending on - `alreadySubscribed`. + the logic is shared, not a one-size-fits-all component. The real-success + message text also now lives here (`successMessage`) instead of + hardcoded 4 times per form. +- **A misconfigured `BREVO_LIST_ID` in Coolify (`2` instead of `5`) broke + every newsletter signup in production for a stretch of time** — + discovered and fixed 2026-07-25 while testing the already-subscribed + feature above. The generic customer-facing error message ("Anmeldung ist + fehlgeschlagen...") gave no hint why; `upsertNewsletterContact()`'s real + `reason` was silently discarded by `/api/newsletter/subscribe/route.ts` + before this, now `console.error`'d server-side (message still stays + generic to the customer — never leak Brevo's internal error text, just + no longer *undiagnosable*). - **`app/lib/email.ts`** — `isValidEmail()`/`validateEmailFormat()`, the single plain-email-format check shared by every newsletter form *and* checkout's own email field (previously duplicated between diff --git a/app/lib/useNewsletterSignup.ts b/app/lib/useNewsletterSignup.ts index 7a56c18..0560970 100644 --- a/app/lib/useNewsletterSignup.ts +++ b/app/lib/useNewsletterSignup.ts @@ -9,11 +9,14 @@ import type { NewsletterOptInSource } from "./brevo"; // hero form, /challenge's EmailCapture) — four places with the same // email+consent+submit shape but different markup/visual style, so only // the logic is shared here rather than a one-size-fits-all component. -// Same two sentences every form already showed hardcoded (per the user's -// own explicit wording request, see the newsletter-DOI memory) — kept -// here once instead of duplicated across all 4 forms now that a second -// variant (already subscribed) needs the same treatment. +// Per the user's own explicit wording request, see the newsletter-DOI +// memory — kept here once rather than duplicated across all 4 forms. const SUCCESS_MESSAGE = "Fast geschafft! Schau kurz in dein Postfach – da wartet schon eine Mail von uns."; +// Deliberately routed through the *error* state, not a success variant — +// per explicit feedback: swapping the whole form out for a bare message +// (the real-success treatment) felt wrong for "you're already signed up, +// nothing to do" — the form should stay visible, with a small note below +// it, exactly like every other inline validation error already does. const ALREADY_SUBSCRIBED_MESSAGE = "Diese E-Mail-Adresse ist schon für unseren Newsletter angemeldet."; export function useNewsletterSignup(source: NewsletterOptInSource) { @@ -22,7 +25,6 @@ export function useNewsletterSignup(source: NewsletterOptInSource) { const [consent, setConsent] = useState(false); const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle"); const [error, setError] = useState(""); - const [successMessage, setSuccessMessage] = useState(SUCCESS_MESSAGE); const emailRef = useRef