Show a theme-colored (#c96) hour/minute/second flip clock on the left of the sale carousel that resets each Tehran day. Co-authored-by: Cursor <cursoragent@cursor.com>
355 lines
12 KiB
TypeScript
355 lines
12 KiB
TypeScript
import Image from "next/image";
|
||
import Link from "next/link";
|
||
import type { ReactNode } from "react";
|
||
import { HeroSlider, type HeroSlide } from "@/app/components/hero-slider";
|
||
import { ProductCarousel } from "@/app/components/product-carousel";
|
||
import { ArticleCarousel } from "@/app/components/article-carousel";
|
||
import { FlipCountdown } from "@/app/components/flip-countdown";
|
||
import { SlotBanner, SlotLinkedImage } from "@/app/components/slot-media";
|
||
import { getStaticImages, listBlogs } from "@/lib/meshkee/api";
|
||
import { formatDate } from "@/lib/meshkee/format";
|
||
import {
|
||
firstSlotImage,
|
||
imageSubtext,
|
||
imageTitleEn,
|
||
imageTitleFa,
|
||
slotImages,
|
||
} from "@/lib/meshkee/static-slots";
|
||
import type { StaticImageItem, StaticImageSlot } from "@/lib/meshkee/types";
|
||
import { blogHref } from "@/lib/meshkee/urls";
|
||
import {
|
||
aboutCopy,
|
||
articles as fallbackArticles,
|
||
categories,
|
||
categoryQuickLinks,
|
||
diningProducts,
|
||
features,
|
||
heroSlides as fallbackHeroSlides,
|
||
products,
|
||
site,
|
||
} from "@/lib/site-data";
|
||
|
||
function SectionTitle({
|
||
eyebrow,
|
||
title,
|
||
href,
|
||
aside,
|
||
}: {
|
||
eyebrow: string;
|
||
title: string;
|
||
href?: string;
|
||
aside?: ReactNode;
|
||
}) {
|
||
return (
|
||
<div className={`section-title${aside ? " section-title-with-aside" : ""}`}>
|
||
<div>
|
||
<p className="eyebrow">{eyebrow}</p>
|
||
<h2>{title}</h2>
|
||
</div>
|
||
{aside}
|
||
{href ? (
|
||
<Link className="section-title-chevron" href={href} aria-label="مشاهده بیشتر">
|
||
<span aria-hidden="true">‹</span>
|
||
<em>MORE</em>
|
||
</Link>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function QuickCategoryIcon({ index }: { index: number }) {
|
||
const shapes = [
|
||
"M5 18h14M7 18v-5a4 4 0 0 1 4-4h2a4 4 0 0 1 4 4v5M8 18v2m8-2v2",
|
||
"M8 20v-7a4 4 0 0 1 8 0v7M7 13h10M9 20h6",
|
||
"M6 18h12M8 18v-8h8v8M10 10v8m4-8v8",
|
||
"M5 18h14M7 18V9h10v9M9 12h6",
|
||
"M5 12h14M8 12v8m8-8v8M7 8h10",
|
||
"M5 18V9h14v9M5 14h14M8 18v2m8-2v2",
|
||
];
|
||
|
||
return (
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d={shapes[index % shapes.length]} />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
function buildHeroSlides(images: StaticImageItem[]): HeroSlide[] {
|
||
if (!images.length) return fallbackHeroSlides;
|
||
|
||
return images.map((image, index) => {
|
||
const fallback = fallbackHeroSlides[index % fallbackHeroSlides.length];
|
||
return {
|
||
eyebrow: imageTitleEn(image) || fallback.eyebrow,
|
||
title: imageTitleFa(image) || fallback.title,
|
||
description: imageSubtext(image) || fallback.description,
|
||
image: image.url || fallback.image,
|
||
linkUrl: image.linkUrl,
|
||
};
|
||
});
|
||
}
|
||
|
||
function mergeQuickCategories(icons: StaticImageItem[]) {
|
||
return categoryQuickLinks.map((item, index) => {
|
||
const image = icons[index];
|
||
return {
|
||
title: imageTitleFa(image) || item.title,
|
||
iconImage: image?.url || item.iconImage,
|
||
href: image?.linkUrl || "#products",
|
||
};
|
||
});
|
||
}
|
||
|
||
const twoBannerFallbacks = [
|
||
{
|
||
eyebrow: "MINIMAL SOFA",
|
||
title: "خرید مبل مینیمال مدرن",
|
||
text: "طراحی ساده، دستههای چوبی و نشیمن راحت برای خانههای مدرن.",
|
||
},
|
||
{
|
||
eyebrow: "INSTALLMENT",
|
||
title: "خرید مبل اقساطی",
|
||
text: "شرایط ویژه برای خرید انواع مبلمان با تحویل و مشاوره فروش.",
|
||
},
|
||
];
|
||
|
||
const threeBannerFallbacks = [
|
||
{
|
||
title: "مبل راحتی مدرن",
|
||
text: "انتخابی مناسب برای چیدمان صمیمی و کاربردی فضای نشیمن.",
|
||
},
|
||
{
|
||
title: "سرویس خواب",
|
||
text: "مدلهای مدرن و کلاسیک برای تکمیل دکوراسیون اتاق خواب.",
|
||
},
|
||
{
|
||
title: "میز جلو مبلی و عسلی",
|
||
text: "هماهنگ با سبک مبلمان و قابل سفارش برای خانههای ایرانی.",
|
||
},
|
||
];
|
||
|
||
export default async function Home() {
|
||
const [homeSlots, blogItems] = await Promise.all([
|
||
getStaticImages("home").catch(() => [] as StaticImageSlot[]),
|
||
listBlogs({ page: 1, pageSize: 8 })
|
||
.then((data) =>
|
||
data.items.map((post) => ({
|
||
title: post.title,
|
||
date: formatDate(post.publishedAt || post.createdAt),
|
||
image: post.titleImageUrl || "/assets/sample/spacejoy-c0JoR_-2x3E-unsplash.jpg",
|
||
href: blogHref(post.id, post.title),
|
||
})),
|
||
)
|
||
.catch(() =>
|
||
fallbackArticles.map((article) => ({
|
||
...article,
|
||
href: "/blog",
|
||
})),
|
||
),
|
||
]);
|
||
|
||
const heroSlides = buildHeroSlides(slotImages(homeSlots, "slider"));
|
||
const quickCategories = mergeQuickCategories(slotImages(homeSlots, "categories-icon"));
|
||
const saleBannerImage = firstSlotImage(homeSlots, "special-sale-banner");
|
||
const featuresImage = firstSlotImage(homeSlots, "product-features-image");
|
||
const contactImage = firstSlotImage(homeSlots, "contactus-image");
|
||
const aboutHomeImage = firstSlotImage(homeSlots, "aboutus-image");
|
||
const twoBanners = slotImages(homeSlots, "two-banner-row");
|
||
const threeBanners = slotImages(homeSlots, "three-banner-row");
|
||
|
||
return (
|
||
<main>
|
||
<HeroSlider slides={heroSlides} />
|
||
|
||
<section className="quick-categories-section">
|
||
<div className="container">
|
||
<div className="quick-category-frame">
|
||
{quickCategories.map((item, index) => (
|
||
<a className="quick-category-item" href={item.href} key={`${item.title}-${index}`}>
|
||
<span className="quick-category-icon">
|
||
{item.iconImage ? (
|
||
<Image src={item.iconImage} alt="" width={96} height={96} aria-hidden="true" />
|
||
) : (
|
||
<QuickCategoryIcon index={index} />
|
||
)}
|
||
</span>
|
||
<span>{item.title}</span>
|
||
</a>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container">
|
||
<div className="grid category-grid">
|
||
{categories.map((category) => (
|
||
<article className="category-card" key={category.title}>
|
||
<Image src={category.image} alt={category.title} width={520} height={460} />
|
||
<div>
|
||
<h3>{category.title}</h3>
|
||
<p>{category.subtitle}</p>
|
||
</div>
|
||
</article>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section section-special-sale">
|
||
<div className="container">
|
||
<SectionTitle
|
||
eyebrow="Special Sale"
|
||
title="فروش ویژه"
|
||
aside={<FlipCountdown />}
|
||
/>
|
||
<ProductCarousel
|
||
products={products}
|
||
ariaLabel="کاروسل فروش ویژه"
|
||
withSaleBanner
|
||
saleBanner={{
|
||
image: saleBannerImage?.url,
|
||
linkUrl: saleBannerImage?.linkUrl,
|
||
ariaLabel: imageTitleFa(saleBannerImage) || "فروش ویژه",
|
||
}}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container">
|
||
<div className="feature-showcase">
|
||
<svg className="feature-connectors" viewBox="0 0 100 100" aria-hidden="true">
|
||
<path d="M55 45 C64 31 75 25 88 17" />
|
||
<path d="M45 45 C35 28 25 24 12 17" />
|
||
<path d="M58 56 C70 70 78 81 90 86" />
|
||
<path d="M42 56 C31 73 22 80 10 86" />
|
||
</svg>
|
||
<div className="feature-sofa">
|
||
<SlotLinkedImage
|
||
image={featuresImage}
|
||
fallbackSrc="/assets/sample/sofa-real-v2.png"
|
||
alt="مبل راحتی هومت"
|
||
width={900}
|
||
height={420}
|
||
/>
|
||
</div>
|
||
{features.map((feature, index) => (
|
||
<article className={`feature-card feature-card-${index + 1}`} key={feature.title}>
|
||
<h3>{feature.title}</h3>
|
||
<p>{feature.subtitle}</p>
|
||
<small className="feature-subtext">{feature.subtext}</small>
|
||
</article>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section" id="products">
|
||
<div className="container">
|
||
<SectionTitle eyebrow="Newest sofa" title="جدیدترین مبل" href="/store" />
|
||
<ProductCarousel products={products} ariaLabel="کاروسل مبل راحتی" />
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container contact-row">
|
||
<div className="contact-card">
|
||
<p className="eyebrow">Contact Us</p>
|
||
<h2>برای انتخاب پارچه و سفارش با ما در ارتباط باشید</h2>
|
||
<p>
|
||
مشاوران هومت هر روز از ساعت 10 صبح الی 20 شب آماده پاسخگویی و
|
||
راهنمایی برای انتخاب مدل، رنگ، پارچه و شرایط خرید هستند.
|
||
</p>
|
||
<ul className="contact-list">
|
||
<li>
|
||
واتساپ / تلگرام:{" "}
|
||
<span className="ltr-number" dir="ltr" lang="en">
|
||
{site.whatsapp} _ {site.mobile}
|
||
</span>
|
||
</li>
|
||
<li>
|
||
شماره تماس:{" "}
|
||
<span className="ltr-number" dir="ltr" lang="en">
|
||
{site.phone}
|
||
</span>
|
||
</li>
|
||
</ul>
|
||
<Link className="primary-btn" href="/contactus">
|
||
ارتباط با مبلمان هومت
|
||
</Link>
|
||
</div>
|
||
<SlotBanner
|
||
image={contactImage}
|
||
className="banner contact-guarantee"
|
||
fallbackEyebrow="GUARANTEE"
|
||
fallbackTitle="ضمانت نامه کتبی مبلمان"
|
||
fallbackText="خرید با اطمینان، ارسال مبل راحتی و کلاسیک و پشتیبانی پس از فروش."
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container">
|
||
<SectionTitle eyebrow="Dining Set Table" title="کاروسل میز ناهارخوری" />
|
||
<ProductCarousel products={diningProducts} ariaLabel="کاروسل میز ناهارخوری" />
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container grid banner-grid">
|
||
{twoBannerFallbacks.map((fallback, index) => (
|
||
<SlotBanner
|
||
key={fallback.title}
|
||
image={twoBanners[index]}
|
||
fallbackEyebrow={fallback.eyebrow}
|
||
fallbackTitle={fallback.title}
|
||
fallbackText={fallback.text}
|
||
/>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container about-panel">
|
||
<SlotLinkedImage
|
||
image={aboutHomeImage}
|
||
fallbackSrc={site.heroImages[1]}
|
||
alt="درباره مبلمان هومت"
|
||
width={720}
|
||
height={620}
|
||
/>
|
||
<div className="about-copy">
|
||
<p className="eyebrow">{aboutCopy.eyebrow}</p>
|
||
<h2>درباره هومت</h2>
|
||
<p>{aboutCopy.story}</p>
|
||
<Link className="primary-btn" href="/about-us">
|
||
بیشتر بدانید
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section">
|
||
<div className="container grid banner-grid three">
|
||
{threeBannerFallbacks.map((fallback, index) => (
|
||
<SlotBanner
|
||
key={fallback.title}
|
||
image={threeBanners[index]}
|
||
fallbackTitle={fallback.title}
|
||
fallbackText={fallback.text}
|
||
/>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="section" id="articles">
|
||
<div className="container">
|
||
<SectionTitle eyebrow="Latest Articles" title="آخــریـن مــقـالــات" href="/blog" />
|
||
<ArticleCarousel articles={blogItems} ariaLabel="کاروسل مقالات" />
|
||
</div>
|
||
</section>
|
||
</main>
|
||
);
|
||
}
|