create banner

This commit is contained in:
amirhosein.ashourloo
2026-08-17 10:29:46 +03:30
parent 1272977923
commit e5f040d3eb
27 changed files with 832 additions and 603 deletions
@@ -0,0 +1,42 @@
import { Carousel } from "@/components/ui/Carousel";
import { ProductCard } from "@/components/ui/ProductCard";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { EmptyState } from "@/components/ui/EmptyState";
import type { ProductGroup } from "@/lib/meshkee";
export function BrandGroupCarousels({ brandGroups }: { brandGroups: ProductGroup[] }) {
if (brandGroups.length === 0) {
return (
<section className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
<SectionTitle title="محصولات برندها" enTitle="Brand Collections" />
<EmptyState message="هنوز محصولی برای برندها ثبت نشده است." />
</div>
</section>
);
}
return (
<>
{brandGroups.map((group) => (
<section key={group.id} className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
<SectionTitle title={group.title} enTitle={group.title} action="مشاهده همه" />
<Carousel ariaLabel={`کاروسل ${group.title}`}>
{group.items.map((product) => (
<ProductCard
key={product.id}
name={product.name}
brand={product.brand ?? ""}
price={product.price}
image={product.image ?? "/images/placeholder.svg"}
variant="brand"
/>
))}
</Carousel>
</div>
</section>
))}
</>
);
}
+50 -25
View File
@@ -1,8 +1,15 @@
"use client";
import { Swiper, SwiperSlide } from "swiper/react";
import { FreeMode } from "swiper/modules";
import "swiper/css";
import "swiper/css/free-mode";
import { Placeholder } from "@/components/ui/Placeholder";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { categories } from "@/data/sample";
import { EmptyState } from "@/components/ui/EmptyState";
import type { Category } from "@/lib/meshkee";
export function FeaturedCategories() {
export function FeaturedCategories({ categories }: { categories: Category[] }) {
return (
<section className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
@@ -11,29 +18,47 @@ export function FeaturedCategories() {
enTitle="Featured Categories"
action="همه دسته‌ها"
/>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-8">
{categories.map((cat, i) => (
<a
key={cat.id}
href="#"
className="animate-slide-in group flex flex-col items-center rounded-xl border border-border bg-surface p-3 transition-all hover:border-red hover:shadow-md"
style={{ animationDelay: `${i * 60}ms` }}
>
<Placeholder
src={cat.image}
alt={cat.name}
width="72px"
height="72px"
label="72×72"
className="mb-3 rounded-full transition-colors group-hover:border-red"
/>
<span className="text-center text-xs font-medium leading-relaxed md:text-sm">
{cat.name}
</span>
<span className="mt-1 text-[11px] text-muted">{cat.count} محصول</span>
</a>
))}
</div>
{categories.length === 0 ? (
<EmptyState message="هنوز دسته‌بندی‌ای ثبت نشده است." />
) : (
<Swiper
modules={[FreeMode]}
dir="rtl"
slidesPerView={2.3}
spaceBetween={12}
breakpoints={{
640: { slidesPerView: 4.3, spaceBetween: 12 },
1024: { slidesPerView: 8, spaceBetween: 12 },
}}
freeMode={{ enabled: true, momentum: true, sticky: true }}
grabCursor
className="!px-1 !py-2"
>
{categories.map((cat, i) => (
<SwiperSlide key={cat.id}>
<a
href="#"
className="animate-slide-in group flex h-full flex-col items-center rounded-xl border border-border bg-surface p-3 transition-all hover:border-red hover:shadow-md"
style={{ animationDelay: `${i * 60}ms` }}
>
<Placeholder
src={cat.image ?? "/images/placeholder.svg"}
alt={cat.name}
width="72px"
height="72px"
className="mb-3 rounded-full transition-colors group-hover:border-red"
/>
<span className="w-full truncate text-center text-xs font-medium leading-relaxed md:text-sm">
{cat.name}
</span>
{cat.count != null && (
<span className="mt-1 text-[11px] text-muted">{cat.count} محصول</span>
)}
</a>
</SwiperSlide>
))}
</Swiper>
)}
</div>
</section>
);
+74 -38
View File
@@ -1,4 +1,31 @@
export function Footer() {
import type { BusinessInfo, Category } from "@/lib/meshkee";
const quickLinks = ["درباره ما", "تماس با ما", "سوالات متداول", "قوانین و مقررات", "پیگیری سفارش"];
const socialLabels: Record<string, string> = {
instagram: "IG",
telegram: "TG",
whatsapp: "WA",
linkedin: "IN",
youtube: "YT",
aparat: "AP",
};
export function Footer({
businessInfo,
categories,
}: {
businessInfo: BusinessInfo | null;
categories: Category[];
}) {
const socialEntries = businessInfo
? Object.entries(businessInfo.socialMedia).filter(([, url]) => !!url)
: [];
const hasContactInfo = Boolean(
businessInfo &&
(businessInfo.addresses.length || businessInfo.phoneNumbers.length || businessInfo.emails.length),
);
return (
<footer className="border-t border-red-dark/20 bg-red-dark text-white">
<div className="mx-auto max-w-7xl px-4 py-12">
@@ -12,60 +39,69 @@ export function Footer() {
SANIHOME
</span>
</div>
<p className="text-sm leading-relaxed text-white/70">
فروشگاه آنلاین لوازم خانگی با بیش از ۱۵ سال سابقه. محصولات اصل،
گارانتی معتبر، ارسال سریع.
</p>
{businessInfo?.about && (
<p className="text-sm leading-relaxed text-white/70">{businessInfo.about}</p>
)}
</div>
<div>
<h4 className="mb-3 text-sm font-bold">دسترسی سریع</h4>
<ul className="space-y-2 text-sm text-white/70">
{["درباره ما", "تماس با ما", "سوالات متداول", "قوانین و مقررات", "پیگیری سفارش"].map(
(link) => (
<li key={link}>
<a href="#" className="transition-colors hover:text-white">
{link}
</a>
</li>
),
)}
{quickLinks.map((link) => (
<li key={link}>
<a href="#" className="transition-colors hover:text-white">
{link}
</a>
</li>
))}
</ul>
</div>
<div>
<h4 className="mb-3 text-sm font-bold">دسته‌بندی‌ها</h4>
<ul className="space-y-2 text-sm text-white/70">
{["یخچال و فریزر", "ماشین لباسشویی", "تلویزیون", "جاروبرقی", "لوازم آشپزخانه"].map(
(link) => (
<li key={link}>
{categories.length === 0 ? (
<p className="text-sm text-white/50">هنوز دسته‌بندی‌ای ثبت نشده است.</p>
) : (
<ul className="space-y-2 text-sm text-white/70">
{categories.slice(0, 5).map((cat) => (
<li key={cat.id}>
<a href="#" className="transition-colors hover:text-white">
{link}
{cat.name}
</a>
</li>
),
)}
</ul>
))}
</ul>
)}
</div>
<div>
<h4 className="mb-3 text-sm font-bold">تماس با ما</h4>
<ul className="space-y-2 text-sm text-white/70">
<li>تهران، خیابان ولیعصر</li>
<li>تلفن: ۰۲۱-۸۸۰۰۱۲۳۴</li>
<li>ایمیل: info@sanihome.ir</li>
</ul>
<div className="mt-4 flex gap-3">
{["IG", "TG", "WA"].map((social) => (
<a
key={social}
href="#"
className="font-display flex h-8 w-8 items-center justify-center rounded-full border border-white/20 text-[10px] font-bold transition-colors hover:border-white hover:bg-white/10"
>
{social}
</a>
))}
</div>
{hasContactInfo ? (
<ul className="space-y-2 text-sm text-white/70">
{businessInfo?.addresses.map((address) => <li key={address}>{address}</li>)}
{businessInfo?.phoneNumbers.map((phone) => (
<li key={phone}>تلفن: {phone}</li>
))}
{businessInfo?.emails.map((email) => <li key={email}>ایمیل: {email}</li>)}
</ul>
) : (
<p className="text-sm text-white/50">هنوز اطلاعات تماس ثبت نشده است.</p>
)}
{socialEntries.length > 0 && (
<div className="mt-4 flex gap-3">
{socialEntries.map(([key, url]) => (
<a
key={key}
href={url}
target="_blank"
rel="noreferrer"
className="font-display flex h-8 w-8 items-center justify-center rounded-full border border-white/20 text-[10px] font-bold transition-colors hover:border-white hover:bg-white/10"
>
{socialLabels[key] ?? key.slice(0, 2).toUpperCase()}
</a>
))}
</div>
)}
</div>
</div>
+264 -82
View File
@@ -1,7 +1,9 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { categories } from "@/data/sample";
import { createPortal } from "react-dom";
import type { Category, CategoryGroup } from "@/lib/meshkee";
import { EmptyState } from "@/components/ui/EmptyState";
const navLinks = [
{ label: "فروش ویژه", href: "#" },
@@ -10,31 +12,202 @@ const navLinks = [
{ label: "تماس با ما", href: "#" },
];
const megaColumns = [
{
title: "سرمایش و گرمایش",
en: "Cooling & Heating",
items: ["یخچال و فریزر", "کولر گازی", "آبگرمکن", "بخاری"],
},
{
title: "شستشو",
en: "Laundry & Care",
items: ["ماشین لباسشویی", "ماشین ظرفشویی", "خشک‌کن", "اتو بخار"],
},
{
title: "صوتی و تصویری",
en: "AV & Display",
items: ["تلویزیون", "ساندبار", "سینمای خانگی", "پروژکتور"],
},
{
title: "آشپزخانه",
en: "Kitchen",
items: ["مایکروویو", "جاروبرقی", "هود", "اجاق گاز"],
},
];
function Logo({ large = false }: { large?: boolean }) {
return (
<a href="/" className="flex shrink-0 items-center">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/images/logo.png"
alt="سانی‌هوم"
className={
large
? "h-[42px] w-auto object-contain md:h-[46px]"
: "h-8 w-auto md:h-9"
}
/>
</a>
);
}
export function Header() {
function ChevronDownIcon({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="m6 9 6 6 6-6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function SearchBar() {
return (
<div className="flex overflow-hidden rounded-xl border border-border bg-background">
<input
type="search"
placeholder="جستجوی محصول، برند یا دسته‌بندی..."
className="flex-1 bg-transparent px-4 py-2.5 text-sm outline-none placeholder:text-muted"
/>
<button
type="button"
className="bg-red px-5 text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
جستجو
</button>
</div>
);
}
function MobileDrawer({
open,
onClose,
categoryGroups,
}: {
open: boolean;
onClose: () => void;
categoryGroups: CategoryGroup[];
}) {
const [categoriesOpen, setCategoriesOpen] = useState(false);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "";
return () => {
document.body.style.overflow = "";
};
}, [open]);
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") onClose();
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onClose]);
if (!mounted) return null;
return createPortal(
<div className="min-[1200px]:hidden" aria-hidden={!open}>
<div
onClick={onClose}
className={`fixed inset-0 z-[100] bg-black/50 backdrop-blur-sm transition-opacity duration-300 ${
open ? "opacity-100" : "pointer-events-none opacity-0"
}`}
/>
<div
className={`fixed top-0 right-0 z-[110] h-dvh w-[85%] max-w-sm overflow-y-auto bg-surface shadow-2xl transition-transform duration-300 ease-out ${
open ? "translate-x-0" : "translate-x-full"
}`}
>
<div className="flex items-center justify-between border-b border-border p-5">
<Logo />
<button
type="button"
onClick={onClose}
aria-label="بستن منو"
className="flex h-9 w-9 items-center justify-center rounded-lg text-xl text-muted transition-colors hover:bg-red-light hover:text-red"
>
✕
</button>
</div>
<nav className="flex flex-col text-sm">
<a href="/" className="border-b border-border px-5 py-4 transition-colors hover:bg-red-light/40 hover:text-red">
خانه
</a>
<div className="border-b border-border">
<button
type="button"
onClick={() => setCategoriesOpen((v) => !v)}
aria-expanded={categoriesOpen}
className="flex w-full items-center justify-between px-5 py-4 text-right transition-colors hover:bg-red-light/40 hover:text-red"
>
دسته‌بندی‌ها
<ChevronDownIcon
className={`h-4 w-4 shrink-0 transition-transform duration-200 ${
categoriesOpen ? "rotate-180" : ""
}`}
/>
</button>
<div
className={`grid transition-all duration-300 ease-out ${
categoriesOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
}`}
>
<div className="overflow-hidden">
{categoryGroups.length === 0 ? (
<div className="bg-background px-5 pb-4">
<EmptyState message="هنوز دسته‌بندی‌ای ثبت نشده است." />
</div>
) : (
<div className="space-y-4 bg-background px-5 pb-4">
{categoryGroups.map((group) => (
<div key={group.id}>
<p className="mb-1.5 pt-3 text-xs font-bold text-red-dark">
{group.title}
</p>
<ul className="space-y-1">
{group.items.map((item) => (
<li key={item.name}>
<a
href="#"
className="block rounded-lg px-3 py-1.5 text-muted transition-colors hover:bg-red-light hover:text-red"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
))}
</div>
)}
</div>
</div>
</div>
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="border-b border-border px-5 py-4 transition-colors hover:bg-red-light/40 hover:text-red"
>
{link.label}
</a>
))}
<button
type="button"
className="flex items-center gap-2 px-5 py-4 text-right font-medium transition-colors hover:bg-red-light/40 hover:text-red"
>
<span aria-hidden>👤</span>
ورود
</button>
</nav>
</div>
</div>,
document.body,
);
}
export function Header({
categoryGroups,
categories,
}: {
categoryGroups: CategoryGroup[];
categories: Category[];
}) {
const [menuOpen, setMenuOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@@ -50,37 +223,31 @@ export function Header() {
return (
<header className="sticky top-0 z-50 border-b border-border bg-surface/95 backdrop-blur-sm">
<div className="mx-auto max-w-7xl px-4">
<div className="flex items-center justify-between gap-4 py-4">
<div className="flex items-center gap-2">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-red text-lg font-bold text-white">
S
</div>
<div>
<p className="font-display text-xl font-bold tracking-tight">
<span className="text-red">SANI</span>
<span className="text-foreground">HOME</span>
</p>
<p className="text-xs text-muted">فروشگاه لوازم خانگی</p>
<div className="grid grid-cols-3 items-center gap-4 py-4 min-[1200px]:flex min-[1200px]:justify-between">
<div className="flex items-center justify-start">
<button
type="button"
onClick={() => setDrawerOpen(true)}
aria-label="منو"
className="flex h-10 w-10 items-center justify-center rounded-lg border border-border text-xl transition-colors hover:border-red hover:text-red min-[1200px]:hidden"
>
<span aria-hidden>☰</span>
</button>
<div className="hidden min-[1200px]:block">
<Logo />
</div>
</div>
<div className="hidden flex-1 px-6 md:block">
<div className="flex overflow-hidden rounded-xl border border-border bg-background">
<input
type="search"
placeholder="جستجوی محصول، برند یا دسته‌بندی..."
className="flex-1 bg-transparent px-4 py-2.5 text-sm outline-none placeholder:text-muted"
/>
<button
type="button"
className="bg-red px-5 text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
جستجو
</button>
<div className="flex items-center justify-center min-[1200px]:flex-1 min-[1200px]:px-6">
<div className="min-[1200px]:hidden">
<Logo large />
</div>
<div className="hidden w-full min-[1200px]:block">
<SearchBar />
</div>
</div>
<div className="flex items-center gap-3 text-sm">
<div className="flex items-center justify-end gap-3 text-sm">
<button
type="button"
className="hidden items-center gap-1.5 text-muted transition-colors hover:text-red sm:flex"
@@ -101,7 +268,11 @@ export function Header() {
</div>
</div>
<nav className="relative flex items-center gap-6 overflow-visible border-t border-border py-3 text-sm">
<div className="border-t border-border py-3 min-[1200px]:hidden">
<SearchBar />
</div>
<nav className="relative hidden items-center gap-6 overflow-visible border-t border-border py-3 text-sm min-[1200px]:flex">
<div
ref={menuRef}
className="relative"
@@ -133,40 +304,45 @@ export function Header() {
</a>
</div>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
{megaColumns.map((column) => (
<div key={column.title}>
<p className="mb-0.5 text-sm font-bold text-red-dark">{column.title}</p>
<p className="font-display mb-3 text-[10px] tracking-wider text-muted uppercase">
{column.en}
</p>
<ul className="space-y-2">
{column.items.map((item) => (
<li key={item}>
<a
href="#"
className="text-sm text-muted transition-colors hover:text-red"
>
{item}
</a>
</li>
))}
</ul>
{categoryGroups.length === 0 ? (
<EmptyState message="هنوز دسته‌بندی‌ای ثبت نشده است." />
) : (
<>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
{categoryGroups.map((column) => (
<div key={column.id}>
<p className="mb-3 text-sm font-bold text-red-dark">{column.title}</p>
<ul className="space-y-2">
{column.items.map((item) => (
<li key={item.name}>
<a
href="#"
className="text-sm text-muted transition-colors hover:text-red"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
))}
</div>
))}
</div>
<div className="mt-5 grid grid-cols-2 gap-2 border-t border-border pt-4 sm:grid-cols-4">
{categories.slice(0, 4).map((cat) => (
<a
key={cat.id}
href="#"
className="rounded-lg bg-red-light/50 px-3 py-2 text-center text-xs font-medium text-red-dark transition-colors hover:bg-red-light"
>
{cat.name}
</a>
))}
</div>
{categories.length > 0 && (
<div className="mt-5 grid grid-cols-2 gap-2 border-t border-border pt-4 sm:grid-cols-4">
{categories.slice(0, 4).map((cat) => (
<a
key={cat.id}
href="#"
className="rounded-lg bg-red-light/50 px-3 py-2 text-center text-xs font-medium text-red-dark transition-colors hover:bg-red-light"
>
{cat.name}
</a>
))}
</div>
)}
</>
)}
</div>
)}
</div>
@@ -182,6 +358,12 @@ export function Header() {
))}
</nav>
</div>
<MobileDrawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
categoryGroups={categoryGroups}
/>
</header>
);
}
+26 -26
View File
@@ -4,48 +4,48 @@ import { useState } from "react";
import { Placeholder } from "@/components/ui/Placeholder";
import { sliderSlides } from "@/data/sample";
const PEEK_OFFSET = 70;
export function HeroSlider() {
const [active, setActive] = useState(1);
const [active, setActive] = useState(0);
const len = sliderSlides.length;
const getIndex = (offset: number) => {
const len = sliderSlides.length;
return (active + offset + len) % len;
const getDiff = (i: number) => {
let diff = i - active;
if (diff > len / 2) diff -= len;
if (diff < -len / 2) diff += len;
return diff;
};
const positions = [
{ offset: -1, scale: "scale-[0.92]", opacity: "opacity-55", z: "z-10" },
{ offset: 0, scale: "scale-100", opacity: "opacity-100", z: "z-20" },
{ offset: 1, scale: "scale-[0.92]", opacity: "opacity-55", z: "z-10" },
];
return (
<section className="overflow-x-clip bg-gradient-to-b from-red-light/30 to-background py-4 md:py-6">
<div className="relative mx-auto w-full max-w-7xl px-4">
<div className="relative h-[220px] sm:h-[280px] md:h-[340px] lg:h-[380px]">
{positions.map(({ offset, scale, opacity, z }) => {
const isCenter = offset === 0;
const slide = sliderSlides[getIndex(offset)];
{sliderSlides.map((slide, i) => {
const diff = getDiff(i);
const isCenter = diff === 0;
const isVisible = Math.abs(diff) <= 1;
return (
<div
key={offset}
className={`absolute top-1/2 -translate-y-1/2 transition-all duration-500 ease-out ${scale} ${opacity} ${z} ${
isCenter
? "inset-x-0 w-full origin-center"
: offset === -1
? "right-full mr-1.5 w-[78%] origin-right sm:w-[70%] md:w-[65%]"
: "left-full ml-1.5 w-[78%] origin-left sm:w-[70%] md:w-[65%]"
} ${isCenter ? "" : "pointer-events-none"}`}
key={slide.id}
className={`absolute inset-0 flex items-center justify-center ${
isCenter ? "z-20" : "z-10 pointer-events-none"
}`}
>
<div className="overflow-hidden rounded-2xl border border-border bg-surface shadow-lg">
<div
className={`h-[200px] overflow-hidden rounded-2xl border border-border bg-surface shadow-lg transition-all duration-500 ease-out sm:h-[260px] md:h-[310px] lg:h-[380px] ${
isCenter
? "w-[94%] scale-100 opacity-100 sm:w-[90%] md:w-[86%] lg:w-[82%]"
: "w-[66%] scale-[0.85] opacity-55 sm:w-[60%] md:w-[54%] lg:w-[48%]"
} ${isVisible ? "" : "opacity-0"}`}
style={{ transform: `translateX(${-diff * PEEK_OFFSET}%)` }}
>
<Placeholder
src={slide.image}
alt={slide.title}
width="100%"
label={isCenter ? "1280×380" : "880×350"}
className={`w-full rounded-none border-0 h-[200px] sm:h-[260px] md:h-[310px] ${
isCenter ? "lg:h-[380px]" : "lg:h-[350px]"
}`}
className="h-full w-full rounded-none border-0"
/>
</div>
</div>
-30
View File
@@ -1,30 +0,0 @@
import { Carousel } from "@/components/ui/Carousel";
import { ProductCard } from "@/components/ui/ProductCard";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { samsungProducts } from "@/data/sample";
export function SamsungCarousel() {
return (
<section className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
<SectionTitle
title="محصولات سامسونگ"
enTitle="Samsung Collection"
action="مشاهده همه"
/>
<Carousel ariaLabel="کاروسل محصولات سامسونگ">
{samsungProducts.map((product) => (
<ProductCard
key={product.id}
name={product.name}
brand={product.brand}
price={product.price}
image={product.image}
variant="brand"
/>
))}
</Carousel>
</div>
</section>
);
}
+22 -18
View File
@@ -2,7 +2,8 @@ import { Carousel } from "@/components/ui/Carousel";
import { Placeholder } from "@/components/ui/Placeholder";
import { ProductCard } from "@/components/ui/ProductCard";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { specialSaleProducts } from "@/data/sample";
import { EmptyState } from "@/components/ui/EmptyState";
import type { Product } from "@/lib/meshkee";
function SpecialSaleBanner() {
return (
@@ -14,14 +15,13 @@ function SpecialSaleBanner() {
src="/images/sale-banner.svg"
alt="فروش ویژه"
width="100%"
label="240×380"
className="h-full min-h-[380px] w-full rounded-none border-0"
/>
</a>
);
}
export function SpecialSaleCarousel() {
export function SpecialSaleCarousel({ products }: { products: Product[] }) {
return (
<section className="border-y border-red-light bg-red-light/30 py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
@@ -30,21 +30,25 @@ export function SpecialSaleCarousel() {
enTitle="Special Sale"
action="مشاهده همه"
/>
<Carousel ariaLabel="کاروسل فروش ویژه">
<SpecialSaleBanner />
{specialSaleProducts.map((product) => (
<ProductCard
key={product.id}
name={product.name}
brand={product.brand}
price={product.price}
oldPrice={product.oldPrice}
discount={product.discount}
image={product.image}
variant="sale"
/>
))}
</Carousel>
{products.length === 0 ? (
<EmptyState message="هنوز محصولی برای فروش ویژه ثبت نشده است." />
) : (
<Carousel ariaLabel="کاروسل فروش ویژه">
<SpecialSaleBanner />
{products.map((product) => (
<ProductCard
key={product.id}
name={product.name}
brand={product.brand ?? ""}
price={product.price}
oldPrice={product.oldPrice}
discount={product.discount}
image={product.image ?? "/images/placeholder.svg"}
variant="sale"
/>
))}
</Carousel>
)}
</div>
</section>
);
+7 -19
View File
@@ -1,9 +1,7 @@
import { Placeholder } from "@/components/ui/Placeholder";
const banners = [
{ title: "ماشین لباسشویی", subtitle: "از ۲۸ میلیون", label: "420×160", image: "/images/promo-1.svg" },
{ title: "جاروبرقی", subtitle: "ارسال فوری", label: "420×160", image: "/images/promo-2.svg" },
{ title: "لوازم آشپزخانه", subtitle: "تا ۲۰٪ تخفیف", label: "420×160", image: "/images/promo-3.svg" },
{ alt: "خانه‌ای تمیزتر، نفس‌هایی آسوده‌تر", image: "/images/banner3/6.jpg" },
{ alt: "خنکای دلنشین، در تمام لحظه‌ها", image: "/images/banner3/7.jpg" },
{ alt: "هر تصویر، یک تجربه تازه", image: "/images/banner3/8.jpg" },
];
export function ThreeBanners() {
@@ -13,22 +11,12 @@ export function ThreeBanners() {
<div className="grid gap-4 md:grid-cols-3">
{banners.map((banner) => (
<a
key={banner.title}
key={banner.image}
href="#"
className="group relative overflow-hidden rounded-xl border border-border bg-surface transition-all hover:border-red hover:shadow-md"
className="group overflow-hidden rounded-xl border border-border bg-surface transition-all hover:border-red hover:shadow-md"
>
<Placeholder
src={banner.image}
alt={banner.title}
width="100%"
height="160px"
label={banner.label}
className="w-full rounded-none border-0"
/>
<div className="absolute inset-0 flex flex-col items-start justify-end bg-gradient-to-t from-black/60 to-transparent p-5">
<h3 className="font-bold text-white">{banner.title}</h3>
<p className="text-sm text-white/75">{banner.subtitle}</p>
</div>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={banner.image} alt={banner.alt} className="h-auto w-full" />
</a>
))}
</div>
+18 -13
View File
@@ -1,9 +1,10 @@
import { Carousel } from "@/components/ui/Carousel";
import { ProductCard } from "@/components/ui/ProductCard";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { topSellingProducts } from "@/data/sample";
import { EmptyState } from "@/components/ui/EmptyState";
import type { Product } from "@/lib/meshkee";
export function TopSellingCarousel() {
export function TopSellingCarousel({ products }: { products: Product[] }) {
return (
<section className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
@@ -12,17 +13,21 @@ export function TopSellingCarousel() {
enTitle="Top Selling"
action="مشاهده همه"
/>
<Carousel ariaLabel="کاروسل پرفروش‌ترین‌ها">
{topSellingProducts.map((product) => (
<ProductCard
key={product.id}
name={product.name}
brand={product.brand}
price={product.price}
image={product.image}
/>
))}
</Carousel>
{products.length === 0 ? (
<EmptyState message="هنوز محصولی ثبت نشده است." />
) : (
<Carousel ariaLabel="کاروسل پرفروش‌ترین‌ها">
{products.map((product) => (
<ProductCard
key={product.id}
name={product.name}
brand={product.brand ?? ""}
price={product.price}
image={product.image ?? "/images/placeholder.svg"}
/>
))}
</Carousel>
)}
</div>
</section>
);
+9 -33
View File
@@ -1,5 +1,3 @@
import { Placeholder } from "@/components/ui/Placeholder";
export function TwoBanners() {
return (
<section className="py-10 md:py-14">
@@ -7,43 +5,21 @@ export function TwoBanners() {
<div className="grid gap-4 md:grid-cols-2">
<a
href="#"
className="group relative overflow-hidden rounded-2xl border border-border bg-surface transition-shadow hover:shadow-lg"
className="group overflow-hidden rounded-2xl border border-border bg-surface transition-shadow hover:shadow-lg"
>
<Placeholder
src="/images/banner-1.svg"
alt="تخفیف ویژه یخچال و فریزر"
width="100%"
height="200px"
label="640×200"
className="w-full rounded-none border-0"
/>
<div className="absolute inset-0 flex flex-col justify-end bg-gradient-to-t from-red-dark/80 to-transparent p-6">
<p className="font-display text-xs tracking-widest text-red-muted uppercase">
Summer Sale
</p>
<h3 className="text-lg font-bold text-white">تخفیف ویژه یخچال و فریزر</h3>
<p className="mt-1 text-sm text-white/80">تا ۲۵٪ تخفیف — تا پایان مرداد</p>
</div>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/images/banner/5.jpg" alt="وقتی که انرژی لازمی" className="h-auto w-full" />
</a>
<a
href="#"
className="group relative overflow-hidden rounded-2xl border border-border bg-surface transition-shadow hover:shadow-lg"
className="group overflow-hidden rounded-2xl border border-border bg-surface transition-shadow hover:shadow-lg"
>
<Placeholder
src="/images/banner-2.svg"
alt="تلویزیون‌های هوشمند ۲۰۲۶"
width="100%"
height="200px"
label="640×200"
className="w-full rounded-none border-0"
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/images/banner/4.jpg"
alt="قدرت بخار، درخشش بی‌نقص"
className="h-auto w-full"
/>
<div className="absolute inset-0 flex flex-col justify-end bg-gradient-to-t from-red-dark/80 to-transparent p-6">
<p className="font-display text-xs tracking-widest text-red-muted uppercase">
Smart TV
</p>
<h3 className="text-lg font-bold text-white">تلویزیون‌های هوشمند ۲۰۲۶</h3>
<p className="mt-1 text-sm text-white/80">QLED و OLED با اقساط ۱۲ ماهه</p>
</div>
</a>
</div>
</div>