Build the Salar Store storefront and connect products, blogs, and home specials to the Meshkee API.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
2783305f07
commit
7d26eaa838
+211
@@ -0,0 +1,211 @@
|
||||
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 ?? "";
|
||||
}
|
||||
Reference in New Issue
Block a user