summaryrefslogtreecommitdiff
path: root/src-migrate
diff options
context:
space:
mode:
authorMiqdad <ahmadmiqdad27@gmail.com>2025-08-22 15:30:08 +0700
committerMiqdad <ahmadmiqdad27@gmail.com>2025-08-22 15:30:08 +0700
commit753be9690f95c288aec2ab92269529131626254d (patch)
tree5bf87d90732fcdab13bba6bc3cb025889aa00e67 /src-migrate
parent8e2af6c1bf6d17b5409c5eb0c1e25e2da882898f (diff)
<Miqdad> Image slider when mobile
Diffstat (limited to 'src-migrate')
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx177
1 files changed, 134 insertions, 43 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
index 192e1dc3..a6b4e6de 100644
--- a/src-migrate/modules/product-detail/components/ProductDetail.tsx
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -2,7 +2,7 @@ import style from '../styles/product-detail.module.css';
import Link from 'next/link';
import { useRouter } from 'next/router';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState, UIEvent } from 'react';
import { Button } from '@chakra-ui/react';
import { MessageCircleIcon, Share2Icon } from 'lucide-react';
@@ -35,6 +35,7 @@ const ProductDetail = ({ product }: Props) => {
const { isDesktop, isMobile } = useDevice();
const router = useRouter();
const auth = getAuth();
+
const {
setAskAdminUrl,
askAdminUrl,
@@ -71,70 +72,168 @@ const ProductDetail = ({ product }: Props) => {
product?.variants?.find((variant) => variant.is_in_bu) ||
product?.variants?.[0];
setSelectedVariant(selectedVariant);
- // setSelectedVariant(product?.variants[0])
- }, []);
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
// Gabungkan semua gambar produk (utama + tambahan)
- const allImages = product.image_carousel ? [...product.image_carousel] : [];
-
- if (product.image) {
- allImages.unshift(product.image); // Tambahkan gambar utama di awal array
- }
- console.log(product);
+ // Gabungkan semua gambar produk (utama + tambahan)
+ const allImages = (() => {
+ 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);
+ }
+ }
+ }
+ return arr;
+ })();
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);
+
+ 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 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>
+
<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'>
- <ProductImage product={{ ...product, image: mainImage }} />
-
- {/* Carousel horizontal */}
- {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)}
- >
+ {/* === 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', // IE/Edge lama
+ scrollbarWidth: 'none', // Firefox
+ }}
+ >
+ {allImages.length > 0 ? (
+ allImages.map((img, i) => (
<img
+ key={i}
src={img}
- alt={`Thumbnail ${index + 1}`}
- className='w-full h-full object-cover rounded-sm'
- loading='lazy'
+ alt={`Gambar ${i + 1}`}
+ className='w-full aspect-square object-cover flex-shrink-0 snap-center'
onError={(e) => {
(e.target as HTMLImageElement).src =
'/path/to/fallback-image.jpg';
}}
/>
- </div>
- ))}
+ ))
+ ) : (
+ <img
+ src={mainImage || '/path/to/fallback-image.jpg'}
+ alt='Gambar produk'
+ className='w-full aspect-square object-cover flex-shrink-0 snap-center'
+ />
+ )}
</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>
@@ -154,14 +253,6 @@ const ProductDetail = ({ product }: Props) => {
/>
)}
- {/* <div className={style['section-card']}>
- <h2 className={style['heading']}>
- Variant ({product.variant_total})
- </h2>
- <div className='h-4' />
- <VariantList variants={product.variants} />
- </div> */}
-
<div className='h-0 md:h-6' />
<div className={style['section-card']}>
@@ -246,4 +337,4 @@ const ProductDetail = ({ product }: Props) => {
);
};
-export default ProductDetail;
+export default ProductDetail; \ No newline at end of file