Files
apps-registry/workloads/ecommerce/app/product/[slug]/page.tsx
T
devops 7a09202c27 fix: forzar ISR en /product/[slug] para que no quede atascado en datos mock del build
generateStaticParams usa el catálogo mock en build time porque
HEADLESS_PROVIDER y MEDUSA_* no existen como build args del Dockerfile.
Sin un revalidate explícito, esa ruta queda estática para siempre con
price:null. El listado no sufre esto porque su ruta es dinámica por
searchParams.
2026-08-12 22:56:03 -05:00

38 lines
3.2 KiB
TypeScript

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 { ProductCarousel } from "@/components/product/product-carousel";
import { ProductGallery } from "@/components/product/product-gallery";
import { ProductPurchase } from "@/components/product/product-purchase";
import { getProductBySlug, getProducts } from "@/lib/headless/client";
interface ProductPageProps { params: Promise<{ slug: string }> }
// Sin esto, la página queda estática para siempre con los datos del build
// (que usa el catálogo mock porque HEADLESS_PROVIDER/MEDUSA_* no existen en
// el contexto del Docker build). El listado sí se refresca porque su ruta
// es dinámica por searchParams; esta ruta necesita el revalidate explícito
// para que el runtime (que sí tiene las env vars de Medusa) la regenere.
export const revalidate = 60;
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({ slug: product.slug }));
}
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
const { slug } = await params;
const product = await getProductBySlug(slug);
return product ? { title: product.name, description: product.shortDescription, openGraph: { images: [product.image] } } : { title: "Producto" };
}
export default async function ProductPage({ params }: ProductPageProps) {
const { slug } = await params;
const product = await getProductBySlug(slug);
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 <><section className="mx-auto grid max-w-7xl gap-10 px-4 py-10 lg:grid-cols-[1.08fr_.92fr] lg:py-16"><ProductGallery images={product.images} name={product.name} /><div><span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">{product.category} · {product.collection}</span><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><div className="mt-8"><ProductPurchase product={product} /></div><Accordion type="single" collapsible className="mt-8"><AccordionItem value="details"><AccordionTrigger>Detalles del producto</AccordionTrigger><AccordionContent>Marca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.</AccordionContent></AccordionItem><AccordionItem value="shipping"><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></AccordionItem></Accordion></div></section>{recommended.length > 0 && <section className="mx-auto max-w-7xl px-4 pb-24"><h2 className="mb-7 text-3xl font-black">También te puede gustar</h2><ProductCarousel products={recommended} /></section>}<MobileAddToCart product={product} /></>;
}