Add schema.org structured data, Vorkasse email notice, newsletter duplicate detection

- Organization (site-wide), Product (/todo-cards), BlogPosting (every
  /blog/[slug]) JSON-LD via new app/lib/structuredData.ts — no new
  Payload fields needed, derived from existing data. Verified locally
  by curling each page and checking the rendered script tag.
- Order confirmation email gains the same "please transfer to this
  account, processed after payment received" notice the invoice PDF
  already had for Vorkasse orders — OrderConfirmationData's new
  isManualPayment flag is set explicitly by each caller (never derived
  from paymentMethodTitle, which already broke once this session after
  a payment-methods rename). CompanySettings gains bankName (existed on
  the backend, was missing from the frontend's type/usage).
- Newsletter signup now detects an already-subscribed email
  (verified empirically: Brevo's doubleOptinConfirmation endpoint gives
  identical 201 responses for new vs. already-confirmed contacts) via a
  GET /v3/contacts/{email} pre-check, and shows a distinct message
  instead of silently resending the confirmation mail. Success message
  text centralized in useNewsletterSignup.ts instead of duplicated
  across 4 forms.
- Bumped @einfach-produktiv/invoicing to the version with the
  unpaid-notice layout fix (full width, more top spacing — was
  squeezed into the narrow paid-badge column).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-25 17:11:48 +00:00
parent 1dbc0c31ff
commit 1706da8598
17 changed files with 295 additions and 31 deletions
+10 -1
View File
@@ -9,12 +9,20 @@ 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.
const SUCCESS_MESSAGE = "Fast geschafft! Schau kurz in dein Postfach da wartet schon eine Mail von uns.";
const ALREADY_SUBSCRIBED_MESSAGE = "Diese E-Mail-Adresse ist schon für unseren Newsletter angemeldet.";
export function useNewsletterSignup(source: NewsletterOptInSource) {
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState("");
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) {
@@ -48,6 +56,7 @@ export function useNewsletterSignup(source: NewsletterOptInSource) {
setStatus("error");
return;
}
setSuccessMessage(data.alreadySubscribed ? ALREADY_SUBSCRIBED_MESSAGE : SUCCESS_MESSAGE);
setStatus("success");
} catch {
setError("Anmeldung ist fehlgeschlagen. Bitte versuche es später erneut.");
@@ -55,5 +64,5 @@ export function useNewsletterSignup(source: NewsletterOptInSource) {
}
}
return { email, emailError, consent, setConsent, status, error, emailRef, handleEmailChange, handleEmailBlur, handleSubmit };
return { email, emailError, consent, setConsent, status, error, successMessage, emailRef, handleEmailChange, handleEmailBlur, handleSubmit };
}