import { MESHKEE_API_BASE, MESHKEE_DOMAIN, } from "@/lib/meshkee/config"; import type { AuthUser, BlogPost, Category, Comment, FaviconInfo, Paginated, Product, StaticImageSlot, StoreItemVariant, TechnicalInfo, Tenant, } from "@/lib/meshkee/types"; export class MeshkeeApiError extends Error { status: number; payload: unknown; constructor(message: string, status: number, payload?: unknown) { super(message); this.name = "MeshkeeApiError"; this.status = status; this.payload = payload; } } type FetchOptions = { method?: string; body?: unknown; token?: string | null; signal?: AbortSignal; cache?: RequestCache; next?: NextFetchRequestConfig; }; async function request(path: string, options: FetchOptions = {}): Promise { const headers: Record = { Accept: "application/json", }; if (options.body !== undefined) { headers["Content-Type"] = "application/json"; } if (options.token) { headers.Authorization = `Bearer ${options.token}`; } const init: RequestInit = { method: options.method ?? "GET", headers, body: options.body !== undefined ? JSON.stringify(options.body) : undefined, signal: options.signal, }; // `next` / `cache` are server-only fetch extensions if (typeof window === "undefined") { if (options.cache) init.cache = options.cache; if (options.next) { (init as RequestInit & { next?: NextFetchRequestConfig }).next = options.next; } } const response = await fetch(`${MESHKEE_API_BASE}${path}`, init); const payload = await response.json().catch(() => null); if (!response.ok) { const record = payload && typeof payload === "object" ? (payload as Record) : null; const errorValue = record?.error; const messageValue = record?.message; const message = (typeof errorValue === "string" && errorValue) || (typeof messageValue === "string" && messageValue) || "خطا در ارتباط با سرور"; throw new MeshkeeApiError(message, response.status, payload); } return payload as T; } function tenantPath(suffix = "") { return `/tenants/${encodeURIComponent(MESHKEE_DOMAIN)}${suffix}`; } export async function getTenant(): Promise { return request(tenantPath(), { next: { revalidate: 300 } }); } export async function getFavicon(): Promise { return request(tenantPath("/website/favicon"), { next: { revalidate: 300 }, }); } export async function getStaticImages( pageKey?: string, ): Promise { const query = pageKey ? `?pageKey=${encodeURIComponent(pageKey)}` : ""; const data = await request<{ items: StaticImageSlot[] }>( `${tenantPath("/website/static-images")}${query}`, { next: { revalidate: 60 } }, ); return data.items ?? []; } export async function getStaticImageSlot( key: string, ): Promise { try { return await request( tenantPath(`/website/static-images/${encodeURIComponent(key)}`), { next: { revalidate: 60 } }, ); } catch { return null; } } export async function getCategories( entityType: "product" | "blog" = "product", ): Promise { const data = await request<{ items: Category[] }>( `${tenantPath("/categories")}?entityType=${entityType}`, { next: { revalidate: 120 } }, ); return data.items ?? []; } export async function listStoreItems(params: { page?: number; pageSize?: number; categoryId?: string; name?: string; productId?: 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); if (params.productId) query.set("productId", params.productId); const qs = query.toString(); return request>( `${tenantPath("/store-items")}${qs ? `?${qs}` : ""}`, { next: { revalidate: 60 } }, ); } export async function getStoreItemVariant( variantId: string, ): Promise { const data = await request<{ variant: StoreItemVariant }>( tenantPath(`/store-items/${encodeURIComponent(variantId)}`), { next: { revalidate: 60 } }, ); return data.variant; } export async function getProductById(productId: string): Promise<{ product: Product; relatedProducts: Product[]; }> { const data = await request<{ product: Product; relatedProducts?: Product[] | null; }>(tenantPath(`/products/by-id/${encodeURIComponent(productId)}`), { next: { revalidate: 60 }, }); return { product: data.product, relatedProducts: data.relatedProducts ?? [], }; } export async function getProductTechnicalInfo( productId: string, ): Promise { return request( tenantPath(`/products/by-id/${encodeURIComponent(productId)}/technical-info`), { next: { revalidate: 120 } }, ); } export async function listBlogs(params: { page?: number; pageSize?: number; } = {}): Promise> { const query = new URLSearchParams(); if (params.page) query.set("page", String(params.page)); if (params.pageSize) query.set("pageSize", String(params.pageSize)); const qs = query.toString(); return request>( `${tenantPath("/blogs")}${qs ? `?${qs}` : ""}`, { next: { revalidate: 60 } }, ); } export async function getBlogById(blogId: string): Promise { const data = await request<{ blog?: BlogPost } & BlogPost>( tenantPath(`/blogs/by-id/${encodeURIComponent(blogId)}`), { next: { revalidate: 60 } }, ); return data.blog ?? data; } export async function listComments(params: { entityType: "product" | "blog"; entityId: string; }): Promise { const query = new URLSearchParams({ entityType: params.entityType, entityId: params.entityId, }); const data = await request<{ items: Comment[] }>( `${tenantPath("/comments")}?${query.toString()}`, { cache: "no-store" }, ); return data.items ?? []; } export async function submitComment( body: { entityType: "product" | "blog"; entityId: string; authorName: string; authorEmail?: string; text: string; }, token?: string | null, ) { return request(tenantPath("/comments"), { method: "POST", body, token, cache: "no-store", }); } export async function submitContact(body: { title: string; name: string; email?: string; cellNumber?: string; text: string; }) { return request(tenantPath("/contact-submissions"), { method: "POST", body, cache: "no-store", }); } export async function getMe(token: string, signal?: AbortSignal): Promise { const data = await request<{ user?: AuthUser } & AuthUser>("/auth/me", { token, signal, cache: "no-store", }); return data.user ?? data; } export async function refreshTokens(refreshToken: string) { return request<{ accessToken: string; refreshToken: string }>("/auth/refresh", { method: "POST", body: { refreshToken }, cache: "no-store", }); } export async function createAuthHandoff(token: string) { return request<{ ticket?: string }>("/auth/handoff", { method: "POST", token, cache: "no-store", }); } export async function getCart(businessId: string, token: string) { return request(`/businesses/${encodeURIComponent(businessId)}/cart`, { token, cache: "no-store", }); } export async function addCartItem( businessId: string, token: string, body: { storeItemVariantId: string; quantity: number }, ) { return request(`/businesses/${encodeURIComponent(businessId)}/cart/items`, { method: "POST", token, body, cache: "no-store", }); } export async function updateCartItem( businessId: string, token: string, itemId: string, quantity: number, ) { return request( `/businesses/${encodeURIComponent(businessId)}/cart/items/${encodeURIComponent(itemId)}`, { method: "PATCH", token, body: { quantity }, cache: "no-store", }, ); } export async function removeCartItem( businessId: string, token: string, itemId: string, ) { return request( `/businesses/${encodeURIComponent(businessId)}/cart/items/${encodeURIComponent(itemId)}`, { method: "DELETE", token, cache: "no-store", }, ); }