diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2023-03-01 09:18:52 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2023-03-01 09:18:52 +0000 |
| commit | a7abbf4ddc70068620e9f44b74dc162ce2e16ee2 (patch) | |
| tree | 74f66253717515d364ce74bd8275015c1f829cbc /src/lib/product | |
| parent | 90e1edab9b6a8ccc09a49fed3addbec2cbc4e4c3 (diff) | |
| parent | a1b9b647a6c4bda1f5db63879639d44543f9557e (diff) | |
Merged in refactor (pull request #1)
Refactor
Diffstat (limited to 'src/lib/product')
| -rw-r--r-- | src/lib/product/api/productApi.js | 9 | ||||
| -rw-r--r-- | src/lib/product/api/productSearchApi.js | 9 | ||||
| -rw-r--r-- | src/lib/product/api/productSimilarApi.js | 10 | ||||
| -rw-r--r-- | src/lib/product/components/Product.jsx | 315 | ||||
| -rw-r--r-- | src/lib/product/components/ProductCard.jsx | 76 | ||||
| -rw-r--r-- | src/lib/product/components/ProductFilter.jsx | 132 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSearch.jsx | 116 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSimilar.jsx | 15 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSlider.jsx | 64 | ||||
| -rw-r--r-- | src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx | 14 | ||||
| -rw-r--r-- | src/lib/product/hooks/useProductSearch.js | 15 | ||||
| -rw-r--r-- | src/lib/product/hooks/useProductSimilar.js | 13 |
12 files changed, 788 insertions, 0 deletions
diff --git a/src/lib/product/api/productApi.js b/src/lib/product/api/productApi.js new file mode 100644 index 00000000..4fe4cd7d --- /dev/null +++ b/src/lib/product/api/productApi.js @@ -0,0 +1,9 @@ +import odooApi from '@/core/api/odooApi' + +const productApi = async ({ id }) => { + if (!id) return + const dataProduct = await odooApi('GET', `/api/v1/product/${id}`) + return dataProduct +} + +export default productApi diff --git a/src/lib/product/api/productSearchApi.js b/src/lib/product/api/productSearchApi.js new file mode 100644 index 00000000..b9acd94b --- /dev/null +++ b/src/lib/product/api/productSearchApi.js @@ -0,0 +1,9 @@ +import _ from 'lodash-contrib' +import axios from 'axios' + +const productSearchApi = async ({ query }) => { + const dataProductSearch = await axios(`${process.env.SELF_HOST}/api/shop/search?${query}`) + return dataProductSearch.data +} + +export default productSearchApi diff --git a/src/lib/product/api/productSimilarApi.js b/src/lib/product/api/productSimilarApi.js new file mode 100644 index 00000000..7142fab4 --- /dev/null +++ b/src/lib/product/api/productSimilarApi.js @@ -0,0 +1,10 @@ +import axios from 'axios' + +const productSimilarApi = async ({ query }) => { + const dataProductSimilar = await axios( + `${process.env.SELF_HOST}/api/shop/search?q=${query}&page=1&orderBy=popular` + ) + return dataProductSimilar.data.response +} + +export default productSimilarApi diff --git a/src/lib/product/components/Product.jsx b/src/lib/product/components/Product.jsx new file mode 100644 index 00000000..9e33316c --- /dev/null +++ b/src/lib/product/components/Product.jsx @@ -0,0 +1,315 @@ +import Badge from '@/core/components/elements/Badge/Badge' +import Divider from '@/core/components/elements/Divider/Divider' +import Image from '@/core/components/elements/Image/Image' +import Link from '@/core/components/elements/Link/Link' +import currencyFormat from '@/core/utils/currencyFormat' +import { useEffect, useState } from 'react' +import Select from 'react-select' +import ProductSimilar from './ProductSimilar' +import LazyLoad from 'react-lazy-load' +import { toast } from 'react-hot-toast' +import { updateItemCart } from '@/core/utils/cart' +import useWishlist from '@/lib/wishlist/hooks/useWishlist' +import { HeartIcon } from '@heroicons/react/24/outline' +import useAuth from '@/core/hooks/useAuth' +import { useRouter } from 'next/router' +import createOrDeleteWishlistApi from '@/lib/wishlist/api/createOrDeleteWishlistApi' + +const informationTabOptions = [ + { value: 'specification', label: 'Spesifikasi' }, + { value: 'description', label: 'Deskripsi' }, + { value: 'important', label: 'Info Penting' } +] + +const Product = ({ product }) => { + const auth = useAuth() + const router = useRouter() + const { wishlist } = useWishlist({ productId: product?.id }) + const [quantity, setQuantity] = useState('1') + const [selectedVariant, setSelectedVariant] = useState(null) + const [informationTab, setInformationTab] = useState(null) + + const [activeVariant, setActiveVariant] = useState({ + id: product.id, + code: product.code, + name: product.name, + price: product.lowestPrice, + stock: product.stockTotal, + weight: product.weight + }) + + const variantOptions = product.variants?.map((variant) => ({ + value: variant.id, + label: + (variant.code ? `[${variant.code}] ` : '') + + (variant.attributes.length > 0 ? variant.attributes.join(', ') : product.name) + })) + + useEffect(() => { + if (!selectedVariant && variantOptions.length == 1) { + setSelectedVariant(variantOptions[0]) + } + }, [selectedVariant, variantOptions]) + + useEffect(() => { + if (selectedVariant) { + const variant = product.variants.find((variant) => variant.id == selectedVariant.value) + const variantAttributes = + variant.attributes.length > 0 ? ' - ' + variant.attributes.join(', ') : '' + + setActiveVariant({ + id: variant.id, + code: variant.code, + name: variant.parent.name + variantAttributes, + price: variant.price, + stock: variant.stock, + weight: variant.weight + }) + } + }, [selectedVariant, product]) + + useEffect(() => { + if (!informationTab) { + setInformationTab(informationTabOptions[0].value) + } + }, [informationTab]) + + const handleClickCart = () => { + if (!selectedVariant) { + toast.error('Pilih varian terlebih dahulu') + return + } + if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) { + toast.error('Jumlah barang minimal 1') + return + } + updateItemCart({ + productId: activeVariant.id, + quantity + }) + toast.success('Berhasil menambahkan ke keranjang') + } + + const toggleWishlist = async () => { + if (!auth) { + router.push('/login') + return + } + const data = { product_id: product.id } + await createOrDeleteWishlistApi({ data }) + if (wishlist.data.productTotal > 0) { + toast.success('Berhasil menghapus dari wishlist') + } else { + toast.success('Berhasil menambahkan ke wishlist') + } + wishlist.refetch() + } + + return ( + <> + <Image + src={product.image} + alt={product.name} + className='h-72 object-contain object-center w-full border-b border-gray_r-4' + /> + + <div className='p-4'> + <div className='flex items-end mb-2'> + {product.manufacture?.name ? ( + <Link href='/'>{product.manufacture?.name}</Link> + ) : ( + <div>-</div> + )} + <button + type='button' + className='ml-auto' + onClick={toggleWishlist} + > + {wishlist.data?.productTotal > 0 ? ( + <HeartIcon className='w-6 fill-red_r-11 text-red_r-11' /> + ) : ( + <HeartIcon className='w-6' /> + )} + </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> + <Badge type='solid-red'>{activeVariant?.price?.discountPercentage}%</Badge> + </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> + </div> + + <Divider /> + + <div className='p-4'> + <div> + <label className='flex justify-between'> + Pilih Varian: + <span className='text-gray_r-11'>{product?.variantTotal} Varian</span> + </label> + <Select + name='variant' + classNamePrefix='form-select' + options={variantOptions} + className='mt-2' + value={selectedVariant} + onChange={(option) => setSelectedVariant(option)} + isSearchable={product.variantTotal > 10} + /> + </div> + <div className='mt-4 mb-2'>Jumlah</div> + <div className='flex gap-x-3'> + <div className='w-2/12'> + <input + name='quantity' + type='number' + className='form-input' + value={quantity} + onChange={(e) => setQuantity(e.target.value)} + /> + </div> + <button + type='button' + className='btn-yellow flex-1' + onClick={handleClickCart} + > + Keranjang + </button> + <button + type='button' + className='btn-solid-red flex-1' + > + Beli + </button> + </div> + </div> + + <Divider /> + + <div className='p-4'> + <h2 className='font-semibold'>Informasi Produk</h2> + <div className='flex gap-x-4 mt-4 mb-3'> + {informationTabOptions.map((option) => ( + <TabButton + value={option.value} + key={option.value} + active={informationTab == option.value} + onClick={() => setInformationTab(option.value)} + > + {option.label} + </TabButton> + ))} + </div> + + <TabContent + active={informationTab == 'specification'} + className='rounded border border-gray_r-6 divide-y divide-gray_r-6' + > + <SpecificationContent label='Jumlah Varian'> + <span>{product?.variantTotal} Varian</span> + </SpecificationContent> + <SpecificationContent label='Nomor SKU'> + <span>SKU-{product?.id}</span> + </SpecificationContent> + <SpecificationContent label='Part Number'> + <span>{activeVariant?.code || '-'}</span> + </SpecificationContent> + <SpecificationContent label='Stok'> + {activeVariant?.stock > 0 && ( + <span className='flex gap-x-1.5'> + <div className='badge-solid-red'>Ready Stock</div> + <div className='badge-gray'>{activeVariant?.stock > 5 ? '> 5' : '< 5'}</div> + </span> + )} + {activeVariant?.stock == 0 && ( + <a + href='https://wa.me' + className='text-red_r-11 font-medium' + > + Tanya Stok + </a> + )} + </SpecificationContent> + <SpecificationContent label='Berat Barang'> + {activeVariant?.weight > 0 && <span>{activeVariant?.weight} KG</span>} + {activeVariant?.weight == 0 && ( + <a + href='https://wa.me' + className='text-red_r-11 font-medium' + > + Tanya Berat + </a> + )} + </SpecificationContent> + </TabContent> + + <TabContent + active={informationTab == 'description'} + className='leading-6 text-gray_r-11' + dangerouslySetInnerHTML={{ + __html: product.description != '' ? product.description : 'Belum ada deskripsi produk.' + }} + /> + </div> + + <Divider /> + + <div className='p-4'> + <h2 className='font-semibold mb-4'>Kamu Mungkin Juga Suka</h2> + <LazyLoad> + <ProductSimilar query={product?.name.split(' ').slice(1, 3).join(' ')} /> + </LazyLoad> + </div> + </> + ) +} + +const TabButton = ({ children, active, ...props }) => { + const activeClassName = active ? 'text-red_r-11 underline underline-offset-4' : 'text-gray_r-11' + return ( + <button + {...props} + type='button' + className={`font-medium pb-1 ${activeClassName}`} + > + {children} + </button> + ) +} + +const TabContent = ({ children, active, className, ...props }) => ( + <div + {...props} + className={`${active ? 'block' : 'hidden'} ${className}`} + > + {children} + </div> +) + +const SpecificationContent = ({ children, label }) => ( + <div className='flex justify-between p-3'> + <span className='text-gray_r-11'>{label}</span> + {children} + </div> +) + +export default Product diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx new file mode 100644 index 00000000..6b88a3bd --- /dev/null +++ b/src/lib/product/components/ProductCard.jsx @@ -0,0 +1,76 @@ +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' + +const ProductCard = ({ product, simpleTitle }) => { + return ( + <> + <div className='rounded shadow-sm border border-gray_r-4 h-full bg-white'> + <Link + href={createSlug('/shop/product/', product?.name, product?.id)} + className='border-b border-gray_r-4 relative' + > + <Image + src={product?.image} + alt={product?.name} + className='w-full object-contain object-center h-36' + /> + {product.variantTotal > 1 && ( + <div className='absolute badge-gray bottom-1.5 left-1.5'> + {product.variantTotal} Varian + </div> + )} + </Link> + <div className='p-2 pb-3 text-caption-2 leading-5'> + {product?.manufacture?.name ? ( + <Link + href={createSlug( + '/shop/brands/', + product?.manufacture?.name, + product?.manufacture.id + )} + className='mb-1' + > + {product.manufacture.name} + </Link> + ) : ( + <div>-</div> + )} + <Link + href={createSlug('/shop/product/', product?.name, product?.id)} + className={`font-medium mb-2 !text-gray_r-12 ${ + simpleTitle ? 'line-clamp-2' : 'line-clamp-3' + }`} + > + {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]'> + {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> + {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> + </> + ) +} + +export default ProductCard diff --git a/src/lib/product/components/ProductFilter.jsx b/src/lib/product/components/ProductFilter.jsx new file mode 100644 index 00000000..eca95f74 --- /dev/null +++ b/src/lib/product/components/ProductFilter.jsx @@ -0,0 +1,132 @@ +import BottomPopup from '@/core/components/elements/Popup/BottomPopup' +import { useRouter } from 'next/router' +import { useState } from 'react' +import _ from 'lodash' +import { toQuery } from 'lodash-contrib' + +const orderOptions = [ + { value: 'price-asc', label: 'Harga Terendah' }, + { value: 'price-desc', label: 'Harga Tertinggi' }, + { value: 'popular', label: 'Populer' }, + { value: 'stock', label: 'Ready Stock' } +] + +const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBrand = null }) => { + const router = useRouter() + const { query } = router + const [order, setOrder] = useState(query?.orderBy) + const [brand, setBrand] = useState(query?.brand) + const [category, setCategory] = useState(query?.category) + const [priceFrom, setPriceFrom] = useState(query?.priceFrom) + const [priceTo, setPriceTo] = useState(query?.priceTo) + + const handleSubmit = () => { + let params = { + q: router.query.q, + orderBy: order, + brand, + category, + priceFrom, + priceTo + } + params = _.pickBy(params, _.identity) + params = toQuery(params) + router.push(`${prefixUrl}?${params}`) + } + + return ( + <BottomPopup + active={active} + close={close} + title='Filter Produk' + > + <div className='flex flex-col gap-y-4'> + {!defaultBrand && ( + <div> + <label>Brand</label> + <select + name='brand' + className='form-input mt-2' + value={brand} + onChange={(e) => setBrand(e.target.value)} + > + <option value=''>Pilih Brand...</option> + {brands.map((brand, index) => ( + <option + value={brand} + key={index} + > + {brand} + </option> + ))} + </select> + </div> + )} + <div> + <label>Kategori</label> + <select + name='category' + className='form-input mt-2' + value={category} + onChange={(e) => setCategory(e.target.value)} + > + <option value=''>Pilih Kategori...</option> + {categories.map((category, index) => ( + <option + value={category} + key={index} + > + {category} + </option> + ))} + </select> + </div> + <div> + <label>Urutkan</label> + <div className='flex mt-2 gap-x-2 overflow-x-auto'> + {orderOptions.map((orderOption) => ( + <button + key={orderOption.value} + className={`btn-light px-3 font-normal flex-shrink-0 ${ + order == orderOption.value ? 'bg-yellow_r-10' : 'bg-transparent' + }`} + onClick={() => setOrder(orderOption.value)} + > + {orderOption.label} + </button> + ))} + </div> + </div> + <div> + <label>Harga</label> + <div className='flex mt-2 gap-x-4 items-center'> + <input + type='number' + className='form-input' + placeholder='Dari' + value={priceFrom} + onChange={(e) => setPriceFrom(e.target.value)} + /> + <span>—</span> + <input + type='number' + className='form-input' + placeholder='Sampai' + value={priceTo} + onChange={(e) => setPriceTo(e.target.value)} + /> + </div> + </div> + <button + type='button' + className='btn-solid-red w-full mt-2' + onClick={handleSubmit} + > + Terapkan Filter + </button> + </div> + </BottomPopup> + ) +} + +export default ProductFilter diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx new file mode 100644 index 00000000..52bd5119 --- /dev/null +++ b/src/lib/product/components/ProductSearch.jsx @@ -0,0 +1,116 @@ +import { useEffect, useState } from 'react' +import useProductSearch from '../hooks/useProductSearch' +import ProductCard from './ProductCard' +import Pagination from '@/core/components/elements/Pagination/Pagination' +import { toQuery } from 'lodash-contrib' +import _ from 'lodash' +import ProductSearchSkeleton from './Skeleton/ProductSearchSkeleton' +import ProductFilter from './ProductFilter' +import useActive from '@/core/hooks/useActive' + +const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { + const { page = 1 } = query + if (defaultBrand) query.brand = defaultBrand.toLowerCase() + const { productSearch } = useProductSearch({ query }) + const [products, setProducts] = useState(null) + const popup = useActive() + + const pageCount = Math.ceil( + productSearch.data?.response.numFound / productSearch.data?.responseHeader.params.rows + ) + const productStart = productSearch.data?.responseHeader.params.start + const productRows = productSearch.data?.responseHeader.params.rows + const productFound = productSearch.data?.response.numFound + + const brands = productSearch.data?.facetCounts?.facetFields?.brandStr?.filter((value, index) => { + if (index % 2 === 0) { + return true + } + }) + const categories = productSearch.data?.facetCounts?.facetFields?.categoryNameStr?.filter( + (value, index) => { + if (index % 2 === 0) { + return true + } + } + ) + + useEffect(() => { + if (!products) { + setProducts(productSearch.data?.response?.products) + } + }, [query, products, productSearch]) + + if (productSearch.isLoading) { + return <ProductSearchSkeleton /> + } + + return ( + <div className='p-4'> + <h1 className='mb-2 font-semibold text-h-sm'>Produk</h1> + + <div className='mb-2 leading-6 text-gray_r-11'> + {productFound > 0 ? ( + <> + Menampilkan + {pageCount > 1 ? ( + <> + {productStart + 1}- + {productStart + productRows > productFound + ? productFound + : productStart + productRows} + dari + </> + ) : ( + '' + )} + {productFound} + produk{' '} + {query.q && ( + <> + untuk pencarian <span className='font-semibold'>{query.q}</span> + </> + )} + </> + ) : ( + 'Mungkin yang anda cari' + )} + </div> + + <button + className='btn-light mb-6 py-2 px-5' + onClick={popup.activate} + > + Filter + </button> + + <div className='grid grid-cols-2 gap-3'> + {products && + products.map((product) => ( + <ProductCard + product={product} + key={product.id} + /> + ))} + </div> + + <Pagination + pageCount={pageCount} + currentPage={parseInt(page)} + url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`} + className='mt-6 mb-2' + /> + + <ProductFilter + active={popup.active} + close={popup.deactivate} + brands={brands || []} + categories={categories || []} + prefixUrl={prefixUrl} + defaultBrand={defaultBrand} + /> + </div> + ) +} + +export default ProductSearch diff --git a/src/lib/product/components/ProductSimilar.jsx b/src/lib/product/components/ProductSimilar.jsx new file mode 100644 index 00000000..63a33089 --- /dev/null +++ b/src/lib/product/components/ProductSimilar.jsx @@ -0,0 +1,15 @@ +import PopularProductSkeleton from '@/lib/home/components/Skeleton/PopularProductSkeleton' +import useProductSimilar from '../hooks/useProductSimilar' +import ProductSlider from './ProductSlider' + +const ProductSimilar = ({ query }) => { + const { productSimilar } = useProductSimilar({ query }) + + if (productSimilar.isLoading) { + return <PopularProductSkeleton /> + } + + return <ProductSlider products={productSimilar.data} /> +} + +export default ProductSimilar diff --git a/src/lib/product/components/ProductSlider.jsx b/src/lib/product/components/ProductSlider.jsx new file mode 100644 index 00000000..060d4638 --- /dev/null +++ b/src/lib/product/components/ProductSlider.jsx @@ -0,0 +1,64 @@ +import { Swiper, SwiperSlide } from 'swiper/react' +import { FreeMode } from 'swiper' +import ProductCard from './ProductCard' +import 'swiper/css' +import Image from '@/core/components/elements/Image/Image' +import Link from '@/core/components/elements/Link/Link' +import { useRef } from 'react' + +const bannerClassName = + 'absolute rounded-r top-0 left-0 h-full max-w-[52%] idt-transition border border-gray_r-6' + +const ProductSlider = ({ products, simpleTitle = false, bannerMode = false }) => { + const bannerRef = useRef('') + + const changeBannerOpacity = (swiper) => { + if (!bannerMode) return + const calculateOpacity = (132 + swiper.translate) / 100 + bannerRef.current.style = `opacity: ${calculateOpacity > 0 ? calculateOpacity : 0}` + } + + return ( + <> + {bannerMode && ( + <div ref={bannerRef}> + <Image + src={products.banner.image} + alt={products.banner.name} + style={{ opacity: 1 }} + className={bannerClassName} + /> + </div> + )} + <Swiper + freeMode={{ enabled: true, sticky: false }} + slidesPerView={2.2} + spaceBetween={8} + onSliderMove={changeBannerOpacity} + onSlideChangeTransitionEnd={changeBannerOpacity} + onSlideChangeTransitionStart={changeBannerOpacity} + prefix='product' + modules={[FreeMode]} + > + {bannerMode && ( + <SwiperSlide> + <Link + href={products.banner.url} + className='w-full h-full block' + ></Link> + </SwiperSlide> + )} + {products?.products?.map((product, index) => ( + <SwiperSlide key={index}> + <ProductCard + product={product} + simpleTitle={simpleTitle} + /> + </SwiperSlide> + ))} + </Swiper> + </> + ) +} + +export default ProductSlider diff --git a/src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx b/src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx new file mode 100644 index 00000000..fa1e175d --- /dev/null +++ b/src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx @@ -0,0 +1,14 @@ +import ProductCardSkeleton from '@/core/components/elements/Skeleton/ProductCardSkeleton' + +const ProductSearchSkeleton = () => ( + <div className='p-4 grid grid-cols-2 gap-4'> + <ProductCardSkeleton /> + <ProductCardSkeleton /> + <ProductCardSkeleton /> + <ProductCardSkeleton /> + <ProductCardSkeleton /> + <ProductCardSkeleton /> + </div> +) + +export default ProductSearchSkeleton diff --git a/src/lib/product/hooks/useProductSearch.js b/src/lib/product/hooks/useProductSearch.js new file mode 100644 index 00000000..0396caec --- /dev/null +++ b/src/lib/product/hooks/useProductSearch.js @@ -0,0 +1,15 @@ +import { useQuery } from 'react-query' +import productSearchApi from '../api/productSearchApi' +import _ from 'lodash-contrib' + +const useProductSearch = ({ query }) => { + const queryString = _.toQuery(query) + const fetchProductSearch = async () => await productSearchApi({ query: queryString }) + const { data, isLoading } = useQuery(`productSearch-${queryString}`, fetchProductSearch) + + return { + productSearch: { data, isLoading } + } +} + +export default useProductSearch diff --git a/src/lib/product/hooks/useProductSimilar.js b/src/lib/product/hooks/useProductSimilar.js new file mode 100644 index 00000000..d16e4c58 --- /dev/null +++ b/src/lib/product/hooks/useProductSimilar.js @@ -0,0 +1,13 @@ +import productSimilarApi from '../api/productSimilarApi' +import { useQuery } from 'react-query' + +const useProductSimilar = ({ query }) => { + const fetchProductSimilar = async () => await productSimilarApi({ query }) + const { data, isLoading } = useQuery(`productSimilar-${query}`, fetchProductSimilar) + + return { + productSimilar: { data, isLoading } + } +} + +export default useProductSimilar |
