diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktopVariant.jsx | 275 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobileVariant.jsx | 240 | ||||
| -rw-r--r-- | src/pages/google_merchant/products/[page].js | 130 | ||||
| -rw-r--r-- | src/pages/shop/product/variant/[slug].jsx | 86 |
4 files changed, 434 insertions, 297 deletions
diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx index ef61bafd..bae00b87 100644 --- a/src/lib/product/components/Product/ProductDesktopVariant.jsx +++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx @@ -1,137 +1,154 @@ -import Image from '@/core/components/elements/Image/Image' -import Link from '@/core/components/elements/Link/Link' -import DesktopView from '@/core/components/views/DesktopView' -import currencyFormat from '@/core/utils/currencyFormat' -import { HeartIcon } from '@heroicons/react/24/outline' -import { useCallback, useEffect, useRef, useState } from 'react' -import LazyLoad from 'react-lazy-load' -import ProductSimilar from '../ProductSimilar' -import { toast } from 'react-hot-toast' -import { updateItemCart } from '@/core/utils/cart' -import { useRouter } from 'next/router' -import { createSlug } from '@/core/utils/slug' -import BottomPopup from '@/core/components/elements/Popup/BottomPopup' -import ProductCard from '../ProductCard' -import productSimilarApi from '../../api/productSimilarApi' -import whatsappUrl from '@/core/utils/whatsappUrl' -import useAuth from '@/core/hooks/useAuth' -import odooApi from '@/core/api/odooApi' -import { useProductCartContext } from '@/contexts/ProductCartContext' -import { Box, Skeleton, Tooltip } from '@chakra-ui/react' -import { Info } from 'lucide-react' - -const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) => { - const router = useRouter() - const auth = useAuth() - const { slug } = router.query - - const [lowestPrice, setLowestPrice] = useState(null) - - const [addCartAlert, setAddCartAlert] = useState(false) - const [isLoadingSLA, setIsLoadingSLA] = useState(true) - - const { setRefreshCart } = useProductCartContext() +import { Box, Skeleton, Tooltip } from '@chakra-ui/react'; +import { HeartIcon } from '@heroicons/react/24/outline'; +import { Info } from 'lucide-react'; +import { useRouter } from 'next/router'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'react-hot-toast'; +import LazyLoad from 'react-lazy-load'; + +import { useProductCartContext } from '@/contexts/ProductCartContext'; +import odooApi from '@/core/api/odooApi'; +import Image from '@/core/components/elements/Image/Image'; +import Link from '@/core/components/elements/Link/Link'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import DesktopView from '@/core/components/views/DesktopView'; +import useAuth from '@/core/hooks/useAuth'; +import { updateItemCart } from '@/core/utils/cart'; +import currencyFormat from '@/core/utils/currencyFormat'; +import { createSlug } from '@/core/utils/slug'; +import whatsappUrl from '@/core/utils/whatsappUrl'; + +import productSimilarApi from '../../api/productSimilarApi'; +import ProductCard from '../ProductCard'; +import ProductSimilar from '../ProductSimilar'; + +const ProductDesktopVariant = ({ + product, + wishlist, + toggleWishlist, + isVariant, +}) => { + const router = useRouter(); + const auth = useAuth(); + const { slug } = router.query; + + const [lowestPrice, setLowestPrice] = useState(null); + + const [addCartAlert, setAddCartAlert] = useState(false); + const [isLoadingSLA, setIsLoadingSLA] = useState(true); + + const { setRefreshCart } = useProductCartContext(); const getLowestPrice = useCallback(() => { - const lowest = product.price + const lowest = product.price; /* const lowest = prices.reduce((lowest, price) => { return price.priceDiscount < lowest.priceDiscount ? price : lowest }, prices[0])*/ - return lowest - }, [product]) + return lowest; + }, [product]); useEffect(() => { - const lowest = getLowestPrice() - setLowestPrice(lowest) - }, [getLowestPrice]) + const lowest = getLowestPrice(); + setLowestPrice(lowest); + }, [getLowestPrice]); - const [informationTab, setInformationTab] = useState(informationTabOptions[0].value) + const [informationTab, setInformationTab] = useState( + informationTabOptions[0].value + ); - const variantQuantityRefs = useRef([]) + const variantQuantityRefs = useRef([]); const setVariantQuantityRef = (variantId) => (element) => { - variantQuantityRefs.current[variantId] = element - } + variantQuantityRefs.current[variantId] = element; + }; const validQuantity = (quantity) => { - let isValid = true + let isValid = true; if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) { - toast.error('Jumlah barang minimal 1') - isValid = false + toast.error('Jumlah barang minimal 1'); + isValid = false; } - return isValid - } + return isValid; + }; const handleAddToCart = (variant) => { if (!auth) { - router.push(`/login?next=/shop/product/${slug}`) - return + router.push(`/login?next=/shop/product/${slug}`); + return; } - const quantity = variantQuantityRefs.current[product.id].value - if (!validQuantity(quantity)) return + const quantity = variantQuantityRefs.current[product.id].value; + if (!validQuantity(quantity)) return; updateItemCart({ productId: product.id, quantity, programLineId: null, selected: true, - source: null + source: null, }).then(() => { - setRefreshCart(true) - }) - setAddCartAlert(true) - } + setRefreshCart(true); + }); + setAddCartAlert(true); + }; const handleBuy = (variant) => { - const quantity = variantQuantityRefs.current[product.id].value - if (!validQuantity(quantity)) return + const quantity = variantQuantityRefs.current[product.id].value; + if (!validQuantity(quantity)) return; updateItemCart({ productId: variant, quantity, programLineId: null, selected: true, - source: 'buy' - }) - router.push(`/shop/checkout?source=buy`) - } + source: 'buy', + }); + router.push(`/shop/checkout?source=buy`); + }; - const variantSectionRef = useRef(null) + const variantSectionRef = useRef(null); const goToVariantSection = () => { if (variantSectionRef.current) { - const position = variantSectionRef.current.getBoundingClientRect() + const position = variantSectionRef.current.getBoundingClientRect(); window.scrollTo({ top: position.top - 120 + window.pageYOffset, - behavior: 'smooth' - }) + behavior: 'smooth', + }); } - } + }; const productSimilarQuery = [ product?.name, `fq=-product_id_i:${product.id}`, - `fq=-manufacture_id_i:${product.manufacture?.id || 0}` - ].join('&') + `fq=-manufacture_id_i:${product.manufacture?.id || 0}`, + ].join('&'); - const [productSimilarInBrand, setProductSimilarInBrand] = useState(null) + const [productSimilarInBrand, setProductSimilarInBrand] = useState(null); useEffect(() => { const loadProductSimilarInBrand = async () => { - const productSimilarQuery = [product?.name, `fq=-product_id_i:${product.id}`].join('&') - const dataProductSimilar = await productSimilarApi({ query: productSimilarQuery }) - setProductSimilarInBrand(dataProductSimilar.products) - } - if (!productSimilarInBrand) loadProductSimilarInBrand() - }, [product, productSimilarInBrand]) + const productSimilarQuery = [ + product?.name, + `fq=-product_id_i:${product.id}`, + ].join('&'); + const dataProductSimilar = await productSimilarApi({ + query: productSimilarQuery, + }); + setProductSimilarInBrand(dataProductSimilar.products); + }; + if (!productSimilarInBrand) loadProductSimilarInBrand(); + }, [product, productSimilarInBrand]); useEffect(() => { const fetchData = async () => { - const dataSLA = await odooApi('GET', `/api/v1/product_variant/${product.id}/stock`) - product.sla = dataSLA + const dataSLA = await odooApi( + 'GET', + `/api/v1/product_variant/${product.id}/stock` + ); + product.sla = dataSLA; - setIsLoadingSLA(false) - } - fetchData() - }, [product]) + setIsLoadingSLA(false); + }; + fetchData(); + }, [product]); return ( <DesktopView> @@ -140,14 +157,16 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) <div className='w-full flex flex-wrap'> <div className='w-5/12'> <Image - src={product.image} + src={product.image + '?variant=True'} alt={product.name} className='h-[430px] object-contain object-center w-full border border-gray_r-4' /> </div> <div className='w-7/12 px-4'> - <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1> + <h1 className='text-title-md leading-10 font-medium'> + {product?.name} + </h1> <div className='mt-10'> <div className='flex p-3'> <div className='w-4/12 text-gray_r-12/70'>Nomor SKU</div> @@ -177,7 +196,9 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) </div> <div className='flex p-3 items-center bg-gray_r-4'> - <div className='w-4/12 text-gray_r-12/70'>Persiapan Barang</div> + <div className='w-4/12 text-gray_r-12/70'> + Persiapan Barang + </div> <div className='w-8/12'> {!product?.sla && <Skeleton width='20%' height='16px' />} {product?.sla && ( @@ -203,8 +224,13 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) <a href={whatsappUrl('product', { name: product.name, - manufacture: product?.manufacture?.name, - url: createSlug('/shop/product/', product.name, product.id, true) + manufacture: product?.manufacture?.name, + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), })} className='text-danger-500 font-medium' > @@ -221,7 +247,12 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) <a href={whatsappUrl('productWeight', { name: product.name, - url: createSlug('/shop/product/', product.name, product.id, true) + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), })} className='text-danger-500 font-medium' > @@ -270,7 +301,8 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) </div> */} </div> <div className='w-[25%]'> - {product?.isFlashsale > 0 && product?.price?.discountPercentage > 0? ( + {product?.isFlashsale > 0 && + product?.price?.discountPercentage > 0 ? ( <> <div className='flex gap-x-1 items-center mt-2'> <div className='badge-solid-red text-caption-1'> @@ -285,7 +317,9 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) </div> <div className='text-gray_r-9 text-base font-normal mt-1'> Termasuk PPN:{' '} - {currencyFormat(product?.price?.priceDiscount * process.env.NEXT_PUBLIC_PPN)} + {currencyFormat( + product?.price?.priceDiscount * process.env.NEXT_PUBLIC_PPN + )} </div> </> ) : ( @@ -295,7 +329,9 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) {currencyFormat(product?.price?.price)} <div className='text-gray_r-9 text-base font-normal mt-1'> Termasuk PPN:{' '} - {currencyFormat(product?.price?.price * process.env.NEXT_PUBLIC_PPN)} + {currencyFormat( + product?.price?.price * process.env.NEXT_PUBLIC_PPN + )} </div> </> ) : ( @@ -305,7 +341,12 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) href={whatsappUrl('product', { name: product.name, manufacture: product.manufacture?.name, - url: createSlug('/shop/product/', product.name, product.id, true) + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), })} className='text-danger-500 underline' rel='noopener noreferrer' @@ -340,7 +381,10 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) </button> </div> <div className='flex mt-4'> - <button className='flex items-center gap-x-1' onClick={toggleWishlist}> + <button + className='flex items-center gap-x-1' + onClick={toggleWishlist} + > {wishlist.data?.productTotal > 0 ? ( <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> ) : ( @@ -366,7 +410,9 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) </div> <div className='my-12'> - <div className='text-h-lg font-semibold mb-6'>Kamu Mungkin Juga Suka</div> + <div className='text-h-lg font-semibold mb-6'> + Kamu Mungkin Juga Suka + </div> <LazyLoad> <ProductSimilar query={productSimilarQuery} /> </LazyLoad> @@ -381,21 +427,28 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) <div className='flex mt-4'> <div className='w-[10%]'> <Image - src={product.image} + src={product.image + '?variant=True'} alt={product.name} className='h-32 object-contain object-center w-full border border-gray_r-4' /> </div> - <div className='ml-3 flex flex-1 items-center font-normal'>{product.name}</div> + <div className='ml-3 flex flex-1 items-center font-normal'> + {product.name} + </div> <div className='ml-3 flex items-center font-normal'> - <Link href='/shop/cart' className='flex-1 py-2 text-gray_r-12 btn-yellow'> + <Link + href='/shop/cart' + className='flex-1 py-2 text-gray_r-12 btn-yellow' + > Lihat Keranjang </Link> </div> </div> <div className='mt-8 mb-4'> - <div className='text-h-sm font-semibold mb-6'>Kamu Mungkin Juga Suka</div> + <div className='text-h-sm font-semibold mb-6'> + Kamu Mungkin Juga Suka + </div> <LazyLoad> <ProductSimilar query={productSimilarQuery} /> </LazyLoad> @@ -403,29 +456,33 @@ const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) </BottomPopup> </div> </DesktopView> - ) -} + ); +}; const informationTabOptions = [ { value: 'description', label: 'Deskripsi' }, - { value: 'information', label: 'Info Penting' } -] + { value: 'information', label: 'Info Penting' }, +]; const TabButton = ({ children, active, ...props }) => { const activeClassName = active ? 'text-danger-500 underline underline-offset-4' - : 'text-gray_r-12/80' + : 'text-gray_r-12/80'; return ( - <button {...props} type='button' className={`font-medium ${activeClassName}`}> + <button + {...props} + type='button' + className={`font-medium ${activeClassName}`} + > {children} </button> - ) -} + ); +}; const TabContent = ({ children, active, className = '', ...props }) => ( <div {...props} className={`${active ? 'block' : 'hidden'} ${className}`}> {children} </div> -) +); -export default ProductDesktopVariant +export default ProductDesktopVariant; diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx index 9888e482..af9e52bb 100644 --- a/src/lib/product/components/Product/ProductMobileVariant.jsx +++ b/src/lib/product/components/Product/ProductMobileVariant.jsx @@ -1,37 +1,40 @@ -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 { updateItemCart } from '@/core/utils/cart' -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 { createSlug } from '@/core/utils/slug' -import BottomPopup from '@/core/components/elements/Popup/BottomPopup' -import whatsappUrl from '@/core/utils/whatsappUrl' -import { gtagAddToCart } from '@/core/utils/googleTag' -import odooApi from '@/core/api/odooApi' -import { Skeleton } from '@chakra-ui/react' +import { Skeleton } from '@chakra-ui/react'; +import { HeartIcon } from '@heroicons/react/24/outline'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import { toast } from 'react-hot-toast'; +import LazyLoad from 'react-lazy-load'; + +import odooApi from '@/core/api/odooApi'; +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 BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import MobileView from '@/core/components/views/MobileView'; +import { updateItemCart } from '@/core/utils/cart'; +import currencyFormat from '@/core/utils/currencyFormat'; +import { gtagAddToCart } from '@/core/utils/googleTag'; +import { createSlug } from '@/core/utils/slug'; +import whatsappUrl from '@/core/utils/whatsappUrl'; + +import ProductSimilar from '../ProductSimilar'; const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { - const router = useRouter() + const router = useRouter(); - const [quantity, setQuantity] = useState('1') - const [selectedVariant, setSelectedVariant] = useState(product.id) - const [informationTab, setInformationTab] = useState(informationTabOptions[0].value) - const [addCartAlert, setAddCartAlert] = useState(false) + const [quantity, setQuantity] = useState('1'); + const [selectedVariant, setSelectedVariant] = useState(product.id); + const [informationTab, setInformationTab] = useState( + informationTabOptions[0].value + ); + const [addCartAlert, setAddCartAlert] = useState(false); - const [isLoadingSLA, setIsLoadingSLA] = useState(true) + const [isLoadingSLA, setIsLoadingSLA] = useState(true); const getLowestPrice = () => { - const lowest = product.lowestPrice - return lowest - } + const lowest = product.lowestPrice; + return lowest; + }; const [activeVariant, setActiveVariant] = useState({ id: null, @@ -40,8 +43,8 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { price: getLowestPrice(), stock: product.stockTotal, weight: product.weight, - isFlashSale: product.isFlashSale - }) + isFlashSale: product.isFlashSale, + }); useEffect(() => { if (selectedVariant) { @@ -52,70 +55,73 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { price: product.price, stock: product.stockTotal, weight: product.weight, - isFlashSale: product.isFlashSale - }) + isFlashSale: product.isFlashSale, + }); } - }, [selectedVariant, product]) + }, [selectedVariant, product]); const validAction = () => { - let isValid = true + let isValid = true; if (!selectedVariant) { - toast.error('Pilih varian terlebih dahulu') - isValid = false + toast.error('Pilih varian terlebih dahulu'); + isValid = false; } if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) { - toast.error('Jumlah barang minimal 1') - isValid = false + toast.error('Jumlah barang minimal 1'); + isValid = false; } - return isValid - } + return isValid; + }; const handleClickCart = () => { - if (!validAction()) return - gtagAddToCart(activeVariant, quantity) + if (!validAction()) return; + gtagAddToCart(activeVariant, quantity); updateItemCart({ productId: variant, quantity, programLineId: null, selected: true, - source: null - }) - setAddCartAlert(true) - } + source: null, + }); + setAddCartAlert(true); + }; const handleClickBuy = () => { - if (!validAction()) return + if (!validAction()) return; updateItemCart({ productId: product.id, quantity, programLineId: null, selected: true, - source: 'buy' - }) - router.push(`/shop/checkout?source=buy`) - } + source: 'buy', + }); + router.push(`/shop/checkout?source=buy`); + }; const productSimilarQuery = [ product?.name, `fq=-product_id_i:${product.id}`, - `fq=-manufacture_id_i:${product.manufacture?.id || 0}` - ].join('&') + `fq=-manufacture_id_i:${product.manufacture?.id || 0}`, + ].join('&'); useEffect(() => { const fetchData = async () => { - const dataSLA = await odooApi('GET', `/api/v1/product_variant/${product.id}/stock`) - product.sla = dataSLA + const dataSLA = await odooApi( + 'GET', + `/api/v1/product_variant/${product.id}/stock` + ); + product.sla = dataSLA; - setIsLoadingSLA(false) - } - fetchData() - }, [product]) + setIsLoadingSLA(false); + }; + fetchData(); + }, [product]); return ( <MobileView> <Image - src={product.image} + src={product.image + '?variant=True'} alt={product.name} className='h-72 object-contain object-center w-full border-b border-gray_r-4' /> @@ -124,7 +130,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { <div className='flex items-end mb-2'> {product.manufacture?.name ? ( <Link - href={createSlug('/shop/brands/', product.manufacture?.name, product.manufacture?.id)} + href={createSlug( + '/shop/brands/', + product.manufacture?.name, + product.manufacture?.id + )} > {product.manufacture?.name} </Link> @@ -141,10 +151,13 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { </div> <h1 className='leading-6 font-medium mb-3'>{activeVariant?.name}</h1> - {activeVariant.isFlashSale && activeVariant?.price?.discountPercentage > 0 ? ( + {activeVariant.isFlashSale && + activeVariant?.price?.discountPercentage > 0 ? ( <> <div className='flex gap-x-1 items-center'> - <div className='badge-solid-red'>{activeVariant?.price?.discountPercentage}%</div> + <div className='badge-solid-red'> + {activeVariant?.price?.discountPercentage}% + </div> <div className='text-gray_r-11 line-through text-caption-1'> {currencyFormat(activeVariant?.price?.price)} </div> @@ -154,7 +167,9 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { </div> <div className='text-gray_r-9 text-base font-normal mt-1'> Termasuk PPN:{' '} - {currencyFormat(activeVariant?.price.priceDiscount * process.env.NEXT_PUBLIC_PPN)} + {currencyFormat( + activeVariant?.price.priceDiscount * process.env.NEXT_PUBLIC_PPN + )} </div> </> ) : ( @@ -164,7 +179,9 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { {currencyFormat(activeVariant?.price?.price)} <div className='text-gray_r-9 text-base font-normal mt-1'> Termasuk PPN:{' '} - {currencyFormat(activeVariant?.price.price * process.env.NEXT_PUBLIC_PPN)} + {currencyFormat( + activeVariant?.price.price * process.env.NEXT_PUBLIC_PPN + )} </div> </> ) : ( @@ -173,7 +190,12 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { <a href={whatsappUrl('product', { name: product.name, - url: createSlug('/shop/product/', product.name, product.id, true) + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), })} className='text-danger-500 underline' > @@ -199,10 +221,18 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { onChange={(e) => setQuantity(e.target.value)} /> </div> - <button type='button' className='btn-yellow flex-1' onClick={handleClickCart}> + <button + type='button' + className='btn-yellow flex-1' + onClick={handleClickCart} + > Keranjang </button> - <button type='button' className='btn-solid-red flex-1' onClick={handleClickBuy}> + <button + type='button' + className='btn-solid-red flex-1' + onClick={handleClickBuy} + > Beli </button> </div> @@ -238,7 +268,9 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { type='button' title={`Masa Persiapan Barang ${product?.sla?.slaDate}`} className={`flex gap-x-1 items-center p-2 h-8 rounded-lg w-full ${ - product?.sla?.slaDate === 'indent' ? 'bg-indigo-900' : 'btn-light' + product?.sla?.slaDate === 'indent' + ? 'bg-indigo-900' + : 'btn-light' }`} > <div @@ -281,14 +313,21 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { {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> + <div className='badge-gray'> + {activeVariant?.stock > 5 ? '> 5' : '< 5'} + </div> </span> )} {activeVariant?.stock == 0 && ( <a href={whatsappUrl('product', { name: product.name, - url: createSlug('/shop/product/', product.name, product.id, true) + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), })} className='text-danger-500 font-medium' > @@ -297,12 +336,19 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { )} </SpecificationContent> <SpecificationContent label='Berat Barang'> - {activeVariant?.weight > 0 && <span>{activeVariant?.weight} KG</span>} + {activeVariant?.weight > 0 && ( + <span>{activeVariant?.weight} KG</span> + )} {activeVariant?.weight == 0 && ( <a href={whatsappUrl('productWeight', { name: product.name, - url: createSlug('/shop/product/', product.name, product.id, true) + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), })} className='text-danger-500 font-medium' > @@ -316,7 +362,10 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { active={informationTab == 'description'} className='leading-6 text-gray_r-11' dangerouslySetInnerHTML={{ - __html: product.description != '' ? product.description : 'Belum ada deskripsi produk.' + __html: + product.description != '' + ? product.description + : 'Belum ada deskripsi produk.', }} /> </div> @@ -338,55 +387,68 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { <div className='flex mt-4'> <div className='w-[15%]'> <Image - src={product.image} + src={product.image + '?variant=True'} alt={product.name} className='h-20 object-contain object-center w-full border border-gray_r-4' /> </div> - <div className='ml-3 flex flex-1 items-center text-sm font-normal'>{product.name}</div> + <div className='ml-3 flex flex-1 items-center text-sm font-normal'> + {product.name} + </div> <div className='ml-3 flex items-center text-sm font-normal'> - <Link href='/shop/cart' className='flex-1 py-2 text-gray_r-12 btn-yellow'> + <Link + href='/shop/cart' + className='flex-1 py-2 text-gray_r-12 btn-yellow' + > Lihat Keranjang </Link> </div> </div> <div className='mt-8 mb-4'> - <div className='text-h-sm font-semibold mb-6'>Kamu Mungkin Juga Suka</div> + <div className='text-h-sm font-semibold mb-6'> + Kamu Mungkin Juga Suka + </div> <LazyLoad> <ProductSimilar query={productSimilarQuery} /> </LazyLoad> </div> </BottomPopup> </MobileView> - ) -} + ); +}; const informationTabOptions = [ - { value: 'specification', label: 'Spesifikasi' } + { value: 'specification', label: 'Spesifikasi' }, // { value: 'description', label: 'Deskripsi' }, // { value: 'information', label: 'Info Penting' } -] +]; const TabButton = ({ children, active, ...props }) => { - const activeClassName = active ? 'text-danger-500 underline underline-offset-4' : 'text-gray_r-11' + const activeClassName = active + ? 'text-danger-500 underline underline-offset-4' + : 'text-gray_r-11'; return ( - <button {...props} type='button' className={`font-medium pb-1 ${activeClassName}`}> + <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 ProductMobileVariant +export default ProductMobileVariant; diff --git a/src/pages/google_merchant/products/[page].js b/src/pages/google_merchant/products/[page].js index c8b4079b..6e0eb703 100644 --- a/src/pages/google_merchant/products/[page].js +++ b/src/pages/google_merchant/products/[page].js @@ -1,94 +1,102 @@ -import { createSlug } from '@/core/utils/slug' -import toTitleCase from '@/core/utils/toTitleCase' -import productSearchApi from '@/lib/product/api/productSearchApi' -import variantSearchApi from '@/lib/product/api/variantSearchApi' -import axios from 'axios' -import _ from 'lodash-contrib' -import { create } from 'xmlbuilder' +import { createSlug } from '@/core/utils/slug'; +import toTitleCase from '@/core/utils/toTitleCase'; +import variantSearchApi from '@/lib/product/api/variantSearchApi'; +import axios from 'axios'; +import _ from 'lodash-contrib'; +import { create } from 'xmlbuilder'; export async function getServerSideProps({ res, query }) { - const titleContent = 'Indoteknik.com: B2B Industrial Supply & Solution' + const titleContent = 'Indoteknik.com: B2B Industrial Supply & Solution'; const descriptionContent = - 'Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.' + 'Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.'; - const { page } = query - const limit = 5000 + const { page } = query; + const limit = 5000; const queries = { limit, page: page.replace('.xml', ''), priceFrom: 1, orderBy: 'popular', - fq: 'image_s:["" TO *]' - } - const products = await variantSearchApi({ query: _.toQuery(queries) }) + fq: 'image_s:["" TO *]', + }; + const products = await variantSearchApi({ query: _.toQuery(queries) }); - const brandsData = {} - const categoriesData = {} + const brandsData = {}; + const categoriesData = {}; - const productItems = [] + const productItems = []; for (const product of products.response.products) { - const productUrl = createSlug('/shop/product/variant/', product.name, product.id, true) - const productId = product.code != '' ? product.code : product.id - const regexHtmlTags = /(<([^>]+)>)/gi - product.description = product.description?.replace(regexHtmlTags, ' ').trim() + const productUrl = createSlug( + '/shop/product/variant/', + product.name, + product.id, + true + ); + const productId = product.code != '' ? product.code : product.id; + const regexHtmlTags = /(<([^>]+)>)/gi; + product.description = product.description + ?.replace(regexHtmlTags, ' ') + .trim(); const defaultProductDescription = - 'Indoteknik.com menawarkan berbagai produk industri, konstruksi, dan teknik terpercaya. Temukan mesin industri, peralatan listrik, alat pengukur, dan banyak lagi. Pengalaman berbelanja mudah dengan deskripsi produk lengkap, spesifikasi teknis, dan gambar jelas. Pembayaran aman, pengiriman cepat ke seluruh Indonesia. Solusi lengkap untuk kebutuhan Kantor, Industri & Teknik Anda.' + 'Indoteknik.com menawarkan berbagai produk industri, konstruksi, dan teknik terpercaya. Temukan mesin industri, peralatan listrik, alat pengukur, dan banyak lagi. Pengalaman berbelanja mudah dengan deskripsi produk lengkap, spesifikasi teknis, dan gambar jelas. Pembayaran aman, pengiriman cepat ke seluruh Indonesia. Solusi lengkap untuk kebutuhan Kantor, Industri & Teknik Anda.'; if (!product.description) { - product.description = defaultProductDescription + product.description = defaultProductDescription; } - let categoryName = null + let categoryName = null; - let brandId = product.manufacture?.id ?? null - let categoryId = null + let brandId = product.manufacture?.id ?? null; + let categoryId = null; if (brandId && brandId in brandsData) { - categoryId = brandsData[brandId].category_ids?.[0] ?? null + categoryId = brandsData[brandId].category_ids?.[0] ?? null; } else { - const solrBrand = await getBrandById(brandId) - brandsData[brandId] = solrBrand - categoryId = solrBrand?.category_ids?.[0] ?? null + const solrBrand = await getBrandById(brandId); + brandsData[brandId] = solrBrand; + categoryId = solrBrand?.category_ids?.[0] ?? null; } if (categoryId && categoryId in categoriesData) { - categoryName = categoriesData[categoryId].name_s ?? null + categoryName = categoriesData[categoryId].name_s ?? null; } else { - const solrCategory = await getCategoryById(categoryId) - categoriesData[categoryId] = solrCategory - categoryName = solrCategory?.name_s ?? null + const solrCategory = await getCategoryById(categoryId); + categoriesData[categoryId] = solrCategory; + categoryName = solrCategory?.name_s ?? null; } - const availability = 'in_stock' + const availability = 'in_stock'; const item = { 'g:id': { '#text': productId }, 'g:title': { '#text': toTitleCase(product.name) }, 'g:description': { '#text': product.description }, 'g:link': { '#text': productUrl }, - 'g:image_link': { '#text': product.image }, + 'g:image_link': { '#text': product.image + '?variant=True' }, 'g:condition': { '#text': 'new' }, 'g:availability': { '#text': availability }, 'g:brand': { '#text': product.manufacture?.name || '' }, - 'g:price': { '#text': `${Math.round(product.lowestPrice.price * 1.11)} IDR` } - } + 'g:price': { + '#text': `${Math.round(product.lowestPrice.price * 1.11)} IDR`, + }, + }; if (product.stockTotal == 0) { - item['g:custom_label_0'] = { '#text': 'Stok Tidak Tersedia' } + item['g:custom_label_0'] = { '#text': 'Stok Tidak Tersedia' }; } else { - item['g:custom_label_1'] = { '#text': 'Stok Tersedia' } + item['g:custom_label_1'] = { '#text': 'Stok Tersedia' }; } if (categoryName) { - item['g:custom_label_2'] = { '#text': categoryName } + item['g:custom_label_2'] = { '#text': categoryName }; } if (product.lowestPrice.discountPercentage > 0) { item['g:sale_price'] = { - '#text': `${Math.round(product.lowestPrice.priceDiscount * 1.11)} IDR` - } + '#text': `${Math.round(product.lowestPrice.priceDiscount * 1.11)} IDR`, + }; } - productItems.push(item) + productItems.push(item); } const googleMerchant = { @@ -99,28 +107,32 @@ export async function getServerSideProps({ res, query }) { title: { '#text': `<![CDATA[${titleContent}]]>` }, link: { '#text': process.env.SELF_HOST }, description: { '#text': `<![CDATA[${descriptionContent}]]>` }, - item: productItems - } - } - } + item: productItems, + }, + }, + }; - res.setHeader('Content-Type', 'text/xml;charset=iso-8859-1') - res.write(create(googleMerchant).end()) - res.end() + res.setHeader('Content-Type', 'text/xml;charset=iso-8859-1'); + res.write(create(googleMerchant).end()); + res.end(); - return { props: {} } + return { props: {} }; } const getBrandById = async (id) => { - const brand = await axios(`${process.env.SOLR_HOST}/solr/brands/select?q=id:${id}`) - return brand.data.response.docs[0] ?? null -} + const brand = await axios( + `${process.env.SOLR_HOST}/solr/brands/select?q=id:${id}` + ); + return brand.data.response.docs[0] ?? null; +}; const getCategoryById = async (id) => { - const category = await axios(`${process.env.SOLR_HOST}/solr/categories/select?q=id:${id}`) - return category.data.response.docs[0] ?? null -} + const category = await axios( + `${process.env.SOLR_HOST}/solr/categories/select?q=id:${id}` + ); + return category.data.response.docs[0] ?? null; +}; export default function GoogleMerchantPage() { - return null + return null; } diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx index 455b248b..cb335e0a 100644 --- a/src/pages/shop/product/variant/[slug].jsx +++ b/src/pages/shop/product/variant/[slug].jsx @@ -1,62 +1,68 @@ -import Seo from '@/core/components/Seo' -import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner' -import { getIdFromSlug } from '@/core/utils/slug' -import PageNotFound from '@/pages/404' -import dynamic from 'next/dynamic' -import { useRouter } from 'next/router' -import cookie from 'cookie' -import variantApi from '@/lib/product/api/variantApi' -import axios from 'axios' -import { useProductContext } from '@/contexts/ProductContext' -import { useEffect } from 'react' +import axios from 'axios'; +import cookie from 'cookie'; +import dynamic from 'next/dynamic'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; -const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout')) -const Product = dynamic(() => import('@/lib/product/components/Product/Product')) +import { useProductContext } from '@/contexts/ProductContext'; +import Seo from '@/core/components/Seo'; +import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'; +import { getIdFromSlug } from '@/core/utils/slug'; +import PageNotFound from '@/pages/404'; -export async function getServerSideProps(context) { - const { slug } = context.query - const cookies = context.req.headers.cookie - const cookieObj = cookies ? cookie.parse(cookies) : {} - const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {} - const tier = auth.pricelist ? auth.pricelist : false - const authToken = auth?.token || '' +const BasicLayout = dynamic(() => + import('@/core/components/layouts/BasicLayout') +); +const Product = dynamic(() => + import('@/lib/product/components/Product/Product') +); +export async function getServerSideProps(context) { + const { slug } = context.query; + const cookies = context.req.headers.cookie; + const cookieObj = cookies ? cookie.parse(cookies) : {}; + const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {}; + const tier = auth.pricelist ? auth.pricelist : false; + const authToken = auth?.token || ''; let response = await axios( - `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=` + getIdFromSlug(slug) +'&auth=' + tier - ) - let product = response.data + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=` + + getIdFromSlug(slug) + + '&auth=' + + tier + ); + let product = response.data; // let product = await variantApi({ id: getIdFromSlug(slug), headers: { Token: authToken } }) - + if (product?.length == 1) { - product = product[0] - /* const regexHtmlTags = /(<([^>]+)>)/gi + product = product[0]; + /* const regexHtmlTags = /(<([^>]+)>)/gi const regexHtmlTagsExceptP = /<\/?(?!p\b)[^>]*>/g product.description = product.description .replace(regexHtmlTagsExceptP, ' ') .replace(regexHtmlTags, ' ') .trim()*/ } else { - product = null + product = null; } return { - props: { product } - } + props: { product }, + }; } export default function ProductDetail({ product }) { - const router = useRouter() + const router = useRouter(); - const { setProduct } = useProductContext() + const { setProduct } = useProductContext(); useEffect(() => { if (product) { - setProduct(product) + setProduct(product); } - }, [product, setProduct]) + }, [product, setProduct]); - if (!product) return <PageNotFound /> + if (!product) return <PageNotFound />; return ( <BasicLayout> @@ -70,16 +76,16 @@ export default function ProductDetail({ product }) { url: product?.image, width: 800, height: 800, - alt: product?.name - } + alt: product?.name, + }, ], - type: 'product' + type: 'product', }} additionalMetaTags={[ { name: 'keywords', - content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}` - } + content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`, + }, ]} /> {!product && ( @@ -89,5 +95,5 @@ export default function ProductDetail({ product }) { )} {product && <Product product={product} isVariant={true} />} </BasicLayout> - ) + ); } |
