Files
apps-registry/workloads/ecommerce/components/product/mobile-add-to-cart.tsx
T
devopsandClaude Sonnet 5 401f43a136
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 28s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 7m38s
feat(ecommerce): página de producto estilo Amazon (buy-box + más ancho)
- max-w de la página de producto pasa de 1280px a 1600px -- aprovecha
  pantallas grandes en vez de dejar margenes vacíos enormes.
- Nueva 3ra columna desde xl: buy-box propio (tarjeta con borde,
  sticky) para precio/variantes/carrito, separado del texto
  descriptivo. Por debajo de xl cae al flujo normal de la 2da columna
  sin duplicar el componente (mismo <ProductPurchase>, un solo
  id="product-buy-button" en el DOM).
- Dentro del buy-box: un producto sugerido real de la misma
  categoría/colección (mismo cálculo que ya alimenta el carrusel de
  abajo, no un dato inventado).
- MobileAddToCart: la barra fija ahora también requiere haber
  scrolleado más de 80px además de que el botón real no esté en
  pantalla -- antes aparecía inmediatamente sobre la vista inicial del
  producto (imagen + variantes) apenas cargaba la página.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
2026-09-13 20:53:01 +00:00

67 lines
2.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { ShoppingBag } from "lucide-react";
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 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 [buttonVisible, setButtonVisible] = useState(true);
const [scrolled, setScrolled] = useState(false);
const outOfStock = product.variants[0]?.available === false;
useEffect(() => {
const target = document.getElementById("product-buy-button");
if (!target) return;
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
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 (
<div
aria-hidden={!visible}
className={`fixed inset-x-0 bottom-0 z-40 border-t border-slate-200 bg-white/95 p-3 shadow-2xl backdrop-blur transition-transform duration-200 lg:hidden ${
visible ? "translate-y-0" : "pointer-events-none translate-y-full"
}`}
>
<Button
variant="primary"
size="lg"
className="w-full"
disabled={outOfStock}
onClick={() => addItem(product, product.variants[0]?.id)}
>
<ShoppingBag />
{outOfStock ? "Agotado" : "Añadir al carrito"}
</Button>
</div>
);
}