- El panel de zoom lateral ahora calcula tamaño/posición contra el viewport real (window.innerWidth/innerHeight) en cada mousemove, en vez de un breakpoint fijo (lg:block) con ancho fijo -- a resoluciones altas con poco espacio a la derecha de la imagen terminaba saliéndose de la pantalla. Si no entra un panel de al menos PANEL_MIN_SIZE, se cae automáticamente al overlay in-place (antes atado a "por debajo de lg", ahora a "no hay espacio real"). - La barra fija de "Añadir al carrito" en mobile quedaba fija desde el primer render, tapando permanentemente parte de la imagen del producto. Ahora el botón real (ProductPurchase) también se muestra en mobile, y la barra sticky solo aparece cuando ese botón sale del viewport (IntersectionObserver) -- mismo patrón que el "Buy Now" sticky de Amazon. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { CheckCircle2, ShoppingBag, TriangleAlert, Truck, XCircle } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import type { Product } from "@/lib/headless/types";
|
|
import { formatMoney, LOW_STOCK_THRESHOLD } from "@/lib/utils";
|
|
import { useCartStore } from "@/store/cart-store";
|
|
|
|
export function ProductPurchase({ product }: { product: Product }) {
|
|
const [variantId, setVariantId] = useState(product.variants[0]?.id);
|
|
const addItem = useCartStore((state) => state.addItem);
|
|
const selectedVariant = product.variants.find((variant) => variant.id === variantId) ?? product.variants[0];
|
|
const outOfStock = selectedVariant?.available === false;
|
|
const lowStock =
|
|
!outOfStock &&
|
|
selectedVariant?.inventoryQuantity !== null &&
|
|
selectedVariant?.inventoryQuantity !== undefined &&
|
|
selectedVariant.inventoryQuantity > 0 &&
|
|
selectedVariant.inventoryQuantity <= LOW_STOCK_THRESHOLD
|
|
? selectedVariant.inventoryQuantity
|
|
: null;
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-5 flex flex-wrap gap-2">
|
|
{product.variants.map((variant) => (
|
|
<button
|
|
key={variant.id}
|
|
onClick={() => setVariantId(variant.id)}
|
|
className={`rounded-full border px-4 py-2 text-sm font-bold ${
|
|
variantId === variant.id ? "border-ari-marino bg-ari-marino text-white" : "border-slate-200"
|
|
} ${variant.available === false ? "opacity-40" : ""}`}
|
|
>
|
|
{variant.value}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className="mb-6 flex items-end gap-3">
|
|
<strong className="text-3xl font-black">{formatMoney(product.price, product.currency)}</strong>
|
|
{product.compareAtPrice && <del className="text-slate-400">{formatMoney(product.compareAtPrice, product.currency)}</del>}
|
|
</div>
|
|
{/* Visible tambien en mobile (antes quedaba oculto <lg porque
|
|
MobileAddToCart quedaba fijo abajo desde el primer render,
|
|
tapando parte de la imagen sin necesidad). id usado por
|
|
MobileAddToCart para saber cuando este boton real sale del
|
|
viewport y recien ahi mostrar su barra sticky -- mismo patron
|
|
que el "Buy Now" sticky de Amazon en mobile. */}
|
|
<Button
|
|
id="product-buy-button"
|
|
variant="primary"
|
|
size="lg"
|
|
className="w-full"
|
|
disabled={outOfStock}
|
|
onClick={() => addItem(product, variantId)}
|
|
>
|
|
<ShoppingBag />
|
|
{outOfStock ? "Agotado" : "Añadir al carrito"}
|
|
</Button>
|
|
<div className="mt-5 grid gap-3 rounded-2xl bg-slate-50 p-4 text-sm">
|
|
{outOfStock ? (
|
|
<span className="flex items-center gap-2 font-bold text-rose-600">
|
|
<XCircle className="size-4" />
|
|
Agotado
|
|
</span>
|
|
) : lowStock !== null ? (
|
|
<span className="flex items-center gap-2 font-bold text-amber-600">
|
|
<TriangleAlert className="size-4" />
|
|
Quedan {lowStock} unidades
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center gap-2 font-bold">
|
|
<CheckCircle2 className="size-4 text-emerald-500" />
|
|
Disponibilidad por confirmar
|
|
</span>
|
|
)}
|
|
<span className="flex items-center gap-2 font-bold">
|
|
<Truck className="size-4 text-cyan-600" />
|
|
Envíos nacionales
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|