1706da8598
- 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>
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
"use client";
|
||
|
||
import { useRef, useState, type FormEvent } from "react";
|
||
import { validateEmailFormat } from "./email";
|
||
import type { NewsletterOptInSource } from "./brevo";
|
||
|
||
// Shared state/submit logic behind every newsletter-signup form
|
||
// (Newsletter.tsx, NewsletterModal.tsx, WeeklyImpulsesHero.tsx's inline
|
||
// 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) {
|
||
setEmail(value);
|
||
if (emailError) setEmailError("");
|
||
}
|
||
|
||
function handleEmailBlur(value: string) {
|
||
setEmailError(validateEmailFormat(value));
|
||
}
|
||
|
||
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
||
e.preventDefault();
|
||
const formatError = validateEmailFormat(email);
|
||
setEmailError(formatError);
|
||
if (formatError) {
|
||
emailRef.current?.focus();
|
||
return;
|
||
}
|
||
setStatus("submitting");
|
||
setError("");
|
||
try {
|
||
const res = await fetch("/api/newsletter/subscribe", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ email, consent, source }),
|
||
});
|
||
const data = await res.json();
|
||
if (!data.ok) {
|
||
setError(data.reason || "Anmeldung ist fehlgeschlagen. Bitte versuche es später erneut.");
|
||
setStatus("error");
|
||
return;
|
||
}
|
||
setSuccessMessage(data.alreadySubscribed ? ALREADY_SUBSCRIBED_MESSAGE : SUCCESS_MESSAGE);
|
||
setStatus("success");
|
||
} catch {
|
||
setError("Anmeldung ist fehlgeschlagen. Bitte versuche es später erneut.");
|
||
setStatus("error");
|
||
}
|
||
}
|
||
|
||
return { email, emailError, consent, setConsent, status, error, successMessage, emailRef, handleEmailChange, handleEmailBlur, handleSubmit };
|
||
}
|