"use client"; import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import { HexCheck } from "@/components/honeycomb"; import { Container, SectionHeading, LinkButton } from "@/components/ui"; import { TrustBadges } from "@/components/trust-badges"; import { CartItemThumbnail } from "@/components/cart-item-thumbnail"; import { QuantityStepper } from "@/components/quantity-stepper"; import { ProductCard } from "@/components/product-card"; import { useCartStore, cartTotalCents, cartItemCount } from "@/store/cart-store"; import WarenkorbLoading from "./loading"; import { useCartStock } from "@/hooks/use-cart-stock"; import { formatPrice } from "@/lib/format"; import { resolveShippingCostCents, getNextShippingTier, type ShippingTierLike } from "@/lib/shipping-calc"; import { resolveBulkDiscount, getNextBulkDiscountTier, type BulkDiscountTierLike } from "@/lib/bulk-discount-calc"; import { useI18n } from "@/i18n/i18n-provider"; import type { ProductWithRelations } from "@/lib/data"; const CROSS_SELL_LIMIT = 3; export function WarenkorbContent({ tiers, availableProducts, bulkDiscountTiers, }: { tiers: ShippingTierLike[]; availableProducts: ProductWithRelations[]; bulkDiscountTiers: BulkDiscountTierLike[]; }) { const { t, locale } = useI18n(); const items = useCartStore((s) => s.items); const hasHydrated = useCartStore((s) => s.hasHydrated); const setQuantity = useCartStore((s) => s.setQuantity); const removeItem = useCartStore((s) => s.removeItem); // Live-Bestand je Position — derselbe Hinweis wie auf der Produktkarte/ // -detailseite, aber hier fuer den ggf. seit dem Hinzufuegen veraenderten // aktuellen Bestand (siehe use-cart-stock.ts). const stock = useCartStock(items.map((item) => item.productId)); const subtotal = cartTotalCents(items); // Gesamtmenge ueber alle Positionen hinweg — mehrere verschiedene Artikel // werden fuer die Versandstaffel zusammengezaehlt, nicht getrennt bewertet. const totalQuantity = cartItemCount(items); const shipping = items.length === 0 ? 0 : resolveShippingCostCents(tiers, totalQuantity); const nextTier = items.length === 0 ? null : getNextShippingTier(tiers, totalQuantity); // Live-Vorschau des serverseitig in api/checkout/route.ts automatisch // angewendeten Mengenrabatts — Hoehe/Menge kommen aus den im Admin-Bereich // (Gutscheine) gepflegten DiscountCode-Zeilen, nicht mehr aus Konstanten. const activeBulkDiscount = items.length === 0 ? null : resolveBulkDiscount(bulkDiscountTiers, totalQuantity); const bulkDiscountCents = activeBulkDiscount ? Math.round((subtotal * activeBulkDiscount.percent) / 100) : 0; const nextBulkDiscount = items.length === 0 ? null : getNextBulkDiscountTier(bulkDiscountTiers, totalQuantity); const cartProductIds = new Set(items.map((item) => item.productId)); const crossSellProducts = availableProducts .filter((product) => !cartProductIds.has(product.id)) .slice(0, CROSS_SELL_LIMIT); // Siehe checkout-form.tsx: ohne diese Weiche flackert kurz die // "leer"-Meldung auf, bevor der Cart-Store aus localStorage nachliest. if (!hasHydrated) { return ; } return (
{items.length === 0 ? (

{t.cart.empty}

{t.cartPage.goToShop}
) : (
{items.map((item) => { const itemStock = stock?.[item.productId]; return (
{item.name}

{item.weightGrams} g

{/* Siehe Kommentar im Cart-Drawer (site-chrome.tsx): persistenter aria-live-Wrapper, damit der erst nach dem ersten Rendern eintreffende Live-Bestand als Ansage bei Screenreadern ankommt. */}
{itemStock !== undefined && itemStock === 0 ? (

{t.product.outOfStock}

) : itemStock !== undefined && itemStock <= 5 ? (

{t.product.onlyLeft(itemStock)}

) : null}
setQuantity(item.productId, quantity)} max={itemStock ?? 10} /> {formatPrice(item.priceCents * item.quantity, "EUR", locale)}
); })}
{t.cart.subtotal} {formatPrice(subtotal, "EUR", locale)}
{t.cartPage.shippingLabel(totalQuantity)} {shipping === 0 ? t.cartPage.free : formatPrice(shipping, "EUR", locale)}
{nextTier && nextTier.unitsUntilNext <= 2 ? (

{t.cartPage.nextTierHint(nextTier.unitsUntilNext, formatPrice(nextTier.priceCents, "EUR", locale))}

) : null} {activeBulkDiscount ? ( {t.cartPage.discountLabel} ({activeBulkDiscount.percent}%) -{formatPrice(bulkDiscountCents, "EUR", locale)} ) : nextBulkDiscount && nextBulkDiscount.unitsUntilNext <= 2 ? ( // Der Weg zum Mengenrabatt als Glas, das sich mit jedem // weiteren Stueck fuellt — nicht nur als Kleingedrucktes.

{t.cartPage.bulkDiscountProgress(nextBulkDiscount.unitsUntilNext, nextBulkDiscount.percent)}

) : null}
{t.cartPage.total} {formatPrice(subtotal - bulkDiscountCents + shipping, "EUR", locale)}
{t.cart.checkout} {t.cartPage.pickupHint}
)} {items.length > 0 && crossSellProducts.length > 0 ? (

{t.productPage.relatedProductsHeading}

{crossSellProducts.map((product, index) => ( ))}
) : null}
); }