Compare commits

..

13 Commits

23 changed files with 2362 additions and 2047 deletions

View File

@@ -1,8 +1,8 @@
import Razorpay from "razorpay"; import Razorpay from "razorpay";
const razorpay = new Razorpay({ const razorpay = new Razorpay({
key_id: process.env.NEXT_RAZORPAY_CLIENT_ID, key_id: "rzp_live_REdeGZclfm8KFo",
key_secret: process.env.NEXT_RAZORPAY_SECRET_KEY, key_secret: "BXgwGPsYhxgUYsozCr2uoEZC",
}); });
export async function POST(req) { export async function POST(req) {

View File

@@ -27,7 +27,7 @@ export const ProductContextProvider = ({ children }) => {
fetchCategory(); fetchCategory();
}, []); }, []);
const cartFn = async (variantId, designId, quantity) => { const cartFn = async (variantId, designId, quantity, showToast = true) => {
try{ try{
const response = await authAxios.post('/orders/cart/manage_item/',{ const response = await authAxios.post('/orders/cart/manage_item/',{
@@ -35,7 +35,9 @@ export const ProductContextProvider = ({ children }) => {
design: designId, design: designId,
quantity: quantity quantity: quantity
}) })
if (showToast) {
toast.success('Modified Cart Successfully!') toast.success('Modified Cart Successfully!')
}
return response return response
} }
catch(error){ catch(error){

View File

@@ -1,19 +1,21 @@
import AddToCardLeftSection from "@/components/products/AddToCardLeftSection"; import AddToCardLeftSection from "@/components/products/AddToCardLeftSection";
import AddToCardRightSection from "@/components/products/AddToCardRightSection"; import AddToCardRightSection from "@/components/products/AddToCardRightSection";
import RelatedProductCards from "@/components/products/RelatedProductCards"; import RelatedProductCards from "@/components/products/RelatedProductCards";
import RelatedVideos from "@/components/products/RelatedVideos"; // import RelatedVideos from "@/components/products/RelatedVideos";
import React from "react"; import React from "react";
const page = ({ params }) => { const page = ({ params }) => {
const slug = params.name;
const productId = slug.split("-").pop();
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-white">
<div className="flex gap-3 min-h-screen sm:flex-row flex-col"> <div className="flex gap-3 min-h-screen sm:flex-row flex-col">
<AddToCardLeftSection productId={params.name} /> <AddToCardLeftSection productId={productId} />
<AddToCardRightSection productId={params.name}/> <AddToCardRightSection productId={productId} />
</div> </div>
<RelatedProductCards productId={params.name}/> <RelatedProductCards productId={productId} />
{/* <RelatedVideos /> */} {/* <RelatedVideos /> */}
</div> </div>
); );

View File

@@ -5,10 +5,12 @@ import React, { useState, useEffect } from "react";
import { backendUrl } from "@/utils/axios"; import { backendUrl } from "@/utils/axios";
import axios from "axios"; import axios from "axios";
import Image from "next/image"; import Image from "next/image";
import slugify from "slugify";
export default function SubcategoryList({ params }) { export default function SubcategoryList({ params }) {
console.log(params); //console.log("category params is", params);
console.log(params.id); //console.log("Category name:", params.name);
const [subcategories, setSubcategories] = useState([]); const [subcategories, setSubcategories] = useState([]);
const [category, setCategory] = useState(null); const [category, setCategory] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -17,23 +19,29 @@ export default function SubcategoryList({ params }) {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
// Fetch category details // Fetch all categories
const categoryResponse = await axios.get( const categoryResponse = await axios.get(`${backendUrl}/products/category/`);
`${backendUrl}/products/category/`
); // Find the category by name
const selectedCategory = categoryResponse.data.find( const selectedCategory = categoryResponse.data.find(
(cat) => cat.id == params.id (cat) => cat.category_name.toLowerCase() === decodeURIComponent(params.name).toLowerCase()
); );
if (!selectedCategory) {
setError("Category not found.");
setLoading(false);
return;
}
setCategory(selectedCategory); setCategory(selectedCategory);
// Fetch subcategories for this category // Fetch subcategories using category id
const subcategoriesResponse = await axios.get( const subcategoriesResponse = await axios.get(
`${backendUrl}/admins/product/subcategories/?category_id=${params.id}` `${backendUrl}/admins/product/subcategories/?category_id=${selectedCategory.id}`
); );
setSubcategories(subcategoriesResponse.data); setSubcategories(subcategoriesResponse.data);
console.log("Selected sub category")
console.log(subcategoriesResponse.data); console.log("Selected subcategories", subcategoriesResponse.data);
// console.log(sub)
} catch (err) { } catch (err) {
console.error("Error fetching data:", err); console.error("Error fetching data:", err);
setError("Failed to load subcategories. Please try again later."); setError("Failed to load subcategories. Please try again later.");
@@ -43,7 +51,7 @@ export default function SubcategoryList({ params }) {
}; };
fetchData(); fetchData();
}, [params.id]); }, [params.name]);
if (loading) { if (loading) {
return <div className="py-16 text-center">Loading subcategories...</div>; return <div className="py-16 text-center">Loading subcategories...</div>;
@@ -71,7 +79,7 @@ export default function SubcategoryList({ params }) {
key={subcategory.id} key={subcategory.id}
className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform transform hover:scale-105" className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform transform hover:scale-105"
> >
<Link href={`/category/${params.id}/subcategory/${subcategory.id}`}> <Link href={`/category/${params.name}/subcategory/${slugify(subcategory.subcategory_name, { lower: true })}`}>
<div className="relative w-full h-64 bg-gray-200"> <div className="relative w-full h-64 bg-gray-200">
<Image <Image
src={`${ src={`${

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import Image from "next/image"; import Image from "next/image";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from "react";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@@ -10,10 +10,13 @@ import {
import Link from "next/link"; import Link from "next/link";
import axios from "axios"; import axios from "axios";
import { backendUrl } from "@/utils/axios"; import { backendUrl } from "@/utils/axios";
import slugify from "slugify";
import ProductContext from "@/app/contexts/productContext";
const DEFAULT_IMAGE = "/sidhi-mala/Designer_30.png"; // Default image const DEFAULT_IMAGE = "/sidhi-mala/Designer_30.png";
const FaqCard = ({ params }) => { const FaqCard = ({ params }) => {
const { products } = useContext(ProductContext); // ✅ get products
const [faq, setFaq] = useState([]); const [faq, setFaq] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
@@ -21,11 +24,46 @@ const FaqCard = ({ params }) => {
useEffect(() => { useEffect(() => {
const fetchFaq = async () => { const fetchFaq = async () => {
try { try {
const response = await axios.get( if (!products || products.length === 0) {
`${backendUrl}/products/faq/${params.id}/` console.log("❌ Products not loaded yet.");
return;
}
// 1⃣ Collect all unique subcategories from products
const subcategories = products.map((p) => p.product_subcategory);
// 2⃣ Find subcategory by slug
const matchedSub = subcategories.find(
(sub) =>
slugify(sub?.subcategory_name || "", { lower: true }) ===
params.subcategory
); );
setFaq(response.data);
console.log("✅ Matched Subcategory for FAQ:", matchedSub);
if (!matchedSub) {
console.warn("⚠️ No subcategory found for slug:", params.subcategory);
setError("Subcategory not found");
setLoading(false);
return;
}
// 3⃣ Fetch FAQs using subcategory ID
const url = `${backendUrl}/products/faq/${matchedSub.category}/`;
console.log("📡 Fetching FAQ from:", url);
const faqRes = await axios.get(url);
console.log("📥 Full FAQ Response:", faqRes);
console.log("✅ FAQ Data:", faqRes.data);
setFaq(faqRes.data);
} catch (err) { } catch (err) {
console.error("❌ FAQ Fetch Error:", err);
if (err.response) {
console.error("⚠️ Response Status:", err.response.status);
console.error("⚠️ Response Data:", err.response.data);
}
setError("Failed to fetch FAQs. Please try again later."); setError("Failed to fetch FAQs. Please try again later.");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -33,24 +71,19 @@ const FaqCard = ({ params }) => {
}; };
fetchFaq(); fetchFaq();
}, [params.id]); }, [products, params.subcategory]);
if (loading) { if (loading)
return <p className="text-center text-xl mt-10">Loading FAQs...</p>; return <p className="text-center text-xl mt-10">Loading FAQs...</p>;
} if (error) return <p className="text-center text-red-600 mt-10">{error}</p>;
if (!faq || faq.length === 0)
if (error) {
return <p className="text-center text-red-600 mt-10">{error}</p>;
}
if (!faq || faq.length === 0) {
return <p className="text-center text-xl mt-10">No FAQs available.</p>; return <p className="text-center text-xl mt-10">No FAQs available.</p>;
}
const hasImage = faq.some((item) => item.image !== null); const hasImage = faq.some((item) => item.image !== null);
const imageToShow = hasImage const imageToShow = hasImage
? `${backendUrl}${faq.find((item) => item.image !== null).image}` ? `${backendUrl}${faq.find((item) => item.image !== null).image}`
: DEFAULT_IMAGE; : DEFAULT_IMAGE;
return ( return (
<div className="min-h-screen sm:block hidden bg-[#FFFFFF]"> <div className="min-h-screen sm:block hidden bg-[#FFFFFF]">
<h1 className="sm:pt-14 pb-8 font-serif mt-4 sm:text-6xl text-2xl text-zinc-800 text-center"> <h1 className="sm:pt-14 pb-8 font-serif mt-4 sm:text-6xl text-2xl text-zinc-800 text-center">

View File

@@ -24,9 +24,14 @@ const Navbar = () => {
SUPPORTED_CURRENCIES, SUPPORTED_CURRENCIES,
error, error,
setUserCountry, setUserCountry,
userCountry userCountry,
} = useCurrency(); } = useCurrency();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen); const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
useEffect(() => { useEffect(() => {
@@ -101,7 +106,7 @@ const Navbar = () => {
const categoryItems = category const categoryItems = category
? category.map((category) => ({ ? category.map((category) => ({
label: category.category_name, label: category.category_name,
url: `/category/${category.id}`, url: `/category/${category.category_name}`,
})) }))
: []; // Fallback to an empty array if category is undefined : []; // Fallback to an empty array if category is undefined
@@ -118,12 +123,13 @@ const Navbar = () => {
<div className="container mx-auto flex justify-between items-center px-4 py-2"> <div className="container mx-auto flex justify-between items-center px-4 py-2">
<div className="flex items-center md:space-x-4"> <div className="flex items-center md:space-x-4">
<button onClick={toggleSidebar} className="z-50"> <button onClick={toggleSidebar} className="z-50">
{isSidebarOpen ? ( {!mounted ? null : isSidebarOpen ? (
<IoMdClose size={28} /> <IoMdClose size={28} />
) : ( ) : (
<Menu size={28} strokeWidth={1} /> <Menu size={28} strokeWidth={1} />
)} )}
</button> </button>
<Link href="/"> <Link href="/">
<Image <Image
src="/logo1.jpg" src="/logo1.jpg"

View File

@@ -33,7 +33,7 @@ const HeroSix = ({ guranteeData, data }) => {
description2: description6, description2: description6,
}} }}
/> />
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-8 mt-4"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-8 mt-4">
{guranteeData.map((item, index) => ( {guranteeData.map((item, index) => (
<motion.div <motion.div
key={item.id} key={item.id}

View File

@@ -1,11 +0,0 @@
import React from 'react'
const Instasection = () => {
return (
<div className='h-[70vh]'>
</div>
)
}
export default Instasection

View File

@@ -2,15 +2,12 @@ import React, { useState, useEffect } from "react";
import { PayPalButtons, PayPalScriptProvider } from "@paypal/react-paypal-js"; import { PayPalButtons, PayPalScriptProvider } from "@paypal/react-paypal-js";
import axios from "axios"; import axios from "axios";
import { useRazorpay } from "react-razorpay"; import { useRazorpay } from "react-razorpay";
import { useCurrency } from "@/app/contexts/currencyContext";
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
const EXCHANGE_RATE_KEY = "exchange_rate_cache";
const PaymentComponent = ({ amount, onSuccess }) => { const PaymentComponent = ({ amount, onSuccess }) => {
const { error: razorpayError, isLoading, Razorpay } = useRazorpay(); const { error: razorpayError, isLoading, Razorpay } = useRazorpay();
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [usdAmount, setUsdAmount] = useState(null);
const [showPopup, setShowPopup] = useState(false); const [showPopup, setShowPopup] = useState(false);
const [shippingData, setShippingData] = useState({ const [shippingData, setShippingData] = useState({
address_line_1: "", address_line_1: "",
@@ -19,50 +16,47 @@ const PaymentComponent = ({ amount, onSuccess }) => {
city: "", city: "",
country_code: "", country_code: "",
postal_code: "", postal_code: "",
phone_number: "",
}); });
const [shippingInfoCollected, setShippingInfoCollected] = useState(false); const [shippingInfoCollected, setShippingInfoCollected] = useState(false);
const { selectedCurrency, convertPrice, exchangeRates } = useCurrency();
const amountInSelectedCurrency = convertPrice(amount);
const [usdAmount, setUsdAmount] = useState(null);
useEffect(() => { useEffect(() => {
const fetchExchangeRate = async () => { const calculateUsdAmount = async () => {
try { try {
const cachedData = localStorage.getItem(EXCHANGE_RATE_KEY); if (selectedCurrency === 'USD') {
if (cachedData) { setUsdAmount(amountInSelectedCurrency);
const { rate, timestamp } = JSON.parse(cachedData);
if (Date.now() - timestamp < CACHE_DURATION) {
setUsdAmount((amount * rate).toFixed(2));
return; return;
} }
if (exchangeRates && exchangeRates['USD']) {
const usdRate = exchangeRates['USD'];
setUsdAmount((amountInSelectedCurrency / usdRate).toFixed(2));
return;
} }
const response = await axios.get("https://apilayer.net/api/live", { const response = await axios.get('https://api.exchangerate-api.com/v4/latest/' + selectedCurrency);
params: { if (response.data && response.data.rates && response.data.rates.USD) {
access_key: "9bcb30907dee1cda9866f7b49f0f8def", const usdRate = response.data.rates.USD;
currencies: "USD", setUsdAmount((amountInSelectedCurrency * usdRate).toFixed(2));
source: "INR",
format: 1,
},
});
if (response.data.success) {
const rate = response.data.quotes.INRUSD;
localStorage.setItem(
EXCHANGE_RATE_KEY,
JSON.stringify({
rate,
timestamp: Date.now(),
})
);
setUsdAmount((amount * rate).toFixed(2));
} else { } else {
throw new Error("Failed to fetch exchange rate"); throw new Error("Failed to fetch USD exchange rate");
} }
} catch (err) { } catch (err) {
console.error("Exchange rate error:", err); console.error("Error calculating USD amount:", err);
if (selectedCurrency === 'INR') {
setUsdAmount((amountInSelectedCurrency / 75).toFixed(2));
} else {
setUsdAmount(amountInSelectedCurrency);
}
} }
}; };
fetchExchangeRate(); calculateUsdAmount();
}, [amount]); }, [amountInSelectedCurrency, selectedCurrency, exchangeRates]);
const createOrder = async () => { const createOrder = async () => {
try { try {
@@ -89,7 +83,7 @@ const PaymentComponent = ({ amount, onSuccess }) => {
const order = await createOrder(); const order = await createOrder();
const options = { const options = {
key: "rzp_test_Xf4wnkvQQeUnCq", key: "rzp_live_REdeGZclfm8KFo",
amount: order.amount, amount: order.amount,
currency: order.currency, currency: order.currency,
name: "Rudraksha", name: "Rudraksha",
@@ -98,7 +92,7 @@ const PaymentComponent = ({ amount, onSuccess }) => {
prefill: { prefill: {
name: shippingData.address_line_1, name: shippingData.address_line_1,
email: shippingData.address_line_2, email: shippingData.address_line_2,
contact: shippingData.city, contact: shippingData.phone_number || shippingData.city,
}, },
handler: (response) => { handler: (response) => {
response.address = shippingData; response.address = shippingData;
@@ -115,24 +109,23 @@ const PaymentComponent = ({ amount, onSuccess }) => {
}; };
const handlePopupSubmit = () => { const handlePopupSubmit = () => {
if (!shippingData.address_line_1 || !shippingData.city || !shippingData.postal_code) { if (!shippingData.address_line_1 || !shippingData.city || !shippingData.postal_code || !shippingData.phone_number) {
setError("Please fill in all required fields."); setError("Please fill in all required fields.");
return; return;
} }
// Validate phone number
const phoneRegex = /^\+?[0-9]{10,15}$/;
if (!phoneRegex.test(shippingData.phone_number)) {
setError("Please enter a valid phone number (10-15 digits).");
return;
}
setShowPopup(false); setShowPopup(false);
setShippingInfoCollected(true); setShippingInfoCollected(true);
setError(null); setError(null);
}; };
const validateShippingInfo = () => {
if (!shippingInfoCollected) {
setShowPopup(true);
return false;
}
return true;
};
return ( return (
<div className="max-w-md mx-auto"> <div className="max-w-md mx-auto">
{error && ( {error && (
@@ -172,8 +165,10 @@ const PaymentComponent = ({ amount, onSuccess }) => {
<PayPalScriptProvider <PayPalScriptProvider
options={{ options={{
"client-id": process.env.NEXT_PUBLIC_CLIENT_ID, "client-id": process.env.NEXT_PUBLIC_CLIENT_ID,
currency: "USD", // PayPal requires USD or other supported currencies
}} }}
> >
{usdAmount && (
<PayPalButtons <PayPalButtons
style={{ style={{
layout: "horizontal", layout: "horizontal",
@@ -213,8 +208,10 @@ const PaymentComponent = ({ amount, onSuccess }) => {
}} }}
onError={(err) => { onError={(err) => {
setError("Payment failed. Please try again."); setError("Payment failed. Please try again.");
console.error("PayPal error:", err);
}} }}
/> />
)}
</PayPalScriptProvider> </PayPalScriptProvider>
<button <button
@@ -272,6 +269,13 @@ const PaymentComponent = ({ amount, onSuccess }) => {
onChange={(e) => setShippingData({ ...shippingData, postal_code: e.target.value })} onChange={(e) => setShippingData({ ...shippingData, postal_code: e.target.value })}
className="w-full p-2 mb-4 border border-gray-300 rounded" className="w-full p-2 mb-4 border border-gray-300 rounded"
/> />
<input
type="tel"
placeholder="Phone Number *"
value={shippingData.phone_number}
onChange={(e) => setShippingData({ ...shippingData, phone_number: e.target.value })}
className="w-full p-2 mb-4 border border-gray-300 rounded"
/>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => setShowPopup(false)} onClick={() => setShowPopup(false)}

View File

@@ -1,5 +1,5 @@
"use client"; "use client";
import React, { useContext, useState } from "react"; import React, { useContext, useState, useRef } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { ChevronLeft, ChevronRight, Award } from "lucide-react"; // Added Award icon import { ChevronLeft, ChevronRight, Award } from "lucide-react"; // Added Award icon
@@ -9,8 +9,10 @@ import Image from "next/image";
const ProductGallery = ({ productId }) => { const ProductGallery = ({ productId }) => {
const { products } = useContext(ProductContext); const { products } = useContext(ProductContext);
const [currentImageIndex, setCurrentImageIndex] = useState(0); const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [showZoom, setShowZoom] = useState(false);
const [zoomPosition, setZoomPosition] = useState({ x: 0, y: 0 });
const imageContainerRef = useRef(null);
const nextImage = () => { const nextImage = () => {
setCurrentImageIndex((prevIndex) => (prevIndex + 1) % productImages.length); setCurrentImageIndex((prevIndex) => (prevIndex + 1) % productImages.length);
@@ -22,6 +24,7 @@ const ProductGallery = ({ productId }) => {
(prevIndex - 1 + productImages.length) % productImages.length (prevIndex - 1 + productImages.length) % productImages.length
); );
}; };
const product = products?.find((pr) => pr.id == productId); const product = products?.find((pr) => pr.id == productId);
if (!product) { if (!product) {
return null; return null;
@@ -30,6 +33,23 @@ const ProductGallery = ({ productId }) => {
const productImages = product?.images?.map((img) => img.image) || []; const productImages = product?.images?.map((img) => img.image) || [];
const hasCertificate = product.certificate && product.certificate !== "NA"; const hasCertificate = product.certificate && product.certificate !== "NA";
const handleMouseMove = (e) => {
if (!imageContainerRef.current) return;
const { left, top, width, height } = imageContainerRef.current.getBoundingClientRect();
// Calculate position as percentage
const x = ((e.clientX - left) / width) * 100;
const y = ((e.clientY - top) / height) * 100;
setZoomPosition({ x, y });
setShowZoom(true);
};
const handleMouseLeave = () => {
setShowZoom(false);
};
return ( return (
<div className="flex flex-col md:flex-row gap-4 p-4 sm:w-[70%] "> <div className="flex flex-col md:flex-row gap-4 p-4 sm:w-[70%] ">
<div className="hidden md:flex flex-col gap-2"> <div className="hidden md:flex flex-col gap-2">
@@ -38,8 +58,8 @@ const ProductGallery = ({ productId }) => {
key={index} key={index}
src={`${backendUrl}${src}`} src={`${backendUrl}${src}`}
alt={`Thumbnail ${index + 1}`} alt={`Thumbnail ${index + 1}`}
width={160} // Width of w-40 (160px) width={160}
height={128} // Height of h-32 (128px) height={128}
className="object-cover cursor-pointer border border-gray-300 hover:border-blue-500" className="object-cover cursor-pointer border border-gray-300 hover:border-blue-500"
onClick={() => setCurrentImageIndex(index)} onClick={() => setCurrentImageIndex(index)}
/> />
@@ -55,14 +75,18 @@ const ProductGallery = ({ productId }) => {
</div> </div>
)} )}
{/* Main image with carousel controls */} <div
<div className="relative mb-4 h-full"> className="relative mb-4 h-full"
ref={imageContainerRef}
// onMouseMove={handleMouseMove}
// onMouseLeave={handleMouseLeave}
>
<Image <Image
src={`${backendUrl}${productImages[currentImageIndex]}`} src={`${backendUrl}${productImages[currentImageIndex]}`}
alt="Main product" alt="Main product"
width={500} width={500}
height={500} height={500}
className="object-cover w-full h-full" className="object-cover w-full h-full cursor-zoom-in"
/> />
<Button <Button
className="absolute top-1/2 left-2 transform -translate-y-1/2 md:hidden" className="absolute top-1/2 left-2 transform -translate-y-1/2 md:hidden"
@@ -78,6 +102,24 @@ const ProductGallery = ({ productId }) => {
> >
<ChevronRight /> <ChevronRight />
</Button> </Button>
{/* Zoom overlay */}
{showZoom && (
<div className="absolute top-0 right-0 w-40 h-40 md:w-60 md:h-60 lg:w-[300px] lg:h-[300px] border-2 border-gray-300 overflow-hidden z-20 bg-white shadow-lg">
<div
className="absolute w-[1000px] h-[1000px]"
style={{
backgroundImage: `url(${backendUrl}${productImages[currentImageIndex]})`,
backgroundPosition: `${zoomPosition.x}% ${zoomPosition.y}%`,
backgroundSize: '250%',
backgroundRepeat: 'no-repeat',
transform: 'translate(-50%, -50%)',
left: '50%',
top: '50%',
}}
/>
</div>
)}
</div> </div>
</Card> </Card>
<div className="my-6"> <div className="my-6">
@@ -101,6 +143,13 @@ const ProductGallery = ({ productId }) => {
<h2 className="my-1">100% Secured Payment</h2> <h2 className="my-1">100% Secured Payment</h2>
<h2>Authenticity Guaranteed</h2> <h2>Authenticity Guaranteed</h2>
</div> </div>
<div className="mt-8 border-t pt-6 hidden md:block">
<h2 className="text-xl font-semibold mb-4">Beej Mantra</h2>
<div
dangerouslySetInnerHTML={{ __html: product.beej_mantra }}
className="text-sm prose prose-sm max-w-none"
/>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -221,6 +221,13 @@ function ProductDetails({ productId }) {
className="text-sm prose prose-sm max-w-none" className="text-sm prose prose-sm max-w-none"
/> />
</div> </div>
<div className="mt-8 border-t pt-6 block md:hidden">
<h2 className="text-xl font-semibold mb-4">Beej Mantra</h2>
<div
dangerouslySetInnerHTML={{ __html: product.beej_mantra }}
className="text-sm prose prose-sm max-w-none"
/>
</div>
</div> </div>
{showCertificate && !product.certificate.toLowerCase().endsWith('.pdf') && ( {showCertificate && !product.certificate.toLowerCase().endsWith('.pdf') && (

View File

@@ -30,12 +30,12 @@ const RelatedProductCards = ({ productId }) => {
You may also like You may also like
</h1> </h1>
</div> </div>
<div className="overflow-x-auto hide-navbar gap-5 flex items-center justify-center"> <div className="overflow-x-auto hide-navbar flex items-center justify-start px-4 pb-4 -mx-4 snap-x">
{relatedProducts.map((product) => ( {relatedProducts.map((product) => (
<Link href={`/products/${product.id}`} key={product.id}> <Link href={`/products/${product.id}`} key={product.id}>
<div <div
key={product.id} key={product.id}
className="relative bg-[#EDE8E0] overflow-hidden sm:h-[350px] sm:w-[300px] " className="relative bg-[#EDE8E0] overflow-hidden sm:h-[350px] sm:w-[300px] flex-shrink-0 mx-3 snap-center"
> >
<div className="absolute top-0 left-0 bg-[#C19A5B] text-white py-1 px-3 rounded-br-lg z-10"> <div className="absolute top-0 left-0 bg-[#C19A5B] text-white py-1 px-3 rounded-br-lg z-10">
Exclusive Exclusive

View File

@@ -75,13 +75,12 @@ const ShippingPolicy = () => {
experience with us. experience with us.
</p> </p>
<h2 className="text-2xl font-semibold mb-4 underline"> <h2 className="text-2xl font-semibold mb-4 underline">
Delivery Services Used: Delivery Services Used : Any Courier and Post Office used
</h2> </h2>
<p className="text-lg mt-6"> <p className="text-lg mt-6">
Note: Gupta Rudraksha only uses the expedited shipping options and Note: Gupta Rudraksha only uses the expedited shipping options and
ensures the fastest delivery using any of the above courier services ensures the fastest delivery using any courier services
based on their estimated delivery date. BlueDart is used for based on their estimated delivery date.
deliveries all over India.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,4 @@
"use client"; "use client";
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { Trash2, Plus } from "lucide-react"; import { Trash2, Plus } from "lucide-react";
@@ -22,26 +23,33 @@ const ShoppingCart = () => {
const { formatPrice } = useCurrency(); const { formatPrice } = useCurrency();
const getCart = async () => { const getCart = async () => {
token = localStorage.getItem("token") const token = localStorage.getItem("token");
if (token == null) {
setCartItems([]) if (!token) {
} else { console.log("User not logged in, setting empty cart");
setCartItems([]);
return;
}
try {
const response = await authAxios.get("/orders/cart/"); const response = await authAxios.get("/orders/cart/");
setCartItems(response.data); setCartItems(response.data);
} catch (error) {
console.error("Error fetching cart:", error);
setCartItems([]);
} }
}; };
useEffect(() => { useEffect(() => {
getCart(); getCart();
}, []); }, []);
const handleQuantityChange = async (variantId, designId, quantityChange) => { const handleQuantityChange = async (variantId, designId, quantityChange, showToast = true) => {
const response = await cartFn(variantId, designId, quantityChange); const response = await cartFn(variantId, designId, quantityChange, showToast);
console.log(response) console.log(response)
if (response?.status == 200 || response?.status == 204) { if (response?.status == 200 || response?.status == 204) {
setCartItems((prev) => { setCartItems((prev) => {
if (!prev) return prev;
const updatedItems = prev[0].items.map((item) => { const updatedItems = prev[0].items.map((item) => {
if (item.variant.id === variantId && item?.design?.id === designId) { if (item.variant.id === variantId && item?.design?.id === designId) {
@@ -79,6 +87,15 @@ const ShoppingCart = () => {
} }
}; };
const clearCart = async () => {
if (!cartItems || !cartItems[0]?.items?.length) return;
for (const item of cartItems[0].items) {
await handleQuantityChange(item.variant.id, item.design?.id, -item.quantity, false);
}
toast.success('Cart cleared successfully!');
};
if (!cartItems) { if (!cartItems) {
return null; return null;
} }
@@ -116,6 +133,7 @@ const ShoppingCart = () => {
const paymentData = { const paymentData = {
paymentId: order.id, paymentId: order.id,
payerEmail: order.payer.email_address, payerEmail: order.payer.email_address,
orderId: order.id,
cartId: cartItems[0].id, cartId: cartItems[0].id,
address: order.address, address: order.address,
}; };
@@ -130,14 +148,6 @@ const ShoppingCart = () => {
} }
}; };
if (!cartItems) {
return null;
}
if (!cartItems[0]?.items?.length) {
return <EmptyCart />;
}
return ( return (
<div className="w-full bg-white"> <div className="w-full bg-white">
<div className="w-full px-4 md:px-8 max-w-[1600px] mx-auto font-['Inter']"> <div className="w-full px-4 md:px-8 max-w-[1600px] mx-auto font-['Inter']">
@@ -224,8 +234,8 @@ const ShoppingCart = () => {
size={18} size={18}
onClick={() => onClick={() =>
handleQuantityChange( handleQuantityChange(
item.variant.id, item?.variant?.id,
item.design.id, item?.design?.id,
-item.quantity -item.quantity
) )
} }
@@ -242,7 +252,10 @@ const ShoppingCart = () => {
<button className="text-[#c19a5b] hover:text-[#ab885b] font-medium order-2 sm:order-1"> <button className="text-[#c19a5b] hover:text-[#ab885b] font-medium order-2 sm:order-1">
<Link href="/"> CONTINUE SHOPPING</Link> <Link href="/"> CONTINUE SHOPPING</Link>
</button> </button>
<button className="text-[#c19a5b] hover:text-[#ab885b] sm:ml-auto font-medium order-1 sm:order-2"> <button
onClick={clearCart}
className="text-[#c19a5b] hover:text-[#ab885b] sm:ml-auto font-medium order-1 sm:order-2"
>
CLEAR SHOPPING CART CLEAR SHOPPING CART
</button> </button>
</div> </div>

View File

@@ -6,6 +6,7 @@ import { backendUrl } from "@/utils/axios";
import Image from "next/image"; import Image from "next/image";
import { useCurrency } from "@/app/contexts/currencyContext"; import { useCurrency } from "@/app/contexts/currencyContext";
import ProductContext from "@/app/contexts/productContext"; import ProductContext from "@/app/contexts/productContext";
import slugify from "slugify";
export default function ExploreSiddhaMala({ params }) { export default function ExploreSiddhaMala({ params }) {
const { products, category } = useContext(ProductContext); const { products, category } = useContext(ProductContext);
@@ -24,12 +25,19 @@ export default function ExploreSiddhaMala({ params }) {
{selectedCategory ? selectedCategory.category_name : "Products"} {selectedCategory ? selectedCategory.category_name : "Products"}
</h2> </h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8">
{filteredProducts?.map((product) => ( {filteredProducts?.map((product) => {
// ✅ generate slug for debugging
const slug = `${slugify(product.product_name, { lower: true })}-${
product.id
}`;
console.log("Generated slug for product:", slug);
return (
<div <div
key={product.id} key={product.id}
className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform transform hover:scale-105" className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform transform hover:scale-105"
> >
<Link href={`/products/${product.id}`}> <Link href={`/products/${slug}`}>
<div className="relative w-full h-64 bg-gray-200"> <div className="relative w-full h-64 bg-gray-200">
<Image <Image
src={`${backendUrl}${product?.images[0]?.image}`} src={`${backendUrl}${product?.images[0]?.image}`}
@@ -50,7 +58,9 @@ export default function ExploreSiddhaMala({ params }) {
? "Loading..." ? "Loading..."
: formatPrice( : formatPrice(
Math.min( Math.min(
...product.variants.map((variant) => variant.price) ...product.variants.map(
(variant) => variant.price
)
) )
)} )}
</p> </p>
@@ -61,7 +71,8 @@ export default function ExploreSiddhaMala({ params }) {
)} )}
</div> </div>
</div> </div>
))} );
})}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -14,7 +14,8 @@ const cleanHTML = (html) => {
const CategoryHero = ({ params }) => { const CategoryHero = ({ params }) => {
const { category } = useContext(ProductContext); const { category } = useContext(ProductContext);
const selectedCategory = category?.find((cat) => cat.id == params.id); const selectedCategory = category?.find((cat) => cat.category_name.toLowerCase() ===
decodeURIComponent(params.name).toLowerCase());
console.log(selectedCategory); console.log(selectedCategory);
@@ -25,8 +26,10 @@ const CategoryHero = ({ params }) => {
const fallbackCategoryName = "Spiritual Essentials"; const fallbackCategoryName = "Spiritual Essentials";
return ( return (
<section className="relative h-[50vh] md:h-[80vh] flex items-center justify-center overflow-hidden"> <section className="relative min-h-[50vh] md:h-[80vh] py-8 md:py-0 flex items-center justify-center">
<div className="absolute inset-0 z-0"> <div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
<div className="relative h-[250px] md:h-[500px] overflow-hidden rounded-lg shadow-xl">
<Image <Image
src={`${ src={`${
selectedCategory?.image selectedCategory?.image
@@ -40,13 +43,12 @@ const CategoryHero = ({ params }) => {
priority priority
/> />
</div> </div>
<div className="text-start flex justify-start flex-col h-full">
<div className="relative z-10 text-center px-6 md:px-12 max-w-3xl">
<motion.h1 <motion.h1
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
className="text-4xl md:text-6xl font-bold text-white mb-4 drop-shadow-lg" className="text-2xl md:text-3xl font-bold text-black mb-4"
> >
{selectedCategory?.category_name || fallbackCategoryName} {selectedCategory?.category_name || fallbackCategoryName}
</motion.h1> </motion.h1>
@@ -55,7 +57,7 @@ const CategoryHero = ({ params }) => {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }} transition={{ duration: 0.8, delay: 0.2 }}
className="text-lg md:text-xl text-gray-200 drop-shadow-md" className="text-black text-sm md:text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: cleanHTML( __html: cleanHTML(
selectedCategory?.descriptions || fallbackDescription selectedCategory?.descriptions || fallbackDescription
@@ -63,6 +65,8 @@ const CategoryHero = ({ params }) => {
}} }}
/> />
</div> </div>
</div>
</div>
</section> </section>
); );
}; };

View File

@@ -23,9 +23,13 @@ const EnhancedSlider = () => {
category: cat, category: cat,
products: products products: products
?.filter((product) => product.product_category?.id === cat.id) ?.filter((product) => product.product_category?.id === cat.id)
.slice(0, 3), .slice(0, 6),
})); }));
const trimText = (text = "", max) =>
text.length > max ? text.slice(0, max) + "…" : text;
return ( return (
<section className="bg-gradient-to-b from-background to-secondary/10 py-16 md:py-24"> <section className="bg-gradient-to-b from-background to-secondary/10 py-16 md:py-24">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
@@ -69,9 +73,9 @@ const EnhancedSlider = () => {
<Link href={`/products/${product.id}`}> <Link href={`/products/${product.id}`}>
<motion.div <motion.div
whileHover={{ y: -5 }} whileHover={{ y: -5 }}
className="group rounded-lg overflow-hidden bg-card shadow-lg transition-shadow hover:shadow-xl" className="group rounded-lg overflow-hidden bg-card shadow-lg transition-shadow hover:shadow-xl h-80"
> >
<div className="aspect-square relative overflow-hidden max-h-[50%]"> <div className="aspect-square relative overflow-hidden max-h-[50%] w-full">
<Image <Image
src={`${backendUrl}${ src={`${backendUrl}${
product.images[0]?.image || "/placeholder.jpg" product.images[0]?.image || "/placeholder.jpg"
@@ -84,11 +88,14 @@ const EnhancedSlider = () => {
</div> </div>
<div className="p-4"> <div className="p-4">
<h3 className="text-lg font-semibold text-foreground group-hover:text-primary transition-colors"> <h3 className="text-lg font-semibold text-foreground group-hover:text-primary transition-colors">
{product.product_name} {trimText(product.product_name, 30)}
</h3> </h3>
<p className="text-sm text-muted-foreground mt-2"> <p className="text-sm text-muted-foreground mt-2">
{product.short_description || {trimText(
"Discover more about this product"} product.short_description ||
"Discover more about this product",
60
)}
</p> </p>
</div> </div>
</motion.div> </motion.div>

View File

@@ -6,17 +6,27 @@ import { backendUrl } from "@/utils/axios";
import Image from "next/image"; import Image from "next/image";
import { useCurrency } from "@/app/contexts/currencyContext"; import { useCurrency } from "@/app/contexts/currencyContext";
import ProductContext from "@/app/contexts/productContext"; import ProductContext from "@/app/contexts/productContext";
import slugify from "slugify";
export default function ExploreSiddhaMala({ params }) { export default function ExploreSiddhaMala({ params }) {
const { products, category } = useContext(ProductContext); const { products, category } = useContext(ProductContext);
console.log("Products ", products) console.log("URL param:", params.subcategory);
//console.log("Products ", products);
const { formatPrice, isLoading } = useCurrency(); const { formatPrice, isLoading } = useCurrency();
const filteredProducts = products?.filter( const filteredProducts = products?.filter(
(product) => product.product_subcategory?.id == params.subCategoryId (product) =>
slugify(product.product_subcategory?.subcategory_name, {
lower: true,
}) === params.subcategory
); );
console.log(filteredProducts)
const selectedCategory = filteredProducts?.length > 0 ? filteredProducts[0].product_subcategory : {"category_name": ""} console.log("subcategory product are :", filteredProducts);
const selectedCategory =
filteredProducts?.length > 0
? filteredProducts[0].product_subcategory
: { category_name: "" };
return ( return (
<section className="py-16 bg-gray-50"> <section className="py-16 bg-gray-50">
@@ -26,12 +36,19 @@ export default function ExploreSiddhaMala({ params }) {
{selectedCategory ? selectedCategory.category_name : "Products"} {selectedCategory ? selectedCategory.category_name : "Products"}
</h2> </h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8">
{filteredProducts?.map((product) => ( {filteredProducts?.map((product) => {
// ✅ generate slug for debugging
const slug = `${slugify(product.product_name, { lower: true })}-${
product.id
}`;
console.log("Generated slug for product:", slug);
return (
<div <div
key={product.id} key={product.id}
className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform transform hover:scale-105" className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform transform hover:scale-105"
> >
<Link href={`/products/${product.id}`}> <Link href={`/products/${slug}`}>
<div className="relative w-full h-64 bg-gray-200"> <div className="relative w-full h-64 bg-gray-200">
<Image <Image
src={`${backendUrl}${product?.images[0]?.image}`} src={`${backendUrl}${product?.images[0]?.image}`}
@@ -52,7 +69,9 @@ export default function ExploreSiddhaMala({ params }) {
? "Loading..." ? "Loading..."
: formatPrice( : formatPrice(
Math.min( Math.min(
...product.variants.map((variant) => variant.price) ...product.variants.map(
(variant) => variant.price
)
) )
)} )}
</p> </p>
@@ -63,7 +82,8 @@ export default function ExploreSiddhaMala({ params }) {
)} )}
</div> </div>
</div> </div>
))} );
})}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,10 +1,12 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from "react";
import { backendUrl } from "@/utils/axios"; import { backendUrl } from "@/utils/axios";
import axios from "axios"; import axios from "axios";
import Image from "next/image"; import Image from "next/image";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import slugify from "slugify";
import ProductContext from "@/app/contexts/productContext";
const cleanHTML = (html) => { const cleanHTML = (html) => {
if (!html) return ""; if (!html) return "";
@@ -12,69 +14,78 @@ const cleanHTML = (html) => {
}; };
const SubcategoryHero = ({ params }) => { const SubcategoryHero = ({ params }) => {
const [selectedCategory, setSubcategory] = useState(null); const { products, category } = useContext(ProductContext);
console.log("SubcategoryHero mounted. Params:", params);
const [selectedSubcategory, setSelectedSubcategory] = useState(null);
useEffect(() => { useEffect(() => {
const fetchSubcategory = async () => { if (products?.length > 0) {
try { const match = products
const response = await axios.get( .map((p) => p.product_subcategory)
`${backendUrl}/admins/product/subcategory/${params.subCategoryId}` .find(
(sub) =>
slugify(sub?.subcategory_name || "", { lower: true }) ===
params.subcategory
); );
console.log(response.data)
const selectedSubcategory = response.data
setSubcategory(selectedSubcategory);
} catch (error) {
console.error("Error fetching subcategory:", error);
}
};
fetchSubcategory();
}, [params.subCategoryId]);
// Fallback values console.log("Matched from products:", match);
setSelectedSubcategory(match || null);
}
}, [products, params.subcategory]);
// Fallbacks
const fallbackImage = "/placeholder-image.jpg"; const fallbackImage = "/placeholder-image.jpg";
const fallbackDescription = const fallbackDescription =
"Explore our curated collection of spiritual items designed to enhance your journey of self-discovery and inner peace."; "Explore our curated collection of spiritual items designed to enhance your journey of self-discovery and inner peace.";
const fallbackCategoryName = "Spiritual Essentials"; const fallbackCategoryName = "Spiritual Essentials";
return ( return (
<section className="relative h-[50vh] md:h-[80vh] flex items-center justify-center overflow-hidden"> <section className="relative min-h-[50vh] md:h-[80vh] py-8 md:py-0 flex mt-3 justify-center">
<div className="absolute inset-0 z-0"> <div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
{/* Hero Image */}
<div className="relative h-[250px] md:h-[500px] overflow-hidden rounded-lg shadow-xl">
<Image <Image
src={`${ src={
selectedCategory?.image_url selectedSubcategory?.image
? backendUrl + selectedCategory?.image_url ? `${backendUrl}${selectedSubcategory.image}`
: "/sidhi-mala/Artboard_1_bf5ccd46-7152-4355-82a8-9e9f27c1bfc2.jpg" : fallbackImage
}`} }
alt="Category Background" alt={
layout="fill" selectedSubcategory?.subcategory_name || "Category Background"
objectFit="cover" }
fill
style={{ objectFit: "cover" }}
quality={100} quality={100}
priority priority
/> />
</div> </div>
<div className="relative z-10 text-center px-6 md:px-12 max-w-3xl"> {/* Hero Text */}
<div className="text-start flex justify-start flex-col h-full">
<motion.h1 <motion.h1
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
className="text-4xl md:text-6xl font-bold text-white mb-4 drop-shadow-lg" className="text-2xl md:text-3xl font-bold text-black mb-4"
> >
{selectedCategory?.subcategory_name || fallbackCategoryName} {selectedSubcategory?.subcategory_name || fallbackCategoryName}
</motion.h1> </motion.h1>
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }} transition={{ duration: 0.8, delay: 0.2 }}
className="text-lg md:text-xl text-gray-200 drop-shadow-md" className="text-black text-sm md:text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: cleanHTML( __html: cleanHTML(
selectedCategory?.description || fallbackDescription selectedSubcategory?.description || fallbackDescription
), ),
}} }}
/> />
</div> </div>
</div>
</div>
</section> </section>
); );
}; };

3533
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-razorpay": "^3.0.1", "react-razorpay": "^3.0.1",
"slugify": "^1.6.6",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },