diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/components/layouts/BasicLayout.jsx | 33 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktopVariant.jsx | 307 | ||||
| -rw-r--r-- | src/pages/_app.jsx | 2 | ||||
| -rw-r--r-- | src/pages/api/shop/variant-detail.js | 2 | ||||
| -rw-r--r-- | src/pages/shop/product/variant/[slug].jsx | 7 | ||||
| -rw-r--r-- | src/utils/solrMapping.js | 2 |
6 files changed, 220 insertions, 133 deletions
diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx index c4674344..d6b96f36 100644 --- a/src/core/components/layouts/BasicLayout.jsx +++ b/src/core/components/layouts/BasicLayout.jsx @@ -8,6 +8,7 @@ import odooApi from '@/core/api/odooApi'; import whatsappUrl from '@/core/utils/whatsappUrl'; import Navbar from '../elements/Navbar/Navbar'; import styles from './BasicLayout.module.css'; // Import modul CSS +import useDevice from '@/core/hooks/useDevice'; const AnimationLayout = dynamic(() => import('./AnimationLayout'), { ssr: false, @@ -24,6 +25,8 @@ const BasicLayout = ({ children }) => { const [buttonPosition, setButtonPosition] = useState(null); const [wobble, setWobble] = useState(false); + const { isDesktop, isMobile } = useDevice(); + const router = useRouter(); const buttonRef = useRef(null); @@ -49,7 +52,7 @@ const BasicLayout = ({ children }) => { const handleMouseOut = (event) => { const rect = buttonRef.current.getBoundingClientRect(); if (event.clientY <= 0) { - setButtonPosition(rect) + setButtonPosition(rect); setHighlight(true); } else { setHighlight(false); @@ -92,13 +95,15 @@ const BasicLayout = ({ children }) => { return ( <> - {highlight && buttonPosition && ( + {highlight && buttonPosition && ( <div className={styles['overlay-highlight']} style={{ - '--button-x': `${buttonPosition.x + buttonPosition.width / 2}px`, + '--button-x': `${buttonPosition.x + buttonPosition.width / 2}px`, '--button-y': `${buttonPosition.y + buttonPosition.height / 2}px`, - '--button-radius': `${Math.max(buttonPosition.width, buttonPosition.height) / 2}px` + '--button-radius': `${ + Math.max(buttonPosition.width, buttonPosition.height) / 2 + }px`, }} onAnimationEnd={() => setHighlight(false)} /> @@ -106,11 +111,21 @@ const BasicLayout = ({ children }) => { <Navbar /> <AnimationLayout> {children} - <div className='fixed bottom-4 right-4 sm:bottom-14 sm:right-10 z-50'> - <div className='flex flex-row items-center'> - <a href={whatsappUrl(templateWA, payloadWA, urlPath)} className='flex flex-row items-center' rel='noopener noreferrer' target='_blank'> - <span className={`text-green-300 text-lg font-bold mr-4 ${wobble ? 'animate-wobble' : ''}`} onAnimationEnd={() => setWobble(false)}> - Whatsapp + <div className='fixed bottom-16 right-4 sm:bottom-14 sm:right-10 z-50'> + <div className='flex flex-row items-center'> + <a + href={whatsappUrl(templateWA, payloadWA, urlPath)} + className='flex flex-row items-center' + rel='noopener noreferrer' + target='_blank' + > + <span + className={`text-green-300 text-lg font-bold mr-4 ${ + wobble ? 'animate-wobble' : '' + }`} + onAnimationEnd={() => setWobble(false)} + > + {isDesktop && 'Whatsapp'} </span> </a> <a diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx index 09b30a44..55effdfb 100644 --- a/src/lib/product/components/Product/ProductDesktopVariant.jsx +++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx @@ -1,7 +1,6 @@ - -import { Box, Skeleton, Tooltip } from '@chakra-ui/react'; +import { Box, Button, Skeleton, Tooltip } from '@chakra-ui/react'; import { HeartIcon } from '@heroicons/react/24/outline'; -import { Info } from 'lucide-react'; +import { Info, MessageCircleIcon, Share2Icon } from 'lucide-react'; import { useRouter } from 'next/router'; import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'react-hot-toast'; @@ -19,9 +18,13 @@ import currencyFormat from '@/core/utils/currencyFormat'; import { createSlug } from '@/core/utils/slug'; import whatsappUrl from '@/core/utils/whatsappUrl'; +import { RWebShare } from 'react-web-share'; import productSimilarApi from '../../api/productSimilarApi'; import ProductCard from '../ProductCard'; import ProductSimilar from '../ProductSimilar'; +import ProductPromoSection from '~/modules/product-promo/components/Section'; + +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDesktopVariant = ({ product, @@ -33,6 +36,8 @@ const ProductDesktopVariant = ({ const auth = useAuth(); const { slug } = router.query; + console.log('ini product variant', product); + const [lowestPrice, setLowestPrice] = useState(null); const [addCartAlert, setAddCartAlert] = useState(false); @@ -40,11 +45,20 @@ const ProductDesktopVariant = ({ const { setRefreshCart } = useProductCartContext(); + const [quantityInput, setQuantityInput] = useState(1); + + const createdAskUrl = whatsappUrl({ + template: 'product', + payload: { + manufacture: product.manufacture.name, + productName: product.name, + url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, + }, + fallbackUrl: router.asPath, + }); + const getLowestPrice = useCallback(() => { const lowest = product.price; - /* const lowest = prices.reduce((lowest, price) => { - return price.priceDiscount < lowest.priceDiscount ? price : lowest - }, prices[0])*/ return lowest; }, [product]); @@ -77,7 +91,7 @@ const ProductDesktopVariant = ({ router.push(`/login?next=/shop/product/${slug}`); return; } - const quantity = variantQuantityRefs.current[product.id].value; + const quantity = quantityInput; if (!validQuantity(quantity)) return; updateItemCart({ productId: product.id, @@ -92,7 +106,7 @@ const ProductDesktopVariant = ({ }; const handleBuy = (variant) => { - const quantity = variantQuantityRefs.current[product.id].value; + const quantity = quantityInput; if (!validQuantity(quantity)) return; updateItemCart({ @@ -160,87 +174,39 @@ const ProductDesktopVariant = ({ <Image src={product.image + '?variant=True'} alt={product.name} - className='h-[430px] object-contain object-center w-full border border-gray_r-4' + className='w-full h-[350px]' /> </div> - <div className='w-7/12 px-4'> + <div className='w-7/12 px-6'> <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> - <div className='w-8/12'>SKU-{product.id}</div> - </div> <div className='flex p-3 bg-gray_r-4'> - <div className='w-4/12 text-gray_r-12/70'>Part Number</div> - <div className='w-8/12'>{product.code || '-'}</div> + <div className='w-4/12 text-gray_r-12/70'>Item Code</div> + <div className='w-8/12'>{product.code}</div> </div> - <div className='flex p-3'> + <div className='flex p-3 items-center '> <div className='w-4/12 text-gray_r-12/70'>Manufacture</div> <div className='w-8/12'> - {product.manufacture?.name ? ( - <Link - href={createSlug( - '/shop/brands/', - product.manufacture?.name, - product.manufacture?.id - )} - > - {product.manufacture?.name} - </Link> - ) : ( - <div>-</div> - )} - </div> - </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-8/12'> - {!product?.sla && <Skeleton width='20%' height='16px' />} - {product?.sla && ( - <Tooltip - placement='top' - label={`Masa Persiapan Barang ${product?.sla?.slaDate}`} - > - <Box className='w-fit flex items-center gap-x-2'> - {product?.sla?.slaDate} - <Info size={16} /> - </Box> - </Tooltip> - )} + <Link + href={createSlug( + '/shop/brands/', + product.manufacture.name, + product.manufacture.id.toString() + )} + > + <Image + width={100} + src={product.manufacture.logo} + alt={product.manufacture.name} + /> + </Link> </div> </div> - <div className='flex p-3'> - <div className='w-4/12 text-gray_r-12/70'>Stock</div> - <div className='w-8/12'> - {!product?.sla && <Skeleton width='10%' height='16px' />} - {product?.sla?.qty > 0 && <span>{product?.sla?.qty}</span>} - {product?.sla?.qty == 0 && ( - <a - href={whatsappUrl('product', { - name: product.name, - manufacture: product?.manufacture?.name, - url: createSlug( - '/shop/product/', - product.name, - product.id, - true - ), - })} - className='text-danger-500 font-medium' - > - Tanya Admin - </a> - )} - </div> - </div> - <div className='flex p-3 bg-gray_r-4'> + <div className='flex p-3 bg-gray_r-4 '> <div className='w-4/12 text-gray_r-12/70'>Berat Barang</div> <div className='w-8/12'> {product?.weight > 0 && <span>{product?.weight} KG</span>} @@ -262,24 +228,55 @@ const ProductDesktopVariant = ({ )} </div> </div> + <div className='flex p-3 items-center '> + <div className='w-4/12 text-gray_r-12/70'>Terjual</div> + <div className='w-8/12'>-</div> + </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-8/12'> + {!product?.sla && <Skeleton width='20%' height='16px' />} + {product?.sla && ( + <Tooltip + placement='top' + label={`Masa Persiapan Barang ${product?.sla?.slaDate}`} + > + <Box className='w-fit flex items-center gap-x-2'> + {product?.sla?.slaDate} + <Info size={16} /> + </Box> + </Tooltip> + )} + </div> + </div> </div> </div> - <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'> - <h2 className='text-h-md md:text-h-lg font-medium'>Informasi Produk</h2> - <div className='h-4' /> - <div - className='leading-relaxed text-gray-700' - dangerouslySetInnerHTML={{ - __html: - !product.parent.description || product.parent.description == '<p><br></p>' - ? 'Belum ada deskripsi' - : product.parent.description, - }} - /> + <div className='p-4 md:p-6 '> + <ProductPromoSection product={product} productId={product.id} /> + + <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'> + <h2 className='text-h-md md:text-h-lg font-medium'> + Informasi Produk + </h2> + <div className='h-4' /> + <div + className='leading-relaxed text-gray-700' + dangerouslySetInnerHTML={{ + __html: + !product.parent.description || + product.parent.description == '<p><br></p>' + ? 'Belum ada deskripsi' + : product.parent.description, + }} + /> + </div> </div> </div> - <div className='w-[25%]'> + <div className='w-[33%]'> {product?.isFlashsale > 0 && product?.price?.discountPercentage > 0 ? ( <> @@ -337,40 +334,120 @@ const ProductDesktopVariant = ({ )} </h3> )} - <div className='flex gap-x-3 mt-4'> - <input - type='number' - className='form-input w-16 py-2 text-center bg-gray_r-1' - ref={setVariantQuantityRef(product.id)} - defaultValue={1} - /> - <button - type='button' + <div className='flex justify-between items-center py-5 px-3'> + <div className='relative flex items-center'> + <button + type='button' + className='absolute left-0 px-2 py-1 h-full text-gray-500' + onClick={() => + setQuantityInput( + String(Math.max(1, Number(quantityInput) - 1)) + ) + } + > + - + </button> + <input + type='number' + id='quantity' + min={1} + value={quantityInput} + onChange={(e) => setQuantityInput(e.target.value)} + className=' w-24 h-10 text-center border border-gray-300 rounded focus:outline-none' + /> + <button + type='button' + className='absolute right-0 px-2 py-1 h-full text-gray-500' + onClick={() => + setQuantityInput(String(Number(quantityInput) + 1)) + } + > + + + </button> + </div> + <div> + <span + className={ + product?.sla?.qty < 10 ? 'text-red-600 font-medium' : '' + } + > + {' '} + Stock : {product?.sla?.qty}{' '} + </span> + </div> + <div> + {product?.sla?.qty > 0 && ( + <Link href='/panduan-pick-up-service' className='group'> + <Image + src='/images/PICKUP-NOW.png' + className='group-hover:scale-105 transition-transform duration-200 w-28' + alt='pickup now' + /> + </Link> + )} + </div> + </div> + <div className='flex gap-x-3'> + <Button onClick={() => handleAddToCart(product.id)} - className='flex-1 py-2 btn-yellow' + className='w-full' + colorScheme='yellow' > Keranjang - </button> - <button - type='button' + </Button> + <Button onClick={() => handleBuy(product.id)} - className='flex-1 py-2 btn-solid-red' + className='w-full' + colorScheme='red' > Beli - </button> + </Button> </div> - <div className='flex mt-4'> - <button - className='flex items-center gap-x-1' - onClick={toggleWishlist} - > - {wishlist.data?.productTotal > 0 ? ( - <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> - ) : ( - <HeartIcon className='w-6' /> - )} - Wishlist - </button> + <div className='flex py-5'> + <div className='flex gap-x-5 items-center justify-center'> + <Button + as={Link} + href={createdAskUrl} + variant='link' + target='_blank' + colorScheme='gray' + leftIcon={<MessageCircleIcon size={18} />} + > + Ask Admin + </Button> + + <span>|</span> + + <button + className='flex items-center gap-x-1' + onClick={toggleWishlist} + > + {wishlist.data?.productTotal > 0 ? ( + <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> + ) : ( + <HeartIcon className='w-6' /> + )} + Wishlist + </button> + + <span>|</span> + + <RWebShare + data={{ + text: 'Check out this product', + title: `${product.name} - Indoteknik.com`, + url: SELF_HOST + router.asPath, + }} + > + <Button + variant='link' + colorScheme='gray' + leftIcon={<Share2Icon size={18} />} + > + Share + </Button> + </RWebShare> + </div> </div> <div className='border border-gray_r-6 overflow-auto mt-4'> <div className='font-medium text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'> diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index bcb41dd6..f52aa5f7 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -85,7 +85,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { return ( <SessionProvider session={session}> <ScrollToTop /> - + <AnimatePresence> {animateLoader && ( <motion.div diff --git a/src/pages/api/shop/variant-detail.js b/src/pages/api/shop/variant-detail.js index 08ce75b8..160ec979 100644 --- a/src/pages/api/shop/variant-detail.js +++ b/src/pages/api/shop/variant-detail.js @@ -1,4 +1,4 @@ -import { productMappingSolr, variantsMappingSolr } from '@/utils/solrMapping' +import { variantsMappingSolr } from '@/utils/solrMapping' import axios from 'axios' export default async function handler(req, res) { diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx index 42f38774..2c0dd64b 100644 --- a/src/pages/shop/product/variant/[slug].jsx +++ b/src/pages/shop/product/variant/[slug].jsx @@ -32,16 +32,9 @@ export async function getServerSideProps(context) { 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 - const regexHtmlTagsExceptP = /<\/?(?!p\b)[^>]*>/g - product.description = product.description - .replace(regexHtmlTagsExceptP, ' ') - .replace(regexHtmlTags, ' ') - .trim()*/ } else { product = null; } diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js index f73e966a..f896a6a8 100644 --- a/src/utils/solrMapping.js +++ b/src/utils/solrMapping.js @@ -74,6 +74,7 @@ export const productMappingSolr = (products, pricelist) => { name: product.manufacture_name_s || '', imagePromotion1: product.image_promotion_1_s || '', imagePromotion2: product.image_promotion_2_s || '', + logo : product.x_logo_manufacture_s || '', }; } @@ -133,6 +134,7 @@ export const variantsMappingSolr = (parent, products, pricelist) => { productMapped.manufacture = { id: product.manufacture_id_i || '', name: product.manufacture_name_s || '', + logo : parent[0]?.x_logo_manufacture_s }; } productMapped.parent = { |
