'use client'; import { ShoppingBag } from 'lucide-react'; import { Link } from '@/core/lib/navigation'; import { useCartPage } from '@/core/hooks/use-cart-page'; import { CartItem } from '@/ui/cart/cart-item'; import { CartUpgradeBanner } from '@/ui/cart/cart-upgrade-banner'; import { CartBundleOfferCard } from '@/ui/cart/cart-bundle-offer'; import { CartSummary } from '@/ui/cart/cart-summary'; import { CouponInput } from '@/ui/cart/coupon-input'; import { CartNudges } from '@/ui/cart/cart-nudges'; import { FreeShippingBar } from '@/ui/cart/free-shipping-bar'; import { ReservationCountdown } from '@/ui/cart/reservation-countdown'; import { CartRecommendationSection } from '@/ui/product/recommendation-section'; import { LoadingSpinner } from '@/ui/shared/loading-spinner'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Card } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { useTranslations } from '@/core/lib/translations'; export function CartView() { const t = useTranslations('cart'); const tc = useTranslations('common'); const tr = useTranslations('reservation'); const { cart, cartLoading, refreshCart, itemCount, cartRecs, upgrades, bundles, reservationExpired, unavailableItems, selectedItemIds, selectedItems, toggleItemSelected, allSelected, setAllSelected, checkoutHref, canProceedToCheckout, onReservationExpired, } = useCartPage(); if (cartLoading) { return (
); } // Empty cart state if (!cart || cart.items.length === 0) { return (
); } return (

{t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})

{/* Reservation countdown. onExpire refreshes the cart and closes the checkout gate below. Passing it is what makes expiry mean anything. */} {cart.reservation?.hasReservation && ( )}
{/* Cart Items */}
{/* Nudges */} {cart.nudges && cart.nudges.length > 0 && ( )} {/* Select-all. Partial checkout: only the ticked lines are sent to createCheckout, and whatever stays unticked is still in the cart (and still for sale) after payment. */}
setAllSelected(checked === true)} />
{/* Cart items */}
{cart.items.map((item) => (
toggleItemSelected(item.id)} aria-label={item.product.name} />
{upgrades?.upgrades?.[item.productId] && ( )}
))}
{/* Bundle offers */} {bundles?.bundles && bundles.bundles.length > 0 && (

{t('bundleOffers')}

{bundles.bundles.map((offer) => ( ))}
)} {/* Coupon input */}
{/* Summary sidebar */}
{/* Totals are for the WHOLE cart. A partial selection is priced authoritatively on the checkout page, which is the only place the server has scoped the discounts and tax to the subset. */} {/* Shown once the shopper unticks anything. At zero selected this is what explains the disabled button below — a count, so it needs no copy of its own. */} {!allSelected && (

{selectedItems.length} {selectedItems.length === 1 ? tc('item') : tc('items')}

)} {/* Proceed to checkout. When the gate is closed this must be a real disabled control, never a styled-down link: an anchor ignores `disabled` and would still navigate. */} {canProceedToCheckout ? ( ) : ( <>

{unavailableItems.length > 0 ? t('unavailableItemsHint') : reservationExpired ? tr('expiredHint') : null}

)} {tc('continueShopping')}
{/* Cross-sell recommendations */} {cartRecs?.recommendations && cartRecs.recommendations.length > 0 && ( )}
); }