feat(ecommerce): aprovechar mejor el espacio en la página de producto
- "Detalles del producto" (marca, edad, categoría, colección) pasa de
acordeón colapsado a una mini tabla siempre visible -- mismos datos
reales de siempre, solo que ya no hace falta un click para verlos.
- Buy-box: nueva franja de confianza real con enlaces a /envios y
/cambios (5 días hábiles, envío de vuelta gratis) -- mismas
políticas ya publicadas en el footer, no texto inventado.
- "También te puede interesar" ahora muestra hasta 2 sugerencias en
vez de 1, mismo cálculo real por categoría/colección que ya
alimentaba el carrusel de abajo.
Investigado de paso (sin fix): la discrepancia de stock reportada
("Quedan 1" vs available:false) no es un bug de ProductPurchase -- la
página de producto usa /store/products de Medusa (en vivo, correcto),
mientras que /store/search (Meilisearch) devuelve inventario
desactualizado para el mismo producto. Eso vive en commerce-backend,
fuera de alcance de este cambio de frontend.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
This commit is contained in:
1 parent
9b545dd850
commit
c3f3857a92
2 files changed
+37
-12
No files matched your search
@@ -34,7 +34,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
if (!product) notFound();
|
if (!product) notFound();
|
||||||
const products = await getProducts();
|
const products = await getProducts();
|
||||||
const recommended = products.filter((candidate) => candidate.id !== product.id && (candidate.category === product.category || candidate.collection === product.collection)).slice(0, 8);
|
const recommended = products.filter((candidate) => candidate.id !== product.id && (candidate.category === product.category || candidate.collection === product.collection)).slice(0, 8);
|
||||||
const suggested = recommended[0];
|
const suggestions = recommended.slice(0, 2);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -51,11 +51,18 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
<span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">{product.category} · {product.collection}</span>
|
<span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">{product.category} · {product.collection}</span>
|
||||||
<h1 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">{product.name}</h1>
|
<h1 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">{product.name}</h1>
|
||||||
<p className="mt-5 text-lg leading-8 text-slate-600">{product.description}</p>
|
<p className="mt-5 text-lg leading-8 text-slate-600">{product.description}</p>
|
||||||
<Accordion type="single" collapsible className="mt-8">
|
{/* Antes escondido detrás de un click en el acordeón --
|
||||||
<AccordionItem value="details">
|
siempre visible, estilo Amazon, con los mismos datos
|
||||||
<AccordionTrigger>Detalles del producto</AccordionTrigger>
|
reales que ya existían (marca/edad/categoría/colección),
|
||||||
<AccordionContent>Marca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.</AccordionContent>
|
sin inventar specs que el catálogo no tiene. */}
|
||||||
</AccordionItem>
|
<h2 className="mb-4 mt-8 text-sm font-black uppercase tracking-wide text-slate-500">Detalles del producto</h2>
|
||||||
|
<dl className="grid grid-cols-2 gap-x-6 gap-y-4 rounded-2xl border border-slate-200 p-5 text-sm sm:grid-cols-4">
|
||||||
|
<div><dt className="text-slate-400">Marca</dt><dd className="mt-0.5 font-bold text-slate-900">{product.brand}</dd></div>
|
||||||
|
<div><dt className="text-slate-400">Edad recomendada</dt><dd className="mt-0.5 font-bold text-slate-900">{product.ageRange}</dd></div>
|
||||||
|
<div><dt className="text-slate-400">Categoría</dt><dd className="mt-0.5 font-bold text-slate-900">{product.category}</dd></div>
|
||||||
|
<div><dt className="text-slate-400">Colección</dt><dd className="mt-0.5 font-bold text-slate-900">{product.collection}</dd></div>
|
||||||
|
</dl>
|
||||||
|
<Accordion type="single" collapsible className="mt-6">
|
||||||
<AccordionItem value="shipping">
|
<AccordionItem value="shipping">
|
||||||
<AccordionTrigger>Envíos y entregas</AccordionTrigger>
|
<AccordionTrigger>Envíos y entregas</AccordionTrigger>
|
||||||
<AccordionContent>Coordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización.</AccordionContent>
|
<AccordionContent>Coordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización.</AccordionContent>
|
||||||
@@ -64,17 +71,19 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
{/* Buy box estilo Amazon: precio/variantes/carrito en su propia
|
{/* Buy box estilo Amazon: precio/variantes/carrito en su propia
|
||||||
tarjeta, sticky desde xl mientras el resto de la pagina
|
tarjeta, sticky desde xl mientras el resto de la pagina
|
||||||
scrollea. El sugerido usa el mismo cálculo de "recommended"
|
scrollea. Los sugeridos (hasta 2) usan el mismo cálculo de
|
||||||
que ya alimenta el carrusel de abajo -- mismo criterio real
|
"recommended" que ya alimenta el carrusel de abajo -- mismo
|
||||||
(categoría/colección), no un dato inventado. */}
|
criterio real (categoría/colección), no un dato inventado. */}
|
||||||
<div className="xl:sticky xl:top-24 xl:self-start">
|
<div className="xl:sticky xl:top-24 xl:self-start">
|
||||||
<div className="rounded-2xl border border-slate-200 p-6 shadow-sm">
|
<div className="rounded-2xl border border-slate-200 p-6 shadow-sm">
|
||||||
<ProductPurchase product={product} />
|
<ProductPurchase product={product} />
|
||||||
</div>
|
</div>
|
||||||
{suggested && (
|
{suggestions.length > 0 && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<h2 className="mb-4 text-xs font-black uppercase tracking-[.14em] text-slate-500">También te puede interesar</h2>
|
<h2 className="mb-4 text-xs font-black uppercase tracking-[.14em] text-slate-500">También te puede interesar</h2>
|
||||||
<ProductCard product={suggested} />
|
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-1">
|
||||||
|
{suggestions.map((item) => <ProductCard key={item.id} product={item} />)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { CheckCircle2, ShoppingBag, TriangleAlert, Truck, XCircle } from "lucide-react";
|
import { CheckCircle2, RotateCcw, ShoppingBag, TriangleAlert, Truck, XCircle } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import type { Product } from "@/lib/headless/types";
|
import type { Product } from "@/lib/headless/types";
|
||||||
@@ -79,6 +80,21 @@ export function ProductPurchase({ product }: { product: Product }) {
|
|||||||
Envíos nacionales
|
Envíos nacionales
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user