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:
@@ -66,6 +66,29 @@ function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
// Vorkasse (Überweisung/manual) instruction — the invoice PDF already
|
||||
// shows this same information (see @einfach-produktiv/invoicing's own
|
||||
// unpaidNoticeText), but a customer often only glances at the email body
|
||||
// itself, not the attached PDF, so it's repeated here in plain text too.
|
||||
// Own full-width block, margin-top matching the other section gaps in
|
||||
// this template (16px) — not squeezed into the narrow Gesamtsumme table
|
||||
// like an initial draft of the invoice version was before that got
|
||||
// widened per feedback.
|
||||
function vorkasseNotice(orderNumber: string, seller: CompanySettings | null): string {
|
||||
const bankLine = seller && (seller.iban || seller.bic)
|
||||
? [seller.bankName, seller.iban && `IBAN ${seller.iban}`, seller.bic && `BIC ${seller.bic}`].filter(Boolean).join(" · ")
|
||||
: null;
|
||||
return `
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin-top:20px;background:${BG_MUTED};border-radius:8px;padding:16px 20px;">
|
||||
<tr>
|
||||
<td style="font-size:13px;line-height:1.6;color:${TEXT_MUTED};text-align:center;">
|
||||
Bitte überweise den Rechnungsbetrag unter Angabe der Bestellnummer ${escapeHtml(orderNumber)}${bankLine ? ` auf folgende Bankverbindung: <strong style="color:${TEXT_PRIMARY};">${escapeHtml(bankLine)}</strong>` : " auf die dir genannte Bankverbindung"}. Deine Bestellung wird nach Zahlungseingang bearbeitet (in der Regel innerhalb von 1–2 Werktagen).
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
// Live-Preview-only fallback (no real order/company-settings fetch there,
|
||||
// see /email-preview/[type]) — the actual send always passes the real
|
||||
// seller (company-settings) through buildLegalFooterLines() below. Email
|
||||
@@ -181,6 +204,14 @@ export type OrderConfirmationData = {
|
||||
discountAmount: number;
|
||||
discountCode: string | null;
|
||||
total: number;
|
||||
// Explicit boolean set by each caller (checkout route's manual branch:
|
||||
// true; the Stripe webhook path: always false, since only a *paid*
|
||||
// Stripe order ever reaches this send) — not derived from
|
||||
// paymentMethodTitle here, since that string ("Online-Zahlung",
|
||||
// "Kreditkarte", "Überweisung (Vorkasse)", ...) is exactly the kind of
|
||||
// fragile thing a payment-methods rename already broke once this
|
||||
// session (see @einfach-produktiv/invoicing's isPaidImmediately()).
|
||||
isManualPayment: boolean;
|
||||
};
|
||||
|
||||
export const SAMPLE_ORDER: OrderConfirmationData = {
|
||||
@@ -202,6 +233,7 @@ export const SAMPLE_ORDER: OrderConfirmationData = {
|
||||
discountAmount: 5,
|
||||
discountCode: "WILLKOMMEN10",
|
||||
total: 37.7,
|
||||
isManualPayment: false,
|
||||
};
|
||||
|
||||
export function renderOrderConfirmationHtml(template: EmailTemplateContent, order: OrderConfirmationData, seller: CompanySettings | null): string {
|
||||
@@ -263,6 +295,7 @@ export function renderOrderConfirmationHtml(template: EmailTemplateContent, orde
|
||||
</tr>
|
||||
${taxRows}
|
||||
</table>
|
||||
${order.isManualPayment ? vorkasseNotice(order.orderNumber, seller) : ""}
|
||||
`;
|
||||
|
||||
return emailShell("✓", escapeHtml(template.heading), body, template.footerText, buildLegalFooterLines(seller));
|
||||
|
||||
Reference in New Issue
Block a user