278 lines
10 KiB
JavaScript
278 lines
10 KiB
JavaScript
import { articles, products } from "./data.js";
|
|
|
|
export const MESHKEE_DOMAIN = "asphp.ir";
|
|
export const MESHKEE_API = "https://api.meshkee.com/api/v1";
|
|
export const CUSTOMER_URL = "https://customer.asphp.ir.com";
|
|
export const DEFAULT_FAVICON = "/assets/favicon.png";
|
|
|
|
async function fetchJson(path, options = {}) {
|
|
const response = await fetch(`${MESHKEE_API}${path}`, {
|
|
cache: "no-store",
|
|
...options,
|
|
headers: {
|
|
Accept: "application/json",
|
|
...(options.body ? { "Content-Type": "application/json" } : {}),
|
|
...(options.headers || {})
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Meshkee API ${response.status}: ${path}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
export async function getTenant() {
|
|
try {
|
|
return await fetchJson(`/tenants/${MESHKEE_DOMAIN}`);
|
|
} catch {
|
|
return { id: "33", domain: MESHKEE_DOMAIN, nameFa: "تجهیزات سرور پارس" };
|
|
}
|
|
}
|
|
|
|
export async function getBranding() {
|
|
try {
|
|
const branding = await fetchJson(`/tenants/${MESHKEE_DOMAIN}/website/favicon`);
|
|
return {
|
|
faviconUrl: branding?.faviconUrl || DEFAULT_FAVICON,
|
|
logoUrl: branding?.logoUrl || "/assets/logo.png",
|
|
logoDarkUrl: branding?.logoDarkUrl || branding?.logoUrl || "/assets/logo.png",
|
|
hasDedicatedFavicon: Boolean(branding?.hasDedicatedFavicon)
|
|
};
|
|
} catch {
|
|
return {
|
|
faviconUrl: DEFAULT_FAVICON,
|
|
logoUrl: "/assets/logo.png",
|
|
logoDarkUrl: "/assets/logo.png",
|
|
hasDedicatedFavicon: false
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function getStoreItems({ page = 1, pageSize = 12, query = "" } = {}) {
|
|
try {
|
|
const search = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
|
|
if (query) search.set("name", query);
|
|
const data = await fetchJson(`/tenants/${MESHKEE_DOMAIN}/store-items?${search}`);
|
|
const items = (data.items || []).map(normalizeStoreItem);
|
|
if (items.length) return { ...data, items };
|
|
} catch {}
|
|
|
|
const items = fallbackStoreItems();
|
|
return { items, total: items.length, page, pageSize, fallback: true };
|
|
}
|
|
|
|
export async function getStoreItem(id) {
|
|
try {
|
|
const data = await fetchJson(`/tenants/${MESHKEE_DOMAIN}/store-items/${encodeURIComponent(id)}`);
|
|
if (data.variant) return normalizeStoreItem(data.variant);
|
|
} catch {}
|
|
|
|
return fallbackStoreItems().find((item) => item.id === id || item.slug === id) || fallbackStoreItems()[0];
|
|
}
|
|
|
|
export async function getBlogs({ page = 1, pageSize = 12 } = {}) {
|
|
try {
|
|
const data = await fetchJson(`/tenants/${MESHKEE_DOMAIN}/blogs?page=${page}&pageSize=${pageSize}`);
|
|
const items = (data.items || []).map(normalizeBlog);
|
|
if (items.length) return { ...data, items };
|
|
} catch {}
|
|
|
|
const items = articles.map((article, index) => ({
|
|
id: `local-blog-${index + 1}`,
|
|
slug: `local-blog-${index + 1}`,
|
|
title: article.title,
|
|
date: article.date,
|
|
image: article.image,
|
|
abstract: article.abstract,
|
|
html: `<p>${article.abstract}</p>`
|
|
}));
|
|
return { items, total: items.length, page, pageSize, fallback: true };
|
|
}
|
|
|
|
export async function getStaticImageSlots({ pageKey = "home" } = {}) {
|
|
const fetchSlots = async (filterPageKey) => {
|
|
const search = filterPageKey ? `?pageKey=${encodeURIComponent(filterPageKey)}` : "";
|
|
const data = await fetchJson(`/tenants/${MESHKEE_DOMAIN}/website/static-images${search}`);
|
|
return Array.isArray(data.items) ? data.items.map(normalizeStaticImageSlot) : [];
|
|
};
|
|
|
|
try {
|
|
const pageSlots = await fetchSlots(pageKey);
|
|
if (pageSlots.length || !pageKey) return pageSlots;
|
|
return await fetchSlots("");
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function getStaticImageSlotMap(pageKey = "home") {
|
|
const slots = await getStaticImageSlots({ pageKey });
|
|
return Object.fromEntries(slots.map((slot) => [slot.key, slot]));
|
|
}
|
|
|
|
export function staticSlotImages(slotMap, key, fallbackImages = []) {
|
|
const slot = slotMap?.[key];
|
|
const images = slot?.images?.length ? slot.images : fallbackImages;
|
|
const pickedImages = slot?.kind === "single" ? images.slice(0, 1) : images;
|
|
|
|
return pickedImages.map((image, index) => {
|
|
const normalized = typeof image === "string" ? { url: image } : image;
|
|
return {
|
|
url: normalized.url || normalized.image || normalized.imageUrl || normalized.src,
|
|
href: normalized.linkUrl || normalized.href || normalized.link,
|
|
titleFa: normalized.titleFa || normalized.nameFa || normalized.title || normalized.alt,
|
|
titleEn: normalized.titleEn || normalized.nameEn || "",
|
|
subtext: normalized.subtext || normalized.description || "",
|
|
alt: normalized.alt || normalized.titleFa || normalized.title || slot?.label || "",
|
|
width: normalized.width,
|
|
height: normalized.height,
|
|
aspectRatio: cssAspectRatio(slot?.aspectRatio || normalized.aspectRatio),
|
|
sortOrder: normalized.sortOrder ?? index
|
|
};
|
|
}).filter((image) => image.url);
|
|
}
|
|
|
|
export async function getBlog(slug) {
|
|
try {
|
|
const data = await fetchJson(`/tenants/${MESHKEE_DOMAIN}/blogs/${encodeURIComponent(slug)}`);
|
|
if (data.blog) return normalizeBlog(data.blog);
|
|
} catch {}
|
|
|
|
const blogs = await getBlogs();
|
|
return blogs.items.find((blog) => blog.slug === slug || blog.id === slug) || blogs.items[0];
|
|
}
|
|
|
|
export async function submitContact(payload) {
|
|
return fetchJson(`/tenants/${MESHKEE_DOMAIN}/contact-submissions`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export async function getCurrentUser(token) {
|
|
return fetchJson("/auth/me", {
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
}
|
|
|
|
export async function addCartItem({ businessId, token, storeItemVariantId, quantity }) {
|
|
return fetchJson(`/businesses/${businessId}/cart/items`, {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ storeItemVariantId, quantity })
|
|
});
|
|
}
|
|
|
|
export function normalizeStoreItem(item) {
|
|
const product = item.product || {};
|
|
const images = [
|
|
...(Array.isArray(item.images) ? item.images : []),
|
|
...(Array.isArray(item.gallery) ? item.gallery : [])
|
|
]
|
|
.map((image) => image.url || image.imageUrl || image)
|
|
.filter(Boolean);
|
|
const image =
|
|
item.imageUrl ||
|
|
item.titleImageUrl ||
|
|
item.mainImageUrl ||
|
|
product.imageUrl ||
|
|
product.main_image ||
|
|
images[0] ||
|
|
"/assets/product-server-g9.jpg";
|
|
|
|
return {
|
|
id: String(item.id || item.variantId || item.storeItemVariantId || item.slug || product.id),
|
|
slug: String(item.slug || item.id || item.variantId || product.slug || product.id),
|
|
productId: String(item.productId || product.id || ""),
|
|
title: item.titleFa || item.nameFa || item.title || product.titleFa || product.nameFa || product.title || "محصول فروشگاه",
|
|
category: item.categoryName || product.categoryName || "تجهیزات سرور",
|
|
image,
|
|
images: images.length ? images : [image],
|
|
price: item.finalPrice || item.salePrice || item.price || item.amount || null,
|
|
currency: item.currency || item.priceCurrency || "تومان",
|
|
description: item.description || item.descriptionHtml || product.description || product.mainTextHtml || "",
|
|
technicalValues: item.technicalValues || item.technicalData || [],
|
|
quantity: item.quantity ?? item.stock ?? null,
|
|
isFestival: Boolean(item.isFestival),
|
|
raw: item
|
|
};
|
|
}
|
|
|
|
export function normalizeStaticImageSlot(slot) {
|
|
return {
|
|
key: slot.key,
|
|
pageKey: slot.pageKey,
|
|
kind: slot.kind || "single",
|
|
label: slot.label || slot.name || slot.key,
|
|
aspectRatio: cssAspectRatio(slot.aspectRatio),
|
|
recommendedWidth: slot.recommendedWidth,
|
|
itemCount: slot.itemCount,
|
|
images: Array.isArray(slot.images)
|
|
? slot.images
|
|
.map((image) => ({
|
|
id: image.id,
|
|
url: image.url || image.image || image.imageUrl || image.src,
|
|
titleFa: image.titleFa || image.nameFa || image.title,
|
|
titleEn: image.titleEn || image.nameEn || "",
|
|
subtext: image.subtext || image.description || "",
|
|
linkUrl: image.linkUrl || image.href || image.link,
|
|
width: image.width,
|
|
height: image.height,
|
|
aspectRatio: cssAspectRatio(image.aspectRatio),
|
|
sortOrder: image.sortOrder
|
|
}))
|
|
.filter((image) => image.url)
|
|
.sort((a, b) => Number(a.sortOrder || 0) - Number(b.sortOrder || 0))
|
|
: []
|
|
};
|
|
}
|
|
|
|
export function cssAspectRatio(value) {
|
|
if (!value) return undefined;
|
|
const text = String(value).trim();
|
|
const comma = text.match(/^(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)$/);
|
|
if (comma) return `${comma[1]} / ${comma[2]}`;
|
|
const colon = text.match(/^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/);
|
|
if (colon) return `${colon[1]} / ${colon[2]}`;
|
|
const slash = text.match(/^(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)$/);
|
|
if (slash) return `${slash[1]} / ${slash[2]}`;
|
|
return undefined;
|
|
}
|
|
|
|
export function fallbackStoreItems() {
|
|
return products.map((product, index) => ({
|
|
id: `local-${index + 1}`,
|
|
slug: `local-${index + 1}`,
|
|
productId: `local-product-${index + 1}`,
|
|
title: product.title,
|
|
category: product.category,
|
|
image: product.image,
|
|
images: [product.image, "/assets/banner-2.jpg", "/assets/banner-3.jpg"],
|
|
price: null,
|
|
currency: "تومان",
|
|
description: "برای قیمت، موجودی و کانفیگ اختصاصی این محصول با کارشناسان فروش تماس بگیرید.",
|
|
technicalValues: [
|
|
{ label: "دسته بندی", value: product.category },
|
|
{ label: "وضعیت قیمت", value: "تماس با فروشگاه" }
|
|
],
|
|
quantity: null,
|
|
isFestival: index === 0,
|
|
raw: product
|
|
}));
|
|
}
|
|
|
|
export function normalizeBlog(blog) {
|
|
return {
|
|
id: String(blog.id || blog.blogId || blog.slug),
|
|
slug: String(blog.slug || blog.id || blog.blogId),
|
|
title: blog.titleFa || blog.title || blog.nameFa || "مقاله",
|
|
date: blog.publishedAt || blog.createdAt || "",
|
|
image: blog.titleImageUrl || blog.imageUrl || blog.main_image || blog.thumbnail || "/assets/banner-1.jpg",
|
|
abstract: blog.abstract || blog.summary || blog.description || "",
|
|
html: blog.mainTextHtml || blog.bodyHtml || blog.text || blog.content || "",
|
|
raw: blog
|
|
};
|
|
}
|