main
Amir Hossein Moghiseh 2025-03-02 16:53:24 +03:30
parent 586da8367c
commit 67e84dbfa2
22 changed files with 658 additions and 221 deletions

View File

@ -10,6 +10,7 @@
"p": "git pull && yarn install && yarn build && pm2 restart next"
},
"dependencies": {
"clsx": "^2.1.1",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^8.5.2",
"fast-average-color": "^9.4.0",
@ -23,7 +24,8 @@
"react-dom": "^19.0.0",
"react-intersection-observer": "^9.15.1",
"react-toastify": "^11.0.3",
"swiper": "^11.2.2"
"swiper": "^11.2.2",
"tailwind-merge": "^3.0.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",

View File

@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
clsx:
specifier: ^2.1.1
version: 2.1.1
embla-carousel-autoplay:
specifier: ^8.5.2
version: 8.5.2(embla-carousel@8.5.2)
@ -47,6 +50,9 @@ dependencies:
swiper:
specifier: ^11.2.2
version: 11.2.4
tailwind-merge:
specifier: ^3.0.2
version: 3.0.2
devDependencies:
'@eslint/eslintrc':
@ -3223,6 +3229,10 @@ packages:
engines: {node: '>= 4.7.0'}
dev: false
/tailwind-merge@3.0.2:
resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
dev: false
/tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

@ -2,6 +2,7 @@ import { ChevronRight, Home } from "lucide-react";
import { getLocale, getMessages } from "next-intl/server";
import Link from "next/link";
import { notFound } from "next/navigation";
import ColorAvg from "src/components/ColorAvg";
import ProductDescription from "src/components/Product/ProductDescription";
import ProductGallery from "src/components/Product/ProductGallery";
import ProductInfo from "src/components/Product/ProductInfo";
@ -10,7 +11,7 @@ import ProductRelated from "src/components/Product/ProductRelated";
import graphql from "src/utils/graphql";
const gql = `
query Products($locale: I18NLocaleCode, $slug: String!) {
query Products($locale: I18NLocaleCode, $slug: String!) {
products(filters: { slug: { eqi: $slug } }, locale: $locale) {
documentId
title
@ -37,6 +38,11 @@ const gql = `
url
}
slug
subcategories {
title
documentId
slug
}
}
slug
discount
@ -73,9 +79,16 @@ const gql = `
key
value
}
subcategories {
title
documentId
slug
}
}
}
`;
const gql_static = `
query Products($locale:I18NLocaleCode,$start:Int,$limit:Int) {
@ -200,10 +213,10 @@ export default async function ProductPage({ params }) {
}
const locale = await getLocale();
const t = await getMessages({ locale });
console.log(t)
return (
<div className="max-w-screen-xl mx-auto px-4 py-8 bg-red-50">
<div className="max-w-screen-xl mx-auto px-4 py-8 ">
{/* <ColorAvg image={product.brand.image.url}/> */}
<div className="flex flex-col lg:flex-row gap-8 items-center lg:items-start">
@ -250,6 +263,7 @@ export default async function ProductPage({ params }) {
category={product.category}
summery={product.summery}
brand={product.brand}
subcategories={product.subcategories}
/>

View File

@ -0,0 +1,110 @@
"use client"
import { useState, useEffect } from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { useTranslations } from "next-intl"
import Link from "next/link"
import { Link2 } from "lucide-react"
// Sample data for the carousel
export default function BackgroundCarousel({ images }) {
const [activeIndex, setActiveIndex] = useState(0)
const [isAutoPlaying, setIsAutoPlaying] = useState(true)
// Auto-play functionality
useEffect(() => {
if (!isAutoPlaying) return
const interval = setInterval(() => {
setActiveIndex((prev) => (prev + 1) % images.length)
}, 5000)
return () => clearInterval(interval)
}, [isAutoPlaying])
// Pause auto-play on hover
const handleMouseEnter = () => setIsAutoPlaying(false)
const handleMouseLeave = () => setIsAutoPlaying(true)
const goToSlide = (index) => {
setActiveIndex(index)
}
const goToPrevSlide = () => {
setActiveIndex((prev) => (prev - 1 + images.length) % images.length)
}
const goToNextSlide = () => {
setActiveIndex((prev) => (prev + 1) % images.length)
}
const t = useTranslations("HomePage.AboutUs")
return (
<section
className="relative h-screen max-h-screen w-full overflow-hidden top-0 text-center"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{/* Carousel Slides */}
{images.map((slide, index) => (
<div
key={slide.id}
className={`absolute inset-0 h-full w-full transition-opacity duration-1000 ${index === activeIndex ? "opacity-100" : "opacity-0 pointer-events-none"
}`}
>
<div className="absolute inset-0 bg-black/60 lg:bg-black/80 z-10" />
<img src={slide.image || "/placeholder.svg"} alt={slide.title} className="h-full w-full object-cover" />
</div>
))}
<div className="absolute inset-0 flex flex-col items-center justify-center text-white z-20">
<h2 className="text-3xl sm:text-4xl md:text-6xl font-bold mb-4"> {t("brandName")}</h2>
<p className="text-lg sm:text-xl md:text-2xl max-w-2xl text-center"> {t("description.0")}</p>
</div>
{/* Navigation Arrows */}
<button
onClick={goToPrevSlide}
className="absolute left-4 top-1/2 z-30 -translate-y-1/2 rounded-full bg-white/20 p-2 text-white backdrop-blur-sm transition-all hover:bg-white/40"
aria-label="Previous slide"
>
<ChevronLeft className="h-6 w-6" />
</button>
<button
onClick={goToNextSlide}
className="absolute right-4 top-1/2 z-30 -translate-y-1/2 rounded-full bg-white/20 p-2 text-white backdrop-blur-sm transition-all hover:bg-white/40"
aria-label="Next slide"
>
<ChevronRight className="h-6 w-6" />
</button>
{/* Dot Navigation with Stretching Effect */}
<div className="absolute bottom-20 left-1/2 z-30 flex -translate-x-1/2 items-center gap-2">
{images.map((slide, index) => (
<Link href={slide.link}
key={slide.id}
onClick={(e) =>{
if (index !== activeIndex) {
e.preventDefault();
}
goToSlide(index)
}}
className={`relative flex h-3 items-center rounded-full bg-white/50 transition-all duration-300 ${index === activeIndex ? "w-auto min-w-3 bg-white p-4" : "w-3 hover:bg-white/70"
}`}
aria-label={`Go to slide ${index + 1}`}
>
{index === activeIndex && (
<span className="whitespace-nowrap text-xs font-medium text-black transition-opacity duration-300 inline-flex gap-2 items-center">
{slide.title}
<Link2/>
</span>
)}
</Link>
))}
</div>
</section>
)
}

View File

@ -4,13 +4,14 @@ import { ShoppingCart, HardHat, ChevronRight } from "lucide-react"
import Link from "next/link"
import { useLocale } from "next-intl"
import { useTranslations } from "next-intl"
import { useEffect } from "react"
import { FastAverageColor } from 'fast-average-color';
export default function ProductCard({ product }) {
const { title, brand, category, price, discount, images, slug } = product
const { title, brand, category, price, discount, images, slug,documentId } = product
const isValidPrice = typeof price === "number" && !isNaN(price)
const isValidDiscount = typeof discount === "number" && !isNaN(discount) && discount > 0 && discount <= 100
@ -23,9 +24,25 @@ export default function ProductCard({ product }) {
const fallbackBrandLetter = brand?.title ? brand.title.charAt(0).toUpperCase() : "#"
const locale = useLocale()
const t = useTranslations("")
useEffect(() => {
const fac = new FastAverageColor();
if (images?.[0]?.url) {
fac.getColorAsync(images?.[0]?.url)
.then(color => {
const container = document.querySelector(`#container-${documentId}`)
// document.body.style.background = color.rgba
// document.body.style.color = color.isDark ? '#fff' : '#000';
container.style.backgroundColor = color.rgba;
// container.style.color = color.isDark ? '#fff' : '#000';
})
.catch(e => {
console.log(e);
});
}
}, [])
return (
<div className="bg-white rounded-lg shadow-md overflow-hidden" dir={locale === "en" ? "ltr": "rtl"} >
<div className="relative h-48 w-full overflow-hidden rounded-lg">
<div className="bg-white rounded-lg shadow-md overflow-hidden" dir={locale === "en" ? "ltr" : "rtl"} >
<div className="relative h-48 w-full overflow-hidden rounded-lg " id={`container-${documentId}`}>
<Image src={images?.[0]?.url} alt={images?.[0]?.alternativeText ?? ""} layout="fill" className="object-contain border" />
{isValidDiscount && (
<div className="absolute top-2 right-2 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full">
@ -73,8 +90,8 @@ export default function ProductCard({ product }) {
)}
</div>
</div>
<Link href={`/product/${slug}`.toLowerCase()}
className={`w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors ${locale === "en" ? "flex-row" : "flex-row-reverse"}`}>
<Link href={`/product/${slug}`.toLowerCase()}
className={`w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors ${locale === "en" ? "flex-row" : "flex-row-reverse"}`}>
{t("Utils.moreDetail")}
<ChevronRight className="h-4 w-4 ml-2" />
</Link>

View File

@ -0,0 +1,24 @@
"use client"
import React, { useEffect } from 'react'
import { FastAverageColor } from 'fast-average-color';
const ColorAvg = ({image}) => {
useEffect(() => {
const fac = new FastAverageColor();
fac.getColorAsync(image)
.then(color => {
console.log("color",color)
// document.body.style.background = color.rgba
// document.body.style.color = color.isDark ? '#fff' : '#000';
// container.style.backgroundColor = color.rgba;
// container.style.color = color.isDark ? '#fff' : '#000';
})
.catch(e => {
console.log(e);
});
}, [])
return (
<></>
)
}
export default ColorAvg

View File

@ -45,23 +45,34 @@ const Navbar = ({ items }) => {
transition: { delay: custom * 0.06 },
}),
};
const locale = useLocale()
const pathname = usePathname()
useEffect(() => {
const handleScroll = () => {
const scrollTop = window.scrollY;
setIsScrolled(scrollTop > 96);
if (pathname !== "/") {
setIsScrolled(true)
return
}
if (pathname === "/") {
setIsScrolled(scrollTop > 182);
}
};
setIsScrolled(pathname !== "/")
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
}, [pathname]);
const locale = useLocale()
const pathname = usePathname()
const changeLocale = () => {
const newLocale = locale === "en" ? "ar-OM" : "en";
@ -74,38 +85,31 @@ const Navbar = ({ items }) => {
{/* <div className="bg-sky-900 p-3 flex">
<p className="mb-0 text-white"> salam</p>
</div> */}
{isScrolled && (
<div className="w-full lg:h-[76px] h-2">
<div
className={` max-w-screen-xl hidden lg:block tr03 mx-auto px-4 lg:px-0`}
>
<div className="mx-auto w-full flex justify-center p-2">
<Image src={`/images/1.png`} width={125} height={125} className="lg:w-auto" />
</div>
{isScrolled && (
<div className="w-full h-[76px]">
)}
<div
className={` max-w-screen-xl hidden lg:block tr03 mx-auto px-4 lg:px-0 absolute top-0 left-0 right-0 z-50`}
>
{
pathname === "/" && (
<Link href={"/"} className="mx-auto w-full flex justify-center p-2">
<Image src={`/images/1.png`} width={125} height={125} className="lg:w-auto" />
</Link>
)
}
{
!isScrolled && (
<nav
className={`${locale === "en" ? "rtl pl-1" : "ltr pr-1"} flex justify-between w-full items-center ${isScrolled ? "bg-gray-200 fixed top-0 z-50 rounded-b-3xl py-2 px-2 left-1/2 -translate-x-1/2 " : "bg-transparent rounded-full"
}
}`}
>
</div>
)}
<nav
className={`${locale === "en" ? "rtl pl-1" : "ltr pr-1"} flex justify-between w-full items-center ${isScrolled ? "bg-gray-100 fixed top-0 z-50 rounded-b-3xl py-2 px-2 left-1/2 -translate-x-1/2 " : " rounded-2xl"
}
bg-gray-100 }`}
>
{
isScrolled &&
(
<div className="relative rounded-lg flex flex-col items-center rounded-tl-2xl justify-center my-auto">
<Image
src={"/images/1.png"}
width={75} height={75}
alt="llc "
className="mx-auto object-contain"
/>
</div>
)
}
{/* <div className={`w-full ${locale === "en" ? "rtl" : "ltr"} px-4 py-4`}>
{/* <div className={`w-full ${locale === "en" ? "rtl" : "ltr"} px-4 py-4`}>
<div className="flex">
<button onClick={changeLocale} className="mr-2 w-fit px-3 p-2 text-sm bg-white flex rounded-xl ">
@ -114,57 +118,145 @@ const Navbar = ({ items }) => {
</div>
</div> */}
<div className={`w-full flex p-2 ${locale === "en" ? "ltr" : "rtl"} `}>
<div className={`w-full flex p-2 mx-auto justify-center pt-8 ${locale === "en" ? "ltr" : "rtl"} `}>
{items.map((item, index) => (
{items.map((item, index) => (
<div
key={index}
className={`mx-2 px-2 w-fit text-sm bg-visa2-200 rounded-full !text-white text-shadow relative group`}
onMouseEnter={() => {
// setHoverItemNavbar(index);
setActiveStepNavbar(item.documentId);
}}
onMouseLeave={(e) => {
// setHoverItemNavbar(-1);
setActiveStepNavbar(null);
}}
// onClick={() => context.setOpenNavBarServices(false)}
>
<Link
href={`${item.link}`}
<div
key={index}
className={`mx-2 px-2 w-fit text-sm rounded-full !text-white text-shadow relative group`}
onMouseEnter={() => {
// setHoverItemNavbar(index);
setActiveStepNavbar(item.documentId);
}}
onMouseLeave={(e) => {
// setHoverItemNavbar(-1);
setActiveStepNavbar(null);
}}
// onClick={() => context.setOpenNavBarServices(false)}
>
<Link
href={`${item.link}`}
className={`text-base whitespace-nowrap text-black py-1 px-4 inline-flex items-center gap-4 rounded-lg group-hover:bg-gray-200`}
>
{item.title}
{item.children.length > 0 && <ChevronDown size={16} />}
</Link>
className={`text-base whitespace-nowrap py-1 px-4 inline-flex items-center gap-4 border-2 border-gray-100 p-4 text-white font-medium rounded-full group-hover:bg-black/60`}
>
{item.title}
{item.children.length > 0 && <ChevronDown size={16} />}
</Link>
{item.children.length > 0 && item.documentId == activeStepNavbar && (
<ul className="absolute left-0 w-48 bg-white text-black rounded-lg shadow-lg z-50 overflow-hidden">
{
item.children.map((c, index) => (
{item.children.length > 0 && item.documentId == activeStepNavbar && (
<ul className="absolute left-0 w-48 bg-black/80 text-white rounded-lg shadow-lg z-50 overflow-hidden">
{
item.children.map((c, index) => (
<li key={index} className="px-4 py-2 hover:bg-gray-100 cursor-pointer text-base">
<Link href={c.link}>
{c.title}
</Link>
</li>
))
}
<Link href={c.link} className="w-full h-full">
<li key={index} className="px-4 py-2 hover:bg-gray-400 cursor-pointer text-base w-full">
{c.title}
</li>
</Link>
))
}
</ul>
)}
</div>
))}
</ul>
)}
</div>
))}
</div>
</nav>
)
}
</nav>
{
isScrolled &&
<nav
className={`${locale === "en" ? "rtl pl-1" : "ltr pr-1"} flex justify-between w-full items-center ${isScrolled ? "bg-gray-100 border-b shadow-md fixed top-0 z-50 rounded-b-3xl py-2 px-2 left-1/2 -translate-x-1/2 " : "bg-transparent rounded-full"
}
}`}
>
{
isScrolled &&
(
<div className="relative rounded-lg flex flex-col items-center rounded-tl-2xl justify-center my-auto">
<Image
src={"/images/1.png"}
width={75} height={75}
alt="llc "
className="mx-auto object-contain"
/>
</div>
)
}
{/* <div className={`w-full ${locale === "en" ? "rtl" : "ltr"} px-4 py-4`}>
<div className="flex">
<button onClick={changeLocale} className="mr-2 w-fit px-3 p-2 text-sm bg-white flex rounded-xl ">
<p className="mb-0">{locale === "en" ? "العربیه" : "English"}</p>
</button>
</div>
</div> */}
<div className={`w-full flex p-2 ${locale === "en" ? "ltr" : "rtl"} `}>
{items.map((item, index) => (
<div
key={index}
className={`mx-2 px-2 w-fit text-sm bg-visa2-200 rounded-full !text-white text-shadow relative group`}
onMouseEnter={() => {
// setHoverItemNavbar(index);
setActiveStepNavbar(item.documentId);
}}
onMouseLeave={(e) => {
// setHoverItemNavbar(-1);
setActiveStepNavbar(null);
}}
// onClick={() => context.setOpenNavBarServices(false)}
>
<Link
href={`${item.link}`}
className={`text-base whitespace-nowrap text-black py-1 px-4 inline-flex items-center gap-4 rounded-lg group-hover:bg-gray-200`}
>
{item.title}
{item.children.length > 0 && <ChevronDown size={16} />}
</Link>
{item.children.length > 0 && item.documentId == activeStepNavbar && (
<ul className="absolute left-0 w-48 bg-white text-black rounded-lg shadow-lg z-50 overflow-hidden">
{
item.children.map((c, index) => (
<Link href={c.link} className="w-full h-full">
<li key={index} className="px-4 py-2 hover:bg-gray-100 cursor-pointer text-base w-full">
{c.title}
</li>
</Link>
))
}
</ul>
)}
</div>
))}
</div>
</nav>
}
</div>
{/* reponsive navbar */}
@ -175,7 +267,7 @@ const Navbar = ({ items }) => {
{/* responsive part */}
<div
className={`sm:block lg:hidden ${isScrolled & !closeNavbar ? "sticky top-0 z-[100] w-full" : " pt-2"
className={`block lg:hidden ${isScrolled & !closeNavbar ? "sticky top-0 z-[100] w-full" : " pt-2 absolute left-0 right-0 top-0 z-50"
}`}
>
<div
@ -186,23 +278,22 @@ const Navbar = ({ items }) => {
>
{/* <Link href={"/"} className="w-full"> */}
<div className=" w-full mx-1 flex items-center ">
<div className=" w-full mx-1 flex items-center relative -mt-2 ">
{/* <button onClick={changeLocale} className="mr-2 w-fit px-3 p-2 h-fit text-sm bg-white flex rounded-xl ">
<p className="mb-0">{locale === "en" ? "العربیه" : "English"}</p>
</button> */}
</div>
<div className=" ">
<Image
src={"/images/1.png"}
width={200}
height={200}
width={70}
height={70}
alt="llc"
className="mx-auto"
className="ml-auto object-contain"
/>
</div>
{/* </Link> */}
<div
className=" p-3 w-full mx-1 text-left"
className=" w-full mx-1 text-left"
onClick={() => setClosNavbar(true)}
>
<svg

View File

@ -1,8 +1,10 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import Image from "next/image";
import { ChevronLeft, ChevronRight, X } from "lucide-react";
import { FastAverageColor } from 'fast-average-color';
export default function ProductGallery({ images }) {
const [selectedImage, setSelectedImage] = useState(images[0]);
@ -26,30 +28,44 @@ export default function ProductGallery({ images }) {
setLightboxIndex((prevIndex) => (prevIndex - 1 + images.length) % images.length);
};
useEffect(() => {
const fac = new FastAverageColor();
fac.getColorAsync(selectedImage.url)
.then(color => {
const container = document.querySelector("#container")
console.log("color", color)
// document.body.style.background = color.rgba
// document.body.style.color = color.isDark ? '#fff' : '#000';
container.style.backgroundColor = color.rgba;
// container.style.color = color.isDark ? '#fff' : '#000';
})
.catch(e => {
console.log(e);
});
}, [selectedImage])
return (
<div className="max-w-[500px] lg:min-w-[500px]">
<div className="aspect-w-1 aspect-h-1 w-full overflow-hidden border-r pr-4">
<div className="max-w-[500px] lg:min-w-[500px] pr-4 lg:border-r">
<div className="aspect-w-1 aspect-h-1 w-full overflow-hidden" id="container">
<Image
src={selectedImage.url || "/placeholder.svg"}
alt={selectedImage.alternativeText ?? ""}
width={500}
height={500}
className="object-contain cursor-pointer aspect-square"
className="object-contain cursor-pointer aspect-square mx-auto"
onClick={() => openLightbox(images.findIndex((img) => img.documentId === selectedImage.documentId))}
/>
</div>
<div className="mt-4 grid grid-cols-4 gap-1">
{images.slice(0,4).map((image, index) => (
{images.slice(0, 4).map((image, index) => (
<Image
key={image.documentId}
src={image.url || "/placeholder.svg"}
alt={image.alternativeText ?? ""}
width={100}
height={100}
className={`object-cover cursor-pointer rounded-lg overflow-hidden ${
selectedImage.documentId === image.documentId ? "ring-2 ring-primary/50" : ""
}`}
className={`object-cover cursor-pointer rounded-lg overflow-hidden ${selectedImage.documentId === image.documentId ? "ring-2 ring-primary/50" : ""
}`}
onClick={() => setSelectedImage(image)}
/>
))}

View File

@ -13,7 +13,7 @@ import { Share2 } from "lucide-react";
import { toast } from "react-toastify";
export default function ProductInfo({ title, price, discount, showPrice, category, summery, brand }) {
export default function ProductInfo({ title, price, discount, showPrice, category, summery, brand, subcategories }) {
const locale = useLocale()
@ -68,12 +68,34 @@ export default function ProductInfo({ title, price, discount, showPrice, categor
<div className="flex items-start w-full justify-between lg:flex-row flex-col-reverse gap-4">
<div>
<h1 className="text-3xl font-bold">{title}</h1>
<Link href={`/products/${category.slug}`} className="mt-4 flex items-center">
<span className={`inline-flex items-center rounded-md bg-gray-100 px-2.5 py-0.5 text-sm font-medium text-gray-800 ltr`}>
<CategoryIcon className="h-4 w-4 mx-1" />
{category.title}
</span>
</Link>
<div className="flex gap-2">
<Link href={`/products/${category.slug}`} className="mt-4 flex items-center underline">
<span className={`inline-flex items-center rounded-md bg-gray-100 hover:bg-gray-200 px-2.5 py-0.5 text-sm font-medium text-gray-800 ltr`}>
<CategoryIcon className="h-4 w-4 mx-1" />
{category.title}
</span>
</Link>
{
brand?.slug &&
<Link href={`/products/${brand?.slug}`} className="mt-4 flex items-center underline">
<span className={`inline-flex items-center rounded-md bg-gray-100 hover:bg-gray-200 px-2.5 py-0.5 text-sm font-medium text-gray-800 ltr`}>
{brand.title}
</span>
</Link>
}
{
[...(brand?.subcategories ?? []), ...(subcategories ?? [])].map(sub => (
<Link key={sub.documentId} href={`/products/${sub.slug}`} className="mt-4 flex items-center underline">
<span className={`inline-flex items-center rounded-md bg-gray-100 hover:bg-gray-200 px-2.5 py-0.5 text-sm font-medium text-gray-800 ltr`}>
{sub.title}
</span>
</Link>
))
}
</div>
<p className="text-sm opacity-75">
{
summery
@ -138,10 +160,10 @@ export default function ProductInfo({ title, price, discount, showPrice, categor
</div>
<div className="mt-10 w-full border-t pt-10 flex gap-2 items-center">
{/* Replaced Button with Tailwind CSS */}
<button onClick={openModal} className="w-full lg:w-fit px-8 ltr flex items-center justify-center py-2 bg-primary text-white rounded-md hover:bg-primary-dark transition-colors">
<button onClick={openModal} className="w-full lg:w-fit px-8 ltr flex items-center justify-center py-2 bg-gradient-to-r from-primary-600 to-primary-400 text-white rounded-md hover:bg-primary-dark transition-colors">
<ShoppingCart className="mr-2 h-4 w-4" /> {t("cta")}
</button>
<button className="btn shadow-none" onClick={() => shareLink()}>
<button className="border p-2 rounded-lg flex flex-col items-center justify-center shadow-none text-black/60" onClick={() => shareLink()}>
<Share2 size={24} />
</button>
</div>

View File

@ -23,7 +23,7 @@
"construction": {
"title": "Construction",
"subtitle": "Building the Future",
"description": "Our Construction arm is dedicated to creating lasting infrastructure and innovative building solutions.From residential projects to commercial complexes, we bring expertise, quality, and sustainability to every construction endeavor"
"description": "Our Construction arm is dedicated to creating lasting infrastructure and innovative building solutions. From residential projects to commercial complexes, we bring expertise, quality, and sustainability to every construction endeavor"
}
},
"products":{

View File

@ -0,0 +1,7 @@
import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs) {
return twMerge(clsx(inputs))
}

View File

@ -25,9 +25,10 @@ const CheckboxGroup = ({ title, items, selectedItem, onItemSelect }) => {
)
}
const Filters = ({ brands, categories }) => {
const Filters = ({ brands, categories,subCategories }) => {
const [selectedCategory, setSelectedCategory] = useState(null)
const [selectedBrand, setSelectedBrand] = useState(null)
const [selectedSubcategoty, setSelectedSubcategory] = useState(null)
const params = useParams()
const router = useRouter()
const handleCategorySelect = (documentId) => {
@ -40,6 +41,12 @@ const Filters = ({ brands, categories }) => {
router.push("/products/"+(documentId ? `${brands.find(b=>b.documentId === documentId)?.slug}` : ""))
}
const handleSubSelect = (documentId) => {
setSelectedSubcategory((prevSelected) => (prevSelected === documentId ? null : documentId))
router.push("/products/"+(documentId ? `${subCategories.find(b=>b.documentId === documentId)?.slug}` : ""))
}
useEffect(()=>{
@ -48,6 +55,7 @@ const Filters = ({ brands, categories }) => {
setSelectedBrand(brands.find(b=>b.slug === categoryOrBrand)?.documentId)
setSelectedCategory(categories.find(c=>c.slug === categoryOrBrand)?.documentId)
setSelectedSubcategory(subCategories.find(c=>c.slug === categoryOrBrand)?.documentId)
},[params])
const t = useTranslations("PLP.filter")
return (
@ -63,6 +71,7 @@ const Filters = ({ brands, categories }) => {
onItemSelect={handleCategorySelect}
/>
<CheckboxGroup title="Brands" items={brands} selectedItem={selectedBrand} onItemSelect={handleBrandSelect} />
<CheckboxGroup title="Use Case" items={subCategories} selectedItem={selectedSubcategoty} onItemSelect={handleSubSelect} />

View File

@ -18,6 +18,11 @@ const brandAndCategoriesGQL = `
documentId
slug
}
subcategories(locale: $locale, pagination: { start: 0, limit: 50 }) {
title
documentId
slug
}
}
`
@ -29,6 +34,7 @@ query Products_connection(
$pageSize: Int
$category: String
$brand: String
$subcategory: String
) {
products_connection(
pagination: { page: $page, pageSize: $pageSize }
@ -36,6 +42,8 @@ query Products_connection(
or: [
{ category: { slug: { eqi: $category } } }
{ brand: { slug: { eqi: $brand } } }
{ brand: { subcategories: { slug: { containsi: $subcategory } } } }
{ subcategories: { slug: { containsi: $subcategory } } }
]
}
locale: $locale
@ -52,7 +60,6 @@ query Products_connection(
title
documentId
slug
}
brand {
documentId
@ -91,10 +98,12 @@ const ProductsListingPage = () => {
const [products, setProducts] = React.useState([])
const [brands, setBrands] = React.useState([])
const [categories, setCategories] = React.useState([])
const [subcategories, setSubcategories] = React.useState([])
const getBrandsAndCategories = async () => {
const { brands, categories } = await graphql(brandAndCategoriesGQL, { locale: "en" });
const { brands, categories,subcategories } = await graphql(brandAndCategoriesGQL, { locale: "en" });
setBrands(brands)
setCategories(categories)
setSubcategories(subcategories)
}
const getProducts = async () => {
@ -106,7 +115,8 @@ const ProductsListingPage = () => {
page: page,
pageSize: 12,
brand: category,
category: category
category: category,
subcategory: category,
})
setProducts(nodes)
setPageInfo(pageInfo)
@ -128,7 +138,7 @@ const ProductsListingPage = () => {
<>
{
brands && brands.length > 0 && categories && categories.length > 0 &&
<Filters brands={brands} categories={categories} />
<Filters subCategories={subcategories} brands={brands} categories={categories} />
}
<div className="flex flex-col w-full items-center justify-center ">
<div className="px-2 pb-20 w-full">

View File

@ -55,7 +55,7 @@ const CategoriesData = async ({ category }) => {
</div>
{
content.title &&
content?.title &&
<Content content={content} />
}

View File

@ -1,69 +1,115 @@
import { useLocale, useTranslations } from "next-intl";
import Image from "next/image";
import React from "react";
"use client"
import { Globe, TrendingUp, Truck, Users } from "lucide-react"
import Image from "next/image"
import { useState } from "react";
import ContactModal from "src/components/ContactUs"
const AboutUs = () => {
const t = useTranslations("HomePage.AboutUs")
const locale = useLocale()
export default function AboutUs() {
const [open, setOpen] = useState(false);
const openModal = () => {
setOpen(true);
};
const closeModal = () => {
setOpen(false);
}
return (
<>
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" className="absolute left-0 top-0 bottom-0 -z-10 right-0">
<pattern id="diagonalLines" width="10" height="10" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="10" stroke="#e2e8f0" stroke-width="1" />
</pattern>
<rect width="100%" height="100%" fill="url(#diagonalLines)" />
</svg>
<div className="min-h-[calc(100vh-108px)] flex flex-col items-center justify-center relative mt-8 -z-10 text-center ">
<div className="max-w-screen-xl mx-auto">
<section className="relative overflow-hidden bg-gradient-to-br from-primary-100 to-white py-20 flex flex-col text-base">
{/* Background Elements */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden opacity-10 pointer-events-none">
<div className="absolute -top-24 -left-24 w-96 h-96 rounded-full bg-blue-400 blur-3xl"></div>
<div className="absolute top-1/2 -right-24 w-80 h-80 rounded-full bg-indigo-500 blur-3xl"></div>
<div className="absolute -bottom-24 left-1/3 w-72 h-72 rounded-full bg-purple-400 blur-3xl"></div>
</div>
<div className="container mx-auto px-4 relative z-10">
{/* Header */}
<div className="flex flex-col items-center text-center mb-16">
<div className="inline-block p-3 bg-white bg-opacity-80 rounded-xl shadow-lg mb-6">
<Image
src="/images/1.png"
alt="AHS Logo"
width={100}
height={80}
className="rounded"
/>
</div>
<h2 className="text-3xl md:text-5xl font-bold text-gray-800 mb-4 tracking-tight">
Advanced Horizon Services
</h2>
<div className="w-24 h-1 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-full mb-6"></div>
<p className="text-lg text-gray-600 max-w-xl">
Revolutionizing wholesale supply chain solutions with excellence and reliability
</p>
</div>
{/* Main Content */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{/* Left Side - Description */}
<div className="bg-white bg-opacity-90 rounded-2xl p-8 md:p-10 shadow-xl transform transition-all duration-500 hover:shadow-2xl">
<h3 className="text-2xl font-bold text-gray-800 mb-6 border-l-4 border-blue-500 pl-4">Who We Are</h3>
<p className="text-gray-700 leading-relaxed mb-8 text-justify">
Advanced Horizon Services is a leading trade company specializing in wholesale supply chain solutions for
a diverse range of products, including high-quality detergents and food items. With years of experience
and extensive relations in the industry, we pride ourselves on our commitment to excellence, reliability,
and customer satisfaction.
</p>
<h3 className="text-2xl font-bold text-gray-800 mb-6 border-l-4 border-indigo-500 pl-4">Our Approach</h3>
<p className="text-gray-700 leading-relaxed text-justify">
At AHS, we understand the complexities of the supply chain and strive to simplify the process for our
partners. Our extensive network of suppliers and manufacturers allows us to source top-notch products at
competitive prices, ensuring that you receive the best value for your investment.
</p>
</div>
<div className="xs:px-3 md:px-10 md:container md:mx-auto ">
<div className="gap-5 items-center">
<div className="relative mb-10 col-span-2">
<h2 className={
`text-5xl font-bold relative z-10`
}>
{t("brandName")}
</h2>
<p className={`mb-0 text-xl mt-5 text-center leading-7 px-5`}>
<span className="text-primary-800 font-semibold text-2xl">
{t("description.0")}
</span>
<br />
<br />
<p className="text-justify leading-8">
{t("description.1")} <br />
</p>
<br />
<p className="text-justify leading-8">
{t("description.2")}
</p>
</p>
{/*
<div className="flex gap-2 mx-auto mt-5 w-full justify-center lg:justify-end">
<button className="btn btn-primary text-base py-3 px-10 ">
{" "}
Export
</button>
<button className="btn btn-primary text-base p-3 px-10">
{" "}
Industrial
</button>
</div> */}
{/* Right Side - Features */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Feature 1 */}
<div className="bg-white bg-opacity-80 rounded-xl p-6 shadow-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<Globe className="w-6 h-6 text-blue-600" />
</div>
<h4 className="text-xl font-semibold text-gray-800 mb-2">Global Network</h4>
<p className="text-gray-600">Extensive connections with suppliers and manufacturers worldwide.</p>
</div>
{/* Feature 2 */}
<div className="bg-white bg-opacity-80 rounded-xl p-6 shadow-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<TrendingUp className="w-6 h-6 text-indigo-600" />
</div>
<h4 className="text-xl font-semibold text-gray-800 mb-2">Competitive Pricing</h4>
<p className="text-gray-600">Best value products sourced at optimal market rates.</p>
</div>
{/* Feature 3 */}
<div className="bg-white bg-opacity-80 rounded-xl p-6 shadow-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
<div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
<Truck className="w-6 h-6 text-purple-600" />
</div>
<h4 className="text-xl font-semibold text-gray-800 mb-2">Supply Chain Experts</h4>
<p className="text-gray-600">Simplifying complex logistics for seamless operations.</p>
</div>
{/* Feature 4 */}
<div className="bg-white bg-opacity-80 rounded-xl p-6 shadow-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
<div className="w-12 h-12 bg-rose-100 rounded-lg flex items-center justify-center mb-4">
<Users className="w-6 h-6 text-rose-600" />
</div>
<h4 className="text-xl font-semibold text-gray-800 mb-2">Customer Focused</h4>
<p className="text-gray-600">Dedicated to excellence and partner satisfaction.</p>
</div>
</div>
</div>
</div>
</>
);
};
export default AboutUs;
{/* Call to Action */}
<div className="mt-20 text-center">
<button onClick={()=>setOpen(true)} className="px-8 py-4 bg-gradient-to-r from-primary-600 to-primary-300 text-white font-medium rounded-lg shadow-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
Partner With Us
</button>
</div>
</div>
<ContactModal close={closeModal} open={open} />
</section>
)
}

View File

@ -15,7 +15,7 @@ const CounterDetail = ({ stats }) => {
}
return (
<div className="w-full bg-couner-data-landing py-32 mb-44">
<div className="w-full bg-couner-data-landing py-32 text-center">
<div className="grid xs:grid-cols-1 lg:grid-cols-3">
{
stats.map(s => (

View File

@ -73,7 +73,7 @@ const Products = async () => {
</div>
<div>
<ProductCarousel products={products2} subtitle={""} title={t.HomePage.products.title[1]} showMoreLink={"/products/constructions"} />
<ProductCarousel products={products2} subtitle={""} title={t.HomePage.products.title[1]} showMoreLink={"/products/construction"} />
</div>
{/* <div className="grid grid-cols-1 lg:grid-cols-3 gap-5">

View File

@ -1,49 +1,88 @@
import React from 'react';
import { ShoppingCart, Building2 } from 'lucide-react'; // Assuming you're using lucide-react for icons
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { ShoppingBag, Construction } from "lucide-react"
import { useTranslations } from "next-intl"
import Link from "next/link"
import { cn } from "src/utils"
const Sides = () => {
export default function ExpertiseSection() {
const t = useTranslations("HomePage.Sides")
return (
<section className="w-full py-12 md:py-24 lg:py-32 bg-background text-sm bg-primary-800/40 ">
<div className="max-w-screen-xl px-4 md:px-6 mx-auto">
<h2 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl text-center mb-12">
{t("title")}
</h2>
<div className="grid gap-6 lg:grid-cols-2">
{/* First Card */}
<Link href={"/products/fmcg"} className="flex flex-col items-center text-center w-full bg-white shadow-lg rounded-lg p-6 hover:scale-105 transition-all">
<div className="flex flex-col items-center">
<ShoppingCart className="w-12 h-12 mb-4 text-primary" />
<h3 className="text-2xl font-bold">{t("fmcg.title")}</h3>
<p className="text-muted-foreground">{t("fmcg.subtitle")}</p>
</div>
<div className="mt-4">
<p className="text-muted-foreground text-base text-justify">
{t("fmcg.description")}
</p>
</div>
</Link>
{/* Second Card */}
<Link href={"/products/construction"} className="flex flex-col items-center text-center bg-white shadow-lg rounded-lg p-6 hover:scale-105 transition-all">
<div className="flex flex-col items-center">
<Building2 className="w-12 h-12 mb-4 text-primary" />
<h3 className="text-2xl font-bold">{t("construction.title")}</h3>
<p className="text-muted-foreground">{t("construction.subtitle")}</p>
const services = [
{
key: "fmcg",
icon: ShoppingBag,
color: "bg-blue-50 dark:bg-blue-950",
iconColor: "text-blue-600 dark:text-blue-400",
borderColor: "border-blue-100 dark:border-blue-800",
},
{
key: "construction",
icon: Construction,
color: "bg-amber-50 dark:bg-amber-950",
iconColor: "text-amber-600 dark:text-amber-400",
borderColor: "border-amber-100 dark:border-amber-800",
},
]
return (
<section className="w-full py-20 bg-gradient-to-b from-slate-50 to-white dark:from-slate-950 dark:to-black mx-auto max-w-screen-xl">
<div className="container px-4 md:px-6 mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl md:text-5xl font-bold tracking-tight mb-4 bg-clip-text text-transparent bg-gradient-to-r from-slate-900 to-slate-700 dark:from-slate-100 dark:to-slate-300">
{t("title")}
</h2>
<div className="w-20 h-1 bg-gradient-to-r from-blue-500 to-amber-500 mx-auto rounded-full"></div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mx-auto">
{services.map((service) => (
<div
key={service.key}
className={cn(
"group relative overflow-hidden rounded-2xl border p-8 transition-all duration-300 hover:shadow-lg",
service.borderColor,
service.color,
)}
>
<div className="absolute -right-16 -top-16 h-40 w-40 rounded-full border border-slate-100 dark:border-slate-800 opacity-50 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative z-10">
<div className={cn("p-3 rounded-xl inline-block mb-4", service.color)}>
<service.icon className={cn("w-8 h-8", service.iconColor)} />
</div>
<h3 className="text-2xl font-bold tracking-tight mb-2 text-slate-900 dark:text-white">
{t(`${service.key}.title`)}
</h3>
<p className="text-sm font-medium text-slate-600 dark:text-slate-400 mb-4">
{t(`${service.key}.subtitle`)}
</p>
<p className="text-base text-slate-700 dark:text-slate-300 leading-relaxed text-justify">
{t(`${service.key}.description`)}
</p>
<div className="mt-6">
<Link href={`/products/${service.key}`} className="inline-flex items-center text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 transition-colors">
Learn more
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-4 h-4 ml-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
</div>
</div>
</div>
<div className="mt-4">
<p className="text-muted-foreground text-base text-justify">
{t("construction.description")}
</p>
</div>
</Link>
))}
</div>
</div>
</section>
);
};
)
}
export default Sides;

View File

@ -6,7 +6,26 @@ import Sides from "./components/Sides";
import graphql from "src/utils/graphql";
import CounterDetail from "./components/CounterDetail";
import { getLocale } from "next-intl/server";
import BackgroundCarousel from "src/components/BackgroundCarousel";
const images = [
{
image: "/images/fmcg.jpg",
title: "FMCG",
link: "/products/fmcg",
},
{
image: "/images/construction.jpg",
title: "Construction",
link: "/products/construction",
},
// {
// url:"/images/feed.jpg",
// name:"Feed",
// link:"/products/feed"
// }
]
const gql_stats = `
query Stats($locale:I18NLocaleCode) {
@ -37,14 +56,15 @@ const Landing = async () => {
const stats = await getStats()
return (
<div className=" text-center text-6xl">
<div className=" ">
{" "}
{/* <HeroSection /> */}
<BackgroundCarousel images={images} />
<AboutUs />
<Sides />
<CounterDetail stats={stats} />
<Sides />
<Products />
{/* <WhyHorizon/> */}