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 + {alt + ); + + 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]; +}