Fix broken email-preview rendering, add payment status to order list, second sample state
- LiveEmailPreviewClient.tsx rendered the email HTML (a full <body>... fragment) via dangerouslySetInnerHTML into a plain div, nesting it inside the page's own already-existing <body> — invalid HTML the browser silently mangled. Switched to an <iframe srcDoc>, giving the email its own real document context, exactly like an actual email client would render it. - Added SAMPLE_ORDER_MANUAL + a toggle in the preview so both order-confirmation states (paid vs. Vorkasse/Überweisung, incl. the new "switch to Kreditkarte/PayPal" mention) are actually visible in the admin's Live Preview — SAMPLE_ORDER alone always had isManualPayment: false, so the Vorkasse branch was never previewable at all before this. - /konto/bestellungen (the order list, not just the single-order detail page) now also shows the payment status badge next to the existing fulfillment status one — CustomerOrder was missing paymentStatus entirely.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useLivePreview } from "@payloadcms/live-preview-react";
|
||||
import {
|
||||
renderOrderConfirmationHtml,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
renderOrderStatusHtml,
|
||||
ORDER_STATUS_EMAIL_ICON,
|
||||
SAMPLE_ORDER,
|
||||
SAMPLE_ORDER_MANUAL,
|
||||
type EmailTemplateContent,
|
||||
} from "../../../lib/emailTemplates";
|
||||
import type { EmailTemplateType } from "../../../lib/payload";
|
||||
@@ -34,13 +36,19 @@ export function LiveEmailPreviewClient({
|
||||
depth: 0,
|
||||
});
|
||||
|
||||
// Only order-confirmation has two meaningfully different rendered
|
||||
// states (isManualPayment true/false change which blocks show at all,
|
||||
// not just text) — every other type has one sample and no toggle.
|
||||
const [sampleVariant, setSampleVariant] = useState<"paid" | "manual">("paid");
|
||||
const orderSample = sampleVariant === "paid" ? SAMPLE_ORDER : SAMPLE_ORDER_MANUAL;
|
||||
|
||||
// No real company-settings fetch in this preview context — passing null
|
||||
// falls back to DEFAULT_LEGAL_FOOTER_LINES (placeholder Anbieterkennzeichnung)
|
||||
// inside buildLegalFooterLines(), same shape as the real send just with
|
||||
// placeholder business data.
|
||||
const html =
|
||||
type === "order-confirmation"
|
||||
? renderOrderConfirmationHtml(data, SAMPLE_ORDER, null)
|
||||
? renderOrderConfirmationHtml(data, orderSample, null)
|
||||
: type === "password-reset"
|
||||
? renderPasswordResetHtml(data, "https://einfach-produktiv.mk360.de/konto/passwort-zuruecksetzen?token=beispiel-token", null)
|
||||
: renderOrderStatusHtml(
|
||||
@@ -53,7 +61,54 @@ export function LiveEmailPreviewClient({
|
||||
|
||||
return (
|
||||
<div style={{ background: "#f4f2ee", minHeight: "100vh", padding: "32px 0" }}>
|
||||
<div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
{type === "order-confirmation" && (
|
||||
<div style={{ display: "flex", justifyContent: "center", gap: 8, marginBottom: 16 }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSampleVariant("paid")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: 999,
|
||||
border: "1px solid #d1cec4",
|
||||
background: sampleVariant === "paid" ? "#f6a701" : "#fff",
|
||||
fontWeight: 700,
|
||||
fontSize: 13,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Online bezahlt
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSampleVariant("manual")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: 999,
|
||||
border: "1px solid #d1cec4",
|
||||
background: sampleVariant === "manual" ? "#f6a701" : "#fff",
|
||||
fontWeight: 700,
|
||||
fontSize: 13,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Vorkasse (Überweisung)
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* An iframe, not dangerouslySetInnerHTML into a plain div — `html`
|
||||
here is a full `<body>...</body>` fragment (see emailTemplates.ts's
|
||||
emailShell()), meant to become an actual email document. Dropped
|
||||
directly into this page's own already-existing <body> via
|
||||
dangerouslySetInnerHTML, that's a nested <body> tag — invalid
|
||||
HTML the browser "fixes" unpredictably, which is why this preview
|
||||
used to render broken (wrong background/padding/font, inline
|
||||
styles not applying). An iframe gives the email HTML its own
|
||||
real document, exactly like an actual email client would. */}
|
||||
<iframe
|
||||
srcDoc={`<!DOCTYPE html><html>${html}</html>`}
|
||||
title="E-Mail-Vorschau"
|
||||
style={{ width: "100%", height: "100vh", border: "none", display: "block" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Footer } from "../../components/Footer";
|
||||
import { formatPrice, formatDate } from "../../lib/format";
|
||||
import { getSessionCustomer, getCustomerOrders } from "../../lib/customerAuth";
|
||||
import { OrderStatusBadge } from "../components/OrderStatusBadge";
|
||||
import { PaymentStatusBadge } from "../components/PaymentStatusBadge";
|
||||
import { LogoutButton } from "../components/LogoutButton";
|
||||
|
||||
// robots: noindex — account area, same reasoning as /checkout.
|
||||
@@ -61,6 +62,10 @@ export default async function KontoBestellungenPage() {
|
||||
<p className="text-label text-text-muted">Status</p>
|
||||
<OrderStatusBadge status={order.status} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-label text-text-muted">Zahlungsstatus</p>
|
||||
<PaymentStatusBadge paymentStatus={order.paymentStatus} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 ml-auto">
|
||||
<p className="text-label text-text-muted">Gesamtbetrag</p>
|
||||
<p className="font-bold text-body-sm text-text-primary">{formatPrice(order.total)}</p>
|
||||
|
||||
+10
-1
@@ -464,6 +464,7 @@ export type CustomerOrder = {
|
||||
createdAt: string;
|
||||
total: number;
|
||||
status: string;
|
||||
paymentStatus: "not_applicable" | "pending" | "paid" | "failed" | "refunded" | "partially_refunded";
|
||||
itemCount: number;
|
||||
/** Raw product relationship ids, in item order — depth=0 keeps them as
|
||||
* plain numbers, not populated objects. Callers resolve these to image
|
||||
@@ -509,13 +510,21 @@ export async function getCustomerOrders(token: string, customerId: number, exclu
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const data: {
|
||||
docs?: { orderNumber: string; createdAt: string; total: number; status: string; items: { product: number }[] }[];
|
||||
docs?: {
|
||||
orderNumber: string;
|
||||
createdAt: string;
|
||||
total: number;
|
||||
status: string;
|
||||
paymentStatus: CustomerOrder["paymentStatus"];
|
||||
items: { product: number }[];
|
||||
}[];
|
||||
} = await res.json();
|
||||
return (data.docs ?? []).map((doc) => ({
|
||||
orderNumber: doc.orderNumber,
|
||||
createdAt: doc.createdAt,
|
||||
total: doc.total,
|
||||
status: doc.status,
|
||||
paymentStatus: doc.paymentStatus,
|
||||
itemCount: doc.items.length,
|
||||
productIds: doc.items.map((item) => item.product),
|
||||
}));
|
||||
|
||||
@@ -246,6 +246,17 @@ export const SAMPLE_ORDER: OrderConfirmationData = {
|
||||
isManualPayment: false,
|
||||
};
|
||||
|
||||
// Second preview fixture — demonstrates the Vorkasse/Überweisung branch
|
||||
// (vorkasseNotice(), incl. the "switch to Kreditkarte/PayPal" mention)
|
||||
// that SAMPLE_ORDER's own isManualPayment: false never shows. Used by
|
||||
// LiveEmailPreviewClient.tsx's toggle, not by any real send — a real
|
||||
// order-confirmation email always computes both flags live per order.
|
||||
export const SAMPLE_ORDER_MANUAL: OrderConfirmationData = {
|
||||
...SAMPLE_ORDER,
|
||||
isManualPayment: true,
|
||||
hasOnlinePaymentOption: true,
|
||||
};
|
||||
|
||||
export function renderOrderConfirmationHtml(template: EmailTemplateContent, order: OrderConfirmationData, seller: CompanySettings | null): string {
|
||||
const rows = order.items
|
||||
.map(
|
||||
|
||||
Reference in New Issue
Block a user