Compare commits

..

10 Commits

Author SHA1 Message Date
hamid zarghami cd266c057c responsive home page and layout 2026-07-19 16:29:37 +03:30
hamid zarghami 2adaac8ec4 footer and first desktop version completed 2026-07-19 16:09:33 +03:30
hamid zarghami 2c9b43c84a contact section 2026-07-19 15:48:30 +03:30
hamid zarghami 36763aace0 home blog section 2026-07-19 15:35:37 +03:30
hamid zarghami 35beea16bd order-regiteration-steps 2026-07-19 15:18:24 +03:30
hamid zarghami 979281bfb1 best seller categories 2026-07-19 15:13:06 +03:30
hamid zarghami fcf89eb2a1 fix shadow 2026-07-19 12:35:46 +03:30
hamid zarghami ea7745179b orderby application section 2026-07-19 12:25:37 +03:30
hamid zarghami 9a58990cae product item + product section 2026-07-19 12:03:21 +03:30
hamid zarghami 7bb9240f21 carousel components 2026-07-18 11:25:53 +03:30
39 changed files with 939 additions and 147 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ const Button: FC<Props> = ({ variant = "primary", className, children, ...rest }
return ( return (
<button <button
className={cn( className={cn(
"inline-flex h-12 items-center justify-center rounded-full px-6 text-sm transition-colors disabled:pointer-events-none disabled:opacity-50", "inline-flex h-10 items-center justify-center rounded-full px-6 text-sm transition-colors disabled:pointer-events-none disabled:opacity-50",
variant === "primary" && "bg-primary text-white hover:bg-primary/90", variant === "primary" && "bg-primary text-white hover:bg-primary/90",
variant === "outline" && "border border-primary bg-transparent text-primary hover:bg-primary/10", variant === "outline" && "border border-primary bg-transparent text-primary hover:bg-primary/10",
className, className,
+217
View File
@@ -0,0 +1,217 @@
"use client";
import { cn } from "@/app/lib/cn";
import useEmblaCarousel from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "iconsax-reactjs";
import {
createContext,
useContext,
useEffect,
useState,
type FC,
type HTMLAttributes,
type ReactNode,
} from "react";
type CarouselApi = {
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
slidesPerView: number;
gap: number;
};
type ViewportRef = (node: HTMLElement | null) => void;
const CarouselContext = createContext<CarouselApi | null>(null);
const CarouselViewportContext = createContext<ViewportRef | null>(null);
function useCarousel() {
const context = useContext(CarouselContext);
if (!context) {
throw new Error("Carousel components must be used within <Carousel>");
}
return context;
}
type ResponsiveNumber = number | { base: number; md?: number; lg?: number };
function useResponsiveNumber(value: ResponsiveNumber): number {
const base = typeof value === "number" ? value : value.base;
const md = typeof value === "number" ? undefined : value.md;
const lg = typeof value === "number" ? undefined : value.lg;
const resolve = () => {
if (typeof window === "undefined") return base;
if (window.matchMedia("(min-width: 1024px)").matches) return lg ?? md ?? base;
if (window.matchMedia("(min-width: 768px)").matches) return md ?? base;
return base;
};
const [resolved, setResolved] = useState(base);
useEffect(() => {
const update = () => setResolved(resolve());
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, [base, md, lg]);
return resolved;
}
type CarouselProps = {
children: ReactNode;
slidesPerView?: ResponsiveNumber;
slidesToScroll?: number;
gap?: ResponsiveNumber;
direction?: "rtl" | "ltr";
className?: string;
};
const Carousel: FC<CarouselProps> = ({
children,
slidesPerView = 5,
slidesToScroll,
gap = 40,
direction = "rtl",
className,
}) => {
const resolvedSlidesPerView = useResponsiveNumber(slidesPerView);
const resolvedGap = useResponsiveNumber(gap);
const scrollBy = slidesToScroll ?? Math.max(1, Math.floor(resolvedSlidesPerView));
const [emblaRef, emblaApi] = useEmblaCarousel({
align: "start",
direction,
slidesToScroll: scrollBy,
containScroll: "trimSnaps",
});
const [canScrollPrev, setCanScrollPrev] = useState(false);
const [canScrollNext, setCanScrollNext] = useState(false);
useEffect(() => {
if (!emblaApi) return;
const updateButtons = () => {
setCanScrollPrev(emblaApi.canScrollPrev());
setCanScrollNext(emblaApi.canScrollNext());
};
updateButtons();
emblaApi.on("select", updateButtons);
emblaApi.on("reInit", updateButtons);
return () => {
emblaApi.off("select", updateButtons);
emblaApi.off("reInit", updateButtons);
};
}, [emblaApi]);
useEffect(() => {
emblaApi?.reInit({ slidesToScroll: scrollBy });
}, [emblaApi, scrollBy, resolvedSlidesPerView, resolvedGap]);
return (
<CarouselContext.Provider
value={{
scrollPrev: () => emblaApi?.scrollPrev(),
scrollNext: () => emblaApi?.scrollNext(),
canScrollPrev,
canScrollNext,
slidesPerView: resolvedSlidesPerView,
gap: resolvedGap,
}}
>
<CarouselViewportContext.Provider value={emblaRef}>
<div className={className}>{children}</div>
</CarouselViewportContext.Provider>
</CarouselContext.Provider>
);
};
type CarouselControlsProps = {
className?: string;
prevLabel?: string;
nextLabel?: string;
};
const CarouselControls: FC<CarouselControlsProps> = ({
className,
prevLabel = "قبلی",
nextLabel = "بعدی",
}) => {
const { scrollPrev, scrollNext, canScrollPrev, canScrollNext } = useCarousel();
return (
<div className={cn("flex gap-2", className)}>
<button
type="button"
aria-label={prevLabel}
disabled={!canScrollPrev}
onClick={scrollPrev}
className="size-8 bg-primary rounded-full flex justify-center items-center disabled:opacity-40 disabled:cursor-not-allowed transition-opacity"
>
<ArrowRight size={20} color="white" />
</button>
<button
type="button"
aria-label={nextLabel}
disabled={!canScrollNext}
onClick={scrollNext}
className="size-8 bg-primary rounded-full flex justify-center items-center disabled:opacity-40 disabled:cursor-not-allowed transition-opacity"
>
<ArrowLeft size={20} color="white" />
</button>
</div>
);
};
type CarouselTrackProps = {
children: ReactNode;
className?: string;
};
const CarouselTrack: FC<CarouselTrackProps> = ({ children, className }) => {
const emblaRef = useContext(CarouselViewportContext);
const { gap } = useCarousel();
if (!emblaRef) {
throw new Error("CarouselTrack must be used within <Carousel>");
}
return (
<div className={cn("overflow-hidden", className)} ref={emblaRef}>
<div className="flex touch-pan-y" style={{ gap }}>
{children}
</div>
</div>
);
};
type CarouselSlideProps = HTMLAttributes<HTMLDivElement> & {
children: ReactNode;
};
const CarouselSlide: FC<CarouselSlideProps> = ({ children, className, style, ...rest }) => {
const { slidesPerView, gap } = useCarousel();
const totalGap = (slidesPerView - 1) * gap;
return (
<div
className={cn("min-w-0 shrink-0 grow-0", className)}
style={{
flexBasis: `calc((100% - ${totalGap}px) / ${slidesPerView})`,
...style,
}}
{...rest}
>
{children}
</div>
);
};
export { Carousel, CarouselControls, CarouselSlide, CarouselTrack };
export default Carousel;
+1 -1
View File
@@ -18,7 +18,7 @@ const Input: FC<Props> = (props) => {
<input <input
type={variant === "search" ? "search" : "text"} type={variant === "search" ? "search" : "text"}
className={cn( className={cn(
"h-12 w-full rounded-2xl border border-secondary bg-white px-5 text-right text-sm text-[#3A4147] placeholder:text-[#D7E0E8] transition-colors focus:border-primary", "h-10 w-full rounded-2xl border border-secondary bg-white px-5 text-right text-sm text-[#3A4147] placeholder:text-[#D7E0E8] transition-colors focus:border-primary",
variant === "search" && "pr-13", variant === "search" && "pr-13",
className, className,
)} )}
+43
View File
@@ -0,0 +1,43 @@
import { cn } from "@/app/lib/cn";
import { ArrowDown2 } from "iconsax-reactjs";
import { FC } from "react";
export type SelectOption = {
label: string;
value: string;
};
type Props = {
options?: SelectOption[];
className?: string;
placeholder?: string;
} & Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "placeholder">;
const Select: FC<Props> = ({ options, className, placeholder, children, ...rest }) => {
return (
<div className="relative w-full" dir="rtl">
<select
className={cn(
"h-10 w-full appearance-none rounded-2xl border border-secondary bg-white px-4 pl-13 text-right text-sm transition-colors focus:border-primary disabled:pointer-events-none disabled:opacity-50",
className,
)}
{...rest}
>
{placeholder && (
<option value="" disabled hidden>
{placeholder}
</option>
)}
{options?.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
{children}
</select>
<ArrowDown2 aria-hidden="true" className="pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-black" color="currentColor" size={18} />
</div>
);
};
export default Select;
+23
View File
@@ -1,5 +1,11 @@
import BannerSection from "./components/BannerSection"; import BannerSection from "./components/BannerSection";
import BestSellerCategories from "./components/BestSellerCategories";
import BlogsSection from "./components/BlogsSection";
import CategorySection from "./components/CategorySection"; import CategorySection from "./components/CategorySection";
import ContactCtaSection from "./components/ContactCtaSection";
import OrderByApplication from "./components/OrderByApplication";
import OrderRegistrationSteps from "./components/OrderRegistrationSteps";
import ProductSection from "./components/ProductSection";
import StepSection from "./components/StepSection"; import StepSection from "./components/StepSection";
const Home = () => { const Home = () => {
@@ -8,6 +14,23 @@ const Home = () => {
<BannerSection /> <BannerSection />
<StepSection /> <StepSection />
<CategorySection /> <CategorySection />
<ProductSection />
<OrderByApplication />
<BestSellerCategories />
<OrderRegistrationSteps />
<BlogsSection />
<div className="mt-12 px-4 sm:mt-16 sm:px-8 lg:mt-[120px] lg:px-[120px]">
<div className="bg-[#004A9005] p-6 sm:p-10 lg:p-16 w-full rounded-3xl lg:rounded-4xl border border-[#E9EEF2]">
<div className="font-bold text-lg sm:text-xl lg:text-2xl">چاپ جعبه، بستهبندی و محصولات تبلیغاتی با طراحی اختصاصی</div>
<div className="mt-4 sm:mt-6 leading-6 font-bold text-[#194873] text-sm">
جعبه، اولین چیزی است که مشتری از برند شما میبیند. ما با ارائه خدمات چاپ جعبه، لیبل، استیکر، کارت ویزیت و سایر محصولات تبلیغاتی، به شما کمک میکنیم تا بستهبندی حرفهای و ماندگاری برای
محصولات خود داشته باشید. با استفاده از متریال باکیفیت، طراحی اختصاصی و چاپ دقیق، سفارش شما در کوتاهترین زمان آماده و ارسال میشود.
</div>
</div>
</div>
<ContactCtaSection />
</div> </div>
); );
}; };
+13 -7
View File
@@ -8,20 +8,26 @@ const BannerSection: FC = () => {
return ( return (
<div className="w-full"> <div className="w-full">
<div className="relative"> <div className="relative">
<Image src={banner} alt="banner" width={2000} height={574} className="w-full max-h-[574px] object-cover" /> <Image
<div className="absolute h-full right-[120px] top-0 bottom-0 m-auto flex flex-col justify-center"> src={banner}
<div className="text-base">چاپ و بسته بندی به روش هوشمندانه</div> alt="banner"
<div className="text-2xl font-black mt-4">مجــتمع تخصصــــــی</div> width={2000}
<div className="text-2xl font-black mt-4"> height={574}
className="w-full h-[420px] sm:h-[480px] lg:h-auto lg:max-h-[574px] object-cover"
/>
<div className="absolute inset-y-0 right-4 sm:right-8 lg:right-[120px] flex flex-col justify-center max-w-[min(100%,420px)] lg:max-w-none">
<div className="text-sm sm:text-base">چاپ و بسته بندی به روش هوشمندانه</div>
<div className="text-xl sm:text-2xl font-black mt-3 sm:mt-4">مجــتمع تخصصــــــی</div>
<div className="text-xl sm:text-2xl font-black mt-3 sm:mt-4">
چــــــاپ و بستــــه بندی <span className="text-primary">دستی</span> و <span className="text-primary">هوشمند</span>{" "} چــــــاپ و بستــــه بندی <span className="text-primary">دستی</span> و <span className="text-primary">هوشمند</span>{" "}
</div> </div>
<div className="mt-4 text-base flex flex-col gap-3"> <div className="mt-3 sm:mt-4 text-sm sm:text-base flex flex-col gap-2 sm:gap-3">
<div>قیمتهای عالی</div> <div>قیمتهای عالی</div>
<div>خدمات مشتریان بینظیر</div> <div>خدمات مشتریان بینظیر</div>
<div>سیستم سفارشگیری ساده</div> <div>سیستم سفارشگیری ساده</div>
</div> </div>
<div className="mt-4 flex gap-6 items-center"> <div className="mt-4 flex flex-wrap gap-3 sm:gap-6 items-center">
<Button> <Button>
<div className="flex gap-2.5"> <div className="flex gap-2.5">
<div>محصولات</div> <div>محصولات</div>
@@ -0,0 +1,70 @@
import { cn } from "@/app/lib/cn";
import bannersImage from "@/assets/images/best-sellers/banners.jpg";
import boxesImage from "@/assets/images/best-sellers/boxes.jpg";
import businessCardsImage from "@/assets/images/best-sellers/business-cards.jpg";
import calendarsImage from "@/assets/images/best-sellers/calendars.jpg";
import catalogsImage from "@/assets/images/best-sellers/catalogs.png";
import { ArrowLeft } from "iconsax-reactjs";
import Image, { type StaticImageData } from "next/image";
import { type FC } from "react";
type CategoryItem = {
title: string;
image: StaticImageData;
featured?: boolean;
};
const categories: CategoryItem[] = [
{ title: "جعبه ها", image: boxesImage, featured: true },
{ title: "کاتالوگ ها", image: catalogsImage },
{ title: "کارت ویزیت", image: businessCardsImage },
{ title: "تقویم ها", image: calendarsImage },
{ title: "بنرها", image: bannersImage },
];
type CategoryTileProps = {
title: string;
image: StaticImageData;
featured?: boolean;
};
const CategoryTile: FC<CategoryTileProps> = ({ title, image, featured }) => {
return (
<div
className={cn(
"relative overflow-hidden rounded-[24px] sm:rounded-[40px]",
featured ? "col-span-2 row-span-2 min-h-[220px] sm:min-h-0" : "aspect-square",
)}
>
<Image src={image} alt={title} fill className="object-cover" sizes={featured ? "(max-width: 768px) 100vw, 50vw" : "(max-width: 768px) 50vw, 25vw"} />
<div
className={cn(
"absolute inset-x-3 bottom-3 sm:inset-x-4 sm:bottom-4 flex items-center justify-between rounded-2xl sm:rounded-3xl bg-[#0A1B2C]/60 backdrop-blur-[2px]",
featured ? "px-4 py-3 sm:px-6 sm:py-3.5" : "px-3 py-2.5 sm:px-5 sm:py-3",
)}
>
<div className={cn("text-white font-bold", featured ? "text-base sm:text-lg" : "text-sm sm:text-base")}>{title}</div>
<div className="flex items-center gap-1.5 text-white">
<span className={cn("font-bold", featured ? "text-xs sm:text-sm" : "text-xs")}>مشاهده</span>
<ArrowLeft size={featured ? 20 : 18} color="currentColor" />
</div>
</div>
</div>
);
};
const BestSellerCategories: FC = () => {
return (
<div className="mt-12 px-4 sm:mt-16 sm:px-8 lg:mt-[120px] lg:px-[120px]">
<div className="font-bold text-lg sm:text-2xl">دسته بندی های پرفروش</div>
<div className="mt-8 sm:mt-12 grid grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 lg:gap-10">
{categories.map((category) => (
<CategoryTile key={category.title} title={category.title} image={category.image} featured={category.featured} />
))}
</div>
</div>
);
};
export default BestSellerCategories;
+21
View File
@@ -0,0 +1,21 @@
import BlogImage from "@/assets/images/blog-image.png";
import { ArrowLeft } from "iconsax-reactjs";
import Image from "next/image";
import { FC } from "react";
const BlogCard: FC = () => {
return (
<div className="bg-white shadow-[0_4px_20px_rgba(0,0,0,0.1)] p-4 rounded-3xl">
<Image src={BlogImage} alt="Blog Image" width={1000} height={1000} className="w-full h-auto rounded-2xl" />
<div className="mt-4 text-[13px] text-[#A7ADB3] font-bold">۰۴ تیر ۱۴۰۵</div>
<div className="mt-2 font-bold">راهنمای انتخاب بنر مناسب</div>
<p className="mt-2 text-sm text-[#67727C]">با انواع بنر، کاربردها و نکات مهم انتخاب آشنا شوید تا بهترین گزینه را متناسب با نیاز و بودجه خود انتخاب کنید.</p>
<div className="mt-2 flex items-center gap-2 text-primary">
<div className="text-xs font-bold">مشاهده</div>
<ArrowLeft size={16} color="currentColor" className="mt-px" />
</div>
</div>
);
};
export default BlogCard;
+18
View File
@@ -0,0 +1,18 @@
import { FC } from "react";
import BlogCard from "./BlogCard";
const BlogsSection: FC = () => {
return (
<div className="mt-12 px-4 sm:mt-16 sm:px-8 lg:mt-[120px] lg:px-[120px]">
<div className="text-lg sm:text-2xl font-bold">مقالات ما</div>
<div className="mt-8 sm:mt-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-10">
<BlogCard />
<BlogCard />
<BlogCard />
<BlogCard />
</div>
</div>
);
};
export default BlogsSection;
+3 -3
View File
@@ -9,9 +9,9 @@ type CategoryCardProps = {
const CategoryCard: FC<CategoryCardProps> = ({ title }) => { const CategoryCard: FC<CategoryCardProps> = ({ title }) => {
return ( return (
<div className="w-full bg-[#D4E3F154]/33 rounded-[40px] p-6"> <div className="w-full bg-[#D4E3F154]/33 rounded-[24px] sm:rounded-[40px] p-4 sm:p-6">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center gap-2">
<div className="text-[#21588C] font-bold">{title}</div> <div className="text-[#21588C] font-bold text-sm sm:text-base">{title}</div>
<div className="bg-white/40 flex justify-center items-center"> <div className="bg-white/40 flex justify-center items-center">
<ArrowLeft size={20} color="currentColor" className="text-primary rotate-45" /> <ArrowLeft size={20} color="currentColor" className="text-primary rotate-45" />
</div> </div>
+11 -65
View File
@@ -1,8 +1,4 @@
"use client"; import { Carousel, CarouselControls, CarouselSlide, CarouselTrack } from "@/app/components/Carousel";
import { ArrowLeft, ArrowRight } from "iconsax-reactjs";
import useEmblaCarousel from "embla-carousel-react";
import { useEffect, useState } from "react";
import CategoryCard from "./CategoryCard"; import CategoryCard from "./CategoryCard";
const categories = [ const categories = [
@@ -19,72 +15,22 @@ const categories = [
]; ];
const CategorySection = () => { const CategorySection = () => {
const [emblaRef, emblaApi] = useEmblaCarousel({
align: "start",
direction: "rtl",
slidesToScroll: 5,
containScroll: "trimSnaps",
});
const [canScrollPrev, setCanScrollPrev] = useState(false);
const [canScrollNext, setCanScrollNext] = useState(false);
useEffect(() => {
if (!emblaApi) return;
const updateButtons = () => {
setCanScrollPrev(emblaApi.canScrollPrev());
setCanScrollNext(emblaApi.canScrollNext());
};
updateButtons();
emblaApi.on("select", updateButtons);
emblaApi.on("reInit", updateButtons);
return () => {
emblaApi.off("select", updateButtons);
emblaApi.off("reInit", updateButtons);
};
}, [emblaApi]);
return ( return (
<div className="mt-[120px] px-[120px]"> <div className="mt-12 px-4 sm:mt-16 sm:px-8 lg:mt-[120px] lg:px-[120px]">
<div className="flex justify-between items-center"> <Carousel slidesPerView={{ base: 1.4, md: 3, lg: 5 }} gap={{ base: 16, md: 24, lg: 40 }}>
<div className="font-bold text-2xl">دسته بندی های محصولات</div> <div className="flex justify-between items-center gap-4">
<div className="flex gap-2"> <div className="font-bold text-lg sm:text-2xl">دسته بندی های محصولات</div>
<button <CarouselControls prevLabel="دسته‌بندی‌های قبلی" nextLabel="دسته‌بندی‌های بعدی" />
type="button"
aria-label="دسته‌بندی‌های قبلی"
disabled={!canScrollPrev}
onClick={() => emblaApi?.scrollPrev()}
className="size-8 bg-primary rounded-full flex justify-center items-center disabled:opacity-40 disabled:cursor-not-allowed transition-opacity"
>
<ArrowRight size={20} color="white" />
</button>
<button
type="button"
aria-label="دسته‌بندی‌های بعدی"
disabled={!canScrollNext}
onClick={() => emblaApi?.scrollNext()}
className="size-8 bg-primary rounded-full flex justify-center items-center disabled:opacity-40 disabled:cursor-not-allowed transition-opacity"
>
<ArrowLeft size={20} color="white" />
</button>
</div> </div>
</div>
<div className="mt-12 overflow-hidden" ref={emblaRef}> <CarouselTrack className="mt-8 sm:mt-12">
<div className="flex touch-pan-y gap-10">
{categories.map((title) => ( {categories.map((title) => (
<div <CarouselSlide key={title}>
key={title}
className="min-w-0 shrink-0 grow-0 basis-[calc((100%-10rem)/5)]"
>
<CategoryCard title={title} /> <CategoryCard title={title} />
</div> </CarouselSlide>
))} ))}
</div> </CarouselTrack>
</div> </Carousel>
</div> </div>
); );
}; };
+25
View File
@@ -0,0 +1,25 @@
import Button from "@/app/components/Button";
import deliveryTruckImage from "@/assets/images/delivery-truck.png";
import { CallCalling } from "iconsax-reactjs";
import Image from "next/image";
import { type FC } from "react";
const ContactCtaSection: FC = () => {
return (
<div className="mt-12 sm:mt-16 lg:mt-[120px] flex flex-col sm:flex-row items-center justify-center gap-4 sm:gap-6 bg-linear-to-l from-primary/10 to-transparent px-4 py-8 sm:py-10">
<div className="flex items-center gap-3 text-center sm:text-right">
<Image src={deliveryTruckImage} alt="" width={40} height={40} className="size-10 object-contain shrink-0" />
<p className="text-base sm:text-xl font-bold text-[#0A1B2C] sm:whitespace-nowrap">
ارسال سریع به سراسر ایران | مشاوره رایگان قبل از سفارش
</p>
</div>
<Button className="gap-2 ps-5 pe-6 font-bold shrink-0">
<CallCalling size={20} color="currentColor" variant="Linear" />
تماس با ما
</Button>
</div>
);
};
export default ContactCtaSection;
@@ -0,0 +1,45 @@
import Carousel, { CarouselControls, CarouselSlide, CarouselTrack } from "@/app/components/Carousel";
import Select from "@/app/components/Select";
import { FC } from "react";
import ProductCard from "./ProductCard";
const OrderByApplication: FC = () => {
return (
<div className="px-4 py-12 sm:px-8 sm:py-16 lg:p-[120px] bg-[#F1F6FA] mt-12 sm:mt-16 lg:mt-[120px]">
<div className="text-lg sm:text-2xl font-bold">سفارش بر اساس کاربرد</div>
<Carousel slidesPerView={{ base: 1.4, md: 3, lg: 5 }} gap={{ base: 16, md: 24, lg: 40 }}>
<div className="flex mt-8 sm:mt-12 justify-between items-center gap-4">
<div className="min-w-0 flex-1 max-w-[240px] sm:max-w-none sm:flex-none">
<Select
options={[
{
label: "محبوب‌ترین محصولات",
value: "popular",
},
{
label: "جدیدترین محصولات",
value: "newest",
},
{
label: "پرفروش‌ترین محصولات",
value: "best_selling",
},
]}
/>
</div>
<CarouselControls prevLabel="محصولات قبلی" nextLabel="محصولات بعدی" />
</div>
<CarouselTrack className="mt-8 sm:mt-12 px-2 sm:px-3 py-4 -mx-2 sm:-mx-3">
{Array.from({ length: 10 }).map((_, index) => (
<CarouselSlide key={index}>
<ProductCard />
</CarouselSlide>
))}
</CarouselTrack>
</Carousel>
</div>
);
};
export default OrderByApplication;
@@ -0,0 +1,20 @@
import ImageSrc from "@/assets/images/OrderRegistrationSteps.png";
import Image from "next/image";
import { FC } from "react";
const OrderRegistrationSteps: FC = () => {
return (
<div className="px-4 py-12 sm:px-8 sm:py-16 lg:p-[120px] bg-[#F1F6FA] mt-12 sm:mt-16 lg:mt-[120px]">
<div className="text-lg sm:text-2xl font-bold">سفارش بر اساس کاربرد</div>
<Image
src={ImageSrc}
alt="Order Registration Steps"
width={1000}
height={1000}
className="w-full mt-8 sm:mt-12 h-auto object-contain"
/>
</div>
);
};
export default OrderRegistrationSteps;
+35
View File
@@ -0,0 +1,35 @@
"use client";
import Button from "@/app/components/Button";
import ProductImage from "@/assets/images/product_image.png";
import Image from "next/image";
import { type FC } from "react";
import { Rating } from "react-simple-star-rating";
const ProductCard: FC = () => {
return (
<div>
<div className="rounded-xl overflow-hidden">
<Image src={ProductImage} alt="product-card" width={500} height={500} className="w-full aspect-square object-cover" />
</div>
<div className="relative bg-white p-4 rounded-xl -mt-6 shadow-[0_4px_20px_rgba(0,0,0,0.1)]">
<div className="text-[#2A3950] font-bold text-center">جعبه کیبوردی</div>
<div className="mt-2 flex justify-center items-center gap-1.5">
<Rating initialValue={4.5} size={20} readonly rtl allowFraction SVGstyle={{ display: "inline-block" }} />
<div className="text-xs mt-1">4.5</div>
</div>
<div className="mt-2 text-[#0A1B2C8F]/56 text-sm text-center">قیمت از </div>
<div className="flex gap-1.5 items-center justify-center">
<div className="text-[#0A1B2C8F]/56 line-through decoration-[#D00003] text-[10px]">10,000,000</div>
<div className="text-sm">1,000,000 تومان</div>
</div>
<div className="mt-3 px-4">
<Button variant="outline" className="h-8! w-full">
جزییات
</Button>
</div>
</div>
</div>
);
};
export default ProductCard;
+26
View File
@@ -0,0 +1,26 @@
import { Carousel, CarouselControls, CarouselSlide, CarouselTrack } from "@/app/components/Carousel";
import { type FC } from "react";
import ProductCard from "./ProductCard";
const ProductSection: FC = () => {
return (
<div className="mt-12 px-4 sm:mt-16 sm:px-8 lg:mt-[120px] lg:px-[120px]">
<Carousel slidesPerView={{ base: 1.4, md: 3, lg: 5 }} gap={{ base: 16, md: 24, lg: 40 }}>
<div className="flex justify-between items-center gap-4">
<div className="font-bold text-lg sm:text-2xl">محبوبترین محصولات</div>
<CarouselControls prevLabel="محصولات قبلی" nextLabel="محصولات بعدی" />
</div>
<CarouselTrack className="mt-8 sm:mt-12 px-2 sm:px-3 py-4 -mx-2 sm:-mx-3">
{Array.from({ length: 10 }).map((_, index) => (
<CarouselSlide key={index}>
<ProductCard />
</CarouselSlide>
))}
</CarouselTrack>
</Carousel>
</div>
);
};
export default ProductSection;
+17 -24
View File
@@ -1,32 +1,25 @@
import { ColorSwatch, Element4, LampCharge, Magicpen, Setting2, TruckFast } from "iconsax-reactjs"; import { ColorSwatch, Element4, LampCharge, Magicpen, Setting2, TruckFast } from "iconsax-reactjs";
import { FC } from "react"; import { FC } from "react";
const steps = [
{ label: "تحلیل نیاز برند", Icon: LampCharge },
{ label: "طراحی و آماده‌سازی فایل", Icon: Magicpen },
{ label: "انتخاب فناوری و متریال", Icon: Element4 },
{ label: "کنترل رنگ و نمونه‌گیری", Icon: ColorSwatch },
{ label: "عملیات تکمیلی", Icon: Setting2 },
{ label: "تحویل به موقع", Icon: TruckFast },
];
const StepSection: FC = () => { const StepSection: FC = () => {
return ( return (
<div className="w-full px-[120px] bg-secondary flex justify-between items-center h-[80px]"> <div className="w-full px-4 sm:px-8 lg:px-[120px] bg-secondary">
<div className="flex gap-4 3tems-center"> <div className="flex gap-6 lg:gap-4 lg:justify-between items-center h-auto py-4 lg:py-0 lg:h-[80px] overflow-x-auto scrollbar-none [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
<LampCharge variant="Bulk" size={24} color="currentColor" className="text-primary" /> {steps.map(({ label, Icon }) => (
<div className="font-bold text-sm">تحلیل نیاز برند</div> <div key={label} className="flex gap-3 items-center shrink-0">
</div> <Icon variant="Bulk" size={24} color="currentColor" className="text-primary" />
<div className="flex gap-3 items-center"> <div className="font-bold text-sm whitespace-nowrap">{label}</div>
<Magicpen variant="Bulk" size={24} color="currentColor" className="text-primary" /> </div>
<div className="font-bold text-sm">طراحی و آمادهسازی فایل</div> ))}
</div>
<div className="flex gap-3 items-center">
<Element4 variant="Bulk" size={24} color="currentColor" className="text-primary" />
<div className="font-bold text-sm">انتخاب فناوری و متریال</div>
</div>
<div className="flex gap-4 i3ems-center">
<ColorSwatch variant="Bulk" size={24} color="currentColor" className="text-primary" />
<div className="font-bold text-sm">کنترل رنگ و نمونهگیری</div>
</div>
<div className="flex gap-3 items-center">
<Setting2 variant="Bulk" size={24} color="currentColor" className="text-primary" />
<div className="font-bold text-sm">عملیات تکمیلی</div>
</div>
<div className="flex gap-43items-center">
<TruckFast variant="Bulk" size={24} color="currentColor" className="text-primary" />
<div className="font-bold text-sm">تحویل به موقع</div>
</div> </div>
</div> </div>
); );
+5 -3
View File
@@ -2,6 +2,7 @@ import "@/assets/iranyekan/fonts.css";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Footer from "./shared/Footer";
import Header from "./shared/Header"; import Header from "./shared/Header";
const geistSans = Geist({ const geistSans = Geist({
@@ -25,10 +26,11 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}> <html lang="fa" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col"> <body className="flex min-h-full flex-col">
<Header /> <Header />
{children} <main className="flex-1">{children}</main>
<Footer />
</body> </body>
</html> </html>
); );
+46
View File
@@ -0,0 +1,46 @@
import Seprator from "@/app/components/Seprator";
import { type FC } from "react";
import FooterContact from "./components/FooterContact";
import FooterLinkColumn from "./components/FooterLinkColumn";
import FooterNewsletter from "./components/FooterNewsletter";
import FooterSocial from "./components/FooterSocial";
import FooterTrustBadges from "./components/FooterTrustBadges";
import FooterTrustValues from "./components/FooterTrustValues";
const guideLinks = ["سوالات متداول", "راهنمای ثبت سفارش", "راهنمای طراحی فایل", "روش‌های ارسال", "شیوه‌های پرداخت", "استعلام قیمت"];
const serviceLinks = ["درباره ما", "تماس با ما", "قوانین و مقررات", "حریم خصوصی", "شرایط ثبت سفارش", "فرصت‌های همکاری"];
const infoLinks = ["پیگیری سفارش", "باشگاه مشتریان", "بلاگ و مقالات", "پیشنهادهای ویژه", "همکاری با ما", "نقشه سایت"];
const categoryLinks = ["چاپ جعبه و بسته‌بندی", "جعبه هاردباکس", "چاپ لیبل و برچسب", "چاپ استیکر", "چاپ ساک دستی", "چاپ کارت ویزیت", "چاپ تراکت و بروشور"];
const Footer: FC = () => {
return (
<footer className="mt-auto border-t border-[#E9EEF2] bg-[#F7F9FB]">
<div className="px-4 sm:px-8 lg:px-[120px] pt-8 sm:pt-12 pb-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:flex xl:justify-between gap-8 xl:gap-6">
<FooterContact />
<FooterLinkColumn title="راهنمای خرید" links={guideLinks} />
<FooterLinkColumn title="خدمات مشتریان" links={serviceLinks} />
<FooterLinkColumn title="اطلاعات" links={infoLinks} />
<FooterLinkColumn title="دسته‌بندی محصولات" links={categoryLinks} />
<FooterTrustValues />
</div>
<Seprator className="my-6 sm:my-8 h-px w-full" />
<div className="flex flex-col lg:flex-row items-center lg:items-start justify-between gap-8">
<FooterSocial />
<Seprator className="hidden lg:block h-16 w-px shrink-0" />
<FooterTrustBadges />
<Seprator className="hidden lg:block h-16 w-px shrink-0" />
<FooterNewsletter />
</div>
<Seprator className="my-6 sm:my-8 h-px w-full" />
<p className="text-center text-xs text-[#6C7680] leading-5 px-2">تمامی حقوق برای سایت نیکوپکجینگ بوده و با ذکر نام لینک به منبع مجاز میباشد.</p>
</div>
</footer>
);
};
export default Footer;
+34 -15
View File
@@ -8,33 +8,51 @@ import HeaderMenu from "./components/HeaderMenu";
const Header = () => { const Header = () => {
return ( return (
<div> <div>
<div className="h-12 bg-secondary flex items-center justify-between px-[120px]"> <div className="min-h-12 bg-secondary flex items-center justify-between gap-3 px-4 sm:px-8 lg:px-[120px] py-2">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center min-w-0">
<span className="text-primary flex items-center gap-2"> <span className="text-primary flex items-center gap-2 min-w-0">
<Truck size={24} color="currentColor" /> <Truck size={20} color="currentColor" className="shrink-0 sm:size-6" />
<div className="text-[#3A4147] text-sm font-bold">ارسال رایگان برای خرید بالای ۱۰۰ میلیون تومان</div> <div className="text-[#3A4147] text-xs sm:text-sm font-bold truncate">ارسال رایگان برای خرید بالای ۱۰۰ میلیون تومان</div>
</span> </span>
</div> </div>
<div className="flex text-sm font-bold gap-2 items-center"> <div className="hidden md:flex text-sm font-bold gap-2 items-center shrink-0">
<span className="text-primary">20%</span> <span className="text-primary">20%</span>
<span>تخفیف برای اولین سفارش خود دریافت کنید |</span> <span className="hidden lg:inline">تخفیف برای اولین سفارش خود دریافت کنید |</span>
<span> <span>
کد تخفیف : <span className="text-primary">FIRSTORDER</span> کد تخفیف : <span className="text-primary">FIRSTORDER</span>
</span> </span>
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center shrink-0">
<Profile className="text-primary" size={24} color="currentColor" /> <Profile className="text-primary" size={20} color="currentColor" />
<div>سلام حمید</div> <div className="hidden sm:block text-sm">سلام حمید</div>
</div> </div>
</div> </div>
<div className="bg-white h-[120px] flex gap-16 items-center border border-secondary px-[120px]">
<Image src={logo} alt="logo" width={116} height={72} /> <div className="bg-white flex flex-col gap-4 lg:flex-row lg:gap-16 lg:items-center lg:h-[120px] border border-secondary px-4 sm:px-8 lg:px-[120px] py-4 lg:py-0">
<div className="flex-1"> <div className="flex items-center justify-between gap-4 lg:contents">
<Image src={logo} alt="logo" width={116} height={72} className="h-12 w-auto sm:h-[72px] shrink-0" />
<div className="flex gap-4 items-center lg:hidden">
<button type="button" aria-label="پیگیری سفارش" className="text-[#6C7680]">
<TruckFast variant="Bold" color="currentColor" size={22} />
</button>
<button type="button" aria-label="تماس" className="text-[#6C7680]">
<Call variant="Bold" color="currentColor" size={22} />
</button>
<div className="relative">
<ShoppingCart variant="Bold" color="black" size={22} />
<div className="absolute -top-1.5 -right-1.5 bg-primary text-white text-xs font-medium rounded-full size-4 flex items-center justify-center">0</div>
</div>
</div>
</div>
<div className="flex-1 w-full">
<Input variant="search" placeholder="جستجوی محصول" /> <Input variant="search" placeholder="جستجوی محصول" />
</div> </div>
<div className="flex gap-8 items-center">
<div className="hidden lg:flex gap-8 items-center shrink-0">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<TruckFast variant="Bold" color="black" /> <TruckFast variant="Bold" color="black" />
<div className="text-[#6C7680] text-sm">پیگیری سفارش</div> <div className="text-[#6C7680] text-sm">پیگیری سفارش</div>
@@ -44,7 +62,7 @@ const Header = () => {
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Call variant="Bold" color="black" /> <Call variant="Bold" color="black" />
<div className="text-[#6C7680] text-sm">(30 خط) 02134782000</div> <div className="text-[#6C7680] text-sm whitespace-nowrap">(30 خط) 02134782000</div>
</div> </div>
<Seprator className="h-5 w-px" /> <Seprator className="h-5 w-px" />
@@ -55,6 +73,7 @@ const Header = () => {
</div> </div>
</div> </div>
</div> </div>
<HeaderMenu /> <HeaderMenu />
</div> </div>
); );
+23 -21
View File
@@ -42,28 +42,30 @@ const columns: CategoryGroup[][] = [
const AllProductsMenu: FC = () => { const AllProductsMenu: FC = () => {
return ( return (
<div className="absolute top-full right-[120px] z-50 mt-2 w-[1099px] rounded-[32px] bg-[#F9FAFB]/90 px-8 pt-8 pb-10"> <div className="absolute top-full z-50 mt-2 inset-x-4 sm:inset-x-8 lg:inset-x-auto lg:right-[120px] w-auto lg:w-[min(1099px,calc(100vw-240px))] max-h-[70vh] overflow-y-auto rounded-2xl sm:rounded-[32px] bg-[#F9FAFB]/95 backdrop-blur-sm px-4 sm:px-8 pt-6 sm:pt-8 pb-6 sm:pb-10 shadow-lg border border-[#E9EEF2]/80">
<div className="flex w-full items-start justify-between"> <div className="flex w-full flex-col lg:flex-row items-start justify-between gap-8">
{columns.map((groups) => ( <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-0 lg:contents w-full lg:w-auto">
<div key={groups[0].title} className="flex w-[228px] shrink-0 flex-col gap-6"> {columns.map((groups) => (
{groups.map((group) => ( <div key={groups[0].title} className="flex w-full lg:w-[228px] shrink-0 flex-col gap-6">
<div key={group.title} className="flex flex-col gap-2"> {groups.map((group) => (
<div className="flex h-10 items-center px-4 text-base font-bold text-black">{group.title}</div> <div key={group.title} className="flex flex-col gap-2">
{group.items.map((item) => ( <div className="flex h-10 items-center px-2 sm:px-4 text-sm sm:text-base font-bold text-black">{group.title}</div>
<button {group.items.map((item) => (
key={item} <button
type="button" key={item}
className="flex h-6 w-full items-center px-4 text-right text-base font-medium text-[#011E3A] transition-colors hover:text-primary" type="button"
> className="flex h-6 w-full items-center px-2 sm:px-4 text-right text-sm sm:text-base font-medium text-[#011E3A] transition-colors hover:text-primary"
{item} >
</button> {item}
))} </button>
</div> ))}
))} </div>
</div> ))}
))} </div>
))}
</div>
<div className="relative h-[157px] w-[163px] shrink-0 overflow-hidden rounded-2xl"> <div className="relative mx-auto lg:mx-0 h-[140px] w-[145px] sm:h-[157px] sm:w-[163px] shrink-0 overflow-hidden rounded-2xl">
<Image src={businessCardMenu} alt="کارت ویزیت" fill className="object-cover" sizes="163px" /> <Image src={businessCardMenu} alt="کارت ویزیت" fill className="object-cover" sizes="163px" />
</div> </div>
</div> </div>
+53
View File
@@ -0,0 +1,53 @@
import { Call, Location, MessageQuestion, Sms } from "iconsax-reactjs";
import { type FC, type ReactNode } from "react";
type ContactItemProps = {
icon: ReactNode;
children: ReactNode;
};
const ContactItem: FC<ContactItemProps> = ({ icon, children }) => {
return (
<div className="flex items-start gap-3">
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg border border-primary text-primary">{icon}</div>
<div className="text-sm leading-6 text-[#3A4147]">{children}</div>
</div>
);
};
const FooterContact: FC = () => {
return (
<div className="flex max-w-none sm:max-w-[280px] flex-col gap-5">
<ContactItem icon={<Location size={18} color="currentColor" variant="Linear" />}>
جاده خاوران، شریف آباد، شهرک صنعتی شنزار، بعد از میدان چهارم، خیابان بوستان پنجم، خیابان گلبهار، پلاک ۱۲۲
</ContactItem>
<ContactItem icon={<Call size={18} color="currentColor" variant="Linear" />}>
<div className="flex flex-col gap-0.5">
<span>۰۲۱-۳۶۹۱۱۵۹۹ | ۰۲۱-۳۶۹۱۰۷۹۹</span>
<span>۰۲۱۳۴۷۸۲۰۰۰ (۳۰ خط)</span>
</div>
</ContactItem>
<ContactItem icon={<Sms size={18} color="currentColor" variant="Linear" />}>
<div className="flex flex-col gap-0.5">
<span className="font-bold text-[#0A1B2C]">پاسخگوی سوالات شما</span>
<a href="mailto:info@example.com" className="hover:text-primary" dir="ltr">
info@example.com
</a>
</div>
</ContactItem>
<ContactItem icon={<MessageQuestion size={18} color="currentColor" variant="Linear" />}>
<div className="flex flex-col gap-0.5">
<span>نیاز به راهنمایی دارید؟</span>
<a href="#" className="font-bold text-[#0A1B2C] hover:text-primary">
از طریق سوالات متداول پاسخ خود را پیدا کنید.
</a>
</div>
</ContactItem>
</div>
);
};
export default FooterContact;
@@ -0,0 +1,25 @@
import { type FC } from "react";
type Props = {
title: string;
links: string[];
};
const FooterLinkColumn: FC<Props> = ({ title, links }) => {
return (
<div className="flex flex-col gap-3">
<h3 className="text-sm font-bold text-[#0A1B2C]">{title}</h3>
<ul className="flex flex-col gap-2.5">
{links.map((link) => (
<li key={link}>
<a href="#" className="text-sm text-[#3A4147] transition-colors hover:text-primary">
{link}
</a>
</li>
))}
</ul>
</div>
);
};
export default FooterLinkColumn;
@@ -0,0 +1,22 @@
import { ArrowLeft } from "iconsax-reactjs";
import { type FC } from "react";
const FooterNewsletter: FC = () => {
return (
<div className="flex w-full max-w-[360px] flex-col gap-4 lg:ms-auto">
<p className="text-sm leading-6 font-bold text-[#0A1B2C]">برای دریافت آخرین پیشنهادها، اخبار و مطالب الهامبخش، در خبرنامه ما عضو شوید.</p>
<form className="relative" action="#">
<input
type="email"
placeholder="ایمیل شما"
className="h-12 w-full rounded-full border border-[#E9EEF2] bg-[#F1F6FA] ps-5 pe-14 text-sm text-[#3A4147] placeholder:text-[#9AA6B2]"
/>
<button type="submit" aria-label="عضویت در خبرنامه" className="absolute top-1/2 left-1.5 flex size-9 -translate-y-1/2 items-center justify-center rounded-full bg-[#0A1B2C] text-white transition-colors hover:bg-[#0A1B2C]/90">
<ArrowLeft size={18} color="currentColor" />
</button>
</form>
</div>
);
};
export default FooterNewsletter;
+78
View File
@@ -0,0 +1,78 @@
import { type FC, type ReactNode } from "react";
type SocialLink = {
label: string;
href: string;
bg: string;
icon: ReactNode;
};
const socialLinks: SocialLink[] = [
{
label: "Facebook",
href: "#",
bg: "bg-[#1877F2]",
icon: (
<svg viewBox="0 0 24 24" className="size-4 fill-white" aria-hidden>
<path d="M14 8.5h2.5V5.2C16.1 5.1 15 5 13.8 5 11.4 5 9.8 6.5 9.8 9.2V12H7v3.5h2.8V23h3.5v-7.5H16l.5-3.5h-3.2V9.4c0-1 .3-1.9 1.7-1.9z" />
</svg>
),
},
{
label: "Instagram",
href: "#",
bg: "bg-linear-to-br from-[#F58529] via-[#DD2A7B] to-[#8134AF]",
icon: (
<svg viewBox="0 0 24 24" className="size-4 fill-white" aria-hidden>
<path d="M12 7.2A4.8 4.8 0 1 0 12 16.8 4.8 4.8 0 1 0 12 7.2zm0 7.9a3.1 3.1 0 1 1 0-6.2 3.1 3.1 0 0 1 0 6.2zm6.3-8.1a1.1 1.1 0 1 1-2.2 0 1.1 1.1 0 0 1 2.2 0zM12 3.5c-2.3 0-2.6 0-3.5.1-2.3.1-3.4 1.2-3.5 3.5-.1.9-.1 1.2-.1 3.5s0 2.6.1 3.5c.1 2.3 1.2 3.4 3.5 3.5.9.1 1.2.1 3.5.1s2.6 0 3.5-.1c2.3-.1 3.4-1.2 3.5-3.5.1-.9.1-1.2.1-3.5s0-2.6-.1-3.5c-.1-2.3-1.2-3.4-3.5-3.5-.9-.1-1.2-.1-3.5-.1zm0 1.5c2.3 0 2.5 0 3.4.1 1.8.1 2.6.9 2.7 2.7.1.9.1 1.1.1 3.4s0 2.5-.1 3.4c-.1 1.8-.9 2.6-2.7 2.7-.9.1-1.1.1-3.4.1s-2.5 0-3.4-.1c-1.8-.1-2.6-.9-2.7-2.7-.1-.9-.1-1.1-.1-3.4s0-2.5.1-3.4c.1-1.8.9-2.6 2.7-2.7.9-.1 1.1-.1 3.4-.1z" />
</svg>
),
},
{
label: "LinkedIn",
href: "#",
bg: "bg-[#0A66C2]",
icon: (
<svg viewBox="0 0 24 24" className="size-4 fill-white" aria-hidden>
<path d="M6.5 9.5H3.8V20h2.7V9.5zM5.1 4a1.6 1.6 0 1 0 0 3.2 1.6 1.6 0 0 0 0-3.2zM20.2 20h-2.7v-5.5c0-1.3 0-3-1.8-3s-2.1 1.4-2.1 2.9V20H11V9.5h2.6v1.4h.1c.4-.7 1.3-1.5 2.7-1.5 2.9 0 3.4 1.9 3.4 4.4V20z" />
</svg>
),
},
{
label: "Telegram",
href: "#",
bg: "bg-[#2AABEE]",
icon: (
<svg viewBox="0 0 24 24" className="size-4 fill-white" aria-hidden>
<path d="M9.8 15.3 9.5 19c.4 0 .6-.2.8-.4l2-1.9 4.1 3c.8.4 1.3.2 1.5-.7l2.7-12.7c.2-1.1-.4-1.5-1.2-1.2L4.2 10.3c-1 .3-1 .9-.2 1.2l4.4 1.4 10.2-6.4c.5-.3.9-.1.5.2L9.8 15.3z" />
</svg>
),
},
{
label: "WhatsApp",
href: "#",
bg: "bg-[#25D366]",
icon: (
<svg viewBox="0 0 24 24" className="size-4 fill-white" aria-hidden>
<path d="M12 3.5A8.4 8.4 0 0 0 5.2 16.3L4 20l3.8-1.2A8.4 8.4 0 1 0 12 3.5zm4.9 12c-.2.6-1.2 1.1-1.7 1.1-.4 0-.9.2-3-.8-2.5-1.2-4.1-4-4.2-4.2-.1-.2-1-1.4-1-2.6s.6-1.9.9-2.1c.2-.2.5-.3.7-.3h.5c.2 0 .4 0 .5.4l.8 1.9c.1.2 0 .4-.1.5l-.3.4c-.1.2-.3.3-.1.6.2.3.7 1.2 1.6 1.9 1.1.9 2 1.2 2.3 1.3.3.1.5.1.7-.1l.6-.7c.2-.2.4-.2.6-.1l1.8.8c.2.1.4.2.4.4 0 .2 0 1.2-.5 1.8z" />
</svg>
),
},
];
const FooterSocial: FC = () => {
return (
<div className="flex flex-col gap-4 items-center lg:items-start text-center lg:text-right">
<h3 className="text-sm font-bold text-[#0A1B2C]">ما را در شبکههای اجتماعی دنبال کنید</h3>
<div className="flex items-center gap-3 flex-wrap justify-center lg:justify-start">
{socialLinks.map((item) => (
<a key={item.label} href={item.href} aria-label={item.label} className={`flex size-9 items-center justify-center rounded-full ${item.bg}`}>
{item.icon}
</a>
))}
</div>
</div>
);
};
export default FooterSocial;
@@ -0,0 +1,20 @@
import { type FC } from "react";
const badges = ["اینماد", "ساماندهی", "اتحادیه"];
const FooterTrustBadges: FC = () => {
return (
<div className="flex flex-col items-center gap-4">
<h3 className="text-sm font-bold text-[#0A1B2C]">نماد های اعتماد</h3>
<div className="flex items-center gap-3 sm:gap-4 flex-wrap justify-center">
{badges.map((badge) => (
<div key={badge} className="flex size-14 sm:size-16 items-center justify-center rounded-full border border-[#E9EEF2] bg-white text-center text-[10px] font-bold text-[#6C7680]">
{badge}
</div>
))}
</div>
</div>
);
};
export default FooterTrustBadges;
@@ -0,0 +1,22 @@
import { TickCircle } from "iconsax-reactjs";
import { type FC } from "react";
const values = ["تضمین کیفیت چاپ", "طراحی اختصاصی", "تحویل سریع", "پشتیبانی قبل و بعد از خرید", "ارسال به سراسر ایران"];
const FooterTrustValues: FC = () => {
return (
<div className="flex flex-col gap-3">
<h3 className="text-sm font-bold text-[#0A1B2C]">اعتماد شما، سرمایه ما</h3>
<ul className="flex flex-col gap-2.5">
{values.map((value) => (
<li key={value} className="flex items-center gap-2 text-sm text-[#3A4147]">
<TickCircle size={18} color="currentColor" variant="Bold" className="shrink-0 text-primary" />
{value}
</li>
))}
</ul>
</div>
);
};
export default FooterTrustValues;
+5 -5
View File
@@ -36,21 +36,21 @@ const HeaderMenu: FC = () => {
}, [isAllProductsOpen]); }, [isAllProductsOpen]);
return ( return (
<div ref={containerRef} className="relative h-[72px] w-full flex-1 px-[120px]"> <div ref={containerRef} className="relative h-14 sm:h-[72px] w-full flex-1 px-4 sm:px-8 lg:px-[120px]">
<div className="flex h-full items-center justify-center gap-8"> <div className="flex h-full items-center gap-4 sm:gap-6 lg:gap-8 overflow-x-auto scrollbar-none [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden lg:justify-center">
<button <button
type="button" type="button"
className={cn("flex items-center gap-2 font-bold transition-colors", isAllProductsOpen && "text-primary")} className={cn("flex items-center gap-2 font-bold transition-colors shrink-0 text-sm sm:text-base", isAllProductsOpen && "text-primary")}
onClick={() => setIsAllProductsOpen((open) => !open)} onClick={() => setIsAllProductsOpen((open) => !open)}
aria-expanded={isAllProductsOpen} aria-expanded={isAllProductsOpen}
aria-haspopup="true" aria-haspopup="true"
> >
<HamburgerMenu size={24} color="currentColor" /> <HamburgerMenu size={22} color="currentColor" />
<span>همه محصولات</span> <span>همه محصولات</span>
</button> </button>
{menuItems.map((item) => ( {menuItems.map((item) => (
<div key={item} className="font-bold"> <div key={item} className="font-bold shrink-0 whitespace-nowrap text-sm sm:text-base">
{item} {item}
</div> </div>
))} ))}
Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

+15 -1
View File
@@ -12,7 +12,8 @@
"iconsax-reactjs": "^0.0.8", "iconsax-reactjs": "^0.0.8",
"next": "16.2.10", "next": "16.2.10",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4" "react-dom": "19.2.4",
"react-simple-star-rating": "^5.1.7"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@@ -5718,6 +5719,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-simple-star-rating": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/react-simple-star-rating/-/react-simple-star-rating-5.1.7.tgz",
"integrity": "sha512-NTFkW8W3uwvI82Fv7JW5i7gmDjEZKxJmj+Z9vn+BjYIXT6ILdnU9qnSUP2cWrWN/WAUlue81f9SgM4CQcenltQ==",
"license": "MIT",
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+2 -1
View File
@@ -13,7 +13,8 @@
"iconsax-reactjs": "^0.0.8", "iconsax-reactjs": "^0.0.8",
"next": "16.2.10", "next": "16.2.10",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4" "react-dom": "19.2.4",
"react-simple-star-rating": "^5.1.7"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",