summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiqdad <ahmadmiqdad27@gmail.com>2025-08-26 11:41:54 +0700
committerMiqdad <ahmadmiqdad27@gmail.com>2025-08-26 11:41:54 +0700
commitbcf7be5a0c1d9635383605afc0600386b0b356ea (patch)
tree83ebd96627aa3d405c5debfee15f1aaeeab0a240
parent8e92e0b4bfb74ebdf99fbc9d4ca7d47e2513c4e0 (diff)
<Miqdad>Push
-rw-r--r--src-migrate/modules/product-detail/components/Breadcrumb.tsx79
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx347
2 files changed, 232 insertions, 194 deletions
diff --git a/src-migrate/modules/product-detail/components/Breadcrumb.tsx b/src-migrate/modules/product-detail/components/Breadcrumb.tsx
index f41859a9..ba53aca4 100644
--- a/src-migrate/modules/product-detail/components/Breadcrumb.tsx
+++ b/src-migrate/modules/product-detail/components/Breadcrumb.tsx
@@ -1,41 +1,70 @@
-import React, { Fragment } from 'react'
-import { useQuery } from 'react-query'
-import { getProductCategoryBreadcrumb } from '~/services/product'
-import Link from 'next/link'
-import { createSlug } from '~/libs/slug'
+import React, { Fragment } from 'react';
+import { useQuery } from 'react-query';
+import { getProductCategoryBreadcrumb } from '~/services/product';
+import Link from 'next/link';
+import { createSlug } from '~/libs/slug';
type Props = {
- id: number,
- name: string
-}
+ id: number;
+ name: string;
+};
+
+const MAX_VISIBLE_CATEGORIES = 2; // tampilkan 2 level terakhir
const Breadcrumb = ({ id, name }: Props) => {
- const query = useQuery({
- queryKey: ['product-category-breadcrumb'],
+ const { data: breadcrumbs = [] } = useQuery({
+ queryKey: ['product-category-breadcrumb', id], // penting: ikutkan id
queryFn: () => getProductCategoryBreadcrumb(id),
- refetchOnWindowFocus: false
- })
+ refetchOnWindowFocus: false,
+ });
- const breadcrumbs = query.data || []
+ const total = breadcrumbs.length;
+ const showEllipsis = total > MAX_VISIBLE_CATEGORIES;
+ const visible = showEllipsis
+ ? breadcrumbs.slice(total - MAX_VISIBLE_CATEGORIES)
+ : breadcrumbs;
+ const hiddenText = showEllipsis
+ ? breadcrumbs
+ .slice(0, total - MAX_VISIBLE_CATEGORIES)
+ .map((c) => c.name)
+ .join(' / ')
+ : '';
return (
- <div className='line-clamp-2 md:line-clamp-1 leading-7 text-caption-1'>
- <Link href='/' className='text-danger-500'>Home</Link>
- <span className='mx-2'>/</span>
- {breadcrumbs.map((category, index) => (
- <Fragment key={index}>
+ <div className='flex items-center whitespace-nowrap overflow-hidden text-caption-1 leading-7'>
+ <Link href='/' className='text-danger-500 shrink-0'>
+ Home
+ </Link>
+ <span className='mx-2 shrink-0'>/</span>
+
+ {showEllipsis && (
+ <>
+ <span className='text-danger-500 shrink-0' title={hiddenText}>
+ …
+ </span>
+ <span className='mx-2 shrink-0'>/</span>
+ </>
+ )}
+
+ {visible.map((category, index) => (
+ <Fragment key={category.id ?? index}>
<Link
- href={createSlug('/shop/category/', category.name, category.id.toString())}
- className='text-danger-500'
+ href={createSlug(
+ '/shop/category/',
+ category.name,
+ String(category.id)
+ )}
+ className='text-danger-500 shrink-0'
>
{category.name}
</Link>
- <span className='mx-2'>/</span>
+ <span className='mx-2 shrink-0'>/</span>
</Fragment>
))}
- <span>{name}</span>
+
+ <span className='truncate min-w-0 flex-1'>{name}</span>
</div>
- )
-}
+ );
+};
-export default Breadcrumb \ No newline at end of file
+export default Breadcrumb;
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
index e6b77fb9..8ecdb2fc 100644
--- a/src-migrate/modules/product-detail/components/ProductDetail.tsx
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -126,214 +126,223 @@ const ProductDetail = ({ product }: Props) => {
};
// ============================================
- 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'>
- {/* ===== 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) => (
+ <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
+ <div
+ key={i}
+ className='w-full flex-shrink-0 snap-center flex justify-center items-center'
+ >
+ {/* gambar diperkecil */}
<img
- key={i}
src={img}
alt={`Gambar ${i + 1}`}
- className='w-full aspect-square object-cover flex-shrink-0 snap-center'
+ className='w-[80%] max-w-[320px] aspect-square object-contain'
onError={(e) => {
(e.target as HTMLImageElement).src =
'/path/to/fallback-image.jpg';
}}
/>
- ))
- ) : (
+ </div>
+ ))
+ ) : (
+ <div className='w-full flex-shrink-0 snap-center flex justify-center items-center'>
<img
src={mainImage || '/path/to/fallback-image.jpg'}
alt='Gambar produk'
- className='w-full aspect-square object-cover flex-shrink-0 snap-center'
+ className='w-[80%] max-w-[320px] aspect-square object-contain'
/>
- )}
- </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 =
- '/path/to/fallback-image.jpg';
- }}
- />
- </div>
- ))}
- </div>
- </div>
- )}
- </>
- )}
- </div>
- {/* <<=== 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>
- </div>
- <div className='h-full'>
- {isMobile && (
- <div className='px-4 pt-6'>
- <PriceAction product={product} />
+ {/* 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 =
+ '/path/to/fallback-image.jpg';
+ }}
+ />
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+ </>
)}
+ </div>
+ {/* <<=== 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>
+ </div>
- <div className='h-4 md:h-10' />
- {!!activeVariantId && !isApproval && (
- <ProductPromoSection
- product={product}
- productId={activeVariantId}
- />
- )}
+ <div className='h-full'>
+ {isMobile && (
+ <div className='px-4 pt-6'>
+ <PriceAction product={product} />
+ </div>
+ )}
- <div className='h-0 md:h-6' />
+ <div className='h-4 md:h-10' />
+ {!!activeVariantId && !isApproval && (
+ <ProductPromoSection
+ product={product}
+ productId={activeVariantId}
+ />
+ )}
- <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 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>
</div>
</div>
+ </div>
- {isDesktop && (
- <div className='md:w-3/12'>
- <PriceAction product={product} />
- <div className='flex gap-x-5 items-center justify-center'>
+ {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,
+ }}
+ >
<Button
- as={Link}
- href={askAdminUrl}
variant='link'
- target='_blank'
colorScheme='gray'
- leftIcon={<MessageCircleIcon size={18} />}
+ leftIcon={<Share2Icon size={18} />}
>
- Ask Admin
+ Share
</Button>
+ </RWebShare>
+ </div>
- <span>|</span>
-
- <AddToWishlist productId={product.id} />
-
- <span>|</span>
+ <div className='h-6' />
+ <div className={style['heading']}>Produk Serupa</div>
- <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 className='h-4' />
- <div className='h-6' />
- <div className={style['heading']}>Produk Serupa</div>
+ <SimilarSide product={product} />
+ </div>
+ )}
- <div className='h-4' />
+ <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'>
+ <div className={style['heading']}>Kamu Mungkin Juga Suka</div>
- <SimilarSide product={product} />
- </div>
- )}
+ <div className='h-6' />
- <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'>
- <div className={style['heading']}>Kamu Mungkin Juga Suka</div>
+ <LazyLoadComponent>
+ <SimilarBottom product={product} />
+ </LazyLoadComponent>
+ </div>
- <div className='h-6' />
+ <div className='h-6 md:h-0' />
+ </div>
+ </>
+);
- <LazyLoadComponent>
- <SimilarBottom product={product} />
- </LazyLoadComponent>
- </div>
- <div className='h-6 md:h-0' />
- </div>
- </>
- );
};
-export default ProductDetail; \ No newline at end of file
+export default ProductDetail;