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:
@@ -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
|
||||
|
||||
@@ -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<HTMLInputElement>(null);
|
||||
|
||||
function handleEmailChange(value: string) {
|
||||
@@ -56,7 +58,11 @@ export function useNewsletterSignup(source: NewsletterOptInSource) {
|
||||
setStatus("error");
|
||||
return;
|
||||
}
|
||||
setSuccessMessage(data.alreadySubscribed ? ALREADY_SUBSCRIBED_MESSAGE : SUCCESS_MESSAGE);
|
||||
if (data.alreadySubscribed) {
|
||||
setError(ALREADY_SUBSCRIBED_MESSAGE);
|
||||
setStatus("error");
|
||||
return;
|
||||
}
|
||||
setStatus("success");
|
||||
} catch {
|
||||
setError("Anmeldung ist fehlgeschlagen. Bitte versuche es später erneut.");
|
||||
@@ -64,5 +70,5 @@ export function useNewsletterSignup(source: NewsletterOptInSource) {
|
||||
}
|
||||
}
|
||||
|
||||
return { email, emailError, consent, setConsent, status, error, successMessage, emailRef, handleEmailChange, handleEmailBlur, handleSubmit };
|
||||
return { email, emailError, consent, setConsent, status, error, successMessage: SUCCESS_MESSAGE, emailRef, handleEmailChange, handleEmailBlur, handleSubmit };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user