Files
salar-store/lib/api.ts
T

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 ?? "";
}