Initial HOM arch studio website (home, about, contact) on Meshkee

This commit is contained in:
dev
2026-10-11 22:48:58 +03:30
commit 73971bf6d9
47 changed files with 3743 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
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: "hero",
label: "تصویر اصلی صفحه اول",
kind: "single",
pageKey: "home",
aspectRatio: "16:9",
itemCount: 1,
recommendedWidth: 1920,
},
{
key: "projects",
label: "پروژه‌های منتخب صفحه اول",
kind: "list",
pageKey: "home",
aspectRatio: "4:5",
itemCount: null,
recommendedWidth: 1200,
},
{
key: "studio-image",
label: "تصویر معرفی استودیو (صفحه اول)",
kind: "single",
pageKey: "home",
aspectRatio: "4:3",
itemCount: 1,
recommendedWidth: 1400,
},
{
key: "about-hero",
label: "تصویر اصلی درباره ما",
kind: "single",
pageKey: "about",
aspectRatio: "16:9",
itemCount: 1,
recommendedWidth: 1920,
},
{
key: "about-gallery",
label: "گالری درباره ما",
kind: "list",
pageKey: "about",
aspectRatio: "1:1",
itemCount: 3,
recommendedWidth: 900,
},
{
key: "contact-image",
label: "تصویر تماس با ما",
kind: "single",
pageKey: "contact",
aspectRatio: "4:5",
itemCount: 1,
recommendedWidth: 1200,
},
];
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;
}