Merge remote-tracking branch into main, resolving conflicts
Kept the local branch's homepage implementation (Meshkee product/category
API integration, redesigned header, Swiper carousels, hardcoded local
slider/banner images) as the source of truth for all conflicting sections,
since it reflects the most recent explicit direction.
Folded in the remote's non-conflicting additions: the static-image-slot
types/helpers in lib/meshkee.ts, the SlotImage component, and the
/meshkee/static-image-slots catalog route. These aren't wired into any
page yet since the live API's slot key ("home-hero") doesn't match the
keys this code expects ("slider", etc.) — worth reconciling once that's
settled.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
allowedDevOrigins: ["sanihome.com", "localhost"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
<img src={url} alt={alt ?? title ?? ""} className={imgClassName} />
|
||||||
|
);
|
||||||
|
|
||||||
|
if (linkUrl) {
|
||||||
|
return (
|
||||||
|
<a href={linkUrl} className={className}>
|
||||||
|
{img}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (className) {
|
||||||
|
return <div className={className}>{img}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
@@ -200,3 +200,94 @@ export async function getSliderSlides(): Promise<Slide[]> {
|
|||||||
| undefined;
|
| undefined;
|
||||||
return (slides ?? []).map(normalizeSlide);
|
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<StaticImageSlot[]> {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user