diff --git a/app/agb/components/VertragspartnerBlock.tsx b/app/agb/components/VertragspartnerBlock.tsx
new file mode 100644
index 0000000..f3a0241
--- /dev/null
+++ b/app/agb/components/VertragspartnerBlock.tsx
@@ -0,0 +1,59 @@
+import { headingId } from "../../components/RichText";
+import type { CompanySettings } from "../../lib/payload";
+import type { TOCSection } from "../../components/SectionTOC";
+
+// Renders "2. Vertragspartner" straight from company-settings, same
+// single-source-of-truth pattern as Impressum's AnbieterAngaben.tsx and
+// Datenschutz's VerantwortlicherBlock.tsx — this used to be hand-typed
+// name/address/email baked into the AGB richText (seed-agb.ts on the
+// Payload side), and had already drifted from the real company-settings
+// values once (the richText's placeholder "Björn Wendt"/"Musterstraße
+// 12" never got updated when the real address was set). Sits mid-document
+// (section 1 "Geltungsbereich" comes before it), which is why AGB's
+// content is split into `content` (section 1) + `contentPart2` (sections
+// 3 onward) rather than just prepending this block like Datenschutz did —
+// see LegalPages.ts's `contentPart2` field comment.
+export function vertragspartnerHeadings(): TOCSection[] {
+ return [{ id: headingId("2. Vertragspartner"), title: "2. Vertragspartner" }];
+}
+
+function Heading({ children }: { children: string }) {
+ return (
+
+
{page ? (
- isPreview ?
:
+ <>
+ {isPreview ?
:
}
+ {/* Name/Adresse/E-Mail kommen direkt aus company-settings,
+ nicht aus der CMS-Richtext — single-sourced, gleiche
+ Begründung wie Impressum/Datenschutz. */}
+ {seller &&
}
+ {page.contentPart2 ? (
+ isPreview ?
:
+ ) : null}
+ >
) : (
Inhalte werden gerade aktualisiert.
)}
diff --git a/app/cart/components/CartContent.tsx b/app/cart/components/CartContent.tsx
index 84ecaee..f182d81 100644
--- a/app/cart/components/CartContent.tsx
+++ b/app/cart/components/CartContent.tsx
@@ -524,7 +524,13 @@ export function CartContent({
)}
-
setVersandOpen(false)} shipping={shippingSettings} />
+ setVersandOpen(false)}
+ shipping={shippingSettings}
+ shippingCost={shippingCost}
+ freeShippingThreshold={freeShippingThreshold}
+ />
>
);
}
diff --git a/app/checkout/components/CheckoutContent.tsx b/app/checkout/components/CheckoutContent.tsx
index f0be638..e0f911d 100644
--- a/app/checkout/components/CheckoutContent.tsx
+++ b/app/checkout/components/CheckoutContent.tsx
@@ -397,6 +397,11 @@ export function CheckoutContent({
selectedShipping?.freeShippingThreshold !== undefined &&
subtotal >= selectedShipping.freeShippingThreshold;
const shipping = items.length === 0 || !cartHasShippableItem(items) || freeShipping ? 0 : selectedShipping?.price ?? 0;
+ // For VersandModal — same "lowest freeShippingThreshold among active
+ // methods" rule as CartContent.tsx/getTrustBadges(), independent of
+ // whichever method is currently selected in the radio list above.
+ const shippingMethodThresholds = shippingMethods.map((m) => m.freeShippingThreshold).filter((t): t is number => t !== null);
+ const lowestFreeShippingThreshold = shippingMethodThresholds.length > 0 ? Math.min(...shippingMethodThresholds) : null;
const { totalSavings, discountAmount, total } = computeCartTotals(items, shipping, discount);
const taxBreakdown = computeTaxBreakdown(
items.map(({ entry, product }) => ({
@@ -1551,7 +1556,13 @@ export function CheckoutContent({
- setVersandOpen(false)} shipping={shippingSettings} />
+ setVersandOpen(false)}
+ shipping={shippingSettings}
+ shippingCost={shippingMethods[0]?.price ?? 0}
+ freeShippingThreshold={lowestFreeShippingThreshold}
+ />
>
);
}
diff --git a/app/components/VersandModal.tsx b/app/components/VersandModal.tsx
index 8f5618e..711889f 100644
--- a/app/components/VersandModal.tsx
+++ b/app/components/VersandModal.tsx
@@ -17,10 +17,14 @@ export function VersandModal({
open,
onClose,
shipping,
+ shippingCost,
+ freeShippingThreshold,
}: {
open: boolean;
onClose: () => void;
shipping: ShippingSettings;
+ shippingCost: number;
+ freeShippingThreshold: number | null;
}) {
const dialogRef = useRef(null);
const closeButtonRef = useRef(null);
@@ -109,7 +113,7 @@ export function VersandModal({
-
+
diff --git a/app/lib/payload.ts b/app/lib/payload.ts
index 28acf5f..aab61c5 100644
--- a/app/lib/payload.ts
+++ b/app/lib/payload.ts
@@ -855,6 +855,12 @@ export type LegalPage = {
type: LegalPageType;
title: string;
content: unknown;
+ // Only populated for pages that need a dynamically-rendered section
+ // MID-document (currently just AGB's "Vertragspartner", sourced from
+ // company-settings — see VertragspartnerBlock.tsx). null everywhere
+ // else, including pages like Datenschutz whose one dynamic section sits
+ // at the very start and so can just be prepended instead of split.
+ contentPart2: unknown | null;
attachment: { url: string; title: string } | null;
updatedAt: string;
};
@@ -863,6 +869,7 @@ type PayloadLegalPage = {
type: LegalPageType;
title: string;
content: unknown;
+ contentPart2?: unknown;
attachment: { url: string; title: string } | number | null;
updatedAt: string;
};
@@ -888,6 +895,7 @@ export async function getLegalPage(type: LegalPageType, options?: { draft?: bool
type: doc.type,
title: doc.title,
content: doc.content,
+ contentPart2: doc.contentPart2 ?? null,
attachment:
typeof doc.attachment === "object" && doc.attachment
? { url: doc.attachment.url, title: doc.attachment.title }
diff --git a/app/lib/shipping.ts b/app/lib/shipping.ts
deleted file mode 100644
index 0b169b8..0000000
--- a/app/lib/shipping.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// Single source of truth for shipping numbers — consumed by both the
-// cart's order summary (CartContent.tsx) and the /versand policy page, so
-// the two can never drift apart.
-//
-// The delivery-timeframe numbers (handling/transit days, Art. 246a § 1
-// Abs. 1 Nr. 8 EGBGB) used to live here too, but now come from Payload's
-// Shipping Settings collection instead (see lib/payload.ts's
-// getShippingSettings()) — that duplication is exactly why the
-// ShippingMethods CMS title once carried a different, drifted day-range
-// than this file did. Cost/threshold below have the same drift risk
-// against Payload's ShippingMethods price/freeShippingThreshold fields,
-// not yet addressed here.
-export const SHIPPING_COST = 2.9;
-export const FREE_SHIPPING_THRESHOLD = 39;
diff --git a/app/versand/components/VersandSections.tsx b/app/versand/components/VersandSections.tsx
index ee062ba..a3a6751 100644
--- a/app/versand/components/VersandSections.tsx
+++ b/app/versand/components/VersandSections.tsx
@@ -1,5 +1,4 @@
import Link from "next/link";
-import { SHIPPING_COST, FREE_SHIPPING_THRESHOLD } from "../../lib/shipping";
import { formatPrice } from "../../lib/format";
import type { ShippingSettings } from "../../lib/payload";
@@ -49,9 +48,21 @@ function Section({
export function VersandSections({
withAnchors = false,
shipping,
+ shippingCost,
+ freeShippingThreshold,
}: {
withAnchors?: boolean;
shipping: ShippingSettings;
+ /** Price of the default (first active, i.e. Standard) ShippingMethod —
+ * same value/estimate CartContent.tsx already computes for its own
+ * order-summary line, threaded down here instead of this component
+ * having its own hardcoded lib/shipping.ts constant, which is exactly
+ * what let this text drift from the real Payload ShippingMethods price
+ * (see lib/shipping.ts's own comment on this). */
+ shippingCost: number;
+ /** Lowest freeShippingThreshold among active ShippingMethods, or null if
+ * none has one — same rule CartContent.tsx/getTrustBadges() already use. */
+ freeShippingThreshold: number | null;
}) {
return (
@@ -72,9 +83,9 @@ export function VersandSections({
- Die Versandkosten innerhalb Deutschlands betragen pauschal {formatPrice(SHIPPING_COST)}{" "}
- pro Bestellung. Ab einem Bestellwert von {formatPrice(FREE_SHIPPING_THRESHOLD)} versenden
- wir kostenlos.
+ Die Versandkosten innerhalb Deutschlands betragen pauschal {formatPrice(shippingCost)}{" "}
+ pro Bestellung.
+ {freeShippingThreshold !== null && ` Ab einem Bestellwert von ${formatPrice(freeShippingThreshold)} versenden wir kostenlos.`}
Alle angegebenen Preise verstehen sich inklusive der gesetzlichen Mehrwertsteuer.
diff --git a/app/versand/page.tsx b/app/versand/page.tsx
index aefc95b..db5693f 100644
--- a/app/versand/page.tsx
+++ b/app/versand/page.tsx
@@ -4,7 +4,7 @@ import { Reveal } from "../components/Reveal";
import { Footer } from "../components/Footer";
import { VersandSections } from "./components/VersandSections";
import { VersandTOC, MobileVersandTOC } from "./components/VersandTOC";
-import { getShippingSettings } from "../lib/payload";
+import { getShippingSettings, getShippingMethods } from "../lib/payload";
export const metadata: Metadata = {
title: "Versand",
@@ -14,7 +14,12 @@ export const metadata: Metadata = {
};
export default async function VersandPage() {
- const shipping = await getShippingSettings();
+ const [shipping, shippingMethods] = await Promise.all([getShippingSettings(), getShippingMethods()]);
+ // Same "first active method / lowest threshold among active methods"
+ // convention as CartContent.tsx/getTrustBadges() — see VersandSections.tsx.
+ const shippingCost = shippingMethods[0]?.price ?? 0;
+ const shippingMethodThresholds = shippingMethods.map((m) => m.freeShippingThreshold).filter((t): t is number => t !== null);
+ const freeShippingThreshold = shippingMethodThresholds.length > 0 ? Math.min(...shippingMethodThresholds) : null;
return (
<>
@@ -47,7 +52,7 @@ export default async function VersandPage() {
-
+