Files
salar-store/lib/mappers.ts
T

151 lines
4.0 KiB
TypeScript

import { PLACEHOLDER, type Post, type Product } from "./data";
import {
getBlogs,
getProducts,
getStoreSpecials,
resolveProductSlug,
type ApiBlog,
type ApiProduct,
type ApiSpecialItem,
type ApiVariant,
} from "./api";
import { formatDate } from "./format";
function productImage(url?: string | null) {
return url || PLACEHOLDER.product;
}
function pickVariantPrice(variants: ApiVariant[]) {
if (!variants.length) {
return { price: 0, oldPrice: undefined as number | undefined, stock: 0 };
}
const inStock = variants.filter((variant) => (variant.stockQuantity ?? 0) > 0);
const pool = inStock.length ? inStock : variants;
const best = pool.reduce((current, next) => {
const currentPrice = current.discountedPrice ?? current.price;
const nextPrice = next.discountedPrice ?? next.price;
return nextPrice < currentPrice ? next : current;
});
const price = best.discountedPrice ?? best.price ?? 0;
const oldPrice = best.discountedPrice ? best.price : undefined;
const stock = variants.reduce((sum, variant) => sum + (variant.stockQuantity ?? 0), 0);
return { price, oldPrice, stock };
}
export function mapProduct(item: ApiProduct): Product {
return {
id: item.id,
name: item.nameFa || item.title,
slug: item.slug,
price: item.store?.minPrice ?? 0,
stock: item.store?.inStock ? item.store.variantCount || 1 : 0,
image: productImage(item.thumbnailUrl || item.image || item.thumbnail),
};
}
export function mapSpecialItem(item: ApiSpecialItem, slug: string): Product {
const { price, oldPrice, stock } = pickVariantPrice(item.variants ?? []);
return {
id: item.productId,
name: item.productNameFa || item.productTitle,
slug,
price,
oldPrice,
stock,
image: productImage(item.productImage),
};
}
export function mapBlog(item: ApiBlog): Post {
return {
slug: item.slug,
title: item.title,
date: formatDate(item.publishedAt || item.createdAt || ""),
image: item.titleImageUrl || PLACEHOLDER.blog,
abstract: item.abstract?.trim() || undefined,
};
}
export async function getSpecialProducts(key: string): Promise<Product[]> {
try {
const specials = await getStoreSpecials();
const special = specials.find((item) => item.isActive && item.key.toLowerCase() === key.toLowerCase());
if (!special) return [];
const items = special.items
.filter((item) => item.isActive)
.sort((a, b) => a.sortOrder - b.sortOrder);
return Promise.all(
items.map(async (item) => {
const slug = await resolveProductSlug(item.productId, item.productTitle || item.productNameFa || "");
return mapSpecialItem(item, slug);
}),
);
} catch {
return [];
}
}
export async function getLatestPosts(pageSize = 12): Promise<Post[]> {
try {
const data = await getBlogs({ page: 1, pageSize });
return data.items.map(mapBlog);
} catch {
return [];
}
}
export async function listMappedProducts(query: {
page?: number;
pageSize?: number;
name?: string;
categoryId?: string;
}) {
try {
const data = await getProducts(query);
return {
...data,
items: data.items.map(mapProduct),
};
} catch {
return {
items: [],
total: 0,
page: query.page ?? 1,
pageSize: query.pageSize ?? 12,
};
}
}
export async function listMappedProductsForCategory(
categoryId: string,
childIds: string[],
page: number,
pageSize: number,
) {
const ids = [categoryId, ...childIds];
if (ids.length === 1) {
return listMappedProducts({ page, pageSize, categoryId });
}
const results = await Promise.all(ids.map((id) => listMappedProducts({ page: 1, pageSize: 50, categoryId: id })));
const seen = new Set<string>();
const items = results.flatMap((result) => result.items).filter((item) => {
if (seen.has(item.id)) return false;
seen.add(item.id);
return true;
});
const start = (page - 1) * pageSize;
return {
items: items.slice(start, start + pageSize),
total: items.length,
page,
pageSize,
};
}