pdp change
parent
a594730535
commit
27a0733667
|
@ -25,7 +25,8 @@
|
||||||
"react-intersection-observer": "^9.15.1",
|
"react-intersection-observer": "^9.15.1",
|
||||||
"react-toastify": "^11.0.3",
|
"react-toastify": "^11.0.3",
|
||||||
"swiper": "^11.2.2",
|
"swiper": "^11.2.2",
|
||||||
"tailwind-merge": "^3.0.2"
|
"tailwind-merge": "^3.0.2",
|
||||||
|
"yet-another-react-lightbox": "^3.21.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|
|
@ -53,6 +53,9 @@ dependencies:
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
|
yet-another-react-lightbox:
|
||||||
|
specifier: ^3.21.7
|
||||||
|
version: 3.21.7(react-dom@19.0.0)(react@19.0.0)
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/eslintrc':
|
'@eslint/eslintrc':
|
||||||
|
@ -3500,6 +3503,17 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/yet-another-react-lightbox@3.21.7(react-dom@19.0.0)(react@19.0.0):
|
||||||
|
resolution: {integrity: sha512-dcdokNuCIl92f0Vl+uzeKULnQhztIGpoZFUMvtVNUPmtwsQWpqWufeieDPeg9JtFyVCcbj4vYw3V00DS0QNoWA==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/yocto-queue@0.1.0:
|
/yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { ChevronRight, Home } from "lucide-react";
|
|
||||||
|
import { ArrowRight, Award, Boxes, CheckCircle2, ChevronRight, Link as LinkIcon, MessageSquare, ShieldCheck, Ship } from "lucide-react";
|
||||||
import { getLocale, getMessages } from "next-intl/server";
|
import { getLocale, getMessages } from "next-intl/server";
|
||||||
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import ColorAvg from "src/components/ColorAvg";
|
import BackgroundColor from "src/components/BackgroundColor";
|
||||||
import ProductDescription from "src/components/Product/ProductDescription";
|
import { ProductGallery } from "src/components/Gallery";
|
||||||
import ProductGallery from "src/components/Product/ProductGallery";
|
|
||||||
import ProductInfo from "src/components/Product/ProductInfo";
|
|
||||||
import ProductProperties from "src/components/Product/ProductProperties";
|
import ProductProperties from "src/components/Product/ProductProperties";
|
||||||
import ProductRelated from "src/components/Product/ProductRelated";
|
import ProductRelated from "src/components/Product/ProductRelated";
|
||||||
import graphql from "src/utils/graphql";
|
import graphql from "src/utils/graphql";
|
||||||
|
@ -47,6 +47,16 @@ query Products($locale: I18NLocaleCode, $slug: String!) {
|
||||||
slug
|
slug
|
||||||
discount
|
discount
|
||||||
showPrice
|
showPrice
|
||||||
|
properties {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
subcategories {
|
||||||
|
title
|
||||||
|
documentId
|
||||||
|
slug
|
||||||
|
}
|
||||||
seo {
|
seo {
|
||||||
id
|
id
|
||||||
metaTitle
|
metaTitle
|
||||||
|
@ -74,16 +84,7 @@ query Products($locale: I18NLocaleCode, $slug: String!) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
properties {
|
|
||||||
id
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
subcategories {
|
|
||||||
title
|
|
||||||
documentId
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +132,8 @@ query Products($locale:I18NLocaleCode,$slug:String!) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
||||||
export async function generateMetadata({ params }) {
|
export async function generateMetadata({ params }) {
|
||||||
const { locale, slug } = await params;
|
const { locale, slug } = await params;
|
||||||
|
|
||||||
|
@ -203,6 +206,23 @@ const getProduct = async (slug) => {
|
||||||
});
|
});
|
||||||
return products[0];
|
return products[0];
|
||||||
};
|
};
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
icon: Award,
|
||||||
|
title: "Certified Product",
|
||||||
|
description: "Meets international quality standards"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Ship,
|
||||||
|
title: "Bulk Shipping",
|
||||||
|
description: "Efficient worldwide delivery solutions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ShieldCheck,
|
||||||
|
title: "Quality Assured",
|
||||||
|
description: "Rigorous testing at every stage"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export default async function ProductPage({ params }) {
|
export default async function ProductPage({ params }) {
|
||||||
const { slug } = await params
|
const { slug } = await params
|
||||||
|
@ -213,67 +233,172 @@ export default async function ProductPage({ params }) {
|
||||||
}
|
}
|
||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
const t = await getMessages({ locale });
|
const t = await getMessages({ locale });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-screen-xl mx-auto px-4 py-8 ">
|
// <div className="max-w-screen-xl mx-auto px-4 py-8 ">
|
||||||
{/* <ColorAvg image={product.brand.image.url}/> */}
|
// {/* <ColorAvg image={product.brand.image.url}/> */}
|
||||||
<div className="flex flex-col lg:flex-row gap-8 items-center lg:items-start">
|
// <div className="flex flex-col lg:flex-row gap-8 items-center lg:items-start">
|
||||||
<ProductGallery images={product.images} />
|
// <ProductGallery images={product.images} />
|
||||||
<div className="flex flex-col gap-4 w-full ">
|
// <div className="flex flex-col gap-4 w-full ">
|
||||||
<div className="flex justify-between w-full">
|
// <div className="flex justify-between w-full">
|
||||||
<div className={`flex items-center gap-2 mb-6 `}>
|
// <div className={`flex items-center gap-2 mb-6 `}>
|
||||||
<Link href="/" className=" underline inline-flex gap-2">
|
// <Link href="/" className=" underline inline-flex gap-2">
|
||||||
<Home />
|
// <Home />
|
||||||
</Link>
|
// </Link>
|
||||||
{
|
// {
|
||||||
product?.category?.slug &&
|
// product?.category?.slug &&
|
||||||
<>
|
// <>
|
||||||
<ChevronRight className={`size-4 ${locale !== "en" && "rotate-180"} `} />
|
// <ChevronRight className={`size-4 ${locale !== "en" && "rotate-180"} `} />
|
||||||
<Link href={`/products/${product.category.slug}`} className=" underline">
|
// <Link href={`/products/${product.category.slug}`} className=" underline">
|
||||||
|
|
||||||
{product.category.title}
|
// {product.category.title}
|
||||||
|
// </Link>
|
||||||
|
// </>
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// product?.brand?.slug &&
|
||||||
|
// <>
|
||||||
|
// <ChevronRight className={`size-4 ${locale !== "en" && "rotate-180"} `} />
|
||||||
|
// <Link href={`/products/${product.brand.slug}`} className=" underline">
|
||||||
|
// {product.brand.title}
|
||||||
|
// </Link>
|
||||||
|
// </>
|
||||||
|
// }
|
||||||
|
// <ChevronRight className={`size-4 ${locale !== "en" && "rotate-180"} `} />
|
||||||
|
// <span>{product.title}</span>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
// <div className="h-full flex justify-between flex-row w-full items-start">
|
||||||
|
|
||||||
|
// <ProductInfo
|
||||||
|
// title={product.title}
|
||||||
|
// price={product.price}
|
||||||
|
// discount={product.discount}
|
||||||
|
// showPrice={product.showPrice}
|
||||||
|
// category={product.category}
|
||||||
|
// summery={product.summery}
|
||||||
|
// brand={product.brand}
|
||||||
|
// subcategories={product.subcategories}
|
||||||
|
// />
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="mt-12">
|
||||||
|
// <ProductProperties properties={product.properties} />
|
||||||
|
// </div>
|
||||||
|
// <div className="mt-12">
|
||||||
|
// <ProductDescription description={product.description} />
|
||||||
|
// </div>
|
||||||
|
// <div className="mt-12">
|
||||||
|
// <ProductRelated brand={product.brand} category={product.category} />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
|
||||||
|
{/* Hero Section */}
|
||||||
|
|
||||||
|
<div className="text-white w-full h-full relative" >
|
||||||
|
<BackgroundColor image={product?.brand?.image?.url} />
|
||||||
|
<div className="max-w-7xl mx-auto px-4 py-28 relative z-10">
|
||||||
|
<div className="flex flex-col md:flex-row gap-8 items-center">
|
||||||
|
<div className="flex-1 space-y-6">
|
||||||
|
<h1 className="text-4xl md:text-5xl font-bold leading-tight">{product.title}</h1>
|
||||||
|
<div className="text-base text-gray-50/50 font-semibold">
|
||||||
|
Price upon request
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
{product?.brand && (
|
||||||
|
<Link href={`/products/${product?.brand?.slug}`} className="inline-flex items-center gap-2 px-3 py-1 bg-white/20 rounded-full text-sm hover:bg-white/30 transition-colors">
|
||||||
|
<div className="relative size-4">
|
||||||
|
<Image
|
||||||
|
src={product.brand?.image?.url}
|
||||||
|
alt={product.brand?.title}
|
||||||
|
className="aspect-square object-contain"
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{product?.brand?.title}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
)}
|
||||||
}
|
<Link href={`/products/${product.category.slug}`} className="inline-flex items-center gap-2 px-3 py-1 bg-white/20 rounded-full text-sm hover:bg-white/30 transition-colors">
|
||||||
{
|
<Boxes className="w-4 h-4" />
|
||||||
product?.brand?.slug &&
|
{product.category.title}
|
||||||
<>
|
</Link>
|
||||||
<ChevronRight className={`size-4 ${locale !== "en" && "rotate-180"} `} />
|
{product.brand?.subcategories.map(subcat => (
|
||||||
<Link href={`/products/${product.brand.slug}`} className=" underline">
|
<Link
|
||||||
{product.brand.title}
|
key={subcat.documentId}
|
||||||
|
href={`/products/${subcat.slug}`}
|
||||||
|
className="inline-flex items-center gap-2 px-3 py-1 bg-white/20 rounded-full text-sm hover:bg-white/30 transition-colors"
|
||||||
|
>
|
||||||
|
<LinkIcon className="w-4 h-4" />
|
||||||
|
{subcat.title}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
))}
|
||||||
}
|
</div>
|
||||||
<ChevronRight className={`size-4 ${locale !== "en" && "rotate-180"} `} />
|
|
||||||
<span>{product.title}</span>
|
</div>
|
||||||
|
<div className="flex-1 w-full md:w-auto">
|
||||||
|
<div className="rounded-2xl overflow-hidden shadow-2xl bg-white/10 backdrop-blur-sm">
|
||||||
|
<ProductGallery images={product.images} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="h-full flex justify-between flex-row w-full items-start">
|
|
||||||
|
|
||||||
<ProductInfo
|
|
||||||
title={product.title}
|
|
||||||
price={product.price}
|
|
||||||
discount={product.discount}
|
|
||||||
showPrice={product.showPrice}
|
|
||||||
category={product.category}
|
|
||||||
summery={product.summery}
|
|
||||||
brand={product.brand}
|
|
||||||
subcategories={product.subcategories}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-12">
|
|
||||||
<ProductProperties properties={product.properties} />
|
|
||||||
|
|
||||||
|
{/* Product Description */}
|
||||||
|
<div className="py-16 bg-gray-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<h2 className="text-3xl font-bold mb-8">Product Description</h2>
|
||||||
|
<div
|
||||||
|
className="prose max-w-none text-gray-600 text-lg leading-relaxed content text-justify"
|
||||||
|
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProductProperties product={product} />
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Features */}
|
||||||
|
<div className="py-16 ">
|
||||||
|
<hr />
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{features.map((feature, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-4 p-6 rounded-xl bg-gray-50">
|
||||||
|
<div className="w-12 h-12 rounded-lg bg-gray-900 text-white flex items-center justify-center">
|
||||||
|
<feature.icon className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
|
||||||
|
<p className="text-gray-600">{feature.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-12">
|
|
||||||
<ProductDescription description={product.description} />
|
|
||||||
</div>
|
{/* Related Products */}
|
||||||
<div className="mt-12">
|
<div className="py-16 bg-gray-50">
|
||||||
<ProductRelated brand={product.brand} category={product.category} />
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
|
||||||
|
<ProductRelated category={product?.category} brand={product?.brand} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
"use client"
|
||||||
|
import React from 'react'
|
||||||
|
import useAvgColor from '../ColorAvg'
|
||||||
|
|
||||||
|
const BackgroundColor = ({ image }) => {
|
||||||
|
|
||||||
|
const {color} = useAvgColor(image)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full absolute bottom-0 top-0 left-0 right-0 bg-gradient-to-b from-[#A4756E] to-black' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BackgroundColor
|
|
@ -1,24 +1,32 @@
|
||||||
"use client"
|
"use client";
|
||||||
import React, { useEffect } from 'react'
|
import { useEffect, useState } from 'react';
|
||||||
import { FastAverageColor } from 'fast-average-color';
|
import { FastAverageColor } from 'fast-average-color';
|
||||||
const ColorAvg = ({image}) => {
|
|
||||||
|
const useAvgColor = (image) => {
|
||||||
|
const [color, setColor] = useState("");
|
||||||
|
const [isDark, setIsDark] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fac = new FastAverageColor();
|
const fac = new FastAverageColor();
|
||||||
fac.getColorAsync(image)
|
console.log("image",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
|
if (image) {
|
||||||
|
fac.getColorAsync(image)
|
||||||
|
.then((colorResult) => {
|
||||||
|
setColor(colorResult.hex ?? "#e18c8c");
|
||||||
|
setIsDark(colorResult.isDark)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
setColor("#e18c8c")
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => fac.destroy(); // Clean up on component unmount
|
||||||
|
}, [image]);
|
||||||
|
|
||||||
|
return {color,isDark};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAvgColor;
|
||||||
|
|
|
@ -36,7 +36,8 @@ export default function ContactModal({ close, open }) {
|
||||||
data: {
|
data: {
|
||||||
email,
|
email,
|
||||||
companyName,
|
companyName,
|
||||||
message
|
message,
|
||||||
|
|
||||||
},
|
},
|
||||||
locale
|
locale
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Send, X, User, Building, Mail, Phone, Package, MessageSquare } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
|
export const EnquiryForm = ({ isOpen, onClose }) => {
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Handle form submission
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
|
||||||
|
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg relative max-h-[90vh] overflow-y-auto">
|
||||||
|
<div className="bg-gradient-to-r from-blue-600 to-blue-700 p-6 rounded-t-2xl">
|
||||||
|
<h3 className="text-2xl font-bold text-white">Request Quote</h3>
|
||||||
|
<p className="text-blue-100 mt-1">Fill out the form below for pricing and more information</p>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute right-4 top-4 text-white/80 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="p-6 space-y-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="relative">
|
||||||
|
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Full Name
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<User className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<label htmlFor="company" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Company Name
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Building className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="company"
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Phone Number
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Phone className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
id="phone"
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<label htmlFor="quantity" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Desired Quantity (MOQ: 1000 units)
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Package className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="quantity"
|
||||||
|
min="1000"
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Additional Requirements
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<MessageSquare className="w-5 h-5 text-gray-400 absolute left-3 top-3" />
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
rows={4}
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 text-white py-3 px-4 rounded-lg hover:from-blue-700 hover:to-blue-800 transition-all flex items-center justify-center gap-2 text-lg font-semibold"
|
||||||
|
>
|
||||||
|
<Send className="w-5 h-5" />
|
||||||
|
Submit Enquiry
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,84 @@
|
||||||
|
"use client"
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import useEmblaCarousel from 'embla-carousel-react';
|
||||||
|
import Autoplay from 'embla-carousel-autoplay';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
|
import Lightbox from "yet-another-react-lightbox";
|
||||||
|
import Zoom from "yet-another-react-lightbox/plugins/zoom";
|
||||||
|
import "yet-another-react-lightbox/styles.css";
|
||||||
|
|
||||||
|
|
||||||
|
export const ProductGallery = ({ images }) => {
|
||||||
|
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [Autoplay()]);
|
||||||
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||||
|
|
||||||
|
const scrollPrev = React.useCallback(() => {
|
||||||
|
if (emblaApi) emblaApi.scrollPrev();
|
||||||
|
}, [emblaApi]);
|
||||||
|
|
||||||
|
const scrollNext = React.useCallback(() => {
|
||||||
|
if (emblaApi) emblaApi.scrollNext();
|
||||||
|
}, [emblaApi]);
|
||||||
|
|
||||||
|
const openLightbox = (index) => {
|
||||||
|
setCurrentImageIndex(index);
|
||||||
|
setLightboxOpen(true);
|
||||||
|
};
|
||||||
|
const slides = images.map(image => ({
|
||||||
|
src: image.url,
|
||||||
|
alt: image.alternativeText || 'Product image'
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="overflow-hidden" ref={emblaRef}>
|
||||||
|
<div className="flex">
|
||||||
|
{images.map((image, index) => (
|
||||||
|
<div key={image.documentId} className="flex-[0_0_100%] min-w-0">
|
||||||
|
<img
|
||||||
|
src={image.url}
|
||||||
|
alt={image.alternativeText || ''}
|
||||||
|
className="w-full h-[400px] object-contain cursor-pointer"
|
||||||
|
onClick={() => openLightbox(index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={scrollPrev}
|
||||||
|
className="absolute left-4 top-1/2 -translate-y-1/2 bg-white/90 p-3 rounded-full shadow-lg hover:bg-white transition-colors opacity-0 group-hover:opacity-100"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-6 h-6 text-gray-900" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={scrollNext}
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 bg-white/90 p-3 rounded-full shadow-lg hover:bg-white transition-colors opacity-0 group-hover:opacity-100"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-6 h-6 text-gray-900" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Lightbox
|
||||||
|
open={lightboxOpen}
|
||||||
|
close={() => setLightboxOpen(false)}
|
||||||
|
index={currentImageIndex}
|
||||||
|
slides={slides}
|
||||||
|
plugins={[Zoom]}
|
||||||
|
zoom={{
|
||||||
|
maxZoomPixelRatio: 5,
|
||||||
|
zoomInMultiplier: 2,
|
||||||
|
doubleTapDelay: 300,
|
||||||
|
doubleClickDelay: 300,
|
||||||
|
doubleClickMaxStops: 2,
|
||||||
|
keyboardMoveDistance: 50,
|
||||||
|
wheelZoomDistanceFactor: 100,
|
||||||
|
pinchZoomDistanceFactor: 100,
|
||||||
|
scrollToZoom: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -86,7 +86,7 @@ const Navbar = ({ items }) => {
|
||||||
<p className="mb-0 text-white"> salam</p>
|
<p className="mb-0 text-white"> salam</p>
|
||||||
</div> */}
|
</div> */}
|
||||||
{isScrolled && (
|
{isScrolled && (
|
||||||
<div className="w-full lg:h-[76px] h-2">
|
<div className="w-full lg:h-[66px] h-2">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -185,14 +185,14 @@ const Navbar = ({ items }) => {
|
||||||
{
|
{
|
||||||
isScrolled &&
|
isScrolled &&
|
||||||
(
|
(
|
||||||
<div className="relative rounded-lg flex flex-col items-center rounded-tl-2xl justify-center my-auto">
|
<Link href={"/"} className="relative rounded-lg flex flex-col items-center rounded-tl-2xl justify-center my-auto">
|
||||||
<Image
|
<Image
|
||||||
src={"/images/1.png"}
|
src={"/images/1.png"}
|
||||||
width={75} height={75}
|
width={75} height={75}
|
||||||
alt="llc "
|
alt="llc "
|
||||||
className="mx-auto object-contain"
|
className="mx-auto object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{/* <div className={`w-full ${locale === "en" ? "rtl" : "ltr"} px-4 py-4`}>
|
{/* <div className={`w-full ${locale === "en" ? "rtl" : "ltr"} px-4 py-4`}>
|
||||||
|
|
|
@ -1,19 +1,89 @@
|
||||||
import { getMessages } from "next-intl/server"
|
"use client"
|
||||||
|
import { CheckCircle2 } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ContactModal from "../ContactUs";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { LinkIcon } from "lucide-react";
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
|
|
||||||
export default async function ProductProperties({ properties }) {
|
export default function ProductProperties({ product }) {
|
||||||
const t = await getMessages()
|
const t = useTranslations("PDP")
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const openModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
const closeModal = () => {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-8">
|
||||||
<h2 className="text-2xl font-bold mb-4">{t.PDP.productSpecifications}</h2>
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
||||||
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
|
<h3 className="text-xl font-semibold mb-4">{t("productSpecification")}</h3>
|
||||||
{properties.map((prop) => (
|
<div className="space-y-4">
|
||||||
<div key={prop.id} className="border-t border-gray-200 pt-6">
|
{product.properties.map(prop => (
|
||||||
<dt className="font-medium text-gray-900 capitalize">{prop.key}</dt>
|
<div key={prop.id} className="flex items-center gap-4">
|
||||||
<dd className="mt-2 text-gray-500">{prop.value}</dd>
|
<CheckCircle2 className="w-5 h-5 text-gray-900 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">{prop.key}:</span>
|
||||||
|
<span className="text-gray-600 ml-2">{prop.value}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{product?.brand &&
|
||||||
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
||||||
|
<h3 className="text-xl font-semibold mb-4">Brand </h3>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="relative size-20">
|
||||||
|
<Image
|
||||||
|
src={product?.brand?.image?.url}
|
||||||
|
alt={product?.brand?.image?.alternativeText}
|
||||||
|
|
||||||
|
fill
|
||||||
|
className="object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-around">
|
||||||
|
<Link href={`/products/${product?.brand?.slug}`} className="font-bold text-lg">
|
||||||
|
{product?.brand?.title}
|
||||||
|
</Link>
|
||||||
|
<Link href={`/products/${product?.brand?.slug}`} className="text-sm text-black/70">
|
||||||
|
View All
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</dl>
|
}
|
||||||
|
{product.brand?.subcategories.lenght &&
|
||||||
|
<div className="bg-white rounded-xl p-6 shadow-sm">
|
||||||
|
<h3 className="text-xl font-semibold mb-4">Use Cases </h3>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{product.brand?.subcategories.map(subcat => (
|
||||||
|
<Link
|
||||||
|
key={subcat.documentId}
|
||||||
|
href={`/products/${subcat.slug}`}
|
||||||
|
className="inline-flex items-center gap-2 px-3 py-1 bg-black/10 rounded-full text-sm hover:bg-black/30 transition-colors"
|
||||||
|
>
|
||||||
|
<LinkIcon className="w-4 h-4" />
|
||||||
|
{subcat.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
// onClick={() => setIsModalOpen(true)}
|
||||||
|
className="w-full bg-gray-900 text-white py-4 rounded-lg hover:bg-gray-800 transition-colors flex items-center justify-center gap-2 font-semibold"
|
||||||
|
>
|
||||||
|
Get Quote
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<ContactModal close={closeModal} open={open} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import ContactModal from 'src/components/ContactUs';
|
||||||
|
|
||||||
|
|
||||||
const Content = ({ content }) => {
|
const Content = ({ content }) => {
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const openModal = () => {
|
const openModal = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
@ -27,7 +27,7 @@ const Content = ({ content }) => {
|
||||||
<div className="lg:w-2/5 p-8 flex flex-col items-center justify-center text-white " >
|
<div className="lg:w-2/5 p-8 flex flex-col items-center justify-center text-white " >
|
||||||
<div className="w-full max-w-[200px] h-[130px] relative mb-6 bg-white rounded-lg p-4 shadow-lg transform transition-transform duration-500 hover:scale-105">
|
<div className="w-full max-w-[200px] h-[130px] relative mb-6 bg-white rounded-lg p-4 shadow-lg transform transition-transform duration-500 hover:scale-105">
|
||||||
<Image
|
<Image
|
||||||
|
|
||||||
src={content.image?.url || "/placeholder.svg"}
|
src={content.image?.url || "/placeholder.svg"}
|
||||||
alt={content.image?.alternativeText || content.title}
|
alt={content.image?.alternativeText || content.title}
|
||||||
fill
|
fill
|
||||||
|
@ -35,7 +35,7 @@ const Content = ({ content }) => {
|
||||||
className="p-2"
|
className="p-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <div className="flex flex-wrap justify-center gap-2 mt-2">
|
{/* <div className="flex flex-wrap justify-center gap-2 mt-2">
|
||||||
<span className="inline-block px-3 py-1 bg-white/20 backdrop-blur-sm rounded-full text-sm font-medium">
|
<span className="inline-block px-3 py-1 bg-white/20 backdrop-blur-sm rounded-full text-sm font-medium">
|
||||||
Detergents
|
Detergents
|
||||||
|
@ -65,7 +65,7 @@ const Content = ({ content }) => {
|
||||||
{/* <button className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-600 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
{/* <button className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-600 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||||
View All Products
|
View All Products
|
||||||
</button> */}
|
</button> */}
|
||||||
<button onClick={()=>setOpen(true)} className="px-6 py-3 font-medium rounded-lg bg-primary-100 transition-all duration-300">
|
<button onClick={() => setOpen(true)} className="px-6 py-3 font-medium rounded-lg bg-primary-100 transition-all duration-300">
|
||||||
Contact Us
|
Contact Us
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,7 +73,7 @@ const Content = ({ content }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ContactModal close={closeModal} open={open} />
|
|
||||||
</section >
|
</section >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue