diff options
| author | HATEC\SPVDEV001 <tri.susilo@altama.co.id> | 2023-07-24 15:33:17 +0700 |
|---|---|---|
| committer | HATEC\SPVDEV001 <tri.susilo@altama.co.id> | 2023-07-24 15:33:17 +0700 |
| commit | 2de322571bad7490baaf439fa2bb12124c646bb5 (patch) | |
| tree | d7c4151eb2bf83b85cd0532d45dedd02aa06694e /src/lib/cart | |
| parent | 8b8ab7680be37e84d0dc6cc1683fe4996f7cc8c1 (diff) | |
| parent | d4f3cce1b07c5d4f75892ffc49c8dbbbbb58922f (diff) | |
Merge branch 'master' into CR/widget_WA
Diffstat (limited to 'src/lib/cart')
| -rw-r--r-- | src/lib/cart/api/CartApi.js | 12 | ||||
| -rw-r--r-- | src/lib/cart/components/Cart.jsx | 691 | ||||
| -rw-r--r-- | src/lib/cart/components/CartOld.jsx | 497 |
3 files changed, 1025 insertions, 175 deletions
diff --git a/src/lib/cart/api/CartApi.js b/src/lib/cart/api/CartApi.js index 7683a935..038479f8 100644 --- a/src/lib/cart/api/CartApi.js +++ b/src/lib/cart/api/CartApi.js @@ -1,6 +1,7 @@ import odooApi from '@/core/api/odooApi' +import { getAuth } from '@/core/utils/auth' -const CartApi = async ({ variantIds }) => { +export const CartApi = async ({ variantIds }) => { if (variantIds) { const dataCart = await odooApi('GET', `/api/v2/product_variant/${variantIds}`) return dataCart @@ -8,4 +9,11 @@ const CartApi = async ({ variantIds }) => { return null } -export default CartApi +// export default CartApi + +export const getCartApi = async () => { + const id = getAuth()?.id + const cart = await odooApi('GET', `/api/v1/user/${id}/cart`) + + return cart +}
\ No newline at end of file diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx index c02f976f..efbcf76b 100644 --- a/src/lib/cart/components/Cart.jsx +++ b/src/lib/cart/components/Cart.jsx @@ -4,7 +4,7 @@ import Image from '@/core/components/elements/Image/Image' import NextImage from 'next/image' import currencyFormat from '@/core/utils/currencyFormat' import { useEffect, useState } from 'react' -import { deleteItemCart, getItemCart, updateItemCart } from '@/core/utils/cart' +import { addCart, deleteItemCart, getCartApi, updateItemCart } from '@/core/utils/cart' import { CheckIcon, TrashIcon } from '@heroicons/react/24/outline' import { createSlug } from '@/core/utils/slug' import { useRouter } from 'next/router' @@ -17,12 +17,54 @@ import DesktopView from '@/core/components/views/DesktopView' import ProductCard from '@/lib/product/components/ProductCard' import productSearchApi from '@/lib/product/api/productSearchApi' import whatsappUrl from '@/core/utils/whatsappUrl' +import useAuth from '@/core/hooks/useAuth' +import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner' +import { getPromotionProgram } from '@/lib/promotinProgram/api/homepageApi' +import PromotionType from '@/lib/promotinProgram/components/PromotionType' import { gtagBeginCheckout } from '@/core/utils/googleTag' const Cart = () => { const router = useRouter() const [products, setProducts] = useState(null) - const { cart } = useCart({ enabled: !products }) + const [isLoading, setIsLoading] = useState(true) + const auth = useAuth() + + const [cart, setCart] = useState(null) + + useEffect(() => { + if (!auth) return + }, [auth]) + + const getCart = async () => { + const listCart = await getCartApi() + setCart(listCart) + } + + useEffect(() => { + getCart() + }, []) + + useEffect(() => { + if (cart) { + const processProducts = async () => { + const updatedProducts = await Promise.all( + cart.products.map(async (product) => { + const id = product.id + const program = await getPromotionProgram({ id }) + const isPromo = program.length > 0 ? 1 : 0 + return { + ...product, + isPromo + } + }) + ) + setProducts(updatedProducts) + } + // processProducts() + setProducts(cart.products) + setIsLoading(false) + } + }, [cart]) const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0) const [totalTaxAmount, setTotalTaxAmount] = useState(0) @@ -31,21 +73,10 @@ const Cart = () => { const [deleteConfirmation, setDeleteConfirmation] = useState(null) const [productRecomendation, setProductRecomendation] = useState(null) - - useEffect(() => { - if (cart.data && !products) { - const productsWithQuantity = cart.data.map((product) => { - const productInCart = getItemCart({ productId: product.id }) - if (!productInCart) return - return { - ...product, - quantity: productInCart.quantity, - selected: productInCart.selected - } - }) - setProducts(productsWithQuantity) - } - }, [cart, products]) + const [promotionType, setPromotionType] = useState(null) + const [variantId, setVariantId] = useState(null) + const [quantity, setQuantity] = useState(null) + const [promotionActiveId, setPromotionActiveId] = useState(null) useEffect(() => { if (!products) return @@ -55,13 +86,12 @@ const Cart = () => { let calculateTotalDiscountAmount = 0 for (const product of products) { if (product.quantity == '') continue - updateItemCart({ - productId: product.id, - quantity: product.quantity, - selected: product.selected - }) if (!product.selected) continue + + if (product.canBuy == false) { + toggleSelected(product.id) + } let priceBeforeTax = product.price.price / 1.11 calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity @@ -74,6 +104,24 @@ const Cart = () => { }, [products]) useEffect(() => { + const updateData = () => { + updateItemCart({ + productId: variantId, + quantity, + programLineId: promotionActiveId, + selected: true + }).then(() => { + getCart().then(() => { + if(promotionActiveId){ + setPromotionType(false) + } + }) + }) + } + updateData() + }, [promotionActiveId]) + + useEffect(() => { const LoadProductSimilar = async () => { const randProductIndex = Math.floor(Math.random() * products.length) const productLoad = await productSearchApi({ @@ -109,8 +157,16 @@ const Cart = () => { quantity = value != '' && value < 1 ? 1 : value break } - productsToUpdate[productIndex].quantity = quantity + let qty = quantity + productsToUpdate[productIndex].quantity = qty + setProducts([...productsToUpdate]) + addCart( + productId, + qty, + productsToUpdate[productIndex].selected, + productsToUpdate[productIndex].program?.id + ) } const toggleSelected = (productId) => { @@ -118,8 +174,16 @@ const Cart = () => { if (productIndex < 0) return let productsToUpdate = products - productsToUpdate[productIndex].selected = !productsToUpdate[productIndex].selected + let isSelected = !productsToUpdate[productIndex].selected + productsToUpdate[productIndex].selected = isSelected + setProducts([...productsToUpdate]) + addCart( + productId, + productsToUpdate[productIndex].quantity, + isSelected, + productsToUpdate[productIndex].program?.id + ) } const selectedProduct = () => { @@ -135,6 +199,13 @@ const Cart = () => { toast.success('Berhasil menghapus barang dari keranjang') } + const handlePopUpPromo = (variantId, quantity, promoId = null) => { + setPromotionType(true) + setVariantId(variantId) + setQuantity(quantity) + setPromotionActiveId(promoId) + } + const handleCheckout = () => { gtagBeginCheckout(products) router.push('/shop/checkout') @@ -173,6 +244,23 @@ const Cart = () => { </div> </BottomPopup> + <BottomPopup + className=' !h-[75%]' + title='Pakai Promo' + active={promotionType} + close={() => setPromotionType(false)} + > + <div className='flex mt-4'> + <PromotionType + isModal={true} + variantId={variantId} + setPromotionActiveId={setPromotionActiveId} + promotionActiveId={promotionActiveId} + quantity={quantity} + ></PromotionType> + </div> + </BottomPopup> + <MobileView> <div className='pt-4'> <div className='flex justify-between mb-4 px-4'> @@ -181,13 +269,13 @@ const Cart = () => { </div> <div className='flex flex-col gap-y-4 h-screen'> - {cart.isLoading && ( + {isLoading && ( <div className='flex justify-center my-4'> <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> </div> )} - {!cart.isLoading && (!products || products?.length == 0) && ( + {!isLoading && (!products || products?.length == 0) && ( <div className='px-4'> <Alert className='text-center my-2' type='info'> Keranjang belanja anda masih kosong @@ -195,85 +283,204 @@ const Cart = () => { </div> )} - {products?.map((product) => ( - <div key={product?.id} className='flex mx-4'> - <input - type='checkbox' - onClick={() => toggleSelected(product.id)} - checked={product?.selected} - className='mr-2 accent-danger-500 w-4' - /> - - <Link - href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)} - className='w-[30%] flex-shrink-0' - > - <Image - src={product?.parent?.image} - alt={product?.name} - className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md' - /> - </Link> - <div className='flex-1 px-2 text-caption-2'> - <Link - href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)} - className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' - > - {product?.parent?.name} - </Link> - <div className='text-gray_r-11 mt-1'> - {product?.code}{' '} - {product?.attributes.length > 0 ? `| ${product?.attributes.join(', ')}` : ''} - </div> - {product?.price?.discountPercentage > 0 && ( - <div className='flex gap-x-1 items-center mt-3'> - <div className='text-gray_r-11 line-through text-caption-2'> - {currencyFormat(product?.price?.price)} + {products && + products?.map((product) => ( + <> + {product.hasProgram > 0 && ( + <div className='flex gap-x-2 bg-yellow-100 p-2 items-center'> + {product.program ? ( + <> + <div className='border border-solid border-red-600 rounded-md p-1 text-caption-2 whitespace-nowrap'> + <span className='text-red-600'>{product.program.type.label}</span> + </div> + <div className='text-caption-2'> + {product.program.type.value == 'merchandise' ? ( + <>Selamat anda mendapatkan merchandise indoteknik.com</> + ) : ( + <> + Selamat! Pembelian anda hemat + <span className='text-red-600 font-semibold text-caption-2'> + {' '} + {currencyFormat(product.program?.totalSavings)} + </span> + </> + )} + </div> + </> + ) : ( + <> + <div className='flex border border-solid border-red-600 rounded-md p-1'> + <span className='text-red-600'>Promo</span> + </div> + <div className='flex'>Pilih Promo Yang Tersedia Bisa lebih Hemat</div> + </> + )} + <div + onClick={() => + handlePopUpPromo(product.id, product.quantity, product.program?.id) + } + className='ml-auto text-red-500 flex gap-x-0 cursor-pointer' + > + <div className='font-semibold text-caption-2 whitespace-nowrap'> + {' '} + Cek Promo + </div> + <div> + <svg + aria-hidden='true' + fill='none' + stroke='currentColor' + stroke-width='1.5' + viewBox='0 0 20 24' + className='h-5 w-5 font-semibold' + > + <path + d='M8.25 4.5l7.5 7.5-7.5 7.5' + stroke-linecap='round' + stroke-linejoin='round' + ></path> + </svg> + </div> </div> - <div className='badge-solid-red'>{product?.price?.discountPercentage}%</div> </div> )} - <div className='font-normal mt-1'> - {currencyFormat(product?.price?.priceDiscount)} - </div> - <div className='flex justify-between items-center mt-1'> - <div className='text-danger-500 font-medium'> - {currencyFormat(product?.price?.priceDiscount * product?.quantity)} - </div> - <div className='flex gap-x-1'> - <button - type='button' - className='btn-light px-2 py-1' - onClick={() => updateQuantity(1, product?.id, 'MINUS')} - disabled={product?.quantity == 1} - > - - - </button> - <input - className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center' - type='number' - value={product?.quantity} - onChange={(e) => updateQuantity(e.target.value, product?.id)} - onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')} + <div key={product?.id} className='flex mx-4 relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <input + type='checkbox' + onClick={() => toggleSelected(product.id)} + checked={product?.selected} + className='mr-2 accent-danger-500 w-4' + /> + + <Link + href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)} + className='w-[30%] flex-shrink-0' + > + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md' /> - <button - type='button' - className='btn-light px-2 py-1' - onClick={() => updateQuantity(1, product?.id, 'PLUS')} - > - + - </button> - <button - className='btn-red p-1 ml-1' - onClick={() => setDeleteConfirmation(product)} + </Link> + <div className='flex-1 px-2 text-caption-2'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' > - <TrashIcon className='w-4' /> - </button> + {product?.parent?.name} + </Link> + <div className='text-gray_r-11 mt-1'> + {product?.code}{' '} + {product?.attributes.length > 0 + ? `| ${product?.attributes.join(', ')}` + : ''} + </div> + {product?.price?.discountPercentage > 0 && ( + <div className='flex gap-x-1 items-center mt-3'> + <div className='text-gray_r-11 line-through text-caption-2'> + {currencyFormat(product?.price?.price)} + </div> + <div className='badge-solid-red'> + {product?.price?.discountPercentage}% + </div> + </div> + )} + <div className='font-normal mt-1'> + {currencyFormat(product?.price?.priceDiscount)} + </div> + <div className='flex justify-between items-center mt-1'> + <div className='text-danger-500 font-medium'> + {currencyFormat(product?.price?.priceDiscount * product?.quantity)} + </div> + <div className='flex gap-x-1'> + <button + type='button' + className='btn-light px-2 py-1' + onClick={() => updateQuantity(1, product?.id, 'MINUS')} + disabled={product?.quantity == 1} + > + - + </button> + <input + className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center' + type='number' + value={product?.quantity} + onChange={(e) => updateQuantity(e.target.value, product?.id)} + onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')} + /> + <button + type='button' + className='btn-light px-2 py-1' + onClick={() => updateQuantity(1, product?.id, 'PLUS')} + > + + + </button> + <button + className='btn-red p-1 ml-1' + onClick={() => setDeleteConfirmation(product)} + > + <TrashIcon className='w-4' /> + </button> + </div> + </div> </div> </div> - </div> - </div> - ))} + {product.program && + product.program.items && + product.program.items.map((item) => ( + <div key={item.id} className='flex mx-4 relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <input className='mr-2 accent-danger-500 w-4' /> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='w-[30%] flex-shrink-0' + > + <Image + src={item?.parent?.image} + alt={item?.name} + className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md' + /> + </Link> + <div className='flex-1 px-2 text-caption-2'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + > + <div className='px-2 text-left mt-2'> + <div className='mb-2'> + <span className='border border-solid border-red-600 rounded-md p-1 text-red-600'> + {product.program.type.label} + </span> + </div> + <div>{item.name}</div> + </div> + </Link> + {item.price?.discountPercentage > 0 && ( + <div className='flex gap-x-1 items-center mt-3 ml-3'> + <div className='text-gray_r-11 line-through text-caption-2'> + {currencyFormat(item?.price?.price)} + </div> + </div> + )} + <div className='text-danger-500 font-medium mt-1 ml-3'>Gratis</div> + </div> + </div> + ))} + </> + ))} <div className='sticky bottom-0 left-0 w-full p-4 mt-auto border-t border-gray_r-6 bg-white'> <div className='flex flex-col gap-y-3 mb-4'> @@ -330,105 +537,240 @@ const Cart = () => { </tr> </thead> <tbody> - {cart.isLoading && ( + {isLoading && ( <tr> <td colSpan={6}> - <div className='flex justify-center my-2'> - <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> </div> </td> </tr> )} - {!cart.isLoading && (!products || products?.length == 0) && ( + {!isLoading && (!products || products?.length == 0) && ( <tr> <td colSpan={6}>Keranjang belanja anda masih kosong</td> </tr> )} {products && products?.map((product) => ( - <tr key={product.id}> - <td> - <input - type='checkbox' - onClick={() => toggleSelected(product.id)} - checked={product?.selected} - className='accent-danger-500 w-4' - /> - </td> - <td className='flex'> - <Link - href={createSlug( - '/shop/product/', - product?.parent.name, - product?.parent.id - )} - className='w-[20%] flex-shrink-0' - > - <Image - src={product?.parent?.image} - alt={product?.name} - className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md' + <> + {product.hasProgram && ( + <tr className='!border-b-0 pb-0'> + <td colSpan={6}> + <div className='flex gap-x-2 bg-yellow-100 p-2 items-center'> + {product.program ? ( + <> + <div className='flex border border-solid border-red-600 rounded-md p-1'> + <span className='text-red-600'> + {product.program.type.label} + </span> + </div> + <div className='flex'> + {product.program.type.value == 'merchandise' ? ( + <>Selamat anda mendapatkan merchandise indoteknik.com</> + ) : ( + <> + Selamat! Pembelian anda lebih hemat + <span className='text-red-600 font-semibold ml-2'> + {' '} + {currencyFormat(product.program?.totalSavings)} + </span> + </> + )} + </div> + </> + ) : ( + <> + <div className='flex border border-solid border-red-600 rounded-md p-1'> + <span className='text-red-600'>Promo</span> + </div> + <div className='flex'> + Pilih Promo Yang Tersedia Bisa lebih Hemat + </div> + </> + )} + <div + onClick={() => + handlePopUpPromo( + product.id, + product.quantity, + product.program?.id + ) + } + className='ml-auto text-red-500 flex gap-x-1 cursor-pointer' + > + <div className='font-semibold'> Cek Promo</div> + <div> + <svg + aria-hidden='true' + fill='none' + stroke='currentColor' + stroke-width='1.5' + viewBox='0 0 20 24' + className='h-5 w-5 font-semibold' + > + <path + d='M8.25 4.5l7.5 7.5-7.5 7.5' + stroke-linecap='round' + stroke-linejoin='round' + ></path> + </svg> + </div> + </div> + </div> + </td> + </tr> + )} + <tr + key={product.id} + className={`${product.hasProgram ? '!border-t-0 !border-b-0' : ''}`} + > + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <input + type='checkbox' + onClick={() => toggleSelected(product.id)} + checked={product?.selected} + className='accent-danger-500 w-4' /> - </Link> - <div className='px-2 text-left'> + </td> + <td className='flex relative'> + <ComponentCanBuy canBuy={product.canBuy} /> <Link href={createSlug( '/shop/product/', product?.parent.name, product?.parent.id )} - className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + className='w-[20%] flex-shrink-0' > - {product?.parent?.name} + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md' + /> </Link> - <div className='text-gray_r-11 mt-2'> - {product?.code}{' '} - {product?.attributes.length > 0 - ? `| ${product?.attributes.join(', ')}` - : ''} - </div> - </div> - </td> - <td> - <input - className='form-input w-16 py-2 text-center bg-gray_r-1' - type='number' - value={product?.quantity} - onChange={(e) => updateQuantity(e.target.value, product?.id)} - onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')} - /> - </td> - <td> - {product?.price?.discountPercentage > 0 && ( - <div className='flex gap-x-1 items-center justify-center mt-3'> - <div className='text-gray_r-11 line-through text-caption-1'> - {currencyFormat(product?.price?.price)} + <div className='px-2 text-left'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + > + {product?.parent?.name} + </Link> + <div className='text-gray_r-11 mt-2'> + {product?.code}{' '} + {product?.attributes.length > 0 + ? `| ${product?.attributes.join(', ')}` + : ''} </div> - <div className='badge-solid-red'> - {product?.price?.discountPercentage}% + </div> + </td> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <input + className='form-input w-16 py-2 text-center bg-gray_r-1' + type='number' + value={product?.quantity} + onChange={(e) => updateQuantity(e.target.value, product?.id)} + onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')} + /> + </td> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + {product?.price?.discountPercentage > 0 && ( + <div className='flex gap-x-1 items-center justify-center mt-3'> + <div className='text-gray_r-11 line-through text-caption-1'> + {currencyFormat(product?.price?.price)} + </div> + <div className='badge-solid-red'> + {product?.price?.discountPercentage}% + </div> </div> + )} + <div className='font-normal mt-1'> + {currencyFormat(product?.price?.priceDiscount)} </div> - )} - <div className='font-normal mt-1'> - {currencyFormat(product?.price?.priceDiscount)} - </div> - </td> - <td> - <div className='text-danger-500 font-medium'> - {currencyFormat(product?.price?.priceDiscount * product?.quantity)} - </div> - </td> - <td> - <div className='flex justify-center items-center h-full'> - <button - className='btn-red p-1 ml-1' - onClick={() => setDeleteConfirmation(product)} - > - <TrashIcon className='w-4' /> - </button> - </div> - </td> - </tr> + </td> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <div className='text-danger-500 font-medium'> + {currencyFormat(product?.price?.priceDiscount * product?.quantity)} + </div> + </td> + <td> + <div className='flex justify-center items-center h-full'> + <button + className='btn-red p-1 ml-1' + onClick={() => setDeleteConfirmation(product)} + > + <TrashIcon className='w-4' /> + </button> + </div> + </td> + </tr> + {product?.program?.items && ( + <tr key={product.program.id} className='!border-t-0'> + {product.program.items.map((item) => ( + <> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + </td> + <td className='flex relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <div className='w-[20%] flex-shrink-0'> + <Image + src={item.parent.image} + alt={item.name} + className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md' + /> + </div> + <div className='px-2 text-left'> + <div className=''> + <span className='border border-solid border-red-600 rounded-md p-1 text-red-600'> + {product.program.type.label} + </span> + </div> + <div className='mt-2 line-clamp-2 leading-6'>{item.name}</div> + </div> + </td> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <input + className='form-input w-16 py-2 text-center bg-gray_r-1' + type='number' + value={1} + disabled + /> + </td> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + {item?.price?.discountPercentage > 0 && ( + <div className='flex gap-x-1 items-center justify-center mt-3'> + <div className='text-gray_r-11 line-through text-caption-1'> + {currencyFormat(product?.price?.price)} + </div> + </div> + )} + <div className='font-normal mt-1'> + {item?.price.priceDiscount > 0 ? 'Gratis' : ''} + </div> + </td> + <td className='relative'> + <ComponentCanBuy canBuy={product.canBuy} /> + <div className='text-danger-500 font-medium'> + {item.price.priceDiscount > 0 ? 'Gratis' : ''} + </div> + </td> + <td></td> + </> + ))} + </tr> + )} + </> ))} </tbody> </table> @@ -506,4 +848,7 @@ const Cart = () => { ) } +const ComponentCanBuy = ({ canBuy }) => + !canBuy && <div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0' /> + export default Cart diff --git a/src/lib/cart/components/CartOld.jsx b/src/lib/cart/components/CartOld.jsx new file mode 100644 index 00000000..718541af --- /dev/null +++ b/src/lib/cart/components/CartOld.jsx @@ -0,0 +1,497 @@ +import Link from '@/core/components/elements/Link/Link' +import useCart from '../hooks/useCart' +import Image from '@/core/components/elements/Image/Image' +import NextImage from 'next/image' +import currencyFormat from '@/core/utils/currencyFormat' +import { useEffect, useState } from 'react' +import { deleteItemCart, getItemCart, updateItemCart } from '@/core/utils/cart' +import { CheckIcon, TrashIcon } from '@heroicons/react/24/outline' +import { createSlug } from '@/core/utils/slug' +import { useRouter } from 'next/router' +import BottomPopup from '@/core/components/elements/Popup/BottomPopup' +import { toast } from 'react-hot-toast' +import Spinner from '@/core/components/elements/Spinner/Spinner' +import Alert from '@/core/components/elements/Alert/Alert' +import MobileView from '@/core/components/views/MobileView' +import DesktopView from '@/core/components/views/DesktopView' +import ProductCard from '@/lib/product/components/ProductCard' +import productSearchApi from '@/lib/product/api/productSearchApi' +import whatsappUrl from '@/core/utils/whatsappUrl' +import useAuth from '@/core/hooks/useAuth' + +const Cart = () => { + const router = useRouter() + const [products, setProducts] = useState(null) + const auth = useAuth() + + useEffect(() => { + if (!auth) return + }, [auth]) + const { cart } = useCart({ enabled: !products }) + + const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0) + const [totalTaxAmount, setTotalTaxAmount] = useState(0) + const [totalDiscountAmount, setTotalDiscountAmount] = useState(0) + + const [deleteConfirmation, setDeleteConfirmation] = useState(null) + + const [productRecomendation, setProductRecomendation] = useState(null) + + useEffect(() => { + if (cart.data && !products) { + const productsWithQuantity = cart.data.map((product) => { + const productInCart = getItemCart({ productId: product.id }) + if (!productInCart) return + return { + ...product, + quantity: productInCart.quantity, + selected: productInCart.selected + } + }) + setProducts(productsWithQuantity) + } + }, [cart, products]) + + useEffect(() => { + if (!products) return + + let calculateTotalPriceBeforeTax = 0 + let calculateTotalTaxAmount = 0 + let calculateTotalDiscountAmount = 0 + for (const product of products) { + if (product.quantity == '') continue + updateItemCart({ + productId: product.id, + quantity: product.quantity, + selected: product.selected + }) + + if (!product.selected) continue + let priceBeforeTax = product.price.price / 1.11 + calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity + calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity + calculateTotalDiscountAmount += + (product.price.price - product.price.priceDiscount) * product.quantity + } + setTotalPriceBeforeTax(calculateTotalPriceBeforeTax) + setTotalTaxAmount(calculateTotalTaxAmount) + setTotalDiscountAmount(calculateTotalDiscountAmount) + }, [products]) + + useEffect(() => { + const LoadProductSimilar = async () => { + const randProductIndex = Math.floor(Math.random() * products.length) + const productLoad = await productSearchApi({ + query: `q=${products?.[randProductIndex].parent.name}&limit=10&operation=OR` + }) + + setProductRecomendation(productLoad) + } + if (products?.length > 0 && !productRecomendation) LoadProductSimilar() + }, [products, productRecomendation]) + + const updateQuantity = (value, productId, operation = '') => { + let productIndex = products.findIndex((product) => product.id == productId) + if (productIndex < 0) return + + let productsToUpdate = products + let quantity = productsToUpdate[productIndex].quantity + if (value != '' && isNaN(parseInt(value))) return + value = value != '' ? parseInt(value) : '' + switch (operation) { + case 'PLUS': + quantity += value + break + case 'MINUS': + if (quantity - value < 1) return + quantity -= value + break + case 'BLUR': + if (value != '' && value > 0) return + quantity = 1 + break + default: + quantity = value != '' && value < 1 ? 1 : value + break + } + productsToUpdate[productIndex].quantity = quantity + setProducts([...productsToUpdate]) + } + + const toggleSelected = (productId) => { + let productIndex = products.findIndex((product) => product.id == productId) + if (productIndex < 0) return + + let productsToUpdate = products + productsToUpdate[productIndex].selected = !productsToUpdate[productIndex].selected + setProducts([...productsToUpdate]) + } + + const selectedProduct = () => { + if (!products) return [] + return products?.filter((product) => product?.selected == true) + } + + const deleteProduct = (productId) => { + const productsToUpdate = products.filter((product) => product.id != productId) + deleteItemCart({ productId }) + setDeleteConfirmation(null) + setProducts([...productsToUpdate]) + toast.success('Berhasil menghapus barang dari keranjang') + } + + return ( + <> + <BottomPopup + active={deleteConfirmation} + close={() => setDeleteConfirmation(null)} + title='Hapus dari Keranjang' + > + <div className='leading-7 text-gray_r-12/80'> + Apakah anda yakin menghapus barang{' '} + <span className='underline'>{deleteConfirmation?.name}</span> dari keranjang? + </div> + <div className='flex mt-6 gap-x-4 md:justify-end'> + <button + className='btn-solid-red flex-1 md:flex-none' + type='button' + onClick={() => deleteProduct(deleteConfirmation?.id)} + > + Ya, Hapus + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => setDeleteConfirmation(null)} + > + Batal + </button> + </div> + </BottomPopup> + + <MobileView> + <div className='pt-4'> + <div className='flex justify-between mb-4 px-4'> + <h1 className='font-semibold'>Keranjang</h1> + <Link href='/'>Cari Produk Lain</Link> + </div> + + <div className='flex flex-col gap-y-4 h-screen'> + {cart.isLoading && ( + <div className='flex justify-center my-4'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + )} + + {!cart.isLoading && (!products || products?.length == 0) && ( + <div className='px-4'> + <Alert className='text-center my-2' type='info'> + Keranjang belanja anda masih kosong + </Alert> + </div> + )} + + {products?.map((product) => ( + <div key={product?.id} className='flex mx-4'> + <input + type='checkbox' + onClick={() => toggleSelected(product.id)} + checked={product?.selected} + className='mr-2 accent-danger-500 w-4' + /> + + <Link + href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)} + className='w-[30%] flex-shrink-0' + > + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md' + /> + </Link> + <div className='flex-1 px-2 text-caption-2'> + <Link + href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + > + {product?.parent?.name} + </Link> + <div className='text-gray_r-11 mt-1'> + {product?.code}{' '} + {product?.attributes.length > 0 ? `| ${product?.attributes.join(', ')}` : ''} + </div> + {product?.price?.discountPercentage > 0 && ( + <div className='flex gap-x-1 items-center mt-3'> + <div className='text-gray_r-11 line-through text-caption-2'> + {currencyFormat(product?.price?.price)} + </div> + <div className='badge-solid-red'>{product?.price?.discountPercentage}%</div> + </div> + )} + <div className='font-normal mt-1'> + {currencyFormat(product?.price?.priceDiscount)} + </div> + <div className='flex justify-between items-center mt-1'> + <div className='text-danger-500 font-medium'> + {currencyFormat(product?.price?.priceDiscount * product?.quantity)} + </div> + <div className='flex gap-x-1'> + <button + type='button' + className='btn-light px-2 py-1' + onClick={() => updateQuantity(1, product?.id, 'MINUS')} + disabled={product?.quantity == 1} + > + - + </button> + <input + className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center' + type='number' + value={product?.quantity} + onChange={(e) => updateQuantity(e.target.value, product?.id)} + onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')} + /> + <button + type='button' + className='btn-light px-2 py-1' + onClick={() => updateQuantity(1, product?.id, 'PLUS')} + > + + + </button> + <button + className='btn-red p-1 ml-1' + onClick={() => setDeleteConfirmation(product)} + > + <TrashIcon className='w-4' /> + </button> + </div> + </div> + </div> + </div> + ))} + + <div className='sticky bottom-0 left-0 w-full p-4 mt-auto border-t border-gray_r-6 bg-white'> + <div className='flex justify-between mb-4'> + <div className='text-gray_r-11'> + Total: + <span className='text-danger-500 font-semibold'> + + {selectedProduct().length > 0 + ? currencyFormat(totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount) + : '-'} + </span> + </div> + </div> + <div className='flex gap-x-3'> + <button + type='button' + className='btn-yellow flex-1' + disabled={selectedProduct().length == 0} + onClick={() => router.push('/shop/quotation')} + > + Quotation + </button> + <button + type='button' + className='btn-solid-red flex-1' + disabled={selectedProduct().length == 0} + onClick={() => router.push('/shop/checkout')} + > + Checkout + </button> + </div> + </div> + </div> + </div> + </MobileView> + + <DesktopView> + <div className='container mx-auto py-10 grid grid-cols-12'> + <div className='col-span-9 border border-gray_r-6 rounded bg-white p-4 pt-6'> + <h1 className='text-title-sm font-semibold mb-6'>Keranjang</h1> + + <table className='table-cart'> + <thead> + <tr> + <th colSpan={2}>Nama Produk</th> + <th>Jumlah</th> + <th>Harga</th> + <th>Subtotal</th> + <th>Action</th> + </tr> + </thead> + <tbody> + {cart.isLoading && ( + <tr> + <td colSpan={6}> + <div className='flex justify-center my-2'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + </td> + </tr> + )} + {!cart.isLoading && (!products || products?.length == 0) && ( + <tr> + <td colSpan={6}>Keranjang belanja anda masih kosong</td> + </tr> + )} + {products && + products?.map((product) => ( + <tr key={product.id}> + <td> + <input + type='checkbox' + onClick={() => toggleSelected(product.id)} + checked={product?.selected} + className='accent-danger-500 w-4' + /> + </td> + <td className='flex'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='w-[20%] flex-shrink-0' + > + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md' + /> + </Link> + <div className='px-2 text-left'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + > + {product?.parent?.name} + </Link> + <div className='text-gray_r-11 mt-2'> + {product?.code}{' '} + {product?.attributes.length > 0 + ? `| ${product?.attributes.join(', ')}` + : ''} + </div> + </div> + </td> + <td> + <input + className='form-input w-16 py-2 text-center bg-gray_r-1' + type='number' + value={product?.quantity} + onChange={(e) => updateQuantity(e.target.value, product?.id)} + onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')} + /> + </td> + <td> + {product?.price?.discountPercentage > 0 && ( + <div className='flex gap-x-1 items-center justify-center mt-3'> + <div className='text-gray_r-11 line-through text-caption-1'> + {currencyFormat(product?.price?.price)} + </div> + <div className='badge-solid-red'> + {product?.price?.discountPercentage}% + </div> + </div> + )} + <div className='font-normal mt-1'> + {currencyFormat(product?.price?.priceDiscount)} + </div> + </td> + <td> + <div className='text-danger-500 font-medium'> + {currencyFormat(product?.price?.priceDiscount * product?.quantity)} + </div> + </td> + <td> + <div className='flex justify-center items-center h-full'> + <button + className='btn-red p-1 ml-1' + onClick={() => setDeleteConfirmation(product)} + > + <TrashIcon className='w-4' /> + </button> + </div> + </td> + </tr> + ))} + </tbody> + </table> + + <div className='pt-2 pb-6 flex items-center gap-x-3'> + <NextImage + src='/images/logo-question.png' + alt='Logo Question Indoteknik' + width={60} + height={60} + /> + <div className='text-gray_r-12/90'> + Tanya stock untuk pembelian anda sebelum melanjutkan pembayaran! + <span> + {' '} + <a href={whatsappUrl()} className='text-danger-500'> + Hubungi Kami + </a> + </span> + </div> + </div> + </div> + + <div className='col-span-3 pl-4'> + <div className='sticky top-48 w-full p-4 rounded border border-gray_r-6 bg-white'> + <h1 className='text-title-sm font-semibold mb-6'>Ringkasan Belanja</h1> + <div className='flex justify-between mb-4'> + <div className='text-gray_r-11'> + Total: + <span className='text-danger-500 font-semibold'> + + {selectedProduct().length > 0 + ? currencyFormat(totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount) + : '-'} + </span> + </div> + </div> + <div className='flex gap-x-3'> + <button + type='button' + className='btn-yellow flex-1' + disabled={selectedProduct().length == 0} + onClick={() => router.push('/shop/quotation')} + > + Quotation + </button> + <button + type='button' + className='btn-solid-red flex-1' + disabled={selectedProduct().length == 0} + onClick={() => router.push('/shop/checkout')} + > + Checkout + </button> + </div> + </div> + </div> + + <div className='col-span-9 pt-2 pb-6 mt-6'> + <h1 className='text-title-sm font-semibold mb-6'>Produk Yang Mungkin Kamu Suka</h1> + <div className='grid grid-cols-5 gap-x-3 gap-y-6'> + {productRecomendation && + productRecomendation.response.products.map((product) => ( + <ProductCard product={product} key={product.id} /> + ))} + </div> + </div> + </div> + </DesktopView> + </> + ) +} + +export default Cart |
