Files
hoomat-store/app/page.tsx
T
Alireza HassaniandCursor 30f1f8d67d Add special-sale title with daily flip countdown.
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>
2026-09-09 21:53:31 +03:30

355 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}