225a8567a9
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.
120 lines
4.1 KiB
TypeScript
120 lines
4.1 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useRef } from "react";
|
||
import { AnimatePresence, motion } from "motion/react";
|
||
import { VersandSections } from "../versand/components/VersandSections";
|
||
import type { ShippingSettings } from "../lib/payload";
|
||
|
||
/**
|
||
* Quick-reference version of /versand, opened from the cart's order
|
||
* summary "Versand" info link — a full page navigation would pull you out
|
||
* of checkout, which is exactly what the link is there to avoid. Reuses
|
||
* VersandSections so the two never carry different numbers/copy.
|
||
* `shipping` is threaded down from CartContent/CheckoutContent's own page
|
||
* (a Server Component), not fetched here — this is a Client Component.
|
||
*/
|
||
export function VersandModal({
|
||
open,
|
||
onClose,
|
||
shipping,
|
||
}: {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
shipping: ShippingSettings;
|
||
}) {
|
||
const dialogRef = useRef<HTMLDivElement>(null);
|
||
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
const prevOverflow = document.body.style.overflow;
|
||
document.body.style.overflow = "hidden";
|
||
return () => {
|
||
document.body.style.overflow = prevOverflow;
|
||
};
|
||
}, [open]);
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
|
||
closeButtonRef.current?.focus({ preventScroll: true });
|
||
|
||
const onKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === "Escape") {
|
||
e.preventDefault();
|
||
onClose();
|
||
return;
|
||
}
|
||
if (e.key !== "Tab") return;
|
||
const focusables = dialogRef.current?.querySelectorAll<HTMLElement>(
|
||
'a[href], button:not([disabled])'
|
||
);
|
||
if (!focusables || focusables.length === 0) return;
|
||
const first = focusables[0];
|
||
const last = focusables[focusables.length - 1];
|
||
if (e.shiftKey && document.activeElement === first) {
|
||
e.preventDefault();
|
||
last.focus({ preventScroll: true });
|
||
} else if (!e.shiftKey && document.activeElement === last) {
|
||
e.preventDefault();
|
||
first.focus({ preventScroll: true });
|
||
}
|
||
};
|
||
|
||
document.addEventListener("keydown", onKeyDown);
|
||
return () => document.removeEventListener("keydown", onKeyDown);
|
||
}, [open, onClose]);
|
||
|
||
return (
|
||
<AnimatePresence>
|
||
{open && (
|
||
<motion.div
|
||
className="fixed inset-0 z-[60] flex items-center justify-center p-4 md:p-8 bg-[rgba(134,134,134,0.9)]"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
transition={{ duration: 0.25, ease: "easeOut" }}
|
||
onClick={(e) => {
|
||
if (e.target === e.currentTarget) onClose();
|
||
}}
|
||
>
|
||
<motion.div
|
||
ref={dialogRef}
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="versand-modal-heading"
|
||
initial={{ opacity: 0, y: 16, scale: 0.97 }}
|
||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||
exit={{ opacity: 0, y: 16, scale: 0.97 }}
|
||
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||
className="relative bg-bg-base rounded-md overflow-hidden w-full max-w-[40rem] max-h-[85vh] overflow-y-auto"
|
||
>
|
||
<div className="sticky top-0 z-10 flex items-center justify-between px-8 py-6 bg-bg-base border-b border-border">
|
||
<p
|
||
id="versand-modal-heading"
|
||
className="font-semibold text-h-small text-text-primary"
|
||
style={{ fontFamily: "var(--font-lora)" }}
|
||
>
|
||
Versand
|
||
</p>
|
||
<button
|
||
ref={closeButtonRef}
|
||
type="button"
|
||
onClick={onClose}
|
||
aria-label="Schließen"
|
||
className="size-6 flex items-center justify-center active:scale-90 transition-transform text-text-muted hover:text-text-primary"
|
||
>
|
||
<span aria-hidden className="text-2xl leading-none">×</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="px-8 py-6 pb-8">
|
||
<VersandSections shipping={shipping} />
|
||
</div>
|
||
</motion.div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
);
|
||
}
|