diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/components/elements/Skeleton/PriceSkeleton.jsx | 9 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktop.jsx | 79 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobile.jsx | 54 | ||||
| -rw-r--r-- | src/lib/product/components/ProductCard.jsx | 111 | ||||
| -rw-r--r-- | src/lib/product/hooks/useProductPrice.js | 13 | ||||
| -rw-r--r-- | src/lib/variant/api/variantPriceApi.js | 8 | ||||
| -rw-r--r-- | src/lib/variant/hooks/useVariantPrice.js | 13 |
7 files changed, 198 insertions, 89 deletions
diff --git a/src/core/components/elements/Skeleton/PriceSkeleton.jsx b/src/core/components/elements/Skeleton/PriceSkeleton.jsx new file mode 100644 index 00000000..0dc4c70f --- /dev/null +++ b/src/core/components/elements/Skeleton/PriceSkeleton.jsx @@ -0,0 +1,9 @@ +const PriceSkeleton = () => ( + <div role='status' className='max-w-sm rounded animate-pulse my-2'> + <div className='h-2.5 bg-gray_r-6 rounded-full w-3/12 mb-1'></div> + <div className='h-2.5 bg-gray_r-6 rounded-full w-6/12 mb-1'></div> + <span className='sr-only'>Loading...</span> + </div> +) + +export default PriceSkeleton diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx index 98b40400..663d5a74 100644 --- a/src/lib/product/components/Product/ProductDesktop.jsx +++ b/src/lib/product/components/Product/ProductDesktop.jsx @@ -8,8 +8,12 @@ import LazyLoad from 'react-lazy-load' import ProductSimilar from '../ProductSimilar' import { toast } from 'react-hot-toast' import { updateItemCart } from '@/core/utils/cart' +import useVariantPrice from '@/lib/variant/hooks/useVariantPrice' +import useProductPrice from '../../hooks/useProductPrice' +import PriceSkeleton from '@/core/components/elements/Skeleton/PriceSkeleton' const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { + const { productPrice } = useProductPrice({ id: product.id }) const [informationTab, setInformationTab] = useState(informationTabOptions[0].value) const variantQuantityRefs = useRef([]) @@ -104,28 +108,33 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { {product.variants.length > 1 && product.lowestPrice.priceDiscount > 0 && ( <div className='text-gray_r-12/80'>Harga mulai dari: </div> )} - {product?.lowestPrice.discountPercentage > 0 && ( - <div className='flex gap-x-1 items-center mt-2'> - <div className='badge-solid-red text-caption-1'> - {product?.lowestPrice.discountPercentage}% - </div> - <div className='text-gray_r-11 line-through text-caption-1'> - {currencyFormat(product?.lowestPrice.price)} - </div> - </div> + {productPrice.isLoading && <PriceSkeleton />} + {productPrice.isFetched && ( + <> + {productPrice?.data?.discount > 0 && ( + <div className='flex gap-x-1 items-center mt-2'> + <div className='badge-solid-red text-caption-1'> + {productPrice?.data?.discount}% + </div> + <div className='text-gray_r-11 line-through text-caption-1'> + {currencyFormat(productPrice?.data?.priceExclude)} + </div> + </div> + )} + <h3 className='text-red_r-11 font-semibold mt-1 text-title-md'> + {productPrice?.data?.priceExcludeAfterDiscount > 0 ? ( + currencyFormat(productPrice?.data?.priceExcludeAfterDiscount) + ) : ( + <span className='text-gray_r-11 leading-6 font-normal'> + Hubungi kami untuk dapatkan harga terbaik, + <a href='https://wa.me/' className='text-red_r-11 underline'> + klik disini + </a> + </span> + )} + </h3> + </> )} - <h3 className='text-red_r-11 font-semibold mt-1 text-title-md'> - {product?.lowestPrice.priceDiscount > 0 ? ( - currencyFormat(product?.lowestPrice.priceDiscount) - ) : ( - <span className='text-gray_r-11 leading-6 font-normal'> - Hubungi kami untuk dapatkan harga terbaik, - <a href='https://wa.me/' className='text-red_r-11 underline'> - klik disini - </a> - </span> - )} - </h3> <button type='button' onClick={goToVariantSection} @@ -166,14 +175,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { <td>{variant.code}</td> <td>{variant.attributes.join(', ')}</td> <td> - {variant.price.discountPercentage > 0 && ( - <> - <span className='line-through text-caption-1 text-gray_r-11'> - {currencyFormat(variant.price.price)} - </span>{' '} - </> - )} - {currencyFormat(variant.price.priceDiscount)} + <VariantPrice id={variant.id} /> </td> <td> <input @@ -245,6 +247,25 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { ) } +const VariantPrice = ({ id }) => { + const { variantPrice } = useVariantPrice({ id }) + + if (variantPrice.isLoading) return <PriceSkeleton /> + + return ( + <> + {variantPrice?.data?.discount > 0 && ( + <> + <span className='line-through text-caption-1 text-gray_r-11'> + {currencyFormat(variantPrice?.data?.priceExclude)} + </span>{' '} + </> + )} + {currencyFormat(variantPrice?.data?.priceExcludeAfterDiscount)} + </> + ) +} + const informationTabOptions = [ { value: 'description', label: 'Deskripsi' }, { value: 'information', label: 'Info Penting' } diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx index c572a58e..f6ed3d40 100644 --- a/src/lib/product/components/Product/ProductMobile.jsx +++ b/src/lib/product/components/Product/ProductMobile.jsx @@ -11,6 +11,8 @@ import { HeartIcon } from '@heroicons/react/24/outline' import { useRouter } from 'next/router' import MobileView from '@/core/components/views/MobileView' import { toast } from 'react-hot-toast' +import useVariantPrice from '@/lib/variant/hooks/useVariantPrice' +import PriceSkeleton from '@/core/components/elements/Skeleton/PriceSkeleton' const ProductMobile = ({ product, wishlist, toggleWishlist }) => { const router = useRouter() @@ -115,26 +117,7 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { </button> </div> <h1 className='leading-6 font-medium'>{activeVariant?.name}</h1> - {activeVariant?.price?.discountPercentage > 0 && ( - <div className='flex gap-x-1 items-center mt-2'> - <div className='text-gray_r-11 line-through text-caption-1'> - {currencyFormat(activeVariant?.price?.price)} - </div> - <div className='badge-solid-red'>{activeVariant?.price?.discountPercentage}%</div> - </div> - )} - <h3 className='text-red_r-11 font-semibold mt-1'> - {activeVariant?.price?.priceDiscount > 0 ? ( - currencyFormat(activeVariant?.price?.priceDiscount) - ) : ( - <span className='text-gray_r-11 leading-6 font-normal'> - Hubungi kami untuk dapatkan harga terbaik, - <a href='https://wa.me/' className='text-red_r-11 underline'> - klik disini - </a> - </span> - )} - </h3> + <VariantPrice id={activeVariant.id} /> </div> <Divider /> @@ -249,6 +232,37 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { ) } +const VariantPrice = ({ id }) => { + const { variantPrice } = useVariantPrice({ id }) + + if (variantPrice.isLoading) return <PriceSkeleton /> + + return ( + <> + {variantPrice?.data?.discount > 0 && ( + <div className='flex gap-x-1 items-center mt-2'> + <div className='text-gray_r-11 line-through text-caption-1'> + {currencyFormat(variantPrice?.data?.priceExclude)} + </div> + <div className='badge-solid-red'>{variantPrice?.data?.discount}%</div> + </div> + )} + <h3 className='text-red_r-11 font-semibold mt-1'> + {variantPrice?.data?.priceExcludeAfterDiscount > 0 ? ( + currencyFormat(variantPrice?.data?.priceExcludeAfterDiscount) + ) : ( + <span className='text-gray_r-11 leading-6 font-normal'> + Hubungi kami untuk dapatkan harga terbaik, + <a href='https://wa.me/' className='text-red_r-11 underline'> + klik disini + </a> + </span> + )} + </h3> + </> + ) +} + const informationTabOptions = [ { value: 'specification', label: 'Spesifikasi' }, { value: 'description', label: 'Deskripsi' }, diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx index 5710e9ea..e48ab88a 100644 --- a/src/lib/product/components/ProductCard.jsx +++ b/src/lib/product/components/ProductCard.jsx @@ -2,20 +2,11 @@ import Image from '@/core/components/elements/Image/Image' import Link from '@/core/components/elements/Link/Link' import currencyFormat from '@/core/utils/currencyFormat' import { createSlug } from '@/core/utils/slug' -import { useEffect, useState } from 'react' -import productPriceApi from '../api/productPriceApi' +import useProductPrice from '../hooks/useProductPrice' +import { LazyLoadComponent } from 'react-lazy-load-image-component' +import PriceSkeleton from '@/core/components/elements/Skeleton/PriceSkeleton' const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { - const [price, setPrice] = useState(null) - - useEffect(() => { - const loadPrice = async () => { - const dataPrice = await productPriceApi({ id: product.id }) - // console.log(dataPrice) - } - loadPrice() - }) - if (variant == 'vertical') { return ( <div className='rounded shadow-sm border border-gray_r-4 h-full bg-white'> @@ -57,22 +48,9 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { > {product?.name} </Link> - {product?.lowestPrice?.discountPercentage > 0 && ( - <div className='flex gap-x-1 mb-1 items-center'> - <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'> - {currencyFormat(product?.lowestPrice?.price)} - </div> - <div className='badge-solid-red'>{product?.lowestPrice?.discountPercentage}%</div> - </div> - )} - - <div className='text-red_r-11 font-semibold mb-2'> - {product?.lowestPrice?.priceDiscount > 0 ? ( - currencyFormat(product?.lowestPrice?.priceDiscount) - ) : ( - <a href='https://wa.me/'>Call for price</a> - )} - </div> + <LazyLoadComponent> + <ProductCardPrice variant='vertical' id={product.id} /> + </LazyLoadComponent> {product?.stockTotal > 0 && ( <div className='flex gap-x-1'> <div className='badge-solid-red'>Ready Stock</div> @@ -128,30 +106,83 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { {product?.name} </Link> - {product?.lowestPrice?.discountPercentage > 0 && ( + <LazyLoadComponent> + <ProductCardPrice variant='horizontal' id={product.id} /> + </LazyLoadComponent> + {product?.stockTotal > 0 && ( + <div className='flex gap-x-1'> + <div className='badge-solid-red'>Ready Stock</div> + <div className='badge-gray'>{product?.stockTotal > 5 ? '> 5' : '< 5'}</div> + </div> + )} + </div> + </div> + ) + } +} + +const ProductCardPrice = ({ variant, id }) => { + const { productPrice } = useProductPrice({ id }) + + if (productPrice.isLoading) return <PriceSkeleton /> + + if (variant == 'vertical') { + return ( + productPrice.isFetched && ( + <> + {productPrice?.data?.discount > 0 && ( <div className='flex gap-x-1 mb-1 items-center'> - <div className='badge-solid-red'>{product?.lowestPrice?.discountPercentage}%</div> - <div className='text-gray_r-11 line-through text-caption-2'> - {currencyFormat(product?.lowestPrice?.price)} + <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'> + {currencyFormat( + productPrice?.data?.priceStartFrom || productPrice?.data?.priceExclude + )} </div> + <div className='badge-solid-red'>{productPrice?.data?.discount}%</div> </div> )} <div className='text-red_r-11 font-semibold mb-2'> - {product?.lowestPrice?.priceDiscount > 0 ? ( - currencyFormat(product?.lowestPrice?.priceDiscount) + {productPrice?.data?.priceExcludeAfterDiscount > 0 ? ( + currencyFormat( + productPrice?.data?.priceDiscStartFrom || + productPrice?.data?.priceExcludeAfterDiscount + ) ) : ( <a href='https://wa.me/'>Call for price</a> )} </div> - {product?.stockTotal > 0 && ( - <div className='flex gap-x-1'> - <div className='badge-solid-red'>Ready Stock</div> - <div className='badge-gray'>{product?.stockTotal > 5 ? '> 5' : '< 5'}</div> + </> + ) + ) + } + + if (variant == 'horizontal') { + return ( + productPrice.isFetched && ( + <> + {productPrice?.data?.discount > 0 && ( + <div className='flex gap-x-1 mb-1 items-center'> + <div className='badge-solid-red'>{productPrice?.data?.discount}%</div> + <div className='text-gray_r-11 line-through text-caption-2'> + {currencyFormat( + productPrice?.data?.priceStartFrom || productPrice?.data?.priceExclude + )} + </div> </div> )} - </div> - </div> + + <div className='text-red_r-11 font-semibold mb-2'> + {productPrice?.data?.priceExcludeAfterDiscount > 0 ? ( + currencyFormat( + productPrice?.data?.priceDiscStartFrom || + productPrice?.data?.priceExcludeAfterDiscount + ) + ) : ( + <a href='https://wa.me/'>Call for price</a> + )} + </div> + </> + ) ) } } diff --git a/src/lib/product/hooks/useProductPrice.js b/src/lib/product/hooks/useProductPrice.js new file mode 100644 index 00000000..f8ef62b8 --- /dev/null +++ b/src/lib/product/hooks/useProductPrice.js @@ -0,0 +1,13 @@ +import { useQuery } from 'react-query' +import productPriceApi from '../api/productPriceApi' + +const useProductPrice = ({ id }) => { + const fetchProductPrice = async () => await productPriceApi({ id }) + const productPrice = useQuery(`productPrice-${id}`, fetchProductPrice, { + refetchOnWindowFocus: false + }) + + return { productPrice } +} + +export default useProductPrice diff --git a/src/lib/variant/api/variantPriceApi.js b/src/lib/variant/api/variantPriceApi.js new file mode 100644 index 00000000..8621ca78 --- /dev/null +++ b/src/lib/variant/api/variantPriceApi.js @@ -0,0 +1,8 @@ +import odooApi from '@/core/api/odooApi' + +const variantPriceApi = async ({ id }) => { + const dataVariantPrice = await odooApi('GET', `/api/v1/product/product/price/${id}`) + return dataVariantPrice +} + +export default variantPriceApi diff --git a/src/lib/variant/hooks/useVariantPrice.js b/src/lib/variant/hooks/useVariantPrice.js new file mode 100644 index 00000000..d00eb810 --- /dev/null +++ b/src/lib/variant/hooks/useVariantPrice.js @@ -0,0 +1,13 @@ +import { useQuery } from 'react-query' +import variantPriceApi from '../api/variantPriceApi' + +const useVariantPrice = ({ id }) => { + const fetchVariantPrice = async () => await variantPriceApi({ id }) + const variantPrice = useQuery(`variantPrice-${id}`, fetchVariantPrice, { + refetchOnWindowFocus: false + }) + + return { variantPrice } +} + +export default useVariantPrice |
