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:
Marco
2026-07-21 12:44:35 +00:00
parent a6edd7ff61
commit 225a8567a9
13 changed files with 254 additions and 97 deletions
+21 -7
View File
@@ -9,12 +9,13 @@ import { formatPrice, discountPercent } from "../../lib/format";
import { Reveal } from "../../components/Reveal";
import { VersandModal } from "../../components/VersandModal";
import { FreeShippingBanner } from "./FreeShippingBanner";
import type { TrustBadge } from "../../lib/payload";
import type { TrustBadge, ShippingSettings } from "../../lib/payload";
export function CartContent({
trustBadges,
shippingCost,
freeShippingThreshold,
shippingSettings,
}: {
trustBadges: TrustBadge[];
/** Price of the default (first active, i.e. Standard) ShippingMethod — an
@@ -24,6 +25,12 @@ export function CartContent({
/** Lowest freeShippingThreshold among active ShippingMethods, or null if
* none has one (in which case FreeShippingBanner just doesn't render). */
freeShippingThreshold: number | null;
/** Delivery-time disclosure (Payload's Shipping Settings), fetched by the
* page and threaded down here — this is a Client Component, so it can't
* fetch it itself. Also passed straight through to VersandModal. Named
* "shippingSettings", not "shipping" — that name is already the local
* computed shipping-cost value below. */
shippingSettings: ShippingSettings;
}) {
const [versandOpen, setVersandOpen] = useState(false);
const cart = useCart();
@@ -222,6 +229,9 @@ export function CartContent({
? `ab ${formatPrice(freeShippingThreshold)} innerhalb Deutschlands`
: "innerhalb Deutschlands"}
</p>
<p className="text-label text-text-muted">
Lieferzeit {shippingSettings.totalDays.min}{shippingSettings.totalDays.max} Werktage
</p>
</div>
<div className="h-px bg-border w-full" />
@@ -247,9 +257,13 @@ export function CartContent({
Zur Kasse gehen
</Link>
<div className="flex gap-[0.625rem] items-center justify-center w-full">
<img alt="" src="/icon-lock.svg" className="w-4 h-[1.125rem]" />
<span className="text-body-sm text-text-muted">Sichere Zahlung</span>
{/* "Sichere SSL-Verschlüsselung", not "Sichere Zahlung" (what
used to be here) — matches /checkout's identical note
under its own buy button; the old wording duplicated the
"Sichere Zahlung" trustBadges entry right below. */}
<div className="flex gap-2 items-center justify-center w-full">
<Image alt="" src="/icon-lock.svg" width={14} height={16} className="w-3.5 h-4" />
<span className="text-body-sm text-text-muted">Sichere SSL-Verschlüsselung</span>
</div>
</div>
@@ -258,7 +272,7 @@ export function CartContent({
<div className="flex flex-col gap-4 items-start w-full">
{trustBadges.map((b) => (
<div key={b.id} className="flex gap-3 items-center w-full">
<img alt="" src={b.icon} className="size-[1.375rem] shrink-0 object-contain" />
<Image alt="" src={b.icon} width={22} height={22} className="size-[1.375rem] shrink-0 object-contain" />
<span className="flex-1 text-body-sm text-text-primary">{b.title}</span>
</div>
))}
@@ -272,7 +286,7 @@ export function CartContent({
<Reveal className="flex flex-col items-start pb-10 px-[var(--layout-padding-x)] w-full">
<div className="bg-bg-muted flex gap-5 items-start p-6 rounded-md w-full lg:max-w-[51.875rem]">
<div className="flex flex-col gap-3 items-center justify-center shrink-0">
<img alt="" src="/icon-envelope-hint.png" className="h-[2.8125rem] w-16 object-contain" />
<Image alt="" src="/icon-envelope-hint.png" width={64} height={45} className="h-[2.8125rem] w-16 object-contain" />
<div className="h-[0.1875rem] w-6 bg-brand" />
</div>
<div className="flex flex-col gap-2 items-start flex-1 min-w-0 text-text-primary">
@@ -294,7 +308,7 @@ export function CartContent({
</Reveal>
)}
<VersandModal open={versandOpen} onClose={() => setVersandOpen(false)} />
<VersandModal open={versandOpen} onClose={() => setVersandOpen(false)} shipping={shippingSettings} />
</>
);
}