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:
@@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+7
-2
@@ -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}
|
||||
/>
|
||||
<RelatedProducts />
|
||||
<TrustRow />
|
||||
|
||||
Reference in New Issue
Block a user