feat(F-103): completed feature
This commit is contained in:
@@ -2,6 +2,7 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchBrandBySlug, fetchProducts, formatPrice } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
@@ -92,8 +93,8 @@ export default async function BrandPage({ params }: Props) {
|
||||
</h3>
|
||||
{product.description && (
|
||||
<div
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 rich-text prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: formatRichText(product.description) }}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-3 pr-2">
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchCategoryBySlug, fetchProducts, fetchBrands } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
@@ -13,7 +14,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
if (!category) return { title: 'Categoría no encontrada' };
|
||||
return {
|
||||
title: category.seoTitle ?? category.name,
|
||||
description: category.seoDescription ?? `${category.name} — Productos naturales y orgánicos en mercadodevida.`,
|
||||
description: category.seoDescription ?? category.description ?? `${category.name} — Productos naturales y orgánicos en mercadodevida.`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,8 +69,8 @@ export default async function CategoryPage({ params }: Props) {
|
||||
<h1 className="text-3xl font-bold text-gray-900" style={{ fontFamily: 'var(--font-heading)' }}>
|
||||
{category.name}
|
||||
</h1>
|
||||
{category.seoDescription && (
|
||||
<p className="mt-2 text-gray-600">{category.seoDescription}</p>
|
||||
{(category.description || category.seoDescription) && (
|
||||
<p className="mt-2 text-gray-600">{category.description || category.seoDescription}</p>
|
||||
)}
|
||||
<p className="mt-1 text-sm text-gray-500">{products.length} producto{products.length !== 1 ? 's' : ''}</p>
|
||||
</div>
|
||||
@@ -105,8 +106,8 @@ export default async function CategoryPage({ params }: Props) {
|
||||
</h3>
|
||||
{product.description && (
|
||||
<div
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 rich-text prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: formatRichText(product.description) }}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-3 pr-2">
|
||||
|
||||
@@ -39,14 +39,18 @@ export default async function CategoriesPage() {
|
||||
{tree.map((cat, i) => {
|
||||
const emoji = cat.emoji ?? icons[cat.slug] ?? '📦';
|
||||
const colorClass = cat.color ?? colors[i % colors.length];
|
||||
const customColors = Boolean(cat.bgColor || cat.textColor);
|
||||
return (
|
||||
<Link key={cat.id} href={`/categories/${cat.slug}`} className="group block">
|
||||
<div className={`relative overflow-hidden rounded-2xl bg-gradient-to-br ${colorClass} p-6 text-white min-h-[140px] flex flex-col justify-between`}>
|
||||
<div
|
||||
className={`relative overflow-hidden rounded-2xl ${customColors ? '' : `bg-gradient-to-br ${colorClass} text-white`} p-6 min-h-[140px] flex flex-col justify-between`}
|
||||
style={customColors ? { backgroundColor: cat.bgColor ?? '#F1F8E9', color: cat.textColor ?? '#2D6A4F' } : undefined}
|
||||
>
|
||||
<div className="absolute top-4 right-4 text-5xl opacity-20">{emoji}</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold group-hover:underline">{cat.name}</h2>
|
||||
{cat.seoDescription && (
|
||||
<p className="mt-1 text-sm text-white/80 line-clamp-2">{cat.seoDescription}</p>
|
||||
<p className="mt-1 text-sm opacity-80 line-clamp-2">{cat.description || cat.seoDescription}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
|
||||
@@ -33,3 +33,29 @@ body {
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-sans), system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* Texto enriquecido de descripciones generadas por IA o editadas en el admin */
|
||||
.rich-text p {
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.rich-text p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.rich-text ul,
|
||||
.rich-text ol {
|
||||
margin: 0 0 0.75rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.rich-text ul {
|
||||
list-style: disc;
|
||||
}
|
||||
.rich-text ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
.rich-text li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.rich-text strong {
|
||||
font-weight: 600;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@/lib/api';
|
||||
import ProductAddToCart from '@/components/cart/ProductAddToCart';
|
||||
import ProductAttributes from '@/components/product/ProductAttributes';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
@@ -199,8 +200,8 @@ export default async function ProductPage({ params }: Props) {
|
||||
<div className="mt-8">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-2">Descripción</h2>
|
||||
<div
|
||||
className="text-gray-600 leading-relaxed prose prose-sm max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
className="text-gray-600 leading-relaxed rich-text prose prose-sm max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: formatRichText(product.description) }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchProducts, fetchBrands, fetchCategories, formatPrice } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Productos — mercadodevida',
|
||||
@@ -73,8 +74,8 @@ export default async function ProductsPage() {
|
||||
</h3>
|
||||
{product.description && (
|
||||
<div
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 rich-text prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: formatRichText(product.description) }}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-3 pr-2">
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchProducts, fetchBrands, fetchCategories, formatPrice } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{ q?: string; brand?: string; category?: string }>;
|
||||
@@ -108,8 +109,8 @@ export default async function SearchPage({ searchParams }: Props) {
|
||||
</h3>
|
||||
{product.description && (
|
||||
<div
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
className="text-gray-500 text-xs mt-1 line-clamp-2 rich-text prose prose-xs max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: formatRichText(product.description) }}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-3 pr-2">
|
||||
|
||||
Reference in New Issue
Block a user