Connect Meshkee-managed imagery and publish sitemap metadata

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 18:45:00 +03:30
co-authored by Cursor
parent e664acb854
commit 00126e5777
10 changed files with 535 additions and 29 deletions
+180 -22
View File
@@ -27,10 +27,24 @@ import {
} from "lucide-react";
import type { FormEvent } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import {
cssAspectRatio,
fetchHomeStaticImageSlots,
type StaticImageSlot,
} from "./lib/meshkee";
const assetBase = "/assets";
const heroSlides = [
type HeroSlide = {
image: string;
eyebrow: string;
title: string;
text: string;
linkUrl?: string;
aspectRatio?: string;
};
const heroSlides: HeroSlide[] = [
{
image: `${assetBase}/i8SsUDDMLv.jpg`,
eyebrow: "WIRE & CABLE MACHINERY",
@@ -164,15 +178,83 @@ export default function Home() {
const [activeSlide, setActiveSlide] = useState(0);
const [query, setQuery] = useState("");
const [searchOpen, setSearchOpen] = useState(false);
const [staticImageSlots, setStaticImageSlots] = useState<StaticImageSlot[]>([]);
const searchInput = useRef<HTMLInputElement>(null);
const slotsByKey = useMemo(
() => new Map(staticImageSlots.map((slot) => [slot.key, slot])),
[staticImageSlots],
);
const activeHeroSlides = useMemo<HeroSlide[]>(() => {
const slot = slotsByKey.get("slider");
if (!slot?.images.length) return heroSlides;
return slot.images.map((image, index) => {
const fallback = heroSlides[index % heroSlides.length];
return {
image: image.url || fallback.image,
eyebrow: image.titleEn || fallback.eyebrow,
title: image.titleFa || fallback.title,
text: image.subtext || fallback.text,
linkUrl: image.linkUrl || undefined,
aspectRatio: cssAspectRatio(slot.aspectRatio),
};
});
}, [slotsByKey]);
const activeCategories = useMemo(() => {
const slot = slotsByKey.get("three-categories");
return categories.map((fallback, index) => {
const image = slot?.images[index];
if (!image) {
return {
...fallback,
linkUrl: undefined,
aspectRatio: undefined,
};
}
return {
...fallback,
image: image.url || fallback.image,
title: image.titleFa || fallback.title,
en: image.titleEn || fallback.en,
count: image.subtext || fallback.count,
linkUrl: image.linkUrl || undefined,
aspectRatio: cssAspectRatio(slot?.aspectRatio),
};
});
}, [slotsByKey]);
const aboutImages = slotsByKey.get("about-us-images");
const aboutMainImage = aboutImages?.images[0];
const aboutSecondaryImage = aboutImages?.images[1];
const activeHeroIndex = activeSlide % activeHeroSlides.length;
const activeHero = activeHeroSlides[activeHeroIndex];
useEffect(() => {
const controller = new AbortController();
fetchHomeStaticImageSlots(controller.signal)
.then(setStaticImageSlots)
.catch(() => {
// Keep the local image fallbacks when the API is unavailable.
});
return () => controller.abort();
}, []);
useEffect(() => {
if (activeHeroSlides.length <= 1) return;
const timer = window.setInterval(
() => setActiveSlide((current) => (current + 1) % heroSlides.length),
() =>
setActiveSlide((current) => (current + 1) % activeHeroSlides.length),
6500,
);
return () => window.clearInterval(timer);
}, []);
}, [activeHeroSlides.length]);
const results = useMemo(() => {
const normalized = query.trim().toLowerCase();
@@ -281,20 +363,32 @@ export default function Home() {
</header>
<section className="hero">
{heroSlides.map((slide, index) => (
{activeHeroSlides.map((slide, index) => (
<div
className={`hero__slide ${index === activeSlide ? "is-active" : ""}`}
key={slide.image}
style={{ backgroundImage: `url("${slide.image}")` }}
/>
className={`hero__slide ${index === activeHeroIndex ? "is-active" : ""}`}
key={`${slide.image}-${index}`}
style={{ aspectRatio: slide.aspectRatio }}
>
{slide.linkUrl ? (
<a
className="hero__slide-link"
href={slide.linkUrl}
aria-label={slide.title}
>
<img src={slide.image} alt={slide.title} />
</a>
) : (
<img src={slide.image} alt={slide.title} />
)}
</div>
))}
<div className="hero__overlay" />
<div className="hero__pattern" />
<div className="container hero__content">
<div className="hero__copy">
<p className="eyebrow">{heroSlides[activeSlide].eyebrow}</p>
<h1>{heroSlides[activeSlide].title}</h1>
<p className="hero__description">{heroSlides[activeSlide].text}</p>
<p className="eyebrow">{activeHero.eyebrow}</p>
<h1>{activeHero.title}</h1>
<p className="hero__description">{activeHero.text}</p>
<div className="hero__buttons">
<a className="button button--red" href="#products">
مشاهده محصولات
@@ -321,7 +415,8 @@ export default function Home() {
<button
onClick={() =>
setActiveSlide(
(activeSlide - 1 + heroSlides.length) % heroSlides.length,
(activeHeroIndex - 1 + activeHeroSlides.length) %
activeHeroSlides.length,
)
}
aria-label="اسلاید قبلی"
@@ -329,17 +424,19 @@ export default function Home() {
<ChevronRight />
</button>
<div>
{heroSlides.map((slide, index) => (
{activeHeroSlides.map((slide, index) => (
<button
className={index === activeSlide ? "active" : ""}
key={slide.image}
className={index === activeHeroIndex ? "active" : ""}
key={`${slide.image}-${index}`}
onClick={() => setActiveSlide(index)}
aria-label={`اسلاید ${index + 1}`}
/>
))}
</div>
<button
onClick={() => setActiveSlide((activeSlide + 1) % heroSlides.length)}
onClick={() =>
setActiveSlide((activeHeroIndex + 1) % activeHeroSlides.length)
}
aria-label="اسلاید بعدی"
>
<ChevronLeft />
@@ -401,7 +498,7 @@ export default function Home() {
{results.length ? (
results.map((product) => (
<a href="#products" key={product.id}>
<img src={product.image} alt="" />
<img src={product.image} alt={product.title} />
<div>
<strong>{product.title}</strong>
<small dir="ltr">{product.en}</small>
@@ -428,8 +525,17 @@ export default function Home() {
title="سه مسیر، یک راهکار کامل صنعتی"
/>
<div className="category-grid">
{categories.map((category, index) => (
<a className="category-card" href="#" key={category.title}>
{activeCategories.map((category, index) => (
<a
className="category-card"
href={category.linkUrl || "#"}
key={`${category.title}-${index}`}
style={
category.aspectRatio
? { aspectRatio: category.aspectRatio, height: "auto" }
: undefined
}
>
<img src={category.image} alt={category.title} />
<div className="category-card__shade" />
<span className="category-card__number">0{index + 1}</span>
@@ -487,9 +593,61 @@ export default function Home() {
<section className="about" id="about">
<div className="container about__grid">
<div className="about__visual">
<img src={`${assetBase}/trEfzlNgmz.jpg`} alt="تجهیزات صنعتی سامان اندیش" />
<div className="about__small-image">
<img src={`${assetBase}/rhipZeVqzD.jpg`} alt="ماشین‌آلات صنعت کابل" />
{aboutMainImage?.linkUrl ? (
<a
className="about__main-image"
href={aboutMainImage.linkUrl}
style={{
aspectRatio: cssAspectRatio(aboutImages?.aspectRatio),
}}
>
<img
src={aboutMainImage.url}
alt={aboutMainImage.titleFa || "تجهیزات صنعتی سامان اندیش"}
/>
</a>
) : (
<img
src={aboutMainImage?.url || `${assetBase}/trEfzlNgmz.jpg`}
alt={aboutMainImage?.titleFa || "تجهیزات صنعتی سامان اندیش"}
style={
aboutMainImage
? {
aspectRatio: cssAspectRatio(aboutImages?.aspectRatio),
height: "auto",
}
: undefined
}
/>
)}
<div
className="about__small-image"
style={
aboutSecondaryImage
? {
aspectRatio: cssAspectRatio(aboutImages?.aspectRatio),
height: "auto",
}
: undefined
}
>
{aboutSecondaryImage?.linkUrl ? (
<a href={aboutSecondaryImage.linkUrl}>
<img
src={aboutSecondaryImage.url}
alt={aboutSecondaryImage.titleFa || "ماشین‌آلات صنعت کابل"}
/>
</a>
) : (
<img
src={
aboutSecondaryImage?.url || `${assetBase}/rhipZeVqzD.jpg`
}
alt={
aboutSecondaryImage?.titleFa || "ماشین‌آلات صنعت کابل"
}
/>
)}
</div>
<div className="experience-badge">
<strong>۲۵</strong>