import React from "react"; import { Document, Page, View, Text, StyleSheet, renderToBuffer } from "@react-pdf/renderer"; import { formatPrice, formatDate } from "./format"; // Generated synchronously in the checkout request (see app/lib/orderEmail.ts) // and attached to the order-confirmation email, plus available on-demand via // app/api/account/orders/[orderNumber]/invoice/route.ts — same render // function both times, so a re-download always matches what was emailed. // // Built-in Helvetica, not a registered web font — this renders inside the // checkout request's own fire-and-forget email step; a font-fetch failure // there is one more way to lose the invoice attachment for no real design // benefit. Same choice as the Payload-side correction-invoice PDF // (src/lib/correctionInvoicePdf.tsx in the backend repo) — brand color via // StyleSheet, not Georgia/Playfair. const BRAND = "#f6a701"; const TEXT_MUTED = "#6b6b69"; const BORDER = "#e5e0d8"; const styles = StyleSheet.create({ page: { padding: 48, fontSize: 10, fontFamily: "Helvetica", color: "#1a1a18" }, wordmark: { fontFamily: "Helvetica-Bold", fontSize: 13, marginBottom: 32 }, kindLabel: { fontFamily: "Helvetica-Bold", fontSize: 20, color: BRAND, marginBottom: 24 }, addressRow: { flexDirection: "row", justifyContent: "space-between", marginBottom: 28 }, addressBlock: { width: "45%" }, addressLabel: { fontSize: 8, color: TEXT_MUTED, marginBottom: 4, textTransform: "uppercase" }, addressLine: { fontSize: 10, lineHeight: 1.5 }, metaRow: { flexDirection: "row", justifyContent: "space-between", marginBottom: 20 }, metaLabel: { fontSize: 8, color: TEXT_MUTED }, metaValue: { fontSize: 10, fontFamily: "Helvetica-Bold" }, table: { marginTop: 8, borderTopWidth: 1, borderTopColor: BORDER }, tableHeader: { flexDirection: "row", borderBottomWidth: 1, borderBottomColor: BORDER, paddingVertical: 6 }, tableRow: { flexDirection: "row", borderBottomWidth: 1, borderBottomColor: BORDER, paddingVertical: 6 }, colName: { flex: 3 }, colQty: { flex: 1, textAlign: "right" }, colPrice: { flex: 1, textAlign: "right" }, colTotal: { flex: 1, textAlign: "right" }, headerCell: { fontSize: 8, color: TEXT_MUTED, textTransform: "uppercase" }, summary: { marginTop: 16, alignItems: "flex-end" }, summaryRow: { flexDirection: "row", width: 220, justifyContent: "space-between", paddingVertical: 2 }, summaryLabel: { fontSize: 10, color: TEXT_MUTED }, summaryValue: { fontSize: 10 }, grandTotalRow: { flexDirection: "row", width: 220, justifyContent: "space-between", paddingTop: 8, marginTop: 6, borderTopWidth: 1, borderTopColor: BORDER, }, grandTotalLabel: { fontSize: 12, fontFamily: "Helvetica-Bold" }, grandTotalValue: { fontSize: 12, fontFamily: "Helvetica-Bold" }, footer: { position: "absolute", bottom: 40, left: 48, right: 48, fontSize: 8, color: TEXT_MUTED, borderTopWidth: 1, borderTopColor: BORDER, paddingTop: 12, }, }); export type InvoiceItem = { productName: string; quantity: number; unitPrice: number }; export type InvoiceOrder = { orderNumber: string; invoiceNumber: string; invoiceIssuedAt: string; customerFirstName: string; customerLastName: string; deliveryMethod: "address" | "packstation"; street?: string | null; packstationNumber?: string | null; postNumber?: string | null; zip: string; city: string; country: string; items: InvoiceItem[]; subtotal: number; shippingCost: number; discountAmount: number; discountCode: string | null; total: number; }; export type InvoiceSeller = { sellerName: string; sellerStreet: string; sellerZip: string; sellerCity: string; sellerCountry: string; sellerEmail: string; vatId: string; taxRatePercent: number; bankDetails?: string | null; }; function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller: InvoiceSeller }) { const rate = seller.taxRatePercent; const grossTotal = order.total; const netTotal = grossTotal / (1 + rate / 100); const taxTotal = grossTotal - netTotal; const deliveryLine = order.deliveryMethod === "address" ? order.street : `Packstation ${order.packstationNumber} · Postnummer ${order.postNumber}`; return ( einfach produktiv. Rechnung Von {seller.sellerName} {seller.sellerStreet} {seller.sellerZip} {seller.sellerCity} {seller.sellerCountry} An {order.customerFirstName} {order.customerLastName} {deliveryLine} {order.zip} {order.city} {order.country} Rechnungs-Nr. {order.invoiceNumber} Datum {formatDate(order.invoiceIssuedAt)} Bestellnummer {order.orderNumber} USt-IdNr. {seller.vatId} Artikel Menge Einzelpreis Betrag {order.items.map((item, i) => ( {item.productName} {item.quantity} {formatPrice(item.unitPrice)} {formatPrice(item.quantity * item.unitPrice)} ))} Zwischensumme {formatPrice(order.subtotal)} {order.discountAmount > 0 && ( Rabatt{order.discountCode ? ` (${order.discountCode})` : ""} -{formatPrice(order.discountAmount)} )} Versand {order.shippingCost === 0 ? "Kostenlos" : formatPrice(order.shippingCost)} Netto {formatPrice(netTotal)} zzgl. {rate}% MwSt. {formatPrice(taxTotal)} Gesamt {formatPrice(grossTotal)} {seller.sellerName} · {seller.sellerStreet}, {seller.sellerZip} {seller.sellerCity} · {seller.sellerEmail} · USt-IdNr.{" "} {seller.vatId} {seller.bankDetails ? {seller.bankDetails} : null} ); } export async function renderInvoicePdf(order: InvoiceOrder, seller: InvoiceSeller): Promise { return renderToBuffer(); }