diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/components/layouts/BasicLayout.jsx | 101 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktopVariant.jsx | 151 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobileVariant.jsx | 281 | ||||
| -rw-r--r-- | src/pages/api/shop/product-detail.js | 30 | ||||
| -rw-r--r-- | src/pages/google_merchant/products/[page].js | 4 | ||||
| -rw-r--r-- | src/pages/google_merchant/products/index.js | 4 | ||||
| -rw-r--r-- | src/pages/shop/product/variant/[slug].jsx | 46 | ||||
| -rw-r--r-- | src/pages/sitemap/blogs.xml.js | 6 | ||||
| -rw-r--r-- | src/pages/sitemap/brands.xml.js | 6 | ||||
| -rw-r--r-- | src/pages/sitemap/categories-brand.xml.js | 6 | ||||
| -rw-r--r-- | src/pages/sitemap/categories-brand/[page].js | 6 | ||||
| -rw-r--r-- | src/pages/sitemap/categories.xml.js | 6 | ||||
| -rw-r--r-- | src/pages/sitemap/products.xml.js | 6 | ||||
| -rw-r--r-- | src/pages/sitemap/products/[page].js | 12 |
14 files changed, 363 insertions, 302 deletions
diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx index 81f8b41f..b13e807d 100644 --- a/src/core/components/layouts/BasicLayout.jsx +++ b/src/core/components/layouts/BasicLayout.jsx @@ -33,6 +33,10 @@ const BasicLayout = ({ children }) => { const { product } = useProductContext(); + const hasPrice = + product && + Number(product?.lowest_price?.price || product?.price?.price) > 0; + useEffect(() => { if ( router.pathname === '/shop/product/[slug]' || @@ -54,8 +58,7 @@ const BasicLayout = ({ children }) => { useEffect(() => { const handleMouseOut = (event) => { - - if (!buttonRef.current) return; + if (!buttonRef.current) return; const rect = buttonRef.current.getBoundingClientRect(); if (event.clientY <= 0) { setButtonPosition(rect); @@ -114,56 +117,58 @@ const BasicLayout = ({ children }) => { onAnimationEnd={() => setHighlight(false)} /> )} - <Navbar isMobile = {isMobile} /> + <Navbar isMobile={isMobile} /> <AnimationLayout> {children} - <div - className={`fixed ${ - isMobile && isProductPage ? 'bottom-40' : 'bottom-16' - } right-4 sm:bottom-14 sm:right-10 z-50`} - > - <div className='flex flex-row items-center'> - <a - href={whatsappUrl(templateWA, payloadWA, urlPath)} - className='flex flex-row items-center' - rel='noopener noreferrer' - target='_blank' - > - <span - className={`text-green-300 text-lg font-bold mr-4 ${ - wobble ? 'animate-wobble' : '' - }`} - onAnimationEnd={() => setWobble(false)} + {(!isProductPage || hasPrice) && ( + <div + className={`fixed ${ + isMobile && isProductPage ? 'bottom-40' : 'bottom-16' + } right-4 sm:bottom-14 sm:right-10 z-50`} + > + <div className='flex flex-row items-center'> + <a + href={whatsappUrl(templateWA, payloadWA, urlPath)} + className='flex flex-row items-center' + rel='noopener noreferrer' + target='_blank' + > + <span + className={`text-green-300 text-lg font-bold mr-4 ${ + wobble ? 'animate-wobble' : '' + }`} + onAnimationEnd={() => setWobble(false)} + > + {isDesktop && 'Whatsapp'} + </span> + </a> + <a + href={whatsappUrl(templateWA, payloadWA, urlPath)} + className='elemen-whatsapp p-4 rounded-full bg-[#4FB84A] border border-green-300 flex items-center' + rel='noopener noreferrer' + target='_blank' + ref={buttonRef} > - {isDesktop && 'Whatsapp'} - </span> - </a> - <a - href={whatsappUrl(templateWA, payloadWA, urlPath)} - className='elemen-whatsapp p-4 rounded-full bg-[#4FB84A] border border-green-300 flex items-center' - rel='noopener noreferrer' - target='_blank' - ref={buttonRef} - > - <Image - src='/images/socials/WHATSAPP.svg' - alt='Whatsapp' - className='block sm:hidden' - width={36} - height={36} - loading='eager' - /> - <Image - src='/images/socials/WHATSAPP.svg' - alt='Whatsapp' - className='hidden sm:block' - width={44} - height={44} - loading='eager' - /> - </a> + <Image + src='/images/socials/WHATSAPP.svg' + alt='Whatsapp' + className='block sm:hidden' + width={36} + height={36} + loading='eager' + /> + <Image + src='/images/socials/WHATSAPP.svg' + alt='Whatsapp' + className='hidden sm:block' + width={44} + height={44} + loading='eager' + /> + </a> + </div> </div> - </div> + )} </AnimationLayout> <BasicFooter /> </> diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx index 44ae04bd..6b4ab1e1 100644 --- a/src/lib/product/components/Product/ProductDesktopVariant.jsx +++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx @@ -1,3 +1,4 @@ +import { AlertTriangle } from 'lucide-react'; import { Box, Button, Skeleton, Tooltip } from '@chakra-ui/react'; import { HeartIcon } from '@heroicons/react/24/outline'; import { Info, MessageCircleIcon, Share2Icon } from 'lucide-react'; @@ -249,6 +250,7 @@ const ProductDesktopVariant = ({ }); router.push('/shop/quotation?source=buy'); }; + const hasPrice = Number(product?.price?.price) > 0; const variantSectionRef = useRef(null); const goToVariantSection = () => { @@ -314,6 +316,17 @@ const ProductDesktopVariant = ({ return ( <DesktopView> + <div className='relative'> + {!hasPrice && ( + <div className='absolute inset-0 z-[20] flex items-center justify-center pointer-events-none select-none'> + <img + src='/images/produk_tidak_tersedia.svg' + alt='Produk tidak tersedia' + className='w-[35%] opacity-50 -translate-x-[3%] -translate-y-[-70%]' + /> + </div> + )} + </div> <div className='container mx-auto pt-10'> <div className='flex'> <div className='w-full flex flex-wrap'> @@ -326,9 +339,21 @@ const ProductDesktopVariant = ({ </div> <div className='w-7/12 px-6'> + {!hasPrice && ( + <div className='bg-red-50 p-2 py-1.5 rounded-lg border border-red-500 flex gap-1 items-center '> + <AlertTriangle + size={18} + className='text-red-600 shrink-0 mx-2' + /> + <h1 className='text-red-600 font-normal text-h-sm'> + Maaf untuk saat ini Produk yang anda cari tidak tersedia + </h1> + </div> + )} <h1 className='text-title-md leading-10 font-medium'> {product?.name} </h1> + <div className='mt-10'> <div className='flex p-3 bg-gray_r-4'> <div className='w-4/12 text-gray_r-12/70'>Item Code</div> @@ -430,73 +455,55 @@ const ProductDesktopVariant = ({ </div> </div> <div className='w-[33%]'> - {product?.isFlashsale > 0 && - product?.price?.discountPercentage > 0 ? ( + {product?.price?.price > 0 && ( <> - <div className='flex gap-x-1 items-center mt-2'> - <div className='badge-solid-red text-caption-1'> - {product?.price?.discountPercentage}% - </div> - <div className='text-gray_r-9 line-through text-caption-1'> - {currencyFormat(product?.price?.price)} - </div> - <div className='text-danger-500 font-semibold text-xl'> - {currencyFormat(product?.price?.priceDiscount)} - </div> - </div> - <div className='text-gray_r-9 text-base font-normal mt-1'> - Termasuk PPN:{' '} - {currencyFormat( - product?.price?.priceDiscount * process.env.NEXT_PUBLIC_PPN - )} - </div> - </> - ) : ( - <h3 className='text-danger-500 font-semibold mt-1 text-title-md'> - {product?.price?.price > 0 ? ( + {product?.isFlashsale > 0 && + product?.price?.discountPercentage > 0 ? ( <> - {currencyFormat(product?.price?.price)} + <div className='flex gap-x-1 items-center mt-2'> + <div className='badge-solid-red text-caption-1'> + {product?.price?.discountPercentage}% + </div> + <div className='text-gray_r-9 line-through text-caption-1'> + {currencyFormat(product?.price?.price)} + </div> + <div className='text-danger-500 font-semibold text-xl'> + {currencyFormat(product?.price?.priceDiscount)} + </div> + </div> <div className='text-gray_r-9 text-base font-normal mt-1'> Termasuk PPN:{' '} {currencyFormat( - product?.price?.price * process.env.NEXT_PUBLIC_PPN + product?.price?.priceDiscount * + process.env.NEXT_PUBLIC_PPN )} </div> </> ) : ( - <span className='text-gray_r-12/90 font-normal text-h-sm'> - Hubungi kami untuk dapatkan harga terbaik, - <a - href={whatsappUrl('product', { - name: product.name, - manufacture: product.manufacture?.name, - url: createSlug( - '/shop/product/', - product.name, - product.id, - true - ), - })} - className='text-danger-500 underline' - rel='noopener noreferrer' - target='_blank' - > - klik disini - </a> - </span> + <h3 className='text-danger-500 font-semibold mt-1 text-title-md'> + {currencyFormat(product?.price?.price)} + <div className='text-gray_r-9 text-base font-normal mt-1'> + Termasuk PPN:{' '} + {currencyFormat( + product?.price?.price * process.env.NEXT_PUBLIC_PPN + )} + </div> + </h3> )} - </h3> + </> )} + <div className='flex gap-x-5 items-center py-5'> <div className='relative flex items-center'> <button type='button' - className='absolute left-0 px-2 py-1 h-full text-gray-500' + className='absolute left-0 px-2 py-1 h-full text-gray-500 disabled:opacity-40' onClick={() => { const n = parseInt(String(quantityInput), 10); const next = Number.isFinite(n) ? Math.max(1, n - 1) : 1; setQuantityInput(next); }} + disabled={!hasPrice} > - </button> @@ -518,43 +525,40 @@ const ProductDesktopVariant = ({ if (['e', 'E', '+', '-', '.'].includes(e.key)) e.preventDefault(); }} - className='w-24 h-10 text-center border border-gray-300 rounded focus:outline-none' + className='w-24 h-10 text-center border border-gray-300 rounded focus:outline-none disabled:bg-gray-100 disabled:text-gray-400' + disabled={!hasPrice} /> <button type='button' - className='absolute right-0 px-2 py-1 h-full text-gray-500' + className='absolute right-0 px-2 py-1 h-full text-gray-500 disabled:opacity-40' onClick={() => { const n = parseInt(String(quantityInput), 10); const next = (Number.isFinite(n) ? n : 0) + 1; setQuantityInput(next); }} + disabled={!hasPrice} > + </button> </div> <div> - {/* <Skeleton + <Skeleton isLoaded={!isLoadingSLA} h='21px' - // w={16} className={ - product?.sla?.qty < 10 ? 'text-red-600 font-medium' : '' + !hasPrice || fakeStock < 10 + ? 'text-red-600 font-medium' + : '' } > - Stock : {product?.sla?.qty}{' '} - </Skeleton> */} - <Skeleton - isLoaded={!isLoadingSLA} - h='21px' - className={fakeStock < 10 ? 'text-red-600 font-medium' : ''} - > - Stock : {fakeStock}{' '} + Stock : {hasPrice ? fakeStock : 'Habis'} </Skeleton> </div> + <div> - {qtyPickUp > 0 && ( + {qtyPickUp > 0 && hasPrice && ( <Link href='/panduan-pick-up-service' className='group'> <Image src='/images/PICKUP-NOW.png' @@ -565,20 +569,23 @@ const ProductDesktopVariant = ({ )} </div> </div> - {qtyPickUp > 0 && ( + {/* {qtyPickUp > 0 && ( <> <div className='text-[12px] mt-1 text-red-500 italic'> * {qtyPickUp} barang bisa di pickup </div> <div className='h-4' /> </> - )} + )} */} <div className='flex gap-x-3'> <Button onClick={() => handleAddToCart(product.id)} className='w-full' colorScheme='red' variant={'outline'} + isDisabled={ + !product?.price?.price || product?.price?.price <= 0 + } > Keranjang </Button> @@ -586,6 +593,9 @@ const ProductDesktopVariant = ({ onClick={() => handleBuy(product.id)} className='w-full' colorScheme='red' + isDisabled={ + !product?.price?.price || product?.price?.price <= 0 + } > Beli </Button> @@ -595,6 +605,7 @@ const ProductDesktopVariant = ({ color={'red'} colorScheme='white' className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center' + isDisabled={!product?.price?.price || product?.price?.price <= 0} > <ImageNext src='/images/doc_red.svg' @@ -692,6 +703,20 @@ const ProductDesktopVariant = ({ alt={product.name} className='h-32 object-contain object-center w-full border border-gray_r-4' /> + {/* Watermark jika produk nonaktif */} + {/* {(!product?.price?.price || product?.price?.price <= 0) && ( + <div className='absolute inset-0 flex items-center justify-center'> + <span + className='text-[60px] font-extrabold text-green-700/20 rotate-[-25deg] select-none' + style={{ + textShadow: '0 0 3px rgba(0,0,0,0.1)', + letterSpacing: '6px', + }} + > + NONAKTIF + </span> + </div> + )} */} </div> <div className='ml-3 flex flex-1 items-center font-normal'> {product.name} diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx index 4cfc63ca..0f4953df 100644 --- a/src/lib/product/components/Product/ProductMobileVariant.jsx +++ b/src/lib/product/components/Product/ProductMobileVariant.jsx @@ -183,10 +183,20 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { return ( <MobileView> - {/* PRICE & ACTIONS: tetap punyamu, hanya hapus input number lama */} - {/* ===== BAR BAWAH (fixed) ===== */} + <div className='relative'> + {!product.price.price > 0 && ( + <div className='absolute inset-0 z-[50] flex items-center justify-center pointer-events-none select-none'> + <img + src='/images/produk_tidak_tersedia.svg' + alt='Produk tidak tersedia' + className='w-[100%] opacity-[1000%] -translate-x-[0%] -translate-y-[-160%]' + /> + </div> + )} + </div> + {/* ===== BAR BAWAH ===== */} <div className='px-4 fixed bottom-0 left-0 right-0 bg-white z-10 pb-6 pt-4 rounded-t-2xl shadow-[rgba(0,0,4,0.1)_0px_-4px_4px_0px]'> - {/* HARGA & PPN (logikamu tetap) */} + {/* HARGA & PPN */} {activeVariant.isFlashSale && activeVariant?.price?.discountPercentage > 0 ? ( <> @@ -220,80 +230,65 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { )} </div> </> - ) : ( - <span className='text-gray_r-11 leading-6 font-normal'> - Hubungi kami untuk dapatkan harga terbaik, - <a - href={whatsappUrl('product', { - name: product.name, - url: createSlug( - '/shop/product/', - product.name, - product.id, - true - ), - })} - className='text-danger-500 underline' - > - klik disini - </a> - </span> - )} + ) : null} </div> )} - {/* ⬇️ TAMBAHKAN BLOK INI DI DALAM BAR: STOK & STEPPER */} <div className='grid grid-cols-12 items-center gap-3 mt-3'> - <div className='col-span-7'> - <div - className={`text-[14px] ${ - product?.sla?.qty < 10 ? 'text-red-600 font-medium' : '' - }`} - > - {/* Stock : {activeVariant?.stock ?? 0} */} - Stock : {fakeStock}{' '} - </div> - {qtyPickUp > 0 && ( - <div className='text-[16px] mt-0.5 text-red-500 italic'> - * {qtyPickUp} barang bisa di pickup + {product?.price?.price > 0 && ( + <div className='col-span-7'> + <div + className={`text-[14px] ${ + product?.sla?.qty < 10 ? 'text-red-600 font-medium' : '' + }`} + > + {/* Stock : {activeVariant?.stock ?? 0} */} + Stock : {fakeStock}{' '} </div> - )} - </div> + {qtyPickUp > 0 && ( + <div className='text-[16px] mt-0.5 text-red-500 italic'> + * {qtyPickUp} barang bisa di pickup + </div> + )} + </div> + )} <div className='col-span-5 flex justify-end'> - <div className='inline-flex items-stretch border rounded-xl overflow-hidden'> - <button - type='button' - className='h-10 w-10 grid place-items-center text-gray-700 hover:bg-gray-100 active:scale-95' - onClick={() => - setQuantityInput( - String(Math.max(1, Number(quantityInput || 1) - 1)) - ) - } - aria-label='Kurangi' - > - <span className='text-2xl leading-none'>–</span> - </button> - <input - type='number' - min={1} - value={quantityInput} - onChange={(e) => setQuantityInput(e.target.value)} - className='h-10 w-16 text-center text-lg outline-none border-x + {product?.price?.price > 0 && ( + <div className='inline-flex items-stretch border rounded-xl overflow-hidden'> + <button + type='button' + className='h-10 w-10 grid place-items-center text-gray-700 hover:bg-gray-100 active:scale-95' + onClick={() => + setQuantityInput( + String(Math.max(1, Number(quantityInput || 1) - 1)) + ) + } + aria-label='Kurangi' + > + <span className='text-2xl leading-none'>–</span> + </button> + <input + type='number' + min={1} + value={quantityInput} + onChange={(e) => setQuantityInput(e.target.value)} + className='h-10 w-16 text-center text-lg outline-none border-x [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none' - /> - <button - type='button' - className='h-10 w-10 grid place-items-center text-gray-700 hover:bg-gray-100 active:scale-95' - onClick={() => - setQuantityInput(String(Number(quantityInput || 1) + 1)) - } - aria-label='Tambah' - > - <span className='text-2xl leading-none'>+</span> - </button> - </div> + /> + <button + type='button' + className='h-10 w-10 grid place-items-center text-gray-700 hover:bg-gray-100 active:scale-95' + onClick={() => + setQuantityInput(String(Number(quantityInput || 1) + 1)) + } + aria-label='Tambah' + > + <span className='text-2xl leading-none'>+</span> + </button> + </div> + )} </div> </div> @@ -306,6 +301,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { className='flex items-center justify-center p-2 border-2 hover:bg-slate-100' variant='outline' title='Lihat Dokumen' + isDisabled={product?.price?.price <= 0} > <ImageNext src='/images/doc.svg' @@ -323,7 +319,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { className='flex-1' colorScheme='red' variant='outline' - isDisabled={product.stock === 0} + isDisabled={product?.price?.price <= 0} > Keranjang </Button> @@ -333,7 +329,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { } className='flex-1' colorScheme='red' - isDisabled={product.stock === 0} + isDisabled={product?.price?.price <= 0} > Beli </Button> @@ -396,87 +392,92 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { active={informationTab == 'specification'} className='rounded border border-gray_r-6 divide-y divide-gray_r-6' > - <SpecificationContent label='Ketersediaan'> - <span> - {isLoadingSLA ? ( - <Skeleton width='100px' height='full' /> - ) : product?.sla?.slaDate != '-' ? ( - <button - type='button' - title={`Masa Persiapan Barang ${product?.sla?.slaDate}`} - className={`flex gap-x-1 items-center p-2 h-8 rounded-lg w-full ${ - product?.sla?.slaDate === 'indent' - ? 'bg-indigo-900' - : 'btn-light' - }`} - > - <div - className={`flex-1 text-sm ${ - product?.sla?.slaDate === 'indent' ? 'text-white' : '' + {(!product?.price?.price || product?.price?.price <= 0) && ( + <SpecificationContent label='Ketersediaan'> + <span> + {isLoadingSLA ? ( + <Skeleton width='100px' height='full' /> + ) : product?.sla?.slaDate != '-' ? ( + <button + type='button' + title={`Masa Persiapan Barang ${product?.sla?.slaDate}`} + className={`flex gap-x-1 items-center p-2 h-8 rounded-lg w-full ${ + product?.sla?.slaDate === 'indent' + ? 'bg-indigo-900' + : 'btn-light' }`} > - {product?.sla?.slaDate} - </div> - <div className='flex-end'> - <svg - aria-hidden='true' - fill='none' - stroke='currentColor' - stroke-width='1.5' - className={`w-7 h-7 text-sm ${ + <div + className={`flex-1 text-sm ${ product?.sla?.slaDate === 'indent' ? 'text-white' : '' }`} > - <path - d='M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z' - stroke-linecap='round' - stroke-linejoin='round' - ></path> - </svg> - </div> - </button> - ) : ( - '-' - )} - </span> - </SpecificationContent> + {product?.sla?.slaDate} + </div> + <div className='flex-end'> + <svg + aria-hidden='true' + fill='none' + stroke='currentColor' + stroke-width='1.5' + className={`w-7 h-7 text-sm ${ + product?.sla?.slaDate === 'indent' ? 'text-white' : '' + }`} + > + <path + d='M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z' + stroke-linecap='round' + stroke-linejoin='round' + ></path> + </svg> + </div> + </button> + ) : ( + '-' + )} + </span> + </SpecificationContent> + )} + <SpecificationContent label='Nomor SKU'> <span>SKU-{product?.id}</span> </SpecificationContent> <SpecificationContent label='Part Number'> <span>{activeVariant?.code || '-'}</span> </SpecificationContent> - <SpecificationContent label='Stok'> - {activeVariant?.stock > 0 && ( - <span className='flex gap-x-1.5'> - <div className='badge-solid-red'>Ready Stock</div> - <div className='badge-gray'> - {activeVariant?.stock > 5 ? '> 5' : '< 5'} - </div> - </span> - )} - {activeVariant?.stock == 0 && ( - <a - href={whatsappUrl('product', { - name: product.name, - url: createSlug( - '/shop/product/', - product.name, - product.id, - true - ), - })} - className='text-danger-500 font-medium' - > - Tanya Stok - </a> - )} - </SpecificationContent> + {product?.price?.price > 0 && ( + <SpecificationContent label='Stok'> + {activeVariant?.stock > 0 && ( + <span className='flex gap-x-1.5'> + <div className='badge-solid-red'>Ready Stock</div> + <div className='badge-gray'> + {activeVariant?.stock > 5 ? '> 5' : '< 5'} + </div> + </span> + )} + {activeVariant?.stock == 0 && ( + <a + href={whatsappUrl('product', { + name: product.name, + url: createSlug( + '/shop/product/', + product.name, + product.id, + true + ), + })} + className='text-danger-500 font-medium' + > + Tanya Stok + </a> + )} + </SpecificationContent> + )} <SpecificationContent label='Berat Barang'> - {activeVariant?.weight > 0 && ( + {activeVariant?.weight > -1 && ( <span>{activeVariant?.weight} KG</span> )} - {activeVariant?.weight == 0 && ( + {activeVariant?.weight == -1 && ( <a href={whatsappUrl('productWeight', { name: product.name, @@ -487,7 +488,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { true ), })} - className='text-danger-500 font-medium' + className={`text-danger-501 font-medium ${ + !product?.price?.price || product?.price?.price <= 0 + ? 'pointer-events-none opacity-50 cursor-default' + : '' + }`} > Tanya Berat </a> diff --git a/src/pages/api/shop/product-detail.js b/src/pages/api/shop/product-detail.js index faa96028..504f9dd6 100644 --- a/src/pages/api/shop/product-detail.js +++ b/src/pages/api/shop/product-detail.js @@ -1,26 +1,32 @@ -import { productMappingSolr, variantsMappingSolr } from '@/utils/solrMapping' -import axios from 'axios' +import { productMappingSolr, variantsMappingSolr } from '@/utils/solrMapping'; +import axios from 'axios'; export default async function handler(req, res) { try { let productTemplate = await axios( - process.env.SOLR_HOST + `/solr/product/select?q=id:${req.query.id}&q.op=OR&indent=true` - ) + process.env.SOLR_HOST + + `/solr/product/select?q=id:${req.query.id}&q.op=OR&indent=true` + ); let productVariants = await axios( process.env.SOLR_HOST + - `/solr/variants/select?q=template_id_i:${req.query.id}&q.op=OR&indent=true&rows=100&fq=-publish_b:false AND price_tier1_v2_f:[1 TO *]` - ) - let auth = req.query.auth === 'false' ? JSON.parse(req.query.auth) : req.query.auth - let result = productMappingSolr(productTemplate.data.response.docs, auth || false) + `/solr/variants/select?q=template_id_i:${req.query.id}&q.op=OR&indent=true&rows=100` + // `/solr/variants/select?q=template_id_i:${req.query.id}&q.op=OR&indent=true&rows=100&fq=-publish_b:false AND price_tier1_v2_f:[1 TO *]` + ); + let auth = + req.query.auth === 'false' ? JSON.parse(req.query.auth) : req.query.auth; + let result = productMappingSolr( + productTemplate.data.response.docs, + auth || false + ); result[0].variants = variantsMappingSolr( productTemplate.data.response.docs[0], productVariants.data.response.docs, auth || false - ) - res.status(200).json(result) + ); + res.status(200).json(result); } catch (error) { - console.error('Error fetching data from Solr:', error) - res.status(500).json({ error: 'Internal Server Error' }) + console.error('Error fetching data from Solr:', error); + res.status(500).json({ error: 'Internal Server Error' }); } } diff --git a/src/pages/google_merchant/products/[page].js b/src/pages/google_merchant/products/[page].js index 161b6aec..2a53f7c0 100644 --- a/src/pages/google_merchant/products/[page].js +++ b/src/pages/google_merchant/products/[page].js @@ -18,7 +18,9 @@ export async function getServerSideProps({ res, query }) { page: page.replace('.xml', ''), priceFrom: 1, orderBy: 'popular', - fq: ['image_s:["" TO *] AND publish_b:true'], + fq: ['image_s:["" TO *] AND publish_b:true AND price_tier1_v2_f:[1 TO *]'], + // product_rating_f: '[8 TO *]', + // price_tier1_v2_f: '[1 TO *]', }; const products = await variantSearchApi({ query: _.toQuery(queries) }); diff --git a/src/pages/google_merchant/products/index.js b/src/pages/google_merchant/products/index.js index b6c7bfef..d6ef413a 100644 --- a/src/pages/google_merchant/products/index.js +++ b/src/pages/google_merchant/products/index.js @@ -8,7 +8,9 @@ export async function getServerSideProps() { const queries = { limit: 1, priceFrom: 1, - fq: 'image_s:["" TO *] AND publish_b:true', + fq: 'image_s:["" TO *] AND publish_b:true AND price_tier1_v2_f:[1 TO *]', + // product_rating_f: '[8 TO *]', + // price_tier1_v2_f: '[1 TO *]', }; const products = await variantSearchApi({ query: _.toQuery(queries) }); const { numFound } = products.response; diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx index 2c0dd64b..32c00a35 100644 --- a/src/pages/shop/product/variant/[slug].jsx +++ b/src/pages/shop/product/variant/[slug].jsx @@ -25,23 +25,39 @@ export async function getServerSideProps(context) { const tier = auth.pricelist ? auth.pricelist : false; const authToken = auth?.token || ''; - let response = await axios( - `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=` + - getIdFromSlug(slug) + - '&auth=' + - tier - ); - let product = response.data; + try { + const response = await axios( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=` + + getIdFromSlug(slug) + + '&auth=' + + tier + ); - if (product?.length == 1) { - product = product[0]; - } else { - product = null; - } + let product = response.data; - return { - props: { product }, - }; + if (product?.length == 1) { + product = product[0]; + } else { + product = null; + } + + return { + props: { product }, + }; + } catch (error) { + // kalau status 500 → tampilkan halaman 404 + if (error.response && error.response.status === 500) { + return { notFound: true }; + } + + // kalau 404 dari API juga langsung 404 + if (error.response && error.response.status === 404) { + return { notFound: true }; + } + + // kalau error lain, lempar agar bisa dilihat di console log server + throw error; + } } export default function ProductDetail({ product }) { diff --git a/src/pages/sitemap/blogs.xml.js b/src/pages/sitemap/blogs.xml.js index 628dc710..1125ad1c 100644 --- a/src/pages/sitemap/blogs.xml.js +++ b/src/pages/sitemap/blogs.xml.js @@ -10,12 +10,12 @@ export async function getServerSideProps({ res }) { 'http://www.sitemaps.org/schemas/sitemap/0.9' ); - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; blogs.blogs.forEach((blog) => { const url = sitemap.ele('url'); url.ele('loc', createSlug(baseUrl, blog.title, blog.id)); - url.ele('lastmod', date); + url.ele('lastmod', date.toISOString().slice(0, 10)); url.ele('changefreq', 'weekly'); url.ele('priority', '0.6'); }); diff --git a/src/pages/sitemap/brands.xml.js b/src/pages/sitemap/brands.xml.js index c2199d85..dc92419c 100644 --- a/src/pages/sitemap/brands.xml.js +++ b/src/pages/sitemap/brands.xml.js @@ -10,12 +10,12 @@ export async function getServerSideProps({ res }) { 'http://www.sitemaps.org/schemas/sitemap/0.9' ); - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; brands.manufactures.forEach((brand) => { const url = sitemap.ele('url'); url.ele('loc', createSlug(baseUrl, brand.name, brand.id)); - url.ele('lastmod', date); + url.ele('lastmod', date.toISOString().slice(0, 10)); url.ele('changefreq', 'daily'); url.ele('priority', '1.0'); }); diff --git a/src/pages/sitemap/categories-brand.xml.js b/src/pages/sitemap/categories-brand.xml.js index faf67b9f..8dfd3a32 100644 --- a/src/pages/sitemap/categories-brand.xml.js +++ b/src/pages/sitemap/categories-brand.xml.js @@ -16,12 +16,12 @@ export async function getServerSideProps({ res }) { 'http://www.sitemaps.org/schemas/sitemap/0.9' ); - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; pages.forEach((page) => { const sitemap = sitemapIndex.ele('sitemap'); sitemap.ele('loc', `${baseUrl}/${page}.xml`); - sitemap.ele('lastmod', date); + sitemap.ele('lastmod', date.toISOString().slice(0, 10)); }); res.setHeader('Content-Type', 'text/xml'); diff --git a/src/pages/sitemap/categories-brand/[page].js b/src/pages/sitemap/categories-brand/[page].js index 4d28ab04..e7f264ee 100644 --- a/src/pages/sitemap/categories-brand/[page].js +++ b/src/pages/sitemap/categories-brand/[page].js @@ -22,13 +22,13 @@ export async function getServerSideProps({ query, res }) { 'http://www.sitemaps.org/schemas/sitemap/0.9' ); - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; categories.data.response.docs.forEach((product) => { const url = sitemap.ele('url'); const loc = product.url_s; url.ele('loc', loc); - url.ele('lastmod', date); + url.ele('lastmod', date.toISOString().slice(0, 10)); url.ele('changefreq', 'daily'); url.ele('priority', '0.8'); }); diff --git a/src/pages/sitemap/categories.xml.js b/src/pages/sitemap/categories.xml.js index 357a2072..18c8a8f3 100644 --- a/src/pages/sitemap/categories.xml.js +++ b/src/pages/sitemap/categories.xml.js @@ -28,11 +28,11 @@ export async function getServerSideProps({ res }) { function addUrlToSitemap(sitemap, name, id) { const baseUrl = process.env.SELF_HOST + '/shop/category/'; - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; const url = sitemap.ele('url'); url.ele('loc', createSlug(baseUrl, name, id)); - url.ele('lastmod', date); + url.ele('lastmod', date.toISOString().slice(0, 10)); url.ele('changefreq', 'weekly'); url.ele('priority', '0.6'); } diff --git a/src/pages/sitemap/products.xml.js b/src/pages/sitemap/products.xml.js index 0269ec59..5ed6b759 100644 --- a/src/pages/sitemap/products.xml.js +++ b/src/pages/sitemap/products.xml.js @@ -14,12 +14,12 @@ export async function getServerSideProps({ res }) { 'http://www.sitemaps.org/schemas/sitemap/0.9' ); - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; pages.forEach((page) => { const sitemap = sitemapIndex.ele('sitemap'); sitemap.ele('loc', `${baseUrl}/${page}.xml`); - sitemap.ele('lastmod', date); + sitemap.ele('lastmod', date.toISOString().slice(0, 10)); }); res.setHeader('Content-Type', 'text/xml'); diff --git a/src/pages/sitemap/products/[page].js b/src/pages/sitemap/products/[page].js index 421c08e3..3603d64c 100644 --- a/src/pages/sitemap/products/[page].js +++ b/src/pages/sitemap/products/[page].js @@ -10,9 +10,9 @@ export async function getServerSideProps({ query, res }) { const queries = { limit, page: page.replace('.xml', ''), - '-publish_b': false, - product_rating_f: '[8 TO *]', - price_tier1_v2_f: '[1 TO *]', + // '-publish_b': false, + // product_rating_f: '[8 TO *]', + // price_tier1_v2_f: '[1 TO *]', }; const products = await productSearchApi({ query: _.toQuery(queries) }); const sitemap = create('urlset', { encoding: 'utf-8' }).att( @@ -20,12 +20,12 @@ export async function getServerSideProps({ query, res }) { 'http://www.sitemaps.org/schemas/sitemap/0.9' ); - // const date = new Date() - const date = '2025-10-30'; + const date = new Date(); + // const date = '2025-10-30'; products.response.products.forEach((product) => { const url = sitemap.ele('url'); url.ele('loc', createSlug(baseUrl, product.name, product.id)); - url.ele('lastmod', date); + url.ele('lastmod', date.toISOString().slice(0, 10)); url.ele('changefreq', 'daily'); url.ele('priority', '0.8'); }); |
