during the products loading show skeleton loading... for each product... also fetch product by product instead of fetching all of them at once...
Wishlist.jsx:
'use client';

import React, { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { CircleArrowLeft, X } from "lucide-react";
import Loading from "@/components/Loading";
import WishlistItem from "@/components/WishlistItem";

export const getToken = () => {
    if (typeof window === 'undefined') return null;
    return localStorage.getItem('token'); // Adjust based on your token storage strategy
};
export const fetchWithAuth = async (url, options = {}) => {
    const token = getToken();
    if (!token) {
        throw new Error('No authentication token found');
    }

    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
        ...options.headers,
    };

    const response = await fetch(url, {
        ...options,
        headers,
    });

    if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'API request failed');
    }

    return response.json();
};
export default function Wishlist() {
    const router = useRouter();
    const [wishlistProductIds, setWishlistProductIds] = useState([]);
    const [wishlistProducts, setWishlistProducts] = useState([]);
    const [isLoading, setIsLoading] = useState(true);
    const [isFetchingProducts, setIsFetchingProducts] = useState(false);
    const [error, setError] = useState(null);

    // Fetch Wishlist IDs
    const fetchWishlistIds = useCallback(async () => {
        try {
            const data = await fetchWithAuth(`${process.env.NEXT_PUBLIC_API}/wishlist`);
            setWishlistProductIds(data.wishlist); // Assuming data.wishlist is an array of IDs
        } catch (err) {
            console.error('Error fetching wishlist IDs:', err);
            setError(err.message);
        }
    }, []);

    // Fetch Product Details
    const fetchWishlistProducts = useCallback(async (ids) => {
        setIsFetchingProducts(true);
        try {
            const productPromises = ids.map(id =>
                fetchWithAuth(`${process.env.NEXT_PUBLIC_API}/products/${id.id}`)
                    .then(data => data.product) // Extract the product object
                    .catch(err => {
                        console.error(`Error fetching product with ID ${id}:`, err);
                        return null; // Return null for failed fetches
                    })
            );

            const products = await Promise.all(productPromises);
            const validProducts = products.filter(product => product !== null);
            setWishlistProducts(validProducts);
        } catch (err) {
            console.error('Error fetching wishlist products:', err);
            setError('Failed to load some wishlist items.');
        } finally {
            setIsFetchingProducts(false);
            setIsLoading(false);
        }
    }, []);

    // Initial Data Fetch
    useEffect(() => {
        const initializeWishlist = async () => {
            await fetchWishlistIds();
        };
        initializeWishlist();
    }, [fetchWishlistIds]);

    // Fetch Products Once IDs are Available
    useEffect(() => {
        if (wishlistProductIds.length > 0) {
            fetchWishlistProducts(wishlistProductIds);
        } else {
            setIsLoading(false); // No items in wishlist
        }
    }, [wishlistProductIds, fetchWishlistProducts]);

    // Handle Remove Item
    const handleRemoveItem = async (productId) => {
        // Optimistically remove the item from UI
        const updatedProductIds = wishlistProductIds.filter(id => id !== productId);
        setWishlistProductIds(updatedProductIds);
        setWishlistProducts(prevProducts => prevProducts.filter(p => p.id !== productId));

        try {
            await fetchWithAuth(`${process.env.NEXT_PUBLIC_API}/wishlist/${productId}`, {
                method: 'DELETE',
            });
            // Successfully removed on server
        } catch (err) {
            console.error('Error removing item:', err);
            setError('Failed to remove item from wishlist.');
            // Revert UI changes
            setWishlistProductIds(prevIds => [...prevIds, productId]);
            // Optionally, re-fetch products to ensure consistency
            await fetchWishlistIds();
        }
    };

    return (
        <>
            <header className="flex items-center mb-6">
                <CircleArrowLeft
                    size={52}
                    strokeWidth={0.7}
                    onClick={() => router.back()}
                    className="p-2 relative z-20 cursor-pointer"
                />
                <h1 className="text-3xl z-10 text-[#181717] left-0 right-0 absolute font-sans text-center font-medium">
                    Wishlist
                </h1>
            </header>
            <main className="flex-grow flex flex-col items-center">
                {isLoading ? (
                    <Loading />
                ) : error ? (
                    <div className="text-red-500 text-center">{error}</div>
                ) : wishlistProducts.length === 0 ? (
                    <>
                        <div className="relative w-full h-[33vh]">
                            <img
                                src="https://storage.naayiq.com/resources/empty_wishlist.gif"
                                alt="Empty wishlist"
                                className="object-contain w-full h-full"
                            />
                        </div>
                        <section>
                            <p className="font-serif text-3xl text-center mb-2 font-semibold">Wishlist is empty!</p>
                            <p className="font-serif text-lg text-center leading-none">Tap the Heart Button to start</p>
                            <p className="font-serif text-lg text-center">saving your favorite items.</p>
                            <Link href="/" className="min-h-[56px] mt-4 items-center font-serif px-3 sm:px-6 bg-[#3B5345] flex justify-center text-white text-xl rounded-lg">
                                Start Browsing
                            </Link>
                        </section>
                    </>
                ) : (
                    <div className="w-full max-w-md">
                        {isFetchingProducts && <Loading />}
                        {wishlistProducts.map(product => (
                            <WishlistItem
                                key={product.id}
                                product={product}
                                onRemove={handleRemoveItem}
                            />
                        ))}
                    </div>
                )}
            </main>
        </>
    );
}
WishlistItem.jsx:
'use client';

import React from "react";
import { useRouter } from "next/navigation";
import { X } from "lucide-react";

const WishlistItem = ({ product, onRemove }) => {
    const router = useRouter();

    const handleShoppingBagClick = () => {
        router.push(`/products/${product.id}`);
    };

    // Function to calculate the cheapest price
    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 cheapestPrice = getCheapestPrice(product);

    const formattedPrice = cheapestPrice !== null
        ? `${cheapestPrice >= 10000 ? cheapestPrice.toLocaleString() : cheapestPrice} IQD`
        : 'N/A';

    return (
        <div className="bg-white rounded-lg p-4 flex items-stretch justify-between mb-4 shadow-sm">
            {/* Product Details */}
            <div className="flex-grow flex flex-col justify-center">
                <h3 className="font-serif font-medium text-base text-left">{product.name}</h3>
                <p className="font-serif text-lg text-left text-gray-600">
                    {formattedPrice}
                </p>
            </div>

            {/* Action Buttons */}
            <div className="flex flex-col justify-center items-center space-y-2">
                <button onClick={() => onRemove(product.id)} className="p-1 hover:text-red-500 transition">
                    <X size={24} />
                </button>
                <button onClick={handleShoppingBagClick} className="bg-[#3B5345] rounded-full p-2 hover:bg-[#2e4234] transition">
                    {/* ShoppingBag Icon */}
                    <svg width="24" height="24" fill="white" viewBox="0 0 24 24">
                        <path d="M6 2L5 6H19L18 2H6ZM5 8V20C5 21.1 5.9 22 7 22H17C18.1 22 19 21.1 19 20V8H5Z" />
                    </svg>
                </button>
            </div>
        </div>
    );
};

export default WishlistItem;
Make it more smooth and stable and compatiable across different browsers and devices (especially mobile)