import React, { useState, useEffect, useRef, Fragment } from "react"; import { Dialog, Transition } from "@headlessui/react"; import { shirt, artPrint } from "../data/icons"; import { useShoppingCart } from "use-shopping-cart"; import { toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; const ToastCheckIcon = () => { return ( <svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="#5cd4ac" strokeWidth={2} > <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /> </svg> ); }; const NftGridItem = (props) => { const { asset } = props; const { addItem } = useShoppingCart(); const [modalOpen, setModalOpen] = useState(false); const cancelButtonRef = useRef(null); const [cartButtonClicked, setCartButtonClicked] = useState(false); const handleCartButtonClicked = () => { setCartButtonClicked(true); setTimeout(() => { setCartButtonClicked(false); }, 3000); }; const [availableProducts, setAvailableProducts] = useState([ { code: "shirt", price_id: "price_1KyHUJAkSLqoMR6qTyXE8z8E", sku: "pc-shirt-0001", name: "T-Shirt", icon: shirt, available_sizes: ["XS", "S", "M", "L", "XL", "2XL"], available_colors: ["Black", "White", "Grey", "Blue", "Red", "Green"], price: 3000, cart_data: { name: "Shirt Test", description: "A test shirt", id: "price_1KyHUJAkSLqoMR6qTyXE8z8E", currency: "USD", image: asset.image_url, product_metadata: { size: "XS", color: "Black", }, }, }, { code: "print", price_id: "price_1KyHUlAkSLqoMR6qLnzQqdP8", sku: "pc-print-0001", name: "Fine Art Print", icon: artPrint, available_sizes: ["O/S"], available_colors: ["O/S"], price: 10000, cart_data: { name: "Shirt Test", description: "A test shirt", id: "price_1KyHUJAkSLqoMR6qTyXE8z8E", currency: "USD", image: asset.image_url, product_metadata: { size: "O/S", color: "O/S", }, }, }, ]); const [availableColors, setAvailableColors] = useState( availableProducts[0].available_colors ); const [availableSizes, setAvailableSizes] = useState( availableProducts[0].available_sizes ); const [selectedProductIndex, setSelectedProductIndex] = useState(0); const [selectedColorIndex, setSelectedColorIndex] = useState(0); const [selectedSizeIndex, setSelectedSizeIndex] = useState(0); const [selectedProduct, setSelectedProduct] = useState(availableProducts[0]); const [selectedColor, setSelectedColor] = useState(availableColors[0]); const [selectedSize, setSelectedSize] = useState(availableSizes[0]); const sanitize = (value) => { if (!value) return ""; return value.replace(/[\W_]+/g, ""); }; const formatSku = (collection, name, code, size, color) => { let sku = `pc-${sanitize(collection)}-${sanitize(name)}-${sanitize(code)}-${sanitize(color)}-${sanitize(size)}`; return sku.toLowerCase(); }; // The cart data contains information about the currently selected item // This is the data that will be added to the cart object upon clicking the Add to Cart button const [cartData, setCartData] = useState({ name: `${asset.name} ${selectedProduct.name}, ${selectedColor}, Size ${selectedSize}`, description: `A ${selectedColor} ${selectedProduct.name} in Size ${selectedSize} featuring the artwork of ${asset.name} from the ${asset.collection.name} collection.`, sku: formatSku( asset.collection.name, asset.name, availableProducts[0].code, availableSizes[0], availableColors[0] ), currency: "USD", image: asset.image_url, price: selectedProduct.price, product_metadata: { code: selectedProduct.code, size: selectedSize, color: selectedColor, }, }); const handleResize = () => { let imageContainers = document.querySelectorAll(".image-container"); for (let i = 0; i < imageContainers.length; i++) { let imageContainer = imageContainers[i]; if (imageContainer) { imageContainer.setAttribute( "style", `height:${imageContainer.offsetWidth}px` ); } let image = imageContainer.querySelector(".asset-image"); if (image) { image.setAttribute( "style", `max-height:${imageContainer.offsetWidth - 24}px` ); } } }; const handleOpenModal = () => { setModalOpen(true); }; const handleCloseModal = () => { setModalOpen(false); }; const handleSelectProduct = (selectedIndex) => { setSelectedProductIndex(selectedIndex); setSelectedProduct(availableProducts[selectedIndex]); // Update sizes to reflect the newly selected product setAvailableSizes(availableProducts[selectedIndex].available_sizes); setSelectedSizeIndex(0); setSelectedSize(availableProducts[selectedIndex].available_sizes[0]); // Update colors to reflect the newly selected product setAvailableColors(availableProducts[selectedIndex].available_colors); setSelectedColorIndex(0); setSelectedColor(availableProducts[selectedIndex].available_colors[0]); // Update the cart data setCartData({ name: `${asset.name} ${availableProducts[selectedIndex].name}, ${availableProducts[selectedIndex].available_colors[0]}, Size ${availableProducts[selectedIndex].available_sizes[0]}`, description: `A ${availableProducts[selectedIndex].available_colors[0]} ${availableProducts[selectedIndex].name} in Size ${availableProducts[selectedIndex].available_sizes[0]} featuring the artwork of ${asset.name} from the ${asset.collection.name} collection.`, sku: formatSku( asset.collection.name, asset.name, availableProducts[selectedIndex].code, availableProducts[selectedIndex].available_sizes[0], availableProducts[selectedIndex].available_colors[0] ), currency: "USD", image: asset.image_url, price: availableProducts[selectedIndex].price, product_metadata: { code: availableProducts[selectedIndex].code, size: availableProducts[selectedIndex].available_sizes[0], color: availableProducts[selectedIndex].available_colors[0], }, }); }; const handleSelectSize = (selectedIndex) => { setSelectedSizeIndex(selectedIndex); setSelectedSize(availableSizes[selectedIndex]); // Update the cart data setCartData({ name: `${asset.name} ${selectedProduct.name}, ${selectedColor}, Size ${availableSizes[selectedIndex]}`, description: `A ${selectedColor} ${selectedProduct.name} in Size ${availableSizes[selectedIndex]} featuring the artwork of ${asset.name} from the ${asset.collection.name} collection.`, sku: formatSku( asset.collection.name, asset.name, selectedProduct.code, availableSizes[selectedIndex], selectedColor ), currency: "USD", image: asset.image_url, price: selectedProduct.price, product_metadata: { code: selectedProduct.code, size: availableSizes[selectedIndex], color: selectedColor, }, }); }; const handleSelectColor = (selectedIndex) => { setSelectedColorIndex(selectedIndex); setSelectedColor(availableColors[selectedIndex]); // Update the cart data setCartData({ name: `${asset.name} ${selectedProduct.name}, ${availableColors[selectedIndex]}, Size ${selectedSize}`, description: `A ${availableColors[selectedIndex]} ${selectedProduct.name} in Size ${selectedSize} featuring the artwork of ${asset.name} from the ${asset.collection.name} collection.`, sku: formatSku( asset.collection.name, asset.name, selectedProduct.code, selectedSize, availableColors[selectedIndex] ), currency: "USD", image: asset.image_url, price: selectedProduct.price, product_metadata: { code: selectedProduct.code, size: selectedSize, color: availableColors[selectedIndex], }, }); }; // Listen for changes to the window size // This allows us to dynamically resize images, etc. based on current window size useEffect(() => { handleResize(); window.addEventListener("resize", handleResize); }, []); const modalMarkup = ( <> <Transition.Root show={modalOpen} as={Fragment}> <Dialog as="div" className="fixed inset-0 z-10 overflow-y-auto" initialFocus={cancelButtonRef} onClose={setModalOpen} > <div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0"> <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0" enterTo="opacity-100" leave="ease-in duration-200" leaveFrom="opacity-100" leaveTo="opacity-0" > <Dialog.Overlay className="fixed inset-0 bg-zinc-700 bg-opacity-75 transition-opacity" /> </Transition.Child> {/* This element is to trick the browser into centering the modal contents. */} <span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true" > ​ </span> <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterTo="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > <div className="relative inline-block w-full transform overflow-hidden rounded-lg bg-zinc-900 text-left align-bottom shadow-xl transition-all sm:my-8 sm:max-w-lg sm:align-middle md:max-w-2xl lg:max-w-4xl xl:max-w-6xl"> <div className="relative rounded-lg bg-zinc-900 shadow"> <div className="relativemx-auto p-12"> <div className=" flex flex-wrap items-center justify-center"> <div className="absolute top-5 right-5 z-50 cursor-pointer text-zinc-700 hover:text-zinc-500" onClick={handleCloseModal} > <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} > <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /> </svg> </div> <div className="image-container align-center relative flex justify-center p-3 lg:w-1/2"> <div className="my-auto max-h-full rounded-2xl"> <img className="asset-image rounded-xl object-contain object-center" src={asset.image_url} alt={asset.name} /> </div> </div> <div className="mt-6 w-full px-6 lg:mt-0 lg:w-1/2 lg:py-6 lg:pl-10"> <h2 className="title-font my-2 mr-2 text-sm font-bold tracking-widest text-[#5cd4ac]"> {asset.collection.name.toUpperCase()} </h2> <h1 className="title-font my-2 mr-2 text-3xl font-medium text-white"> {asset.name} </h1> <p className="my-2 mr-2 leading-relaxed text-zinc-100"> {asset.description} </p> <hr className="mr-2 border-zinc-500" /> <h2 className="title-font mt-4 mb-2 mr-2 text-sm font-bold text-white"> Choose your product </h2> <div className="flex flex-row items-center justify-center"> {availableProducts.map((product, index) => { const active = selectedProductIndex == index ? true : false; return ( <div className={`mr-2 flex w-full cursor-pointer select-none items-center justify-center rounded-lg border-2 border-zinc-400 py-2 text-sm text-white ${ active ? "border-[#5cd4ac] bg-[#5cd4ac]" : "hover:border-white" }`} onClick={() => handleSelectProduct(index)} > {product.icon} </div> ); })} </div> <h2 className="title-font mt-4 mb-2 text-sm font-bold text-white"> Choose your size </h2> <div className="flex flex-row items-center justify-center"> {availableSizes.map((size, index) => { const active = selectedSizeIndex == index ? true : false; return ( <div className={`mr-2 flex w-full cursor-pointer select-none items-center justify-center rounded-lg border-2 border-zinc-400 py-2 text-sm text-white ${ active ? "border-[#5cd4ac] bg-[#5cd4ac]" : "hover:border-white" }`} onClick={() => handleSelectSize(index)} > {size} </div> ); })} </div> <h2 className="title-font mt-4 mb-2 text-sm font-bold text-white"> Choose your color </h2> <div className="mb-2 flex flex-row items-center justify-center"> {availableColors.map((color, index) => { const active = selectedColorIndex == index ? true : false; return ( <div className={`mr-2 flex w-full cursor-pointer select-none items-center justify-center rounded-lg border-2 border-zinc-400 py-2 text-sm text-white ${ active ? "border-[#5cd4ac] bg-[#5cd4ac]" : "hover:border-white" }`} onClick={() => handleSelectColor(index)} > {color} </div> ); })} </div> <div className="mt-8 flex flex-row items-center justify-center"> {!cartButtonClicked ? ( <button className="snipcart-add-item mr-2 flex w-full cursor-pointer select-none items-center justify-center rounded-lg border-2 border-[#5cd4ac] bg-[#5cd4ac] py-2 text-base text-white hover:border-[#35c897] hover:bg-[#35c897]" onClick={() => { addItem(cartData); handleCartButtonClicked(); notify(); }} > Add to cart </button> ) : ( <span className="mr-2 flex w-full cursor-pointer select-none items-center justify-center rounded-lg border-2 border-[#5cd4ac] bg-[#5cd4ac] py-2 text-base text-white hover:border-[#35c897] hover:bg-[#35c897]"> <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} > <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /> </svg> Added! </span> )} </div> </div> </div> </div> </div> </div> </Transition.Child> </div> </Dialog> </Transition.Root> </> ); const notify = () => { toast.success("Added to cart!", { icon: ToastCheckIcon }); }; return ( <> {modalMarkup} <div className="relative max-w-sm cursor-pointer overflow-hidden rounded-2xl border-2 border-transparent bg-zinc-700 bg-opacity-30 backdrop-blur-lg backdrop-filter hover:border-[#5cd4ac]" onClick={() => setModalOpen(true)} > <div className="image-container align-center relative flex justify-center p-3"> <div className="my-auto max-h-full rounded-2xl"> <img className="asset-image rounded-xl object-contain object-center" src={asset.image_url} alt={asset.name} /> </div> </div> <div className="px-4 py-4"> <div className="mb-2 text-base font-bold text-white"> {asset.name} </div> <p className="text-xs font-bold text-gray-400"> {asset.collection.name} </p> </div> </div> </> ); }; export default NftGridItem;