import { meshkeeApiBase, websiteDomain } from "@/lib/config"; export type SlotKind = "single" | "list"; export type SlotImage = { id: string; url: string; titleFa?: string | null; titleEn?: string | null; subtext?: string | null; linkUrl?: string | null; width?: number | null; height?: number | null; sortOrder?: number | null; }; export type StaticImageSlotCatalogItem = { key: string; label: string; kind: SlotKind; pageKey: string; aspectRatio: string; itemCount: number | null; recommendedWidth: number; }; export type StaticImageSlot = StaticImageSlotCatalogItem & { images: SlotImage[]; }; export type SlotMediaItem = { id: string; image: string; href?: string; title?: string; fallbackSrc?: string; }; /** * Homepage slot catalog published at GET /meshkee/static-image-slots. * Ratios match the live homepage layout at max-w-[1280px] (inner 1232px). * If a section’s size or aspect ratio changes, update this catalog — the * Meshkee dashboard reads it as the static-image source of truth. */ export const STATIC_IMAGE_SLOTS: StaticImageSlotCatalogItem[] = [ { key: "slider", label: "Homepage slider", kind: "list", pageKey: "home", aspectRatio: "40:13", itemCount: null, recommendedWidth: 1280, }, { key: "two-banner-below-slider", label: "Two banners below slider", kind: "list", pageKey: "home", aspectRatio: "61:11", itemCount: 2, recommendedWidth: 610, }, { key: "categories", label: "Featured categories", kind: "list", pageKey: "home", aspectRatio: "1:1", itemCount: null, recommendedWidth: 168, }, { key: "two-banner-row", label: "Two banner row", kind: "list", pageKey: "home", aspectRatio: "305:59", itemCount: 2, recommendedWidth: 610, }, { key: "three-banner-row", label: "Three banner row", kind: "list", pageKey: "home", aspectRatio: "403:118", itemCount: 3, recommendedWidth: 403, }, { key: "about-us-image", label: "About us image", kind: "single", pageKey: "home", aspectRatio: "10:11", itemCount: 1, recommendedWidth: 560, }, ]; export function catalogSlot(key: string) { return STATIC_IMAGE_SLOTS.find((slot) => slot.key === key); } export function parseAspectRatio(ratio?: string | null) { if (!ratio) return undefined; const match = /^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/.exec(ratio.trim()); if (!match) return undefined; return `${match[1]} / ${match[2]}`; } export function pickSlot(slots: StaticImageSlot[], key: string) { return slots.find((slot) => slot.key === key); } function normalizeImages(images: SlotImage[] | undefined) { return [...(images ?? [])] .filter((image) => Boolean(image?.url)) .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)); } export function imagesFromSlot(slot?: StaticImageSlot | null): SlotImage[] { if (!slot) return []; const images = normalizeImages(slot.images); if (slot.kind === "single") return images.slice(0, 1); return images; } function toMedia(image: SlotImage, fallbackSrc?: string): SlotMediaItem { return { id: String(image.id), image: image.url, href: image.linkUrl?.trim() || undefined, title: image.titleFa?.trim() || image.titleEn?.trim() || undefined, fallbackSrc, }; } export function overlaySlotImages( slot: StaticImageSlot | undefined, fallbacks: SlotMediaItem[], key: string, ): { items: SlotMediaItem[]; aspectRatio?: string } { const catalog = catalogSlot(key); const aspectRatio = slot?.aspectRatio || catalog?.aspectRatio; const kind = slot?.kind ?? catalog?.kind ?? "list"; const itemCount = slot?.itemCount ?? catalog?.itemCount ?? null; const apiImages = imagesFromSlot(slot ? { ...slot, kind, images: slot.images ?? [] } : undefined); if (kind === "single") { const image = apiImages[0]; const fallback = fallbacks[0]; return { aspectRatio, items: image ? [toMedia(image, fallback?.image)] : fallbacks.slice(0, 1), }; } if (apiImages.length === 0) { return { aspectRatio, items: itemCount ? fallbacks.slice(0, itemCount) : fallbacks, }; } if (itemCount) { return { aspectRatio, items: Array.from({ length: itemCount }, (_, index) => { const image = apiImages[index]; const fallback = fallbacks[index] ?? fallbacks[fallbacks.length - 1]; return image ? toMedia(image, fallback?.image) : fallback; }).filter(Boolean), }; } return { aspectRatio, items: apiImages.map((image, index) => toMedia(image, fallbacks[index]?.image)), }; } function readItems(payload: unknown): StaticImageSlot[] { if (Array.isArray(payload)) return payload as StaticImageSlot[]; if (payload && typeof payload === "object") { const record = payload as { items?: StaticImageSlot[]; slots?: StaticImageSlot[] }; return record.items ?? record.slots ?? []; } return []; } export async function fetchStaticImageSlots(pageKey = "home"): Promise { const url = `${meshkeeApiBase}/tenants/${encodeURIComponent(websiteDomain)}/website/static-images?pageKey=${encodeURIComponent(pageKey)}`; try { const response = await fetch(url, { next: { revalidate: 60 }, }); if (!response.ok) return []; return readItems(await response.json()); } catch { return []; } }