feat(shipping): move delivery-time settings to Payload, polish product/cart CTAs
The delivery-time range (handling + transit days) was a hardcoded HANDLING_DAYS/TRANSIT_DAYS_DE pair in lib/shipping.ts — changing it needed a code deploy. Now sourced from Payload's new Shipping Settings collection via getShippingSettings(), threaded down as a prop to the few Client Components (Cart/Checkout/VersandModal) that can't fetch it themselves, with the old code constants removed. Also: the delivery-time note is now shown on every purchase CTA (shop grid, home spotlight, ToDo-Karten hero + pricing panel), not just one of them — required next to each buy button per Art. 246a §1 Abs.1 Nr.8 EGBGB, not just somewhere reachable via a link. Checkout's sidebar was missing the "ab 39€ kostenlos" note Cart already had; that's fixed too, and both now show the delivery-time range on its own line instead of crammed onto the shipping-cost line. Related smaller fixes bundled in since they touch the same files: price/delivery-time spacing tightened into its own group, the redundant "Sichere Zahlung" note under Cart's checkout button (already shown via the trustBadges list right below) replaced with "Sichere SSL-Verschlüsselung" to match Checkout, and ToDo-Karten's pricing panel no longer shows a premature payment-security note at the add-to-cart step.
This commit is contained in:
+89
-1
@@ -1,3 +1,5 @@
|
||||
import { formatPrice } from "./format";
|
||||
|
||||
const PAYLOAD_URL = process.env.PAYLOAD_URL || "https://payload.mk360.de";
|
||||
const TENANT_SLUG = "einfach-produktiv";
|
||||
|
||||
@@ -251,10 +253,44 @@ async function fetchTrustBadgeList(collection: "trust-badges" | "cart-trust-badg
|
||||
}));
|
||||
}
|
||||
|
||||
// Substitutes literal "{{lieferzeit}}" / "{{kostenfreiab}}" tokens (e.g. in
|
||||
// a badge's "In {{lieferzeit}} bei dir." or "Ab {{kostenfreiab}}
|
||||
// Bestellwert innerhalb DE." copy) with the live delivery-time range /
|
||||
// free-shipping threshold — lets an editor reference these numbers from
|
||||
// free text without duplicating and hand-maintaining them, which is
|
||||
// exactly how the "Schneller Versand" and "Versandkostenfrei" badges used
|
||||
// to drift from the real numbers whenever those changed elsewhere but not
|
||||
// here too. The threshold comes from the lowest freeShippingThreshold
|
||||
// among active ShippingMethods (same rule /cart's own banner uses), not
|
||||
// lib/shipping.ts's separate FREE_SHIPPING_THRESHOLD constant — that
|
||||
// constant is a third, independent copy of the same fact and not
|
||||
// necessarily what actually governs checkout.
|
||||
function resolveShippingTokens(text: string, shipping: ShippingSettings, freeShippingThreshold: number | null): string {
|
||||
let result = text.replaceAll("{{lieferzeit}}", `${shipping.totalDays.min}–${shipping.totalDays.max} Werktagen`);
|
||||
if (freeShippingThreshold !== null) {
|
||||
result = result.replaceAll("{{kostenfreiab}}", formatPrice(freeShippingThreshold));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Powers TrustRow.tsx — the horizontal "Schneller Versand /
|
||||
// Versandkostenfrei / Mit Liebe verpackt" row.
|
||||
export async function getTrustBadges(): Promise<TrustBadge[]> {
|
||||
return fetchTrustBadgeList("trust-badges");
|
||||
const [badges, shipping, shippingMethods] = await Promise.all([
|
||||
fetchTrustBadgeList("trust-badges"),
|
||||
getShippingSettings(),
|
||||
getShippingMethods(),
|
||||
]);
|
||||
const thresholds = shippingMethods
|
||||
.map((m) => m.freeShippingThreshold)
|
||||
.filter((t): t is number => t !== null);
|
||||
const freeShippingThreshold = thresholds.length > 0 ? Math.min(...thresholds) : null;
|
||||
|
||||
return badges.map((b) => ({
|
||||
...b,
|
||||
title: resolveShippingTokens(b.title, shipping, freeShippingThreshold),
|
||||
description: resolveShippingTokens(b.description, shipping, freeShippingThreshold),
|
||||
}));
|
||||
}
|
||||
|
||||
// Powers the "Sichere Zahlung / 14 Tage Rückgaberecht / Nachhaltig
|
||||
@@ -301,6 +337,58 @@ export async function getShippingMethods(): Promise<ShippingMethod[]> {
|
||||
}));
|
||||
}
|
||||
|
||||
export type ShippingSettings = {
|
||||
handlingDays: { min: number; max: number };
|
||||
transitDays: { min: number; max: number };
|
||||
/** Derived here, not stored in Payload — a third independently-editable
|
||||
* copy of the same fact is exactly the drift this collection replaces. */
|
||||
totalDays: { min: number; max: number };
|
||||
};
|
||||
|
||||
type PayloadShippingSettings = {
|
||||
handlingDaysMin: number;
|
||||
handlingDaysMax: number;
|
||||
transitDaysMin: number;
|
||||
transitDaysMax: number;
|
||||
};
|
||||
|
||||
// Falls back to the site's long-standing real-world numbers (1–2 handling,
|
||||
// 2–4 transit) if Payload has no row yet or the fetch fails — same values
|
||||
// the old hardcoded HANDLING_DAYS/TRANSIT_DAYS_DE constants used, so
|
||||
// nothing regresses before this collection gets seeded/edited.
|
||||
const SHIPPING_SETTINGS_FALLBACK: ShippingSettings = {
|
||||
handlingDays: { min: 1, max: 2 },
|
||||
transitDays: { min: 2, max: 4 },
|
||||
totalDays: { min: 3, max: 6 },
|
||||
};
|
||||
|
||||
export async function getShippingSettings(): Promise<ShippingSettings> {
|
||||
const params = new URLSearchParams({
|
||||
"where[tenant.slug][equals]": TENANT_SLUG,
|
||||
limit: "1",
|
||||
});
|
||||
|
||||
const res = await fetch(`${PAYLOAD_URL}/api/shipping-settings?${params}`, {
|
||||
next: { revalidate: 60 },
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error(`getShippingSettings: Payload returned ${res.status} ${res.statusText}`);
|
||||
return SHIPPING_SETTINGS_FALLBACK;
|
||||
}
|
||||
|
||||
const data: { docs?: PayloadShippingSettings[] } = await res.json();
|
||||
const doc = Array.isArray(data.docs) ? data.docs[0] : undefined;
|
||||
if (!doc) return SHIPPING_SETTINGS_FALLBACK;
|
||||
|
||||
const handlingDays = { min: doc.handlingDaysMin, max: doc.handlingDaysMax };
|
||||
const transitDays = { min: doc.transitDaysMin, max: doc.transitDaysMax };
|
||||
return {
|
||||
handlingDays,
|
||||
transitDays,
|
||||
totalDays: { min: handlingDays.min + transitDays.min, max: handlingDays.max + transitDays.max },
|
||||
};
|
||||
}
|
||||
|
||||
export type PaymentMethod = { id: number; title: string; icons: string[] };
|
||||
|
||||
type PayloadPaymentMethod = {
|
||||
|
||||
Reference in New Issue
Block a user