diff --git a/next.config.ts b/next.config.ts
index e9ffa30..ef8450f 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ allowedDevOrigins: ["sanihome.com", "localhost"],
};
export default nextConfig;
diff --git a/src/app/meshkee/static-image-slots/route.ts b/src/app/meshkee/static-image-slots/route.ts
new file mode 100644
index 0000000..49e9367
--- /dev/null
+++ b/src/app/meshkee/static-image-slots/route.ts
@@ -0,0 +1,14 @@
+import { NextResponse } from "next/server";
+import { STATIC_IMAGE_SLOT_CATALOG } from "@/lib/meshkee";
+
+export function GET() {
+ return NextResponse.json(
+ { slots: STATIC_IMAGE_SLOT_CATALOG },
+ {
+ headers: {
+ "Access-Control-Allow-Origin": "*",
+ "Cache-Control": "public, max-age=60",
+ },
+ },
+ );
+}
diff --git a/src/components/ui/SlotImage.tsx b/src/components/ui/SlotImage.tsx
new file mode 100644
index 0000000..49a3fdc
--- /dev/null
+++ b/src/components/ui/SlotImage.tsx
@@ -0,0 +1,37 @@
+type SlotImageProps = {
+ url: string;
+ title?: string | null;
+ linkUrl?: string | null;
+ alt?: string;
+ className?: string;
+ imgClassName?: string;
+};
+
+export function SlotImage({
+ url,
+ title,
+ linkUrl,
+ alt,
+ className,
+ imgClassName = "block h-full w-full object-cover",
+}: SlotImageProps) {
+ const img = (
+ // Meshkee slot URLs are used as-is; do not route through next/image.
+ // eslint-disable-next-line @next/next/no-img-element
+
+ );
+
+ if (linkUrl) {
+ return (
+
+ {img}
+
+ );
+ }
+
+ if (className) {
+ return
{img}
;
+ }
+
+ return img;
+}
diff --git a/src/lib/meshkee.ts b/src/lib/meshkee.ts
index d610e37..0e81e6c 100644
--- a/src/lib/meshkee.ts
+++ b/src/lib/meshkee.ts
@@ -200,3 +200,94 @@ export async function getSliderSlides(): Promise {
| undefined;
return (slides ?? []).map(normalizeSlide);
}
+
+// Admin-managed static/marketing images (slider photos, promo banners) for
+// fixed homepage slots. Not currently wired into any page — the slot keys
+// the live API returns (e.g. "home-hero") don't match STATIC_IMAGE_KEYS yet.
+export type StaticImage = {
+ id: string;
+ url?: string | null;
+ title?: string | null;
+ linkUrl?: string | null;
+ width?: number | null;
+ height?: number | null;
+ sortOrder?: number;
+};
+
+export type StaticImageWithUrl = StaticImage & { url: string };
+
+export type StaticImageSlot = {
+ id: string;
+ key: string;
+ kind: "single" | "list";
+ label?: string;
+ aspectRatio?: string;
+ recommendedWidth?: number;
+ sortOrder?: number;
+ images?: StaticImage[];
+};
+
+export const STATIC_IMAGE_KEYS = {
+ slider: "slider",
+ bannerInSpecialSale: "banner-in-special-sale",
+ categories: "categories",
+ twoBannerRow: "two-banner-row",
+} as const;
+
+export const STATIC_IMAGE_SLOT_CATALOG = [
+ {
+ key: STATIC_IMAGE_KEYS.slider,
+ label: "Homepage slider",
+ kind: "list" as const,
+ pageKey: "home",
+ aspectRatio: "16:9",
+ itemCount: null,
+ recommendedWidth: 1440,
+ },
+ {
+ key: STATIC_IMAGE_KEYS.bannerInSpecialSale,
+ label: "Banner in special sale",
+ kind: "single" as const,
+ pageKey: "home",
+ aspectRatio: "12:19",
+ itemCount: 1,
+ recommendedWidth: 480,
+ },
+ {
+ key: STATIC_IMAGE_KEYS.categories,
+ label: "Featured category images",
+ kind: "list" as const,
+ pageKey: "home",
+ aspectRatio: "1:1",
+ itemCount: null,
+ recommendedWidth: 400,
+ },
+ {
+ key: STATIC_IMAGE_KEYS.twoBannerRow,
+ label: "Two banner row",
+ kind: "list" as const,
+ pageKey: "home",
+ aspectRatio: "16:5",
+ itemCount: 2,
+ recommendedWidth: 1440,
+ },
+];
+
+export async function getStaticImageSlots(): Promise {
+ const res = await apiGet<{ items: StaticImageSlot[] }>(`/tenants/${DOMAIN}/website/static-images`);
+ return res?.items ?? [];
+}
+
+export function imagesForSlot(slots: StaticImageSlot[], key: string): StaticImageWithUrl[] {
+ const images = slots.find((slot) => slot.key === key)?.images ?? [];
+ return [...images]
+ .filter((image): image is StaticImageWithUrl => Boolean(image.url))
+ .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
+}
+
+export function firstImageForSlot(
+ slots: StaticImageSlot[],
+ key: string,
+): StaticImageWithUrl | undefined {
+ return imagesForSlot(slots, key)[0];
+}