diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-09-26 22:10:22 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-09-26 22:10:22 +0700 |
| commit | c660178c19cf33380cfe0bf585f48f98031d0006 (patch) | |
| tree | be194ea37810c9c7e33966dfd8e41a7b8415b932 /src-migrate | |
| parent | ecd8bbcbee558893126d1f99792b9371d5b5680a (diff) | |
<Miqdad> try fix crawl
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductDetail.tsx | 392 |
1 files changed, 141 insertions, 251 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index f0679da4..39011620 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -1,13 +1,17 @@ import style from '../styles/product-detail.module.css'; - import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useEffect, useRef, useState, UIEvent } from 'react'; +import { useEffect, useMemo, useState } from 'react'; +import dynamic from 'next/dynamic'; import { Button } from '@chakra-ui/react'; import { MessageCircleIcon, Share2Icon } from 'lucide-react'; import { LazyLoadComponent } from 'react-lazy-load-image-component'; -import { RWebShare } from 'react-web-share'; + +const RWebShare = dynamic( + () => import('react-web-share').then(m => m.RWebShare), + { ssr: false } +); import useDevice from '@/core/hooks/useDevice'; import { getAuth } from '~/libs/auth'; @@ -22,19 +26,22 @@ import Information from './Information'; import PriceAction from './PriceAction'; import SimilarBottom from './SimilarBottom'; import SimilarSide from './SimilarSide'; - import { gtagProductDetail } from '@/core/utils/googleTag'; -type Props = { - product: IProductDetail; -}; +type Props = { product: IProductDetail }; -const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST || ''; const ProductDetail = ({ product }: Props) => { const { isDesktop, isMobile } = useDevice(); const router = useRouter(); - const auth = getAuth(); + + const [auth, setAuth] = useState<any>(null); + useEffect(() => { + try { + setAuth(getAuth() ?? null); + } catch {} + }, []); const { setAskAdminUrl, @@ -43,306 +50,189 @@ const ProductDetail = ({ product }: Props) => { setIsApproval, isApproval, setSelectedVariant, - setSla, } = useProductDetail(); useEffect(() => { - gtagProductDetail(product); + try { + gtagProductDetail(product); + } catch {} }, [product]); + const currentPath = router?.asPath || '/'; useEffect(() => { const createdAskUrl = whatsappUrl({ template: 'product', payload: { - manufacture: product.manufacture.name, - productName: product.name, - url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, + manufacture: product.manufacture?.name ?? '', + productName: product.name ?? '', + url: SELF_HOST + currentPath, }, - fallbackUrl: router.asPath, + fallbackUrl: currentPath, }); - setAskAdminUrl(createdAskUrl); - }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]); + }, [currentPath, product.manufacture?.name, product.name, setAskAdminUrl]); useEffect(() => { - if (typeof auth === 'object') { - setIsApproval(auth?.feature?.soApproval); + if (auth && typeof auth === 'object') { + setIsApproval(!!auth?.feature?.soApproval); } const selectedVariant = - product?.variants?.find((variant) => variant.is_in_bu) || - product?.variants?.[0]; + product?.variants?.find(v => v.is_in_bu) || product?.variants?.[0] || null; setSelectedVariant(selectedVariant); - }, []); + }, [auth, product?.variants, setIsApproval, setSelectedVariant]); - // Gabungkan semua gambar produk (utama + tambahan) - const allImages = (() => { + const allImages = useMemo(() => { const arr: string[] = []; - if (product?.image) arr.push(product.image); // selalu masukkan utama, baik mobile maupun desktop - if ( - Array.isArray(product?.image_carousel) && - product.image_carousel.length - ) { - // hindari duplikat jika image utama juga ada di carousel - const set = new Set(arr); - for (const img of product.image_carousel) { - if (!set.has(img)) { - arr.push(img); - set.add(img); - } - } - } + if (Array.isArray(product?.image_carousel)) arr.push(...product.image_carousel); + if (product?.image) arr.unshift(product.image); return arr; - })(); + }, [product?.image, product?.image_carousel]); const [mainImage, setMainImage] = useState(allImages[0] || ''); - useEffect(() => { - // update mainImage jika sumber gambar berubah dan mainImage tidak ada di daftar - if (!allImages.includes(mainImage)) { - setMainImage(allImages[0] || ''); - } - }, [allImages]); // eslint-disable-line react-hooks/exhaustive-deps - - // ===== Slider mobile (tanpa dependency) ===== - const sliderRef = useRef<HTMLDivElement | null>(null); - const [currentIdx, setCurrentIdx] = useState(0); + if (!allImages.includes(mainImage)) setMainImage(allImages[0] || ''); + }, [allImages, mainImage]); - const handleMobileScroll = (e: UIEvent<HTMLDivElement>) => { - const el = e.currentTarget; - if (!el) return; - const idx = Math.round(el.scrollLeft / el.clientWidth); - if (idx !== currentIdx) { - setCurrentIdx(idx); - setMainImage(allImages[idx] || ''); - } - }; + const canShare = + typeof navigator !== 'undefined' && typeof (navigator as any).share === 'function'; - const scrollToIndex = (i: number) => { - const el = sliderRef.current; - if (!el) return; - el.scrollTo({ left: i * el.clientWidth, behavior: 'smooth' }); - setCurrentIdx(i); - setMainImage(allImages[i] || ''); - }; - // ============================================ + return ( + <> + <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} /> + </div> -return ( - <> - <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} /> - </div> + <div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'> + <div className='md:flex md:flex-wrap'> + <div className='md:w-4/12'> + <ProductImage product={{ ...product, image: mainImage }} /> - <div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'> - <div className='md:flex md:flex-wrap'> - {/* ===== Kolom kiri: gambar ===== */} - <div className='md:w-4/12'> - {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} - {isMobile ? ( - <div className='relative'> - <div - ref={sliderRef} - onScroll={handleMobileScroll} - className='flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar' - style={{ - scrollBehavior: 'smooth', - msOverflowStyle: 'none', - scrollbarWidth: 'none', - }} - > - {allImages.length > 0 ? ( - allImages.map((img, i) => ( - // slide tetap selebar viewport untuk snap mulus + {allImages.length > 0 && ( + <div className='mt-4 overflow-x-auto'> + <div className='flex space-x-3 pb-3'> + {allImages.map((img, index) => ( <div - key={i} - className='w-full flex-shrink-0 snap-center flex justify-center items-center' + 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' + }`} + onClick={() => setMainImage(img)} > - {/* gambar diperkecil */} <img src={img} - alt={`Gambar ${i + 1}`} - className='w-[85%] aspect-square object-contain' - onError={(e) => { - (e.target as HTMLImageElement).src = - '/images/noimage.jpeg'; + alt={`Thumbnail ${index + 1}`} + className='w-full h-full object-cover rounded-sm' + loading='lazy' + onError={e => { + (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} /> </div> - )) - ) : ( - <div className='w-full flex-shrink-0 snap-center flex justify-center items-center'> - <img - src={mainImage || '/images/noimage.jpeg'} - alt='Gambar produk' - className='w-[85%] aspect-square object-contain' - /> - </div> - )} - </div> - - {/* Dots indicator */} - {allImages.length > 1 && ( - <div className='absolute bottom-2 left-0 right-0 flex justify-center gap-2'> - {allImages.map((_, i) => ( - <button - key={i} - aria-label={`Ke slide ${i + 1}`} - className={`w-2 h-2 rounded-full ${ - currentIdx === i ? 'bg-gray-800' : 'bg-gray-300' - }`} - onClick={() => scrollToIndex(i)} - /> ))} </div> - )} - </div> - ) : ( - <> - {/* === DESKTOP: Tetap seperti sebelumnya === */} - <ProductImage product={{ ...product, image: mainImage }} /> - - {/* Carousel horizontal (thumbnail) – hanya desktop */} - {allImages.length > 0 && ( - <div className='mt-4 overflow-x-auto'> - <div className='flex space-x-3 pb-3'> - {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' - }`} - onClick={() => setMainImage(img)} - > - <img - src={img} - alt={`Thumbnail ${index + 1}`} - className='w-full h-full object-cover rounded-sm' - loading='lazy' - onError={(e) => { - (e.target as HTMLImageElement).src = - '/images/noimage.jpeg'; - }} - /> - </div> - ))} - </div> - </div> - )} - </> - )} - </div> - {/* <<=== TUTUP kolom kiri */} + </div> + )} + </div> - {/* ===== 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 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> </div> - </div> - <div className='h-full'> - {isMobile && ( - <div className='px-4 pt-6'> - <PriceAction product={product} /> - </div> - )} + <div className='h-full'> + {isMobile && ( + <div className='px-4 pt-6'> + <PriceAction product={product} /> + </div> + )} - <div className='h-4 md:h-10' /> - {!!activeVariantId && !isApproval && ( - <ProductPromoSection - product={product} - productId={activeVariantId} - /> - )} + <div className='h-4 md:h-10' /> + {!!activeVariantId && !isApproval && ( + <ProductPromoSection product={product} productId={activeVariantId} /> + )} - <div className='h-0 md:h-6' /> + <div className='h-0 md:h-6' /> - <div className={style['section-card']}> - <h2 className={style['heading']}>Informasi Produk</h2> - <div className='h-4' /> - <div className='overflow-x-auto'> - <div - className={style['description']} - dangerouslySetInnerHTML={{ - __html: - !product.description || product.description == '<p><br></p>' - ? 'Belum ada deskripsi' - : product.description, - }} - /> + <div className={style['section-card']}> + <h2 className={style['heading']}>Informasi Produk</h2> + <div className='h-4' /> + <div className='overflow-x-auto'> + <div + className={style['description']} + dangerouslySetInnerHTML={{ + __html: + !product.description || product.description === '<p><br></p>' + ? 'Belum ada deskripsi' + : product.description, + }} + /> + </div> </div> </div> </div> - </div> - - {isDesktop && ( - <div className='md:w-3/12'> - <PriceAction product={product} /> - <div className='flex gap-x-5 items-center justify-center'> - <Button - as={Link} - href={askAdminUrl} - variant='link' - target='_blank' - colorScheme='gray' - leftIcon={<MessageCircleIcon size={18} />} - > - Ask Admin - </Button> - <span>|</span> - - <AddToWishlist productId={product.id} /> - - <span>|</span> - - <RWebShare - data={{ - text: 'Check out this product', - title: `${product.name} - Indoteknik.com`, - url: SELF_HOST + router.asPath, - }} - > + {isDesktop && ( + <div className='md:w-3/12'> + <PriceAction product={product} /> + <div className='flex gap-x-5 items-center justify-center'> <Button + as={Link} + href={/* askAdminUrl sudah di-set di effect */ askAdminUrl} variant='link' + target='_blank' colorScheme='gray' - leftIcon={<Share2Icon size={18} />} + leftIcon={<MessageCircleIcon size={18} />} > - Share + Ask Admin </Button> - </RWebShare> - </div> - <div className='h-6' /> - <div className={style['heading']}>Produk Serupa</div> - - <div className='h-4' /> - - <SimilarSide product={product} /> - </div> - )} + <span>|</span> - <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> - <div className={style['heading']}>Kamu Mungkin Juga Suka</div> + <AddToWishlist productId={product.id} /> - <div className='h-6' /> + <span>|</span> - <LazyLoadComponent> - <SimilarBottom product={product} /> - </LazyLoadComponent> - </div> + {canShare && ( + <RWebShare + data={{ + text: 'Check out this product', + title: `${product.name} - Indoteknik.com`, + url: SELF_HOST + currentPath, + }} + > + <Button variant='link' colorScheme='gray' leftIcon={<Share2Icon size={18} />}> + Share + </Button> + </RWebShare> + )} + </div> - <div className='h-6 md:h-0' /> - </div> - </> -); + <div className='h-6' /> + <div className={style['heading']}>Produk Serupa</div> + <div className='h-4' /> + <SimilarSide product={product} /> + </div> + )} + <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> + <div className={style['heading']}>Kamu Mungkin Juga Suka</div> + <div className='h-6' /> + <LazyLoadComponent> + <SimilarBottom product={product} /> + </LazyLoadComponent> + </div> + <div className='h-6 md:h-0' /> + </div> + </> + ); }; export default ProductDetail; |
