From 225a8567a958ae8c5bf0075ea2a5ecc0192567e8 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 21 Jul 2026 12:44:35 +0000 Subject: [PATCH] feat(shipping): move delivery-time settings to Payload, polish product/cart CTAs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/cart/components/CartContent.tsx | 28 ++++-- app/cart/page.tsx | 9 +- app/checkout/components/CheckoutContent.tsx | 18 +++- app/checkout/page.tsx | 12 ++- app/components/ProductSpotlight.tsx | 21 +++-- app/components/VersandModal.tsx | 15 +++- app/lib/payload.ts | 90 +++++++++++++++++++- app/lib/shipping.ts | 27 +++--- app/shop/components/ProductGrid.tsx | 24 ++++-- app/todo-cards/components/Pricing.tsx | 35 ++++---- app/todo-cards/components/TodoKartenHero.tsx | 35 +++++--- app/versand/components/VersandSections.tsx | 30 ++++--- app/versand/page.tsx | 7 +- 13 files changed, 254 insertions(+), 97 deletions(-) diff --git a/app/cart/components/CartContent.tsx b/app/cart/components/CartContent.tsx index 5aedc35..b32dcc8 100644 --- a/app/cart/components/CartContent.tsx +++ b/app/cart/components/CartContent.tsx @@ -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"}

+

+ Lieferzeit {shippingSettings.totalDays.min}–{shippingSettings.totalDays.max} Werktage +

@@ -247,9 +257,13 @@ export function CartContent({ Zur Kasse gehen -
- - Sichere Zahlung + {/* "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. */} +
+ + Sichere SSL-Verschlüsselung
@@ -258,7 +272,7 @@ export function CartContent({
{trustBadges.map((b) => (
- + {b.title}
))} @@ -272,7 +286,7 @@ export function CartContent({
- +
@@ -294,7 +308,7 @@ export function CartContent({ )} - setVersandOpen(false)} /> + setVersandOpen(false)} shipping={shippingSettings} /> ); } diff --git a/app/cart/page.tsx b/app/cart/page.tsx index e1f03fa..4f26604 100644 --- a/app/cart/page.tsx +++ b/app/cart/page.tsx @@ -3,7 +3,7 @@ import { CartContent } from "./components/CartContent"; import { RelatedProducts } from "./components/RelatedProducts"; import { TrustRow } from "../components/TrustRow"; import { Footer } from "../components/Footer"; -import { getCartTrustBadges, getShippingMethods } from "../lib/payload"; +import { getCartTrustBadges, getShippingMethods, getShippingSettings } from "../lib/payload"; // robots: noindex — transactional page (mirrors a specific shopper's cart // contents), per the figma-to-nextjs skill's Step 5 guidance: indexing @@ -18,7 +18,11 @@ export const metadata: Metadata = { }; export default async function CartPage() { - const [trustBadges, shippingMethods] = await Promise.all([getCartTrustBadges(), getShippingMethods()]); + const [trustBadges, shippingMethods, shipping] = await Promise.all([ + getCartTrustBadges(), + getShippingMethods(), + getShippingSettings(), + ]); // The cart doesn't ask which shipping method the shopper wants yet // (that's /checkout) — it just estimates using the first active method @@ -38,6 +42,7 @@ export default async function CartPage() { trustBadges={trustBadges} shippingCost={defaultShipping?.price ?? 0} freeShippingThreshold={freeShippingThreshold} + shippingSettings={shipping} /> diff --git a/app/checkout/components/CheckoutContent.tsx b/app/checkout/components/CheckoutContent.tsx index 77dc3db..6d41956 100644 --- a/app/checkout/components/CheckoutContent.tsx +++ b/app/checkout/components/CheckoutContent.tsx @@ -10,7 +10,7 @@ import { Reveal } from "../../components/Reveal"; import { VersandModal } from "../../components/VersandModal"; import { CheckoutSteps } from "../../components/CheckoutSteps"; import { ORDER_KEY, generateOrderNumber, type OrderSnapshot } from "../../lib/order"; -import type { ShippingMethod, PaymentMethod, TrustBadge } from "../../lib/payload"; +import type { ShippingMethod, PaymentMethod, TrustBadge, ShippingSettings } from "../../lib/payload"; function FormField({ label, @@ -32,10 +32,15 @@ export function CheckoutContent({ shippingMethods, paymentMethods, trustBadges, + shippingSettings, }: { shippingMethods: ShippingMethod[]; paymentMethods: PaymentMethod[]; trustBadges: TrustBadge[]; + /** Delivery-time disclosure (Payload's Shipping Settings) — named + * "shippingSettings", not "shipping", since that name is already the + * local computed shipping-cost value below. */ + shippingSettings: ShippingSettings; }) { const cart = useCart(); const products = useProducts(); @@ -386,7 +391,14 @@ export function CheckoutContent({ {shipping === 0 ? "Kostenlos" : formatPrice(shipping)}
-

{selectedShipping?.description ?? "innerhalb Deutschlands"}

+

+ {shipping === 0 && selectedShipping?.freeShippingThreshold != null + ? `ab ${formatPrice(selectedShipping.freeShippingThreshold)} innerhalb Deutschlands` + : (selectedShipping?.description ?? "innerhalb Deutschlands")} +

+

+ Lieferzeit {shippingSettings.totalDays.min}–{shippingSettings.totalDays.max} Werktage +

@@ -448,7 +460,7 @@ export function CheckoutContent({
- setVersandOpen(false)} /> + setVersandOpen(false)} shipping={shippingSettings} /> ); } diff --git a/app/checkout/page.tsx b/app/checkout/page.tsx index 2959f7c..05cb82a 100644 --- a/app/checkout/page.tsx +++ b/app/checkout/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { CheckoutContent } from "./components/CheckoutContent"; import { TrustRow } from "../components/TrustRow"; import { Footer } from "../components/Footer"; -import { getShippingMethods, getPaymentMethods, getCartTrustBadges } from "../lib/payload"; +import { getShippingMethods, getPaymentMethods, getCartTrustBadges, getShippingSettings } from "../lib/payload"; // robots: noindex — transactional page, same reasoning as /cart. export const metadata: Metadata = { @@ -15,16 +15,22 @@ export const metadata: Metadata = { }; export default async function CheckoutPage() { - const [shippingMethods, paymentMethods, trustBadges] = await Promise.all([ + const [shippingMethods, paymentMethods, trustBadges, shippingSettings] = await Promise.all([ getShippingMethods(), getPaymentMethods(), getCartTrustBadges(), + getShippingSettings(), ]); return ( <>
- +