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();
}