diff options
Diffstat (limited to 'src-migrate')
5 files changed, 147 insertions, 44 deletions
diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx index 147fd6d2..0dc39c1c 100644 --- a/src-migrate/modules/product-detail/components/AddToCart.tsx +++ b/src-migrate/modules/product-detail/components/AddToCart.tsx @@ -66,6 +66,8 @@ const AddToCart = ({ weight: '', isFlashSale: false, }); + const hasPrice = + !!product?.lowest_price && Number(product.lowest_price.price) > 0; useEffect(() => { const fetchData = async () => { @@ -183,6 +185,7 @@ const AddToCart = ({ colorScheme={btnConfig[source].colorScheme} variant={btnConfig[source].variant} className='w-full' + isDisabled={!hasPrice || status === 'loading'} > {btnConfig[source].text} </Button> @@ -194,6 +197,7 @@ const AddToCart = ({ colorScheme={btnConfig[source].colorScheme} variant={btnConfig[source].variant} className='w-full' + isDisabled={!hasPrice || status === 'loading'} > {btnConfig[source].text} </Button> @@ -208,7 +212,10 @@ const AddToCart = ({ {/* ===== MOBILE LAYOUT: konten scroll + footer fixed di dalam popup ===== */} <div className='md:hidden flex flex-col max-h-[75vh]'> {/* area scroll */} - <div className='flex-1 overflow-y-auto' style={{ scrollbarWidth: 'none' }}> + <div + className='flex-1 overflow-y-auto' + style={{ scrollbarWidth: 'none' }} + > {/* HEADER ITEM */} <div className='flex mt-4'> <div className='w-[25%]'> diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx index ebfcef32..3e811330 100644 --- a/src-migrate/modules/product-detail/components/AddToQuotation.tsx +++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx @@ -60,6 +60,8 @@ const AddToQuotation = ({ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`, ].join('&'); const [addCartAlert, setAddCartAlert] = useState(false); + const hasPrice = + !!product?.lowest_price && Number(product.lowest_price.price) > 0; const handleButton = async () => { if (typeof auth !== 'object') { @@ -124,9 +126,10 @@ const AddToQuotation = ({ color={'red'} colorScheme='white' className='w-full border-2 p-2 gap-1 hover:bg-slate-100 flex items-center' + isDisabled={!hasPrice} > <ImageNext - src= {isDesktop ? '/images/doc_red.svg' : '/images/doc.svg'} + src={isDesktop ? '/images/doc_red.svg' : '/images/doc.svg'} alt='penawaran instan' className='' width={25} @@ -191,7 +194,20 @@ const AddToQuotation = ({ </> )} - {!!product.lowest_price && product.lowest_price.price === 0 && ( + {(!!product.lowest_price && product.lowest_price.price === 0) || + product.lowest_price.price < 0 ? ( + <span> + Hubungi kami untuk dapatkan harga terbaik,{' '} + <Link + href={askAdminUrl} + target='_blank' + className='font-medium underline' + color={'red'} + > + klik disini + </Link> + </span> + ) : ( <span> Hubungi kami untuk dapatkan harga terbaik,{' '} <Link diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index ffc9ba40..d73ab5f6 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -37,6 +37,7 @@ const PriceAction = ({ product }: Props) => { } = useProductDetail(); const [qtyPickUp, setQtyPickUp] = useState(0); const { isDesktop, isMobile } = useDevice(); + useEffect(() => { setActive(selectedVariant); if (product.variants.length > 2 && product.variants[0].price.price === 0) { @@ -77,6 +78,7 @@ const PriceAction = ({ product }: Props) => { const validJsonString = stringVoucher.replace(/'/g, '"'); voucherPastiHemat = JSON.parse(validJsonString); } + const hasPrice = Number(product?.lowest_price?.price) > 0; return ( <div @@ -144,7 +146,7 @@ const PriceAction = ({ product }: Props) => { </> )} - {!!activePrice && activePrice.price === 0 && ( + {/* {!!activePrice && activePrice.price === 0 && ( <span> Hubungi kami untuk dapatkan harga terbaik,{' '} <Link @@ -155,7 +157,7 @@ const PriceAction = ({ product }: Props) => { klik disini </Link> </span> - )} + )} */} <DesktopView> <div className='h-4' /> @@ -164,27 +166,32 @@ const PriceAction = ({ product }: Props) => { <div className='relative flex items-center'> <button type='button' - className='absolute left-0 px-2 py-1 h-full text-gray-500' + className='absolute left-0 px-2 py-1 h-full text-gray-500 disabled:opacity-40' onClick={() => setQuantityInput(String(Math.max(1, Number(quantityInput) - 1))) } + disabled={!hasPrice} > - </button> + <input type='number' id='quantity' min={1} value={quantityInput} onChange={(e) => setQuantityInput(e.target.value)} - className={style['quantity-input']} + className={`${style['quantity-input']} disabled:bg-gray-100 disabled:text-gray-400`} + disabled={!hasPrice} /> + <button type='button' - className='absolute right-0 px-2 py-1 h-full text-gray-500' + className='absolute right-0 px-2 py-1 h-full text-gray-500 disabled:opacity-40' onClick={() => setQuantityInput(String(Number(quantityInput) + 1)) } + disabled={!hasPrice} > + </button> @@ -195,9 +202,11 @@ const PriceAction = ({ product }: Props) => { <Skeleton isLoaded={sla} h='21px' - className={sla?.qty < 10 ? 'text-red-600 font-medium' : ''} + className={ + !hasPrice || sla?.qty < 10 ? 'text-red-600 font-medium' : '' + } > - Stock : {sla?.qty}{' '} + Stock : {hasPrice ? sla?.qty : 'Habis'} </Skeleton> </div> @@ -218,9 +227,9 @@ const PriceAction = ({ product }: Props) => { )} </div> </div> - <span className='text-[12px] text-red-500 italic'> + {/* <span className='text-[12px] text-red-500 italic'> * {qtyPickUp} barang bisa di pickup - </span> + </span> */} </DesktopView> {/* ===== MOBILE: grid kiri-kanan, kanan hanya qty ===== */} @@ -255,11 +264,11 @@ const PriceAction = ({ product }: Props) => { )} </div> - {qtyPickUp > 0 && ( + {/* {qtyPickUp > 0 && ( <div className='text-[12px] mt-1 text-red-500 italic'> * {qtyPickUp} barang bisa di pickup </div> - )} + )} */} </div> {/* Kanan: hanya qty, rata kanan */} @@ -274,6 +283,7 @@ const PriceAction = ({ product }: Props) => { ) } aria-label='Kurangi' + disabled={!hasPrice} > <span className='text-2xl leading-none'>–</span> </button> @@ -288,6 +298,7 @@ const PriceAction = ({ product }: Props) => { [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none' + disabled={!hasPrice} /> <button @@ -297,6 +308,7 @@ const PriceAction = ({ product }: Props) => { setQuantityInput(String(Number(quantityInput) + 1)) } aria-label='Tambah' + disabled={!hasPrice} > <span className='text-2xl leading-none'>+</span> </button> diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index f32bb38e..e4ba2b2f 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -5,7 +5,12 @@ import { useRouter } from 'next/router'; import { useEffect, useRef, useState, UIEvent } from 'react'; import { Button } from '@chakra-ui/react'; -import { MessageCircleIcon, Share2Icon } from 'lucide-react'; +import { + AlertCircle, + AlertTriangle, + MessageCircleIcon, + Share2Icon, +} from 'lucide-react'; import { LazyLoadComponent } from 'react-lazy-load-image-component'; import useDevice from '@/core/hooks/useDevice'; @@ -23,7 +28,6 @@ import SimilarBottom from './SimilarBottom'; import SimilarSide from './SimilarSide'; import dynamic from 'next/dynamic'; - import { gtagProductDetail } from '@/core/utils/googleTag'; type Props = { @@ -31,7 +35,7 @@ type Props = { }; const RWebShare = dynamic( - () => import('react-web-share').then(m => m.RWebShare), + () => import('react-web-share').then((m) => m.RWebShare), { ssr: false } ); @@ -42,7 +46,9 @@ const ProductDetail = ({ product }: Props) => { const router = useRouter(); const [auth, setAuth] = useState<any>(null); useEffect(() => { - try { setAuth(getAuth() ?? null); } catch { } + try { + setAuth(getAuth() ?? null); + } catch {} }, []); const canShare = @@ -87,7 +93,6 @@ const ProductDetail = ({ product }: Props) => { setSelectedVariant(selectedVariant); }, []); - const allImages = (() => { const arr: string[] = []; if (product?.image) arr.push(product.image); @@ -95,7 +100,6 @@ const ProductDetail = ({ product }: Props) => { Array.isArray(product?.image_carousel) && product.image_carousel.length ) { - const set = new Set(arr); for (const img of product.image_carousel) { if (!set.has(img)) { @@ -108,15 +112,14 @@ const ProductDetail = ({ product }: Props) => { })(); const [mainImage, setMainImage] = useState(allImages[0] || ''); + const hasPrice = Number(product?.lowest_price?.price) > 0; useEffect(() => { - if (!allImages.includes(mainImage)) { setMainImage(allImages[0] || ''); } }, [allImages]); - const sliderRef = useRef<HTMLDivElement | null>(null); const [currentIdx, setCurrentIdx] = useState(0); @@ -138,9 +141,32 @@ const ProductDetail = ({ product }: Props) => { setMainImage(allImages[i] || ''); }; - return ( <> + <div className='relative'> + {isDesktop && !hasPrice && ( + <div className='absolute inset-0 z-[20] flex items-center justify-center pointer-events-none select-none'> + <img + src='/images/produk_tidak_tersedia.svg' + alt='Produk tidak tersedia' + className='w-[47%] opacity-50 -translate-x-[3%] -translate-y-[-70%]' + /> + </div> + )} + </div> + + <div className='relative'> + {isMobile && !hasPrice && ( + <div className='absolute inset-0 z-[50] flex items-center justify-center pointer-events-none select-none'> + <img + src='/images/produk_tidak_tersedia.svg' + alt='Produk tidak tersedia' + className='w-[100%] opacity-[1000%] -translate-x-[0%] -translate-y-[-197%]' + /> + </div> + )} + </div> + <div className='md:flex md:flex-wrap'> <div className='w-full mb-4 md:mb-0 px-4 md:px-0'> <Breadcrumb id={product.id} name={product.name} /> @@ -165,7 +191,6 @@ const ProductDetail = ({ product }: Props) => { > {allImages.length > 0 ? ( allImages.map((img, i) => ( - <div key={i} className='w-full flex-shrink-0 snap-center flex justify-center items-center' @@ -200,8 +225,9 @@ const ProductDetail = ({ product }: Props) => { <button key={i} aria-label={`Ke slide ${i + 1}`} - className={`w-2 h-2 rounded-full ${currentIdx === i ? 'bg-gray-800' : 'bg-gray-300' - }`} + className={`w-2 h-2 rounded-full ${ + currentIdx === i ? 'bg-gray-800' : 'bg-gray-300' + }`} onClick={() => scrollToIndex(i)} /> ))} @@ -220,10 +246,11 @@ const ProductDetail = ({ product }: Props) => { {allImages.map((img, index) => ( <div key={index} - className={`flex-shrink-0 w-16 h-16 cursor-pointer border-2 rounded-md transition-colors ${mainImage === img - ? 'border-red-500 ring-2 ring-red-200' - : 'border-gray-200 hover:border-gray-300' - }`} + className={`flex-shrink-0 w-16 h-16 cursor-pointer border-2 rounded-md transition-colors ${ + mainImage === img + ? 'border-red-500 ring-2 ring-red-200' + : 'border-gray-200 hover:border-gray-300' + }`} onClick={() => setMainImage(img)} > <img @@ -247,13 +274,45 @@ const ProductDetail = ({ product }: Props) => { {/* <<=== TUTUP kolom kiri */} {/* ===== Kolom kanan: info ===== */} - <div className='md:w-8/12 px-4 md:pl-6'> - <div className='h-6 md:h-0' /> - <h1 className={style['title']}>{product.name}</h1> - <div className='h-3 md:h-0' /> - <Information product={product} /> - <div className='h-6' /> - </div> + {isDesktop && ( + <div className='md:w-8/12 px-4 md:pl-6'> + {!hasPrice && ( + <div className='bg-red-50 p-2 py-1.5 rounded-lg border border-red-500 flex gap-1 items-center '> + <AlertTriangle + size={18} + className='text-red-600 shrink-0 mx-2' + /> + <h1 className='text-red-600 font-normal text-h-sm'> + Maaf untuk saat ini Produk yang anda cari tidak tersedia + </h1> + </div> + )} + <div className='h-6 md:h-0' /> + <h1 className={style['title']}>{product.name}</h1> + <div className='h-3 md:h-0' /> + <Information product={product} /> + <div className='h-6' /> + </div> + )} + {isMobile && ( + <div className='md:w-8/12 px-4 md:pl-6 relative'> + {!hasPrice && ( + <div className='bg-red-50 p-2 py-1.5 border-b border-red-500 flex gap-1 items-center w-screen relative left-1/2 right-1/2 -translate-x-1/2'> + <AlertTriangle + size={18} + className='text-red-600 shrink-0 mx-2' + /> + <h1 className='text-red-600 font-normal text-h-sm'> + Maaf untuk saat ini Produk yang anda cari tidak tersedia + </h1> + </div> + )} + <h1 className={style['title']}>{product.name}</h1> + <div className='h-3 md:h-0' /> + <Information product={product} /> + <div className='h-6' /> + </div> + )} </div> <div className='h-full'> @@ -281,7 +340,8 @@ const ProductDetail = ({ product }: Props) => { className={style['description']} dangerouslySetInnerHTML={{ __html: - !product.description || product.description == '<p><br></p>' + !product.description || + product.description == '<p><br></p>' ? 'Belum ada deskripsi' : product.description, }} @@ -302,13 +362,16 @@ const ProductDetail = ({ product }: Props) => { target='_blank' colorScheme='gray' leftIcon={<MessageCircleIcon size={18} />} + isDisabled={!hasPrice} > Ask Admin </Button> <span>|</span> - <AddToWishlist productId={product.id} /> + <div className={hasPrice ? '' : 'opacity-40 pointer-events-none'}> + <AddToWishlist productId={product.id} /> + </div> <span>|</span> @@ -317,10 +380,17 @@ const ProductDetail = ({ product }: Props) => { data={{ text: 'Check out this product', title: `${product.name} - Indoteknik.com`, - url: (process.env.NEXT_PUBLIC_SELF_HOST || '') + (router?.asPath || '/'), + url: + (process.env.NEXT_PUBLIC_SELF_HOST || '') + + (router?.asPath || '/'), }} > - <Button variant='link' colorScheme='gray' leftIcon={<Share2Icon size={18} />}> + <Button + variant='link' + colorScheme='gray' + leftIcon={<Share2Icon size={18} />} + isDisabled={!hasPrice} + > Share </Button> </RWebShare> @@ -350,8 +420,6 @@ const ProductDetail = ({ product }: Props) => { </div> </> ); - - }; export default ProductDetail; diff --git a/src-migrate/pages/shop/product/[slug].tsx b/src-migrate/pages/shop/product/[slug].tsx index 058e4832..8d6558b1 100644 --- a/src-migrate/pages/shop/product/[slug].tsx +++ b/src-migrate/pages/shop/product/[slug].tsx @@ -49,7 +49,7 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ( Array.isArray(product.variants) && product.variants.some((v) => (v?.price?.price ?? 0) > 0); - if (!hasValidVariant) return { notFound: true }; + // if (!hasValidVariant) return { notFound: true }; // bikin canonical path yang BERSIH dan KONSISTEN dari data produk, // bukan dari URL request user (jadi gak ikut ?utm_source, ?ref=, dsb) |
