134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import type { Category, Product, Brand } from '@/types/api';
|
|
|
|
interface CmsPage {
|
|
id: string;
|
|
slug: string;
|
|
title: string;
|
|
body: string;
|
|
status: string;
|
|
}
|
|
|
|
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`);
|
|
if (!res.ok) throw new Error(`Failed to fetch categories: ${res.status}`);
|
|
const data = await res.json();
|
|
return data.items ?? [];
|
|
}
|
|
|
|
export async function fetchProducts(params?: {
|
|
limit?: number;
|
|
offset?: number;
|
|
q?: string;
|
|
categorySlug?: string;
|
|
brandSlug?: string;
|
|
}): Promise<Product[]> {
|
|
const sp = new URLSearchParams();
|
|
sp.set('limit', String(params?.limit ?? 8));
|
|
if (params?.offset !== undefined) sp.set('offset', String(params.offset));
|
|
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}`, { credentials: 'include' });
|
|
if (!res.ok) throw new Error(`Failed to fetch products: ${res.status}`);
|
|
const data = await res.json();
|
|
return data.items ?? [];
|
|
}
|
|
|
|
export async function fetchSearchSuggestions(term: string): Promise<string[]> {
|
|
const sp = new URLSearchParams({ q: term });
|
|
const res = await fetch(`${BASE}/products/suggest?${sp}`, {
|
|
cache: 'no-store',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) return [];
|
|
const data = await res.json();
|
|
return data.suggestions ?? [];
|
|
}
|
|
|
|
export async function fetchBrandBySlug(slug: string): Promise<Brand | null> {
|
|
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}`);
|
|
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`);
|
|
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}`);
|
|
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}`);
|
|
if (res.status === 404) return null;
|
|
if (!res.ok) throw new Error(`Failed to fetch product: ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
export interface ProductVariant {
|
|
id: string;
|
|
productId: string;
|
|
sku: string;
|
|
ean: string | null;
|
|
attributes: Record<string, unknown>;
|
|
weightGrams: number | null; // F-193
|
|
}
|
|
|
|
export interface VariantPrice {
|
|
variantId: string;
|
|
netUnitAmountCents: number;
|
|
vatRate: 'general' | 'reduced';
|
|
currency: string;
|
|
}
|
|
|
|
export interface StockAvailability {
|
|
available: boolean;
|
|
availableQuantity: number;
|
|
}
|
|
|
|
export async function fetchProductVariants(productId: string): Promise<ProductVariant[]> {
|
|
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}`);
|
|
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`);
|
|
if (!res.ok) throw new Error(`Failed to fetch stock: ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
export function formatPrice(cents: number): string {
|
|
return `€${(cents / 100).toFixed(2)}`;
|
|
}
|
|
|
|
export function calcGrossPrice(netCents: number, vatRate: 'general' | 'reduced'): number {
|
|
const rate = vatRate === 'general' ? 0.21 : 0.1;
|
|
return Math.round(netCents * (1 + rate));
|
|
}
|