diff --git a/workloads/ecommerce/app/product/[slug]/page.tsx b/workloads/ecommerce/app/product/[slug]/page.tsx index 96dfb01..276d1ea 100644 --- a/workloads/ecommerce/app/product/[slug]/page.tsx +++ b/workloads/ecommerce/app/product/[slug]/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { MobileAddToCart } from "@/components/product/mobile-add-to-cart"; +import { ProductCard } from "@/components/product/product-card"; import { ProductCarousel } from "@/components/product/product-carousel"; import { ProductGallery } from "@/components/product/product-gallery"; import { ProductPurchase } from "@/components/product/product-purchase"; @@ -33,5 +34,58 @@ export default async function ProductPage({ params }: ProductPageProps) { if (!product) notFound(); const products = await getProducts(); const recommended = products.filter((candidate) => candidate.id !== product.id && (candidate.category === product.category || candidate.collection === product.collection)).slice(0, 8); - return <>
{product.category} · {product.collection}

{product.name}

{product.description}

Detalles del productoMarca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.Envíos y entregasCoordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización.
{recommended.length > 0 &&

También te puede gustar

}; + const suggested = recommended[0]; + + return ( + <> + {/* max-w-[1600px] (antes max-w-7xl/1280px) y una 3ra columna desde + xl -- aprovecha pantallas grandes en vez de dejar el buy box + apretado contra el texto o cortado por el panel de zoom de al + lado (ver ProductGallery). Por debajo de xl, el buy box cae al + flujo normal en la 2da columna, sin duplicar el componente en + el DOM (evita el id="product-buy-button" repetido que rompería + el IntersectionObserver de MobileAddToCart). */} +
+ +
+ {product.category} · {product.collection} +

{product.name}

+

{product.description}

+ + + Detalles del producto + Marca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping. + + + Envíos y entregas + Coordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización. + + +
+ {/* Buy box estilo Amazon: precio/variantes/carrito en su propia + tarjeta, sticky desde xl mientras el resto de la pagina + scrollea. El sugerido usa el mismo cálculo de "recommended" + que ya alimenta el carrusel de abajo -- mismo criterio real + (categoría/colección), no un dato inventado. */} +
+
+ +
+ {suggested && ( +
+

También te puede interesar

+ +
+ )} +
+
+ {recommended.length > 0 && ( +
+

También te puede gustar

+ +
+ )} + + + ); } diff --git a/workloads/ecommerce/components/product/mobile-add-to-cart.tsx b/workloads/ecommerce/components/product/mobile-add-to-cart.tsx index c81421e..20501b7 100644 --- a/workloads/ecommerce/components/product/mobile-add-to-cart.tsx +++ b/workloads/ecommerce/components/product/mobile-add-to-cart.tsx @@ -6,16 +6,22 @@ import { Button } from "@/components/ui/button"; import type { Product } from "@/lib/headless/types"; import { useCartStore } from "@/store/cart-store"; +const SCROLL_THRESHOLD_PX = 80; + // Antes esta barra quedaba fija desde el primer render en mobile, // tapando permanentemente parte de la imagen del producto solo para -// duplicar un boton que ya está visible más abajo. Ahora se muestra -// recien cuando el boton real (#product-buy-button en ProductPurchase, -// visible tambien en mobile) sale del viewport -- mismo patron que el -// "Buy Now" sticky de Amazon, que solo aparece al scrollear mas alla -// del boton real. +// duplicar un boton que ya está visible más abajo. Ahora combina dos +// condiciones, igual que el "Buy Now" sticky de Amazon: +// - scrolled: recien aparece despues de que el usuario empieza a +// scrollear (nunca sobre la vista inicial de la imagen/hero). +// - !buttonVisible: el boton real (#product-buy-button en +// ProductPurchase, visible tambien en mobile) no esta en pantalla -- +// ya sea porque todavia no se llego a el, o porque se scrolleo mas +// alla. export function MobileAddToCart({ product }: { product: Product }) { const addItem = useCartStore((state) => state.addItem); - const [visible, setVisible] = useState(false); + const [buttonVisible, setButtonVisible] = useState(true); + const [scrolled, setScrolled] = useState(false); const outOfStock = product.variants[0]?.available === false; useEffect(() => { @@ -23,12 +29,21 @@ export function MobileAddToCart({ product }: { product: Product }) { if (!target) return; const observer = new IntersectionObserver((entries) => { const entry = entries[0]; - if (entry) setVisible(!entry.isIntersecting); + if (entry) setButtonVisible(entry.isIntersecting); }, { threshold: 0 }); observer.observe(target); return () => observer.disconnect(); }, []); + useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > SCROLL_THRESHOLD_PX); + onScroll(); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + const visible = scrolled && !buttonVisible; + return (