feat(F-132): completed feature
This commit is contained in:
@@ -4,6 +4,9 @@ import type { Metadata } from 'next';
|
||||
import { fetchBrandBySlug, fetchProducts, formatPrice } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// ISR para fichas de marca (F-132).
|
||||
export const revalidate = 3600;
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { Metadata } from 'next';
|
||||
import { fetchBrands } from '@/lib/api';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
// ISR para /brands (F-132).
|
||||
export const revalidate = 3600;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Marcas — mercadodevida',
|
||||
description: 'Todas las marcas de productos naturales y ecológicos.',
|
||||
|
||||
@@ -4,6 +4,9 @@ import type { Metadata } from 'next';
|
||||
import { fetchCategoryBySlug, fetchProducts, fetchBrands } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// ISR para fichas de categoría (F-132).
|
||||
export const revalidate = 3600;
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { Metadata } from 'next';
|
||||
import { fetchCategories } from '@/lib/api';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
// ISR para /categories (F-132).
|
||||
export const revalidate = 3600;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Categorías — mercadodevida',
|
||||
description: 'Explora todas las categorías de productos naturales y orgánicos.',
|
||||
|
||||
@@ -15,6 +15,9 @@ import ProductAddToCart from '@/components/cart/ProductAddToCart';
|
||||
import ProductAttributes from '@/components/product/ProductAttributes';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// ISR para fichas de producto (F-132).
|
||||
export const revalidate = 3600;
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { fetchProducts, fetchBrands, fetchCategories, formatPrice } from '@/lib/
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
// ISR: revalidar listado cada hora para que la home / productos reflejen
|
||||
// cambios del catálogo sin necesidad de redeploy (F-132).
|
||||
export const revalidate = 3600;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Productos — mercadodevida',
|
||||
description: 'Todos los productos naturales y orgánicos.',
|
||||
|
||||
@@ -4,6 +4,10 @@ import type { Metadata } from 'next';
|
||||
import { fetchProducts, fetchBrands, fetchCategories, formatPrice } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// Search es por-query (searchParams dinámicos), forzamos dinámico: cada
|
||||
// búsqueda debe ver datos frescos (no se cachea) (F-132).
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{ q?: string; brand?: string; category?: string }>;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ async function readIfFile(filePath: string): Promise<Buffer | null> {
|
||||
|
||||
async function findOriginal(filename: string): Promise<{ root: string; buffer: Buffer } | null> {
|
||||
for (const root of UPLOAD_ROOTS) {
|
||||
const buffer = await readIfFile(path.join(root, filename));
|
||||
const buffer = await readIfFile(path.join(/*turbopackIgnore: true*/ root, filename));
|
||||
if (buffer) return { root, buffer };
|
||||
}
|
||||
return null;
|
||||
@@ -114,7 +114,7 @@ export async function GET(
|
||||
|
||||
// Cached thumbnail already on disk?
|
||||
for (const root of UPLOAD_ROOTS) {
|
||||
const cached = await readIfFile(path.join(root, sizeSegment, filename));
|
||||
const cached = await readIfFile(path.join(/*turbopackIgnore: true*/ root, sizeSegment, filename));
|
||||
if (cached) return imageResponse(cached, filename);
|
||||
}
|
||||
|
||||
@@ -126,9 +126,9 @@ export async function GET(
|
||||
if (!thumbnail) return imageResponse(original.buffer, filename);
|
||||
|
||||
try {
|
||||
const cacheDir = path.join(original.root, sizeSegment);
|
||||
const cacheDir = path.join(/*turbopackIgnore: true*/ original.root, sizeSegment);
|
||||
await mkdir(cacheDir, { recursive: true });
|
||||
await writeFile(path.join(cacheDir, filename), thumbnail);
|
||||
await writeFile(path.join(/*turbopackIgnore: true*/ cacheDir, filename), thumbnail);
|
||||
} catch {
|
||||
// Serving the thumbnail matters more than caching it.
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ interface CmsPage {
|
||||
const BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||
|
||||
export async function fetchCategories(): Promise<Category[]> {
|
||||
const res = await fetch(`${BASE}/categories/tree`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/categories/tree`);
|
||||
if (!res.ok) throw new Error(`Failed to fetch categories: ${res.status}`);
|
||||
const data = await res.json();
|
||||
return data.items ?? [];
|
||||
@@ -30,7 +30,7 @@ export async function fetchProducts(params?: {
|
||||
if (params?.q) sp.set('q', params.q);
|
||||
if (params?.categorySlug) sp.set('categorySlug', params.categorySlug);
|
||||
if (params?.brandSlug) sp.set('brandSlug', params.brandSlug);
|
||||
const res = await fetch(`${BASE}/products/search?${sp}`, { cache: 'no-store', credentials: 'include' });
|
||||
const res = await fetch(`${BASE}/products/search?${sp}`, { credentials: 'include' });
|
||||
if (!res.ok) throw new Error(`Failed to fetch products: ${res.status}`);
|
||||
const data = await res.json();
|
||||
return data.items ?? [];
|
||||
@@ -48,35 +48,35 @@ export async function fetchSearchSuggestions(term: string): Promise<string[]> {
|
||||
}
|
||||
|
||||
export async function fetchBrandBySlug(slug: string): Promise<Brand | null> {
|
||||
const res = await fetch(`${BASE}/marca/${slug}`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/marca/${slug}`);
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error(`Failed to fetch brand: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchPage(slug: string): Promise<CmsPage | null> {
|
||||
const res = await fetch(`${BASE}/cms/pages/${slug}`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/cms/pages/${slug}`);
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error(`Failed to fetch page: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchBrands(): Promise<Brand[]> {
|
||||
const res = await fetch(`${BASE}/brands`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/brands`);
|
||||
if (!res.ok) throw new Error(`Failed to fetch brands: ${res.status}`);
|
||||
const data = await res.json();
|
||||
return data.items ?? [];
|
||||
}
|
||||
|
||||
export async function fetchCategoryBySlug(slug: string): Promise<Category | null> {
|
||||
const res = await fetch(`${BASE}/categoria/${slug}`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/categoria/${slug}`);
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error(`Failed to fetch category: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchProductBySlug(slug: string): Promise<Product | null> {
|
||||
const res = await fetch(`${BASE}/productos/${slug}`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/productos/${slug}`);
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error(`Failed to fetch product: ${res.status}`);
|
||||
return res.json();
|
||||
@@ -103,21 +103,21 @@ export interface StockAvailability {
|
||||
}
|
||||
|
||||
export async function fetchProductVariants(productId: string): Promise<ProductVariant[]> {
|
||||
const res = await fetch(`${BASE}/products/${productId}/variants`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/products/${productId}/variants`);
|
||||
if (!res.ok) throw new Error(`Failed to fetch variants: ${res.status}`);
|
||||
const data = await res.json();
|
||||
return data.items ?? [];
|
||||
}
|
||||
|
||||
export async function fetchVariantPrice(variantId: string): Promise<VariantPrice | null> {
|
||||
const res = await fetch(`${BASE}/pricing/variants/${variantId}`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/pricing/variants/${variantId}`);
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error(`Failed to fetch price: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchStockAvailability(variantId: string): Promise<StockAvailability> {
|
||||
const res = await fetch(`${BASE}/inventory/${variantId}/availability`, { cache: 'no-store' });
|
||||
const res = await fetch(`${BASE}/inventory/${variantId}/availability`);
|
||||
if (!res.ok) throw new Error(`Failed to fetch stock: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user