212 lines
5.0 KiB
TypeScript
212 lines
5.0 KiB
TypeScript
import { API } from "./site";
|
|
|
|
export class ApiError extends Error {
|
|
status: number;
|
|
|
|
constructor(status: number, message: string) {
|
|
super(message);
|
|
this.status = status;
|
|
}
|
|
}
|
|
|
|
type Query = Record<string, string | number | boolean | undefined>;
|
|
|
|
async function apiGet<T>(path: string, query?: Query): Promise<T> {
|
|
const url = new URL(`${API.base}${path}`);
|
|
if (query) {
|
|
for (const [key, value] of Object.entries(query)) {
|
|
if (value !== undefined && value !== "") {
|
|
url.searchParams.set(key, String(value));
|
|
}
|
|
}
|
|
}
|
|
|
|
const response = await fetch(url, { next: { revalidate: 120 } });
|
|
if (!response.ok) {
|
|
throw new ApiError(response.status, `API ${response.status} ${path}`);
|
|
}
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
export type Paginated<T> = {
|
|
items: T[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
};
|
|
|
|
export type ApiProduct = {
|
|
id: string;
|
|
title: string;
|
|
nameFa: string | null;
|
|
slug: string;
|
|
summary?: string | null;
|
|
descriptionHtml?: string | null;
|
|
categoryId?: string | null;
|
|
categoryName?: string | null;
|
|
categoryNameFa?: string | null;
|
|
thumbnailUrl?: string | null;
|
|
image?: string | null;
|
|
thumbnail?: string | null;
|
|
images?: { mediaId: string; url: string }[];
|
|
commentCount?: number;
|
|
store?: {
|
|
variantCount: number;
|
|
minPrice: number | null;
|
|
maxPrice: number | null;
|
|
inStock: boolean;
|
|
} | null;
|
|
};
|
|
|
|
export type ApiComment = {
|
|
id: string;
|
|
authorName?: string | null;
|
|
text?: string | null;
|
|
createdAt?: string | null;
|
|
};
|
|
|
|
export type ApiBlog = {
|
|
id: string;
|
|
title: string;
|
|
slug: string;
|
|
abstract?: string | null;
|
|
mainTextHtml?: string | null;
|
|
categoryName?: string | null;
|
|
titleImageUrl?: string | null;
|
|
publishedAt?: string | null;
|
|
createdAt?: string | null;
|
|
author?: {
|
|
firstName?: string | null;
|
|
lastName?: string | null;
|
|
} | null;
|
|
};
|
|
|
|
export type ApiCategory = {
|
|
id: string;
|
|
parentId: string | null;
|
|
name: string;
|
|
nameFa: string | null;
|
|
slug: string;
|
|
isActive?: boolean;
|
|
};
|
|
|
|
export type ApiVariant = {
|
|
id: string;
|
|
label?: string | null;
|
|
price: number;
|
|
discountedPrice?: number | null;
|
|
stockQuantity?: number | null;
|
|
sortOrder?: number;
|
|
selections?: {
|
|
variationId: string;
|
|
variationName: string;
|
|
optionId: string;
|
|
value: string;
|
|
}[];
|
|
};
|
|
|
|
export type ApiSpecialItem = {
|
|
id: string;
|
|
productId: string;
|
|
productTitle: string;
|
|
productNameFa: string | null;
|
|
productImage: string | null;
|
|
isActive: boolean;
|
|
sortOrder: number;
|
|
variants: ApiVariant[];
|
|
};
|
|
|
|
export type ApiStoreSpecial = {
|
|
id: string;
|
|
key: string;
|
|
title: string;
|
|
isActive: boolean;
|
|
sortOrder: number;
|
|
items: ApiSpecialItem[];
|
|
};
|
|
|
|
export type ApiStoreItem = {
|
|
id: string;
|
|
productId: string;
|
|
variants: ApiVariant[];
|
|
};
|
|
|
|
const tenant = () => `/tenants/${API.domain}`;
|
|
|
|
export async function getProducts(query: {
|
|
page?: number;
|
|
pageSize?: number;
|
|
name?: string;
|
|
categoryId?: string;
|
|
} = {}) {
|
|
return apiGet<Paginated<ApiProduct>>(`${tenant()}/products`, query);
|
|
}
|
|
|
|
export async function getProduct(slug: string) {
|
|
try {
|
|
const data = await apiGet<{ product: ApiProduct }>(`${tenant()}/products/${slug}`);
|
|
return data.product;
|
|
} catch (error) {
|
|
if (error instanceof ApiError && error.status === 404) return null;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getBlogs(query: { page?: number; pageSize?: number; title?: string } = {}) {
|
|
return apiGet<Paginated<ApiBlog>>(`${tenant()}/blogs`, query);
|
|
}
|
|
|
|
export async function getBlog(slug: string) {
|
|
try {
|
|
const data = await apiGet<{ blog: ApiBlog }>(`${tenant()}/blogs/${slug}`);
|
|
return data.blog;
|
|
} catch (error) {
|
|
if (error instanceof ApiError && error.status === 404) return null;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getStoreSpecials() {
|
|
const data = await apiGet<{ items: ApiStoreSpecial[] }>(`${tenant()}/store-specials`);
|
|
return data.items ?? [];
|
|
}
|
|
|
|
export async function getStoreItemByProduct(productId: string) {
|
|
try {
|
|
const data = await apiGet<{ storeItem: ApiStoreItem }>(`${tenant()}/store-items/by-product/${productId}`);
|
|
return data.storeItem;
|
|
} catch (error) {
|
|
if (error instanceof ApiError && error.status === 404) return null;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getCategories() {
|
|
try {
|
|
const data = await apiGet<{ items: ApiCategory[] }>(`${tenant()}/categories`, { entityType: "product" });
|
|
return data.items ?? [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function getCategoryBySlug(slug: string) {
|
|
const categories = await getCategories();
|
|
return categories.find((category) => category.slug === slug) ?? null;
|
|
}
|
|
|
|
export async function getComments(entityType: "product" | "blog", entityId: string) {
|
|
try {
|
|
const data = await apiGet<{ items: ApiComment[] }>(`${tenant()}/comments`, { entityType, entityId });
|
|
return data.items ?? [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function resolveProductSlug(productId: string, name: string) {
|
|
if (!name) return "";
|
|
const { items } = await getProducts({ name, pageSize: 20 });
|
|
return items.find((item) => item.id === productId)?.slug ?? "";
|
|
}
|