import { toFa } from "@/lib/format"; const API_BASE = "https://api.meshkee.com/api/v1"; export const TENANT_DOMAIN = "aradarisman.com"; export type ProductStore = { variantCount: number; minPrice: number | null; maxPrice: number | null; inStock: boolean; }; export type Product = { id: string; businessId: string; title: string; nameFa: string; summary: string; descriptionHtml: string; slug: string; status: string; categoryId: string | null; categoryName: string | null; categoryNameFa: string | null; brandId: string | null; brand: string | null; tags: string[]; thumbnailUrl: string | null; thumbnail: string | null; image: string | null; images: string[]; commentCount: number; variantCount: number; createdAt: string; updatedAt: string; store: ProductStore; }; export type ProductListResponse = { items: Product[]; total: number; page: number; pageSize: number; }; async function meshkeeFetch(path: string, revalidateSeconds = 300): Promise { const res = await fetch(`${API_BASE}${path}`, { next: { revalidate: revalidateSeconds }, }); if (!res.ok) { throw new Error(`Meshkee API ${path} failed: ${res.status}`); } return res.json(); } export function getProducts(params: { page?: number; pageSize?: number; categoryId?: string; name?: string; } = {}): Promise { const query = new URLSearchParams(); if (params.page) query.set("page", String(params.page)); if (params.pageSize) query.set("pageSize", String(params.pageSize)); if (params.categoryId) query.set("categoryId", params.categoryId); if (params.name) query.set("name", params.name); const qs = query.toString(); return meshkeeFetch( `/tenants/${TENANT_DOMAIN}/products${qs ? `?${qs}` : ""}` ); } export async function getProduct(slug: string): Promise { const res = await fetch( `${API_BASE}/tenants/${TENANT_DOMAIN}/products/${slug}`, { next: { revalidate: 300 } } ); if (res.status === 404) return null; if (!res.ok) throw new Error(`Meshkee API product "${slug}" failed: ${res.status}`); const data = await res.json(); return data.product as Product; } export function productImage(product: Product): string | null { return product.thumbnail || product.image || product.thumbnailUrl || null; } export function productPriceLabel(product: Product): string { if (product.store?.minPrice) { const formatted = new Intl.NumberFormat("fa-IR").format(product.store.minPrice); return `${formatted} تومان`; } return "تماس بگیرید"; } export function stripHtml(html: string, maxLength = 140): string { const text = html .replace(/<[^>]*>/g, " ") .replace(/ /g, " ") .replace(/\s+/g, " ") .trim(); if (text.length <= maxLength) return text; return text.slice(0, maxLength).trim() + "…"; } /** * Blog field names are inferred from the Product/Category convention on this * same backend (bilingual title/nameFa, descriptionHtml, thumbnail/image, * categoryNameFa) since no tenant currently has published posts to verify * against. Accessors below fall back across the plausible field names so the * UI keeps working once real posts appear, even if the exact name differs. */ export type Post = { id: string; businessId?: string; title?: string; titleFa?: string; nameFa?: string; summary?: string; excerpt?: string; descriptionHtml?: string; contentHtml?: string; content?: string; slug: string; status?: string; categoryId?: string | null; categoryName?: string | null; categoryNameFa?: string | null; tags?: string[]; thumbnail?: string | null; thumbnailUrl?: string | null; image?: string | null; images?: string[]; commentCount?: number; publishedAt?: string | null; createdAt: string; updatedAt?: string; }; export type PostListResponse = { items: Post[]; total: number; page: number; pageSize: number; }; export function getPosts(params: { page?: number; pageSize?: number; categoryId?: string; title?: string; type?: "news" | "article" | "blog"; } = {}): Promise { const query = new URLSearchParams(); if (params.page) query.set("page", String(params.page)); if (params.pageSize) query.set("pageSize", String(params.pageSize)); if (params.categoryId) query.set("categoryId", params.categoryId); if (params.title) query.set("title", params.title); if (params.type) query.set("type", params.type); const qs = query.toString(); return meshkeeFetch( `/tenants/${TENANT_DOMAIN}/blogs${qs ? `?${qs}` : ""}` ); } export async function getPost(slug: string): Promise { const res = await fetch(`${API_BASE}/tenants/${TENANT_DOMAIN}/blogs/${slug}`, { next: { revalidate: 300 }, }); if (res.status === 404) return null; if (!res.ok) throw new Error(`Meshkee API blog "${slug}" failed: ${res.status}`); const data = await res.json(); return (data.blog ?? data.post ?? data) as Post; } export function postTitle(post: Post): string { return post.nameFa || post.titleFa || post.title || "بدون عنوان"; } export function postContent(post: Post): string { return post.descriptionHtml || post.contentHtml || post.content || ""; } export function postImage(post: Post): string | null { return post.thumbnail || post.image || post.thumbnailUrl || null; } export function postExcerpt(post: Post, maxLength = 140): string { if (post.summary) return post.summary; if (post.excerpt) return post.excerpt; return stripHtml(postContent(post), maxLength); } export function postDate(post: Post): string { const iso = post.publishedAt || post.createdAt; return new Intl.DateTimeFormat("fa-IR-u-ca-persian", { year: "numeric", month: "long", day: "numeric", }).format(new Date(iso)); } export function postReadingTime(post: Post): string { const text = stripHtml(postContent(post), 100000); const words = text.split(/\s+/).filter(Boolean).length; const minutes = Math.max(1, Math.round(words / 150)); return `${toFa(minutes)} دقیقه مطالعه`; }