ProductDetailModel, clicking outside the model will lead to the product! clicking outside the model should have closed the model!

'use client'
import React, { useState, useEffect, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Minus, Plus, ShoppingCart } from 'lucide-react';
import { useNotification } from "@/components/NotificationContext";
import '@/components/NotificationStyles.css';
import Link from "next/link";

const ProductModal = ({ isOpen, onClose, productData }) => {
    const [selectedSize, setSelectedSize] = useState(null);
    const [quantity, setQuantity] = useState(1);
    const [isInCart, setIsInCart] = useState(false);
    const [totalPrice, setTotalPrice] = useState(0);
    const { addNotification } = useNotification();

    const product = productData.product;
    const updateTotalPrice = useCallback(() => {
        if (selectedSize) {
            setTotalPrice(parseFloat(selectedSize.price) * quantity);
        }
    }, [selectedSize, quantity]);
    const checkIfInCart = useCallback(() => {
        const cart = JSON.parse(localStorage.getItem('cart')) || [];
        const isItemInCart = cart.some(item =>
            item.product_id === product.id &&
            item.size_id === selectedSize?.id
        );
        setIsInCart(isItemInCart);
    }, [product.id, selectedSize]);
    useEffect(() => {
        if (product && product.sizes && product.sizes.length > 0) {
            setSelectedSize(product.sizes[0]);
        }
    }, [product]);

    useEffect(() => {
        updateTotalPrice();
    }, [selectedSize, quantity, updateTotalPrice]);

    useEffect(() => {
        checkIfInCart();
    }, [selectedSize, checkIfInCart]);


    const handleAddToCart = useCallback(() => {
        if (selectedSize) {
            const cartItem = {
                product_id: product.id,
                size_id: selectedSize.id,
                qty: quantity,
                price: selectedSize.price
            };

            const cart = JSON.parse(localStorage.getItem('cart')) || [];
            const existingItemIndex = cart.findIndex(item =>
                item.product_id === cartItem.product_id &&
                item.size_id === cartItem.size_id
            );

            if (existingItemIndex > -1) {
                cart[existingItemIndex].qty += quantity;
            } else {
                cart.push(cartItem);
            }

            localStorage.setItem('cart', JSON.stringify(cart));
            setIsInCart(true);
            addNotification('success', 'Added to cart successfully');
            checkIfInCart();
        }
    }, [product.id, selectedSize, quantity, addNotification, checkIfInCart]);

    if (!isOpen || !product) return null;

    return (
        <AnimatePresence>
            <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                className="fixed font-serif inset-0 bg-black bg-opacity-30 flex items-end justify-center z-[999999]"
                onClick={onClose}
            >
                <motion.div
                    initial={{ y: "100%" }}
                    animate={{ y: 0 }}
                    exit={{ y: "100%" }}
                    transition={{ type: "spring", damping: 30, stiffness: 500 }}
                    className="bg-white rounded-t-3xl w-full p-6 relative"
                    onClick={(e) => e.stopPropagation()}
                >
                    <div className="w-9 h-1 bg-[#00003C] opacity-70 rounded-full mx-auto mb-6" />

                    <h2 className="text-xl mb-4 font-sans">{product.name}</h2>

                    {product.has_size && (
                        <div className="mb-4">
                            <select
                                value={selectedSize ? selectedSize.id : ''}
                                onChange={(e) => setSelectedSize(product.sizes.find(size => size.id.toString() === e.target.value))}
                                className="w-44 p-2 border border-[rgba(105,92,92,0.5)] rounded-lg font-serif text-lg"
                            >
                                <option value="" disabled={true}>Select a Size</option>
                                {product.sizes.map((size) => (
                                    <option key={size.id} value={size.id}>
                                        {size.name}
                                    </option>
                                ))}
                            </select>
                            {!selectedSize && (
                                <p className="text-[#C91C1C] text-xs mt-2 font-sans">Please choose Size</p>
                            )}
                        </div>
                    )}

                    {product.has_color && product.colors && product.colors.length > 0 && (
                        <div className="mb-6">
                            <h3 className="text-xl font-medium mb-2 font-sans">Color</h3>
                            <div className="flex space-x-5 mb-2">
                                {product.colors.map((color) => (
                                    <button
                                        key={color.id}
                                        className={`w-20 h-20 rounded-full border-2 ${
                                            selectedSize && selectedSize.color_id === color.id ? 'border-[rgba(105,92,92,0.5)]' : 'border-transparent'
                                        } flex items-center justify-center`}
                                    >
                                        <div
                                            className="w-15 h-15 rounded-full"
                                            style={{ backgroundColor: color.hex }}
                                        />
                                    </button>
                                ))}
                            </div>
                            <div className="flex justify-between px-2">
                                {product.colors.map((color) => (
                                    <span key={color.id} className="text-sm font-serif">{color.name}</span>
                                ))}
                            </div>
                        </div>
                    )}

                    <div className="flex justify-between items-center mb-6">
                        <p className="text-lg font-medium">
                            {totalPrice.toFixed(2)} IQD
                        </p>
                        <div className="flex items-center space-x-4">
                            <button
                                className="w-8 h-8 rounded-full border border-gray-300 flex items-center justify-center"
                                onClick={() => setQuantity(Math.max(1, quantity - 1))}
                            >
                                <Minus className="w-4 h-4 text-[#3B5345]" />
                            </button>
                            <span className="text-lg">{quantity}</span>
                            <button
                                className="w-8 h-8 rounded-full border border-gray-300 flex items-center justify-center"
                                onClick={() => setQuantity(quantity + 1)}
                            >
                                <Plus className="w-4 h-4 text-[#3B5345]" />
                            </button>
                        </div>
                    </div>

                    <div className="flex space-x-4">
                        {isInCart ? (
                            <Link
                                href="/cart"
                                className="flex-1 font-serif bg-[rgba(59,83,69,0.05)] text-[#3B5345] py-3 rounded-lg font-medium text-base md:text-lg flex items-center justify-center transition duration-300 border border-[#3B5345]"
                            >
                                <ShoppingCart className="mr-2" />
                                Buy Now
                            </Link>
                        ) : (
                            <button
                                className={`flex-1 ${selectedSize ? 'bg-[#3B5345] text-white' : 'bg-gray-300 text-gray-500 cursor-not-allowed'} py-3 rounded-lg font-medium text-base md:text-lg flex items-center justify-center`}
                                onClick={handleAddToCart}
                                disabled={!selectedSize}
                            >
                                <ShoppingCart className="mr-2" />
                                Add To Cart
                            </button>
                        )}
                        <button
                            className="flex-1 bg-gray-200 text-gray-800 py-3 rounded-lg font-medium text-base md:text-lg"
                            onClick={onClose}
                        >
                            Close
                        </button>
                    </div>
                </motion.div>
            </motion.div>
        </AnimatePresence>
    );
};

export default React.memo(ProductModal);
'use client'
import React, { useState, memo, useCallback } from 'react';
import Image from 'next/image';
import dynamic from 'next/dynamic';
import { NotificationProvider } from "@/components/NotificationContext";
import WishlistHeart from "@/components/WishlistHeart";

const ProductModal = dynamic(() => import("@/components/ProductDetailModal"), {
    ssr: false
});

const ImageSkeleton = () => (
    <div className="absolute inset-0 bg-gray-200 animate-pulse rounded-t-lg" />
);

const ProductItem = memo(({ id, name, price, imageUrl, product, handleClick }) => {
    const [isModalOpen, setIsModalOpen] = useState(false);
    const [imageLoaded, setImageLoaded] = useState(false);

    const handleProductClick = useCallback((e) => {
        if (!e.target.closest('.heart-icon')) {
            handleClick();
        }
    }, [handleClick]);

    const handleCartClick = useCallback((e) => {
        e.stopPropagation();
        setIsModalOpen(true);
    }, []);

    const handleImageLoad = useCallback(() => {
        setImageLoaded(true);
    }, []);

    return (
        <>
            <div className="rounded-lg" onClick={handleProductClick}>
                <div className="bg-white aspect-[186/275] relative flex flex-col w-full drop-shadow overflow-hidden group">
                    <button className="absolute left-1 top-4 z-20 heart-icon" onClick={(e) => e.stopPropagation()}>
                        <WishlistHeart productId={id} />
                    </button>
                    {!imageLoaded && <ImageSkeleton />}
                    <Image
                        src={`https://storage.naayiq.com/resources/${imageUrl}`}
                        alt={name}
                        fill={true}
                        sizes="(max-width: 768px) 50vw, (max-width: 1200px) 25vw, 10vw"
                        onError={(e) => {
                            e.target.src = "https://storage.naayiq.com/resources/noimage.webp";
                        }}
                        onLoad={handleImageLoad}
                        className={`object-cover relative rounded-t-lg w-full h-full overflow-hidden transition-transform duration-300 group-hover:scale-110 ${
                            imageLoaded ? 'opacity-100' : 'opacity-0'
                        }`}
                    />
                    <div className="absolute min-h-[84px] bottom-0 left-0 right-0 bg-gradient-custom rounded-t-lg p-2 flex flex-col justify-end">
                        <h3 className="font-serif line-clamp-2 capitalize overflow-ellipsis font-medium text-xs sm:text-sm leading-tight tracking-tight text-[#181717] mb-auto">
                            {name}
                        </h3>
                        <div className="flex justify-between items-center mt-2">
                            <p className="font-semibold font-serif text-lg leading-tight tracking-tight text-[#181717]">
                                {price}
                            </p>
                            <button
                                className="w-8 h-8 bg-[#3B5345] rounded-full flex items-center justify-center"
                                onClick={handleCartClick}
                            >
                                <CartIcon />
                            </button>
                        </div>
                    </div>
                </div>
            </div>
            {isModalOpen && (
                <NotificationProvider>
                    <ProductModal
                        productData={{ product }}
                        isOpen={isModalOpen}
                        onClose={() => setIsModalOpen(false)}
                    />
                </NotificationProvider>
            )}
        </>
    );
});

const CartIcon = memo(() => (
    <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
        <rect width="32" height="32" rx="16" fill="#3B5345"/>
        <path d="M12.5715 12.7009V11.9618C12.5715 10.2476 13.9506 8.56375 15.6649 8.40375C17.7068 8.20565 19.4287 9.81327 19.4287 11.8171V12.8685" stroke="white" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round"/>
        <path d="M13.7143 23.619H18.2858C21.3486 23.619 21.8972 22.3923 22.0572 20.899L22.6286 16.3276C22.8343 14.4685 22.301 12.9523 19.0477 12.9523H12.9524C9.69909 12.9523 9.16576 14.4685 9.37147 16.3276L9.9429 20.899C10.1029 22.3923 10.6515 23.619 13.7143 23.619Z" stroke="white" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round"/>
        <path d="M18.6631 15.9999H18.67" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
        <path d="M13.3292 15.9999H13.336" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
));

ProductItem.displayName = "ProductItem";
CartIcon.displayName = "CartIcon";

export default ProductItem;

ProductList.jsx:
'use client'
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useInView } from 'react-intersection-observer';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import Image from 'next/image';
import ProductItem from './ProductItem';
import ProductLoading from '@/components/ProductLoading';
import NoProductsFound from '@/components/NoProductsFound';
import { usePathname } from 'next/navigation';
import Loading from "./Loading"
const SearchComponent = dynamic(() => import('@/components/SearchComponent'), {
    ssr: false,
    loading: () => <SearchComponentSkeleton />,
});

export default function ProductList({
                                        initialProducts,
                                        hasMore,
                                        initialPage,
                                        initialQuery,
                                    }) {
    const [products, setProducts] = useState(initialProducts);
    const [page, setPage] = useState(initialPage);
    const [loading, setLoading] = useState(false);
    const [noMoreProducts, setNoMoreProducts] = useState(!hasMore);
    const [query, setQuery] = useState(initialQuery);
    const [isNavigating, setIsNavigating] = useState(false);
    const { ref, inView } = useInView({ threshold: 0 });
    const router = useRouter();
    const searchParams = useSearchParams();
    const pathname = usePathname();

    const loadMoreProducts = useCallback(() => {
        if (loading || noMoreProducts) return;

        setLoading(true);
        const nextPage = page + 1;
        const params = new URLSearchParams(Array.from(searchParams.entries()));
        params.set('page', nextPage.toString());
        router.push(`/products?${params.toString()}`, { scroll: false });
    }, [page, loading, noMoreProducts, searchParams, router]);
    useEffect(() => {
        if (inView && !noMoreProducts) {
            loadMoreProducts();
        }
    }, [inView, loadMoreProducts, noMoreProducts]);

    const handleSearch = useCallback(
        (newQuery) => {
            setQuery(newQuery);
            const params = new URLSearchParams(Array.from(searchParams.entries()));
            params.set('query', newQuery);
            params.set('page', '1');
            router.push(`/products?${params.toString()}`, { scroll: false });
        },
        [searchParams, router]
    );

    useEffect(() => {
        setProducts(initialProducts);
        setPage(initialPage);
        setNoMoreProducts(!hasMore);
        setQuery(initialQuery);
        setLoading(false);
    }, [initialProducts, initialPage, hasMore, initialQuery]);

    const memoizedProducts = useMemo(
        () =>
            products.map((product) => ({
                ...product,
                cheapestPrice: getCheapestPrice(product),
            })),
        [products]
    );

    const handleProductClick = useCallback(
        (product) => {
            setIsNavigating(true);
            router.push(`/products/${product.id}`);
        },
        [router]
    );

    useEffect(() => {
        // This effect will run when the pathname changes
        setIsNavigating(false);
    }, [pathname]);
    if (isNavigating) {
        return <Loading />
    }
    return (
        <>
            <header className="flex items-center mb-6">
                <Link prefetch={false} className="relative z-20" href={'/'}>
                    <Image
                        src="https://storage.naayiq.com/resources/arrow-left.svg"
                        width={40}
                        unoptimized={true}
                        height={40}
                        alt="left"
                        priority
                    />
                </Link>
                <h1 className="text-3xl z-10 text-[#181717] left-0 right-0 absolute font-sans text-center font-medium">
                    Products
                </h1>
            </header>
            <SearchComponent query={query} setQuery={handleSearch} />
            {memoizedProducts.length > 0 ? (
                <>
                    <div className="grid grid-cols-2 w-full justify-between gap-4 sm:gap-6 ssm3:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
                        {memoizedProducts.map((product) => (
                            <div
                                key={product.id}
                                onClick={() => handleProductClick(product)}
                                className="cursor-pointer"
                            >
                                <ProductItem
                                    id={product.id}
                                    name={product.name}
                                    product={product}
                                    handleClick={() => handleProductClick(product)}
                                    price={formatPrice(product.cheapestPrice)}
                                    imageUrl={product.images[0]}
                                />
                            </div>
                        ))}
                    </div>
                </>
            ) : (
                <NoProductsFound />
            )}
            {loading && <ProductLoading />}
            {!loading && !noMoreProducts && (
                <div ref={ref} style={{ height: '20px' }}></div>
            )}
        </>
    );
}

const getCheapestPrice = (product) => {
    const prices = [
        product.price && product.price !== '0.00'
            ? parseFloat(product.price)
            : Infinity,
        ...(product.sizes?.map((size) => parseFloat(size.price)) || []),
        ...(product.colors?.flatMap((color) => [
            parseFloat(color.price),
            ...(color.sizes?.map((size) => parseFloat(size.price)) || []),
        ]) || []),
    ].filter((price) => !isNaN(price) && isFinite(price));

    return prices.length > 0 ? Math.min(...prices) : null;
};

const formatPrice = (price) => {
    if (price == null || price === Infinity) return 'N/A';
    return `${price >= 10000 ? price.toLocaleString() : price} IQD`;
};

const SearchComponentSkeleton = () => (
    <div className="flex mb-4 items-center space-x-2 animate-pulse">
        <div className="h-10 bg-gray-200 rounded-md flex-grow"></div>
        <div className="h-10 w-[5.5rem] bg-gray-200 rounded-md"></div>
    </div>
);