summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIT Fixcomart <it@fixcomart.co.id>2025-09-27 02:39:35 +0000
committerIT Fixcomart <it@fixcomart.co.id>2025-09-27 02:39:35 +0000
commit3ccfa710d65ff607f21c8eec5d77e343b6ed800a (patch)
treebe194ea37810c9c7e33966dfd8e41a7b8415b932
parentecd8bbcbee558893126d1f99792b9371d5b5680a (diff)
parentc660178c19cf33380cfe0bf585f48f98031d0006 (diff)
Merged in crawl-fix (pull request #466)
<Miqdad> try fix crawl
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx392
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;