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