correctivo
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// src/app/product/[id]/page.tsx
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, Truck, ShieldCheck } from "lucide-react";
|
||||
@@ -7,37 +6,51 @@ import { Product } from "@/types";
|
||||
import ProductCard from "@/components/ui/ProductCard";
|
||||
import ProductActions from "@/components/product/ProductActions";
|
||||
|
||||
// 1. Función para obtener el producto actual
|
||||
// 1. Función para obtener el producto actual + NOMBRE de categoría
|
||||
async function getProduct(id: string) {
|
||||
// Solicitamos el producto y hacemos JOIN con la tabla categories
|
||||
const { data, error } = await supabase
|
||||
.from('products')
|
||||
.select('*')
|
||||
.select(`
|
||||
*,
|
||||
categories (
|
||||
name
|
||||
)
|
||||
`)
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (error || !data) return null;
|
||||
return data as Product;
|
||||
|
||||
const categoryName = data.categories?.name || "General";
|
||||
|
||||
const productWithCategory = {
|
||||
...data,
|
||||
category: categoryName, // Llenamos el campo visual con el nombre real
|
||||
};
|
||||
|
||||
return productWithCategory as unknown as Product;
|
||||
}
|
||||
|
||||
// 2. Función para obtener productos relacionados
|
||||
async function getRelatedProducts(category: string, currentId: string) {
|
||||
// 2. Función para obtener productos relacionados (por ID de categoría)
|
||||
async function getRelatedProducts(categoryId: string, currentId: string) {
|
||||
const { data } = await supabase
|
||||
.from('products')
|
||||
.select('*')
|
||||
.eq('category', category)
|
||||
.eq('category_id', categoryId) // Filtramos por ID, es más seguro
|
||||
.neq('id', currentId)
|
||||
.eq('is_active', true)
|
||||
.limit(3);
|
||||
|
||||
// Como estos son para tarjetas pequeñas, si no tienen el nombre de la categoría mapeado
|
||||
// no es crítico, pero idealmente haríamos el mismo join si quisiéramos mostrarlo.
|
||||
return (data as Product[]) || [];
|
||||
}
|
||||
|
||||
// 3. DEFINICIÓN DEL COMPONENTE (Aquí estaba el error de tipos)
|
||||
// En lugar de una interfaz separada, tipamos inline para evitar conflictos con Next.js 15
|
||||
// 3. DEFINICIÓN DEL COMPONENTE
|
||||
export default async function ProductPage(props: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
// Await obligatorio en Next.js 15 antes de acceder a params
|
||||
const params = await props.params;
|
||||
const { id } = params;
|
||||
|
||||
@@ -47,14 +60,18 @@ export default async function ProductPage(props: {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const relatedProducts = await getRelatedProducts(product.category, product.id);
|
||||
// Usamos category_id para buscar relacionados
|
||||
const relatedProducts = await getRelatedProducts(product.category_id, product.id);
|
||||
const imageUrl = getProductImageUrl(product.image_url);
|
||||
|
||||
// Fallback visual por si la categoría vino vacía
|
||||
const displayCategory = product.category || "Colección";
|
||||
|
||||
return (
|
||||
<div className="bg-white min-h-screen pb-32">
|
||||
{/* Navegación Breadcrumb */}
|
||||
<div className="pt-32 px-6 md:px-12 max-w-[1400px] mx-auto mb-8">
|
||||
<Link href="/" className="inline-flex items-center gap-2 text-sm text-gray-400 hover:text-dark transition-colors">
|
||||
<Link href="/shop" className="inline-flex items-center gap-2 text-sm text-gray-400 hover:text-dark transition-colors">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Volver al catálogo
|
||||
</Link>
|
||||
@@ -83,7 +100,7 @@ export default async function ProductPage(props: {
|
||||
{/* COLUMNA DERECHA: Detalles */}
|
||||
<div className="flex flex-col justify-center lg:py-12">
|
||||
<span className="text-accent text-sm tracking-[0.2em] uppercase font-medium mb-4 block">
|
||||
{product.category}
|
||||
{displayCategory}
|
||||
</span>
|
||||
|
||||
<h1 className="text-5xl md:text-6xl font-light text-dark mb-6 leading-[1.1] tracking-tight">
|
||||
@@ -108,7 +125,7 @@ export default async function ProductPage(props: {
|
||||
<p>{product.description || "Sin descripción disponible para este producto."}</p>
|
||||
</div>
|
||||
|
||||
{/* Componente Interactivo */}
|
||||
{/* Componente Interactivo (Cliente) */}
|
||||
<ProductActions product={product} />
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-xs text-gray-400 uppercase tracking-wider font-medium">
|
||||
@@ -134,7 +151,7 @@ export default async function ProductPage(props: {
|
||||
key={prod.id}
|
||||
id={prod.id}
|
||||
title={prod.name}
|
||||
category={prod.category}
|
||||
category={prod.category || "Sugerencia"}
|
||||
price={prod.price}
|
||||
imageUrl={getProductImageUrl(prod.image_url)}
|
||||
index={idx + 1}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// src/types/index.ts
|
||||
|
||||
export interface Product {
|
||||
id: string;
|
||||
created_at: string;
|
||||
@@ -5,21 +7,23 @@ export interface Product {
|
||||
description: string | null;
|
||||
price: number;
|
||||
stock: number;
|
||||
category_id: string;
|
||||
image_url: string | null; // El path dentro del bucket
|
||||
image_url: string | null;
|
||||
is_active: boolean;
|
||||
stripe_product_id?: string;
|
||||
stripe_price_id?: string;
|
||||
|
||||
// NUEVO: El ID real de la base de datos
|
||||
category_id: string;
|
||||
|
||||
// COMPATIBILIDAD: Mantenemos 'category' como opcional para no romper la Home
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
name: string; // Ej: "Frutas & Verduras"
|
||||
slug: string; // Ej: "frutas-verduras" (útil para URLs)
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface CartItem extends Product {
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user