Route already-subscribed through the error state, not success

Per explicit feedback: swapping the whole form out for a bare success
message felt wrong for "you're already signed up, nothing to do" — the
form stays visible with a small red note below it instead, same as
every other inline validation error. No per-form UI changes needed,
they already render the error state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-25 17:27:50 +00:00
parent bccfc28012
commit fd98f68ba6
2 changed files with 37 additions and 17 deletions
+24 -10
View File
@@ -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" && <p className="text-red-600
...">{error}</p>}`).
- **`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