feat(ADM-018): completed feature
This commit is contained in:
105
project/frontend/src/app/brands/[slug]/page.tsx
Normal file
105
project/frontend/src/app/brands/[slug]/page.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchBrandBySlug, fetchProducts, formatPrice } from '@/lib/api';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const brand = await fetchBrandBySlug(slug);
|
||||
if (!brand) return { title: 'Marca no encontrada' };
|
||||
return {
|
||||
title: brand.seoTitle ?? brand.name,
|
||||
description: brand.seoDescription ?? `Productos ${brand.name} en MercadoDeVida.`,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function BrandPage({ params }: Props) {
|
||||
const { slug } = await params;
|
||||
const [brand, products] = await Promise.all([
|
||||
fetchBrandBySlug(slug),
|
||||
fetchProducts({ brandSlug: slug, limit: 24 }),
|
||||
]);
|
||||
|
||||
if (!brand) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 py-16 text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-4">Marca no encontrada</h1>
|
||||
<Link href="/brands" className="text-[#70ad47] font-medium hover:underline">
|
||||
Ver todas las marcas →
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="mb-6" aria-label="Breadcrumb">
|
||||
<ol className="flex items-center gap-2 text-sm text-gray-500">
|
||||
<li><Link href="/" className="hover:text-[#70ad47]">Inicio</Link></li>
|
||||
<li><span className="text-gray-300">/</span></li>
|
||||
<li><Link href="/brands" className="hover:text-[#70ad47]">Marcas</Link></li>
|
||||
<li><span className="text-gray-300">/</span></li>
|
||||
<li className="text-gray-900 font-medium">{brand.name}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex items-center gap-4">
|
||||
<div className="w-16 h-16 bg-[#70ad47]/10 rounded-2xl flex items-center justify-center">
|
||||
<span className="text-2xl font-bold text-[#70ad47]">
|
||||
{brand.name.slice(0, 2).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900" style={{ fontFamily: 'var(--font-heading)' }}>
|
||||
{brand.name}
|
||||
</h1>
|
||||
{brand.seoDescription && (
|
||||
<p className="mt-1 text-gray-600">{brand.seoDescription}</p>
|
||||
)}
|
||||
<p className="mt-1 text-sm text-gray-500">{products.length} producto{products.length !== 1 ? 's' : ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Products */}
|
||||
{products.length === 0 ? (
|
||||
<div className="py-16 text-center">
|
||||
<p className="text-gray-500">No hay productos de esta marca todavía.</p>
|
||||
<Link href="/brands" className="text-[#70ad47] font-medium hover:underline mt-4 inline-block">
|
||||
Ver otras marcas →
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 justify-items-center">
|
||||
{products.map((product) => (
|
||||
<Link key={product.id} href={`/products/${product.slug}`} className="group block">
|
||||
<div className="bg-gray-50 rounded-xl overflow-hidden border border-gray-100 hover:border-[#70ad47] transition-all hover:shadow-md">
|
||||
<div className="aspect-square relative bg-white flex items-center justify-center">
|
||||
{product.images?.[0] ? (
|
||||
<Image src={product.images[0].url} alt={product.name} fill className="object-cover" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw" />
|
||||
) : (
|
||||
<span className="text-5xl">🌿</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="font-semibold text-gray-900 group-hover:text-[#70ad47] transition-colors line-clamp-2 text-sm">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p className="text-gray-500 text-xs mt-1 line-clamp-2">{product.description}</p>
|
||||
<div className="mt-3 pr-2">
|
||||
<span className="text-lg font-bold text-[#70ad47]">{formatPrice(0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
project/frontend/src/app/brands/page.tsx
Normal file
40
project/frontend/src/app/brands/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Link from 'next/link';
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchBrands } from '@/lib/api';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Marcas — MercadoDeVida',
|
||||
description: 'Todas las marcas de productos naturales y ecológicos.',
|
||||
};
|
||||
|
||||
export default async function BrandsPage() {
|
||||
const brands = await fetchBrands();
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900" style={{ fontFamily: 'var(--font-heading)' }}>
|
||||
Nuestras marcas
|
||||
</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Descubre las marcas de confianza que trabajan con nosotros.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4 justify-center">
|
||||
{brands.map((brand) => (
|
||||
<Link key={brand.id} href={`/brands/${brand.slug}`}>
|
||||
<div className="p-6 bg-gray-50 hover:bg-[#70ad47] hover:text-white rounded-xl border border-gray-200 hover:border-[#70ad47] transition-all text-center group">
|
||||
<div className="w-14 h-14 mx-auto mb-3 bg-[#70ad47]/10 group-hover:bg-white/20 rounded-full flex items-center justify-center">
|
||||
<span className="text-xl font-bold text-[#70ad47] group-hover:text-white">
|
||||
{brand.name.slice(0, 2).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="font-semibold text-sm">{brand.name}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user