diff options
Diffstat (limited to 'src-migrate/modules')
7 files changed, 166 insertions, 65 deletions
diff --git a/src-migrate/modules/product-detail/components/Image.tsx b/src-migrate/modules/product-detail/components/Image.tsx index 96ae2027..d99b683c 100644 --- a/src-migrate/modules/product-detail/components/Image.tsx +++ b/src-migrate/modules/product-detail/components/Image.tsx @@ -50,8 +50,7 @@ const Image = ({ product }: Props) => { <ImageUI src={image} alt={product.name} - width={256} - height={256} + fill className={style['image']} loading='eager' priority diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index ffc9ba40..6cc2f0bf 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -66,17 +66,21 @@ const PriceAction = ({ product }: Props) => { setQuantityInput('1'); }, [selectedVariant]); - let voucherPastiHemat = 0; + const price = activePrice?.price_discount || activePrice?.price || 0; + const pricedigit = String(Math.floor(price)).length; + const fontSize = pricedigit >= 9 ? '20px' : undefined; - if ( - product?.voucher_pasti_hemat - ? product?.voucher_pasti_hemat.length - : voucherPastiHemat > 0 - ) { - const stringVoucher = product?.voucher_pasti_hemat[0]; - const validJsonString = stringVoucher.replace(/'/g, '"'); - voucherPastiHemat = JSON.parse(validJsonString); - } + // let voucherPastiHemat = 0; + + // if ( + // product?.voucher_pasti_hemat + // ? product?.voucher_pasti_hemat.length + // : voucherPastiHemat > 0 + // ) { + // const stringVoucher = product?.voucher_pasti_hemat[0]; + // const validJsonString = stringVoucher.replace(/'/g, '"'); + // voucherPastiHemat = JSON.parse(validJsonString); + // } return ( <div @@ -91,25 +95,40 @@ const PriceAction = ({ product }: Props) => { <DesktopView> <div className='flex items-end gap-x-2'> {activePrice.discount_percentage > 0 && ( - <> - <div className={style['disc-badge']}> - {Math.floor(activePrice.discount_percentage)}% - </div> - <div className={style['disc-price']}> - Rp {formatCurrency(activePrice.price || 0)} - </div> - </> + <div className={style['disc-badge']}> + {Math.floor(activePrice.discount_percentage)}% + </div> )} - <div className={style['main-price']}> - Rp {formatCurrency(activePrice.price_discount || 0)} + <div + className={style['main-price']} + style={fontSize ? { fontSize } : undefined} + > + Rp{' '} + {formatCurrency( + activePrice.discount_percentage > 0 + ? activePrice.price_discount || 0 + : activePrice.price || 0 + )} </div> + {activePrice.discount_percentage > 0 && ( + <div className={style['disc-price']}> + Rp {formatCurrency(activePrice.price || 0)} + </div> + )} </div> <div className='h-1' /> <div className={style['secondary-text']}> Termasuk PPN: Rp{' '} - {formatCurrency(Math.round(activePrice.price_discount * PPN))} + {formatCurrency( + Math.round( + (activePrice.discount_percentage > 0 + ? activePrice.price_discount + : activePrice.price) * PPN + ) + )} </div> </DesktopView> + <MobileView> <div className='flex items-end gap-x-2'> {activePrice.discount_percentage > 0 ? ( @@ -159,7 +178,7 @@ const PriceAction = ({ product }: Props) => { <DesktopView> <div className='h-4' /> - <div className='flex gap-x-5 items-center'> + <div className='flex gap-x-4 items-center'> {/* Qty */} <div className='relative flex items-center'> <button @@ -191,20 +210,20 @@ const PriceAction = ({ product }: Props) => { </div> {/* Stok */} - <div> + <div className='min-w-[89px]'> <Skeleton isLoaded={sla} h='21px' className={sla?.qty < 10 ? 'text-red-600 font-medium' : ''} > - Stock : {sla?.qty}{' '} + Stock : {sla?.qty} </Skeleton> </div> {/* Pickup badge */} - <div> + <div className='shrink-0'> {qtyPickUp > 0 && ( - <div className='flex items-center gap-2'> + <div className='flex items-center'> <Link href='/panduan-pick-up-service' className='group'> <Image src='/images/PICKUP-NOW.png' @@ -218,7 +237,7 @@ const PriceAction = ({ product }: Props) => { )} </div> </div> - <span className='text-[12px] text-red-500 italic'> + <span className='block text-[12px] text-red-500 italic mt-1'> * {qtyPickUp} barang bisa di pickup </span> </DesktopView> diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index f32bb38e..51b080ef 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -22,16 +22,17 @@ import PriceAction from './PriceAction'; import SimilarBottom from './SimilarBottom'; import SimilarSide from './SimilarSide'; import dynamic from 'next/dynamic'; - +import { TicketIcon } from '@heroicons/react/24/solid'; import { gtagProductDetail } from '@/core/utils/googleTag'; +import currencyFormat from '@/core/utils/currencyFormat'; type Props = { product: IProductDetail; }; const RWebShare = dynamic( - () => import('react-web-share').then(m => m.RWebShare), + () => import('react-web-share').then((m) => m.RWebShare), { ssr: false } ); @@ -42,7 +43,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 +90,6 @@ const ProductDetail = ({ product }: Props) => { setSelectedVariant(selectedVariant); }, []); - const allImages = (() => { const arr: string[] = []; if (product?.image) arr.push(product.image); @@ -95,7 +97,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 +109,14 @@ const ProductDetail = ({ product }: Props) => { })(); const [mainImage, setMainImage] = useState(allImages[0] || ''); + const [discount, setDiscount] = useState(0); useEffect(() => { - if (!allImages.includes(mainImage)) { setMainImage(allImages[0] || ''); } }, [allImages]); - const sliderRef = useRef<HTMLDivElement | null>(null); const [currentIdx, setCurrentIdx] = useState(0); @@ -138,6 +138,47 @@ const ProductDetail = ({ product }: Props) => { setMainImage(allImages[i] || ''); }; + const voucherNew = Array.isArray(product?.new_voucher_pasti_hemat) + ? product.new_voucher_pasti_hemat[0] + : undefined; + + const voucher = voucherNew ?? null; + + const type = voucher?.discount_type ?? ''; // 'percentage' | 'percent' | 'fixed' + const amount = Number( + voucher?.discountAmount ?? voucher?.discount_amount ?? 0 + ); + const max = Number(voucher?.max_discount ?? 0); + const min = Number(voucher?.min_purchase ?? 0); + + const basePrice = + Number(product?.lowest_price?.price_discount ?? 0) || + Number(product?.lowest_price?.price ?? 0); + + function calcVoucherDiscount() { + if (!voucher || !basePrice) return 0; + if (min > 0 && basePrice < min) return 0; + + const percent = type.toLowerCase().startsWith('percent') + ? amount <= 1 + ? amount * 100 + : amount + : 0; + + let cut = 0; + if (type.toLowerCase().startsWith('percent')) { + cut = basePrice * (percent / 100); + } else { + cut = amount || 0; + } + + if (max > 0) cut = Math.min(cut, max); + return Math.max(0, cut); + } + + useEffect(() => { + setDiscount(calcVoucherDiscount()); + }, [product?.lowest_price]); return ( <> @@ -165,7 +206,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 +240,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 +261,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 @@ -249,6 +291,17 @@ const ProductDetail = ({ product }: Props) => { {/* ===== Kolom kanan: info ===== */} <div className='md:w-8/12 px-4 md:pl-6'> <div className='h-6 md:h-0' /> + {isMobile && ( + <div className='text text-sm font-medium'> + <TicketIcon className='inline text-yellow-300 w-5 h-5' />{' '} + Pakai{' '} + <span className='text-green-600 font-extrabold'> + {' '} + Voucher belanja hemat {currencyFormat(discount)} + </span>{' '} + saat checkout + </div> + )} <h1 className={style['title']}>{product.name}</h1> <div className='h-3 md:h-0' /> <Information product={product} /> @@ -281,7 +334,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, }} @@ -317,10 +371,16 @@ 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} />} + > Share </Button> </RWebShare> @@ -350,8 +410,6 @@ const ProductDetail = ({ product }: Props) => { </div> </> ); - - }; export default ProductDetail; diff --git a/src-migrate/modules/product-detail/styles/image.module.css b/src-migrate/modules/product-detail/styles/image.module.css index e472fe8d..f33a659b 100644 --- a/src-migrate/modules/product-detail/styles/image.module.css +++ b/src-migrate/modules/product-detail/styles/image.module.css @@ -1,9 +1,21 @@ -.wrapper { +/* .wrapper { @apply h-[250px] md:h-[340px] flex items-center justify-center border border-gray-200 rounded-lg p-4 relative; } .image { @apply object-contain object-center h-full w-full; +} */ + +.wrapper { + @apply relative border border-gray-200 rounded-lg overflow-hidden; + + @apply w-full aspect-[1/1]; + + @apply mx-auto; +} + +.image { + @apply w-full h-full object-cover object-center block; } .absolute-info { diff --git a/src-migrate/modules/product-detail/styles/price-action.module.css b/src-migrate/modules/product-detail/styles/price-action.module.css index cea50bff..b94c33f7 100644 --- a/src-migrate/modules/product-detail/styles/price-action.module.css +++ b/src-migrate/modules/product-detail/styles/price-action.module.css @@ -2,16 +2,16 @@ @apply font-medium text-gray-500; } .main-price { - @apply font-medium text-danger-500 text-title-md; + @apply font-medium text-danger-500 text-title-sm; } .action-wrapper { @apply flex gap-x-2.5; } .quantity-input { - @apply w-24 h-10 text-center border border-gray-300 rounded focus:outline-none; - /* Padding di kiri dan kanan untuk memberi ruang bagi tombol */ - padding-left: 2rem; - padding-right: 2rem; + @apply w-24 h-10 text-center border border-gray-300 rounded focus:outline-none; + /* Padding di kiri dan kanan untuk memberi ruang bagi tombol */ + padding-left: 2rem; + padding-right: 2rem; } .contact-us { diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx index 0c225c74..c2c65766 100644 --- a/src-migrate/modules/promo/components/Voucher.tsx +++ b/src-migrate/modules/promo/components/Voucher.tsx @@ -123,7 +123,7 @@ const VoucherComponent = () => { <> <h1 className={style['title']}>Pakai Voucher Belanja</h1> - <div className='h-6' /> + {/* <div className='h-6' /> */} {voucherQuery?.isLoading && ( <div className='grid grid-cols-3 gap-x-4 animate-pulse'> diff --git a/src-migrate/modules/promo/styles/voucher.module.css b/src-migrate/modules/promo/styles/voucher.module.css index 22d07f91..ab652a5f 100644 --- a/src-migrate/modules/promo/styles/voucher.module.css +++ b/src-migrate/modules/promo/styles/voucher.module.css @@ -2,42 +2,55 @@ @apply text-h-sm md:text-h-lg font-semibold; } +/* beri ruang dari footer */ .voucher-section { - @apply w-full; + @apply w-full pb-6 md:pb-8; +} + +/* biar card bisa full-width tanpa ada gutter ekstra */ +.voucher-section :global(.swiper-slide) { + @apply flex items-start px-0 md:px-0; } .voucher-card { - @apply w-full md:w-11/12 h-3/4 rounded-xl border items-center border-gray-200 shadow-md p-4 flex gap-x-4 ; + /* FULL width (hapus md:w-11/12), tetap horizontal & kompak */ + @apply w-full rounded-xl border border-gray-200 shadow-md + p-4 flex items-start gap-x-4 bg-white; } .voucher-image { - @apply bg-gray-100 rounded-lg w-4/12 h-fit object-contain object-center; + /* gambar kecil & stabil */ + @apply bg-gray-100 rounded-lg w-3/12 md:w-2/12 h-auto object-contain object-center; } .voucher-content { - @apply flex-1 flex flex-col; + /* penting: min-w-0 supaya teks bisa wrap/ellipsis dan card memanjang mulus */ + @apply flex-1 min-w-0 flex flex-col justify-between; } .voucher-title { - @apply font-medium text-body-1 leading-6 mb-1; + @apply font-medium text-body-1 leading-6 line-clamp-2 mb-1; } - .voucher-desc { - @apply text-gray-800 line-clamp-2 text-caption-1; + @apply text-gray-800 text-caption-1 line-clamp-1; } .voucher-bottom { - @apply flex justify-between mt-2; + /* biar rapih di semua ukuran layar */ + @apply flex flex-col sm:flex-row justify-between items-start sm:items-center + gap-2 sm:gap-3 mt-3; } .voucher-code-desc { @apply text-gray-500 text-caption-1; } - .voucher-code { @apply text-red-700 font-medium; } +/* tombol tetap kecil, tapi lebar penuh di mobile; tidak keluar dari card */ .voucher-copy { - @apply bg-gray-200 hover:bg-danger-500 text-danger-500 hover:text-white transition-colors rounded-lg flex items-center justify-center px-6; + @apply bg-gray-200 hover:bg-danger-500 text-danger-500 hover:text-white transition-colors + rounded-lg flex items-center justify-center px-5 py-1.5 text-sm whitespace-nowrap + w-full md:w-auto; } |
