Pedido del usuario con capturas reales: la barra fija seguía superponiéndose sobre contenido de abajo al scrollear (ej. sobre la tarjeta de "También te puede interesar"), incluso después de varias vueltas ajustando cuándo debía aparecer/ocultarse. En vez de seguir parchando esa lógica, se saca la barra flotante por completo -- queda un solo botón "Añadir al carrito", visible en su lugar normal dentro de la página también en mobile (sin duplicar), igual que la mayoría de tiendas reales. Se borra components/product/mobile-add-to-cart.tsx (ya sin uso) y el id="product-buy-button"/id="recommendations-section" que solo existían para el IntersectionObserver de ese componente. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
98 lines
4.2 KiB
TypeScript
98 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import { CheckCircle2, RotateCcw, ShoppingBag, TriangleAlert, Truck, XCircle } from "lucide-react";
|
|
import Link from "next/link";
|
|
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>
|
|
{/* Un solo botón, visible también en mobile -- sin barra flotante
|
|
aparte (se probó, pero terminaba tapando contenido de abajo al
|
|
scrollear, ej. "También te puede interesar"; el usuario pidió
|
|
sacarla y dejar el botón normal como en la mayoría de tiendas). */}
|
|
<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>
|
|
{/* Franja de confianza real (estilo Walmart) -- enlaza a las
|
|
páginas reales de /envios y /cambios en vez de repetir texto
|
|
suelto, con los datos reales que confirmó el usuario (5 días
|
|
hábiles, envío de vuelta a cargo de ARI Shopping). Nada
|
|
inventado: mismas políticas ya publicadas en el footer. */}
|
|
<div className="mt-3 grid gap-2 text-sm">
|
|
<Link href="/envios" className="flex items-center gap-2 font-bold text-slate-600 hover:text-ari-marino hover:underline">
|
|
<Truck className="size-4 text-cyan-600" />
|
|
Envíos calculados en el checkout · ver detalles
|
|
</Link>
|
|
<Link href="/cambios" className="flex items-center gap-2 font-bold text-slate-600 hover:text-ari-marino hover:underline">
|
|
<RotateCcw className="size-4 text-ari-marino" />
|
|
Cambios en 5 días hábiles, envío de vuelta gratis
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|