131 lines
3.7 KiB
TypeScript
131 lines
3.7 KiB
TypeScript
import { cache } from "react";
|
||
|
||
import { meshkeeGet, tenantPath } from "@/lib/meshkee";
|
||
|
||
export type StaticImage = {
|
||
url?: string | null;
|
||
titleFa?: string | null;
|
||
titleEn?: string | null;
|
||
subtext?: string | null;
|
||
linkUrl?: string | null;
|
||
};
|
||
|
||
export type StaticImageSlot = {
|
||
key: string;
|
||
label: string;
|
||
kind: "single" | "list";
|
||
pageKey: string;
|
||
aspectRatio: string;
|
||
itemCount: number | null;
|
||
recommendedWidth: number;
|
||
};
|
||
|
||
/**
|
||
* Catalog served at `GET /meshkee/static-image-slots`.
|
||
* Keep `aspectRatio` / `recommendedWidth` in sync with the on-page frames —
|
||
* if a frame’s size or ratio changes in development, update the matching entry here.
|
||
*/
|
||
export const STATIC_IMAGE_SLOTS: StaticImageSlot[] = [
|
||
{
|
||
key: "slider",
|
||
label: "اسلایدر تمامصفحه صفحه اول (قابل تکرار)",
|
||
kind: "list",
|
||
pageKey: "home",
|
||
aspectRatio: "16:9",
|
||
itemCount: null,
|
||
recommendedWidth: 1920,
|
||
},
|
||
{
|
||
key: "four-category",
|
||
label: "چهار خدمت / تخصص صفحه اول",
|
||
kind: "list",
|
||
pageKey: "home",
|
||
aspectRatio: "3:4",
|
||
itemCount: 4,
|
||
recommendedWidth: 900,
|
||
},
|
||
{
|
||
key: "project-images",
|
||
label: "پروژههای منتخب صفحه اول (میسونری — اندازه آزاد)",
|
||
kind: "list",
|
||
pageKey: "home",
|
||
aspectRatio: "3:4",
|
||
itemCount: null,
|
||
recommendedWidth: 1200,
|
||
},
|
||
{
|
||
key: "sticky-frame-bg",
|
||
label: "پسزمینه فریم استیکی OpeningFrame",
|
||
kind: "single",
|
||
pageKey: "home",
|
||
aspectRatio: "16:9",
|
||
itemCount: 1,
|
||
recommendedWidth: 1920,
|
||
},
|
||
{
|
||
key: "instagram-row",
|
||
label: "فریم صفحه اینستاگرام (اسکرینشات موبایل)",
|
||
kind: "single",
|
||
pageKey: "home",
|
||
aspectRatio: "9:16",
|
||
itemCount: 1,
|
||
recommendedWidth: 720,
|
||
},
|
||
{
|
||
key: "brands",
|
||
label: "لوگوی برندها / همکاران (قابل تکرار)",
|
||
kind: "list",
|
||
pageKey: "home",
|
||
aspectRatio: "3:2",
|
||
itemCount: null,
|
||
recommendedWidth: 480,
|
||
},
|
||
{
|
||
key: "about-us-image",
|
||
label: "تصاویر درباره ما (۲ تصویر)",
|
||
kind: "list",
|
||
pageKey: "about",
|
||
aspectRatio: "16:9",
|
||
itemCount: 2,
|
||
recommendedWidth: 1600,
|
||
},
|
||
];
|
||
|
||
type StaticImagesResponse = {
|
||
items?: {
|
||
key: string;
|
||
aspectRatio?: string | null;
|
||
images?: (StaticImage & { sortOrder?: number })[];
|
||
}[];
|
||
};
|
||
|
||
export type PageSlots = Record<string, { images: StaticImage[]; aspectRatio: string | null }>;
|
||
|
||
/** `GET /tenants/{domain}/website/static-images?pageKey=…` grouped by slot key. */
|
||
export const getPageSlots = cache(async (pageKey: string): Promise<PageSlots> => {
|
||
const data = await meshkeeGet<StaticImagesResponse>(
|
||
tenantPath(`/website/static-images?pageKey=${encodeURIComponent(pageKey)}`),
|
||
{},
|
||
);
|
||
const grouped: PageSlots = {};
|
||
for (const slot of data.items ?? []) {
|
||
const images = (slot.images ?? [])
|
||
.filter((image) => image?.url)
|
||
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||
if (images.length) grouped[slot.key] = { images, aspectRatio: slot.aspectRatio ?? null };
|
||
}
|
||
return grouped;
|
||
});
|
||
|
||
/** CSS `aspect-ratio` for a slot: API value first, then the local catalog. */
|
||
export function slotRatio(key: string, slots?: PageSlots) {
|
||
const fromApi = slots?.[key]?.aspectRatio;
|
||
const fromCatalog = STATIC_IMAGE_SLOTS.find((entry) => entry.key === key)?.aspectRatio;
|
||
return (fromApi || fromCatalog || "4:3").replace(":", " / ");
|
||
}
|
||
|
||
/** Images for a slot, or the local fallback list when CMS has none. */
|
||
export function slotImages(key: string, slots: PageSlots, fallback: StaticImage[]) {
|
||
return slots[key]?.images?.length ? slots[key].images : fallback;
|
||
}
|