summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafi Zadanly <zadanlyr@gmail.com>2024-01-04 10:05:25 +0700
committerRafi Zadanly <zadanlyr@gmail.com>2024-01-04 10:05:25 +0700
commit67398e6f10d6f7729d8f1ace7005ef13d32c5ddd (patch)
tree7d47ad6c1a7093e595e22bcecb40016a626162f6
parent89f32128f37d99b490de7590e2116a9cfd853f89 (diff)
Update promotion program feature
-rw-r--r--package.json2
-rw-r--r--src-migrate/common/components/elements/Modal.tsx2
-rw-r--r--src-migrate/modules/cart/components/Detail.tsx1
-rw-r--r--src-migrate/modules/cart/components/Item.tsx9
-rw-r--r--src-migrate/modules/cart/components/ItemPromo.tsx3
-rw-r--r--src-migrate/modules/cart/stores/useCartStore.ts3
-rw-r--r--src-migrate/modules/cart/styles/item-promo.module.css6
-rw-r--r--src-migrate/modules/cart/styles/item.module.css2
-rw-r--r--src-migrate/modules/product-promo/components/AddToCart.tsx19
-rw-r--r--src-migrate/modules/product-promo/components/Card.tsx10
-rw-r--r--src-migrate/modules/product-promo/components/Item.tsx14
-rw-r--r--src-migrate/modules/product-promo/components/ModalContent.tsx14
-rw-r--r--src-migrate/modules/product-promo/styles/item.module.css4
-rw-r--r--src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx3
-rw-r--r--src/core/utils/googleTag.js2
-rw-r--r--src/lib/cart/components/Cartheader.jsx181
-rw-r--r--src/lib/checkout/components/Checkout.jsx930
-rw-r--r--src/lib/home/api/categoryHomeApi.js15
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx763
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx411
-rw-r--r--src/lib/product/components/ProductSearch.jsx379
-rw-r--r--src/pages/shop/cart.jsx41
-rw-r--r--src/pages/shop/product/[slug].jsx98
23 files changed, 1493 insertions, 1419 deletions
diff --git a/package.json b/package.json
index 33f6dd38..8e13edc6 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"lodash-contrib": "^4.1200.1",
"lucide-react": "^0.279.0",
"midtrans-client": "^1.3.1",
+ "moment": "^2.29.4",
"next": "13.0.0",
"next-auth": "^4.22.3",
"next-progress": "^2.2.0",
@@ -46,6 +47,7 @@
"react-loading-skeleton": "^3.3.1",
"react-query": "^3.39.3",
"react-select": "^5.7.0",
+ "snakecase-keys": "^5.5.0",
"swiper": "^8.4.4",
"tw-merge": "^0.0.1-alpha.3",
"usehooks-ts": "^2.9.1",
diff --git a/src-migrate/common/components/elements/Modal.tsx b/src-migrate/common/components/elements/Modal.tsx
index 8e488a3a..ea95c0b1 100644
--- a/src-migrate/common/components/elements/Modal.tsx
+++ b/src-migrate/common/components/elements/Modal.tsx
@@ -37,7 +37,7 @@ const Modal = ({
const modalClassNames = clsxm(
"fixed bg-white max-h-[80vh] overflow-auto p-4 pt-0 z-[60] border-gray_r-6",
{
- "left-1/2 -translate-x-1/2 translate-y-1/2 bottom-1/2 w-11/12 md:w-1/4 lg:w-1/3 border rounded-xl": mode === 'desktop',
+ "left-1/2 -translate-x-1/2 translate-y-1/2 bottom-1/2 w-11/12 md:w-[500px] border rounded-xl": mode === 'desktop',
"left-0 w-full border-t bottom-0 rounded-t-xl": mode === 'mobile'
},
className
diff --git a/src-migrate/modules/cart/components/Detail.tsx b/src-migrate/modules/cart/components/Detail.tsx
index c9de086b..ccb0bb4d 100644
--- a/src-migrate/modules/cart/components/Detail.tsx
+++ b/src-migrate/modules/cart/components/Detail.tsx
@@ -38,7 +38,6 @@ const CartDetail = () => {
return (
<div className={style.wrapper}>
<div className='w-full md:w-3/4'>
- {/* <div className='border border-gray-300 rounded-lg p-4 md:p-6'> */}
<div className=''>
<div className='text-h-lg font-semibold mb-6'>Keranjang Belanja</div>
<div className='grid grid-cols-1 gap-y-4'>
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx
index 92beda86..baf48bb6 100644
--- a/src-migrate/modules/cart/components/Item.tsx
+++ b/src-migrate/modules/cart/components/Item.tsx
@@ -3,6 +3,9 @@ import style from '../styles/item.module.css'
import Image from 'next/image'
import React from 'react'
import { Skeleton, SkeletonProps, Tooltip } from '@chakra-ui/react'
+import { InfoIcon } from 'lucide-react'
+
+import { PROMO_CATEGORY } from '~/constants/promotion'
import formatCurrency from '~/common/libs/formatCurrency'
import { CartItem as CartItemProps } from '~/common/types/cart'
@@ -10,8 +13,6 @@ import { CartItem as CartItemProps } from '~/common/types/cart'
import CartItemPromo from './ItemPromo'
import CartItemAction from './ItemAction'
import CartItemSelect from './ItemSelect'
-import { PROMO_CATEGORY } from '~/constants/promotion'
-import { InfoIcon } from 'lucide-react'
type Props = {
item: CartItemProps
@@ -37,7 +38,7 @@ const CartItem = ({ item, editable = true }: Props) => {
<div>
Selamat! Pembelian anda lebih hemat {' '}
<span className={style.savingAmt}>
- Rp {formatCurrency((item.package_price || 0) - item.subtotal)}
+ Rp{formatCurrency((item.package_price || 0) * item.quantity - item.subtotal)}
</span>
</div>
</div>
@@ -61,7 +62,7 @@ const CartItem = ({ item, editable = true }: Props) => {
Rp {formatCurrency((item.package_price || 0))}
</span>
<span className={style.price}>
- Rp {formatCurrency(item.subtotal)}
+ Rp {formatCurrency(item.price.price)}
</span>
</div>
)}
diff --git a/src-migrate/modules/cart/components/ItemPromo.tsx b/src-migrate/modules/cart/components/ItemPromo.tsx
index 951d4d6a..bb286e8b 100644
--- a/src-migrate/modules/cart/components/ItemPromo.tsx
+++ b/src-migrate/modules/cart/components/ItemPromo.tsx
@@ -5,7 +5,6 @@ import React from 'react'
import { CartProduct } from '~/common/types/cart'
-
type Props = {
product: CartProduct
}
@@ -19,7 +18,7 @@ const CartItemPromo = ({ product }: Props) => {
<div className={style.details}>
<div className={style.name}>{product.display_name}</div>
- <div className='flex'>
+ <div className='flex w-full'>
<div className="flex flex-col">
<div className={style.code}>{product.code}</div>
<div>
diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts
index d3eaadb7..0643b8e6 100644
--- a/src-migrate/modules/cart/stores/useCartStore.ts
+++ b/src-migrate/modules/cart/stores/useCartStore.ts
@@ -48,7 +48,8 @@ const computeSummary = (cart: CartProps) => {
if (!item.selected) continue;
let price = 0;
- if (item.cart_type === 'promotion') price = item?.package_price || 0;
+ if (item.cart_type === 'promotion')
+ price = (item?.package_price || 0) * item.quantity;
else if (item.cart_type === 'product')
price = item.price.price * item.quantity;
diff --git a/src-migrate/modules/cart/styles/item-promo.module.css b/src-migrate/modules/cart/styles/item-promo.module.css
index 17dbf1c7..5bc192c0 100644
--- a/src-migrate/modules/cart/styles/item-promo.module.css
+++ b/src-migrate/modules/cart/styles/item-promo.module.css
@@ -1,5 +1,5 @@
.wrapper {
- @apply md:ml-16 ml-12 mt-4 flex;
+ @apply md:ml-12 ml-8 mt-4 flex;
}
.imageWrapper {
@@ -14,7 +14,7 @@
}
.details {
- @apply ml-4 flex flex-col gap-y-1;
+ @apply ml-4 w-full flex flex-col gap-y-1;
}
.name {
@@ -27,5 +27,5 @@
}
.quantity {
- @apply w-12 min-w-[48px] py-2.5 bg-gray-100 border border-gray-300 h-fit my-auto rounded-md ml-auto font-medium text-center;
+ @apply w-12 min-w-[42px] py-2.5 bg-gray-100 border border-gray-300 h-fit my-auto rounded-md ml-auto font-medium text-center;
}
diff --git a/src-migrate/modules/cart/styles/item.module.css b/src-migrate/modules/cart/styles/item.module.css
index 6380cdad..dfbbf5e8 100644
--- a/src-migrate/modules/cart/styles/item.module.css
+++ b/src-migrate/modules/cart/styles/item.module.css
@@ -7,7 +7,7 @@
}
.badgeType {
- @apply min-w-fit p-2 flex gap-x-1.5 rounded-md border border-danger-500 text-danger-500;
+ @apply min-w-fit p-2 flex items-center gap-x-1.5 rounded-md border border-danger-500 text-danger-500;
}
.mainProdWrapper {
diff --git a/src-migrate/modules/product-promo/components/AddToCart.tsx b/src-migrate/modules/product-promo/components/AddToCart.tsx
index 9d856ccf..58bb2ad7 100644
--- a/src-migrate/modules/product-promo/components/AddToCart.tsx
+++ b/src-migrate/modules/product-promo/components/AddToCart.tsx
@@ -4,6 +4,8 @@ import { IPromotion } from '~/common/types/promotion'
import { upsertUserCart } from '~/services/cart'
import { getAuth } from '~/common/libs/auth'
import { Button, Spinner, useToast } from '@chakra-ui/react'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
type Props = {
promotion: IPromotion
@@ -14,11 +16,26 @@ type Status = 'idle' | 'loading' | 'success'
const ProductPromoAddToCart = ({ promotion }: Props) => {
const auth = getAuth()
const toast = useToast()
+ const router = useRouter()
const [status, setStatus] = useState<Status>('idle')
const handleButton = async () => {
- if (typeof auth !== 'object') return
+ if (typeof auth !== 'object') {
+ const currentUrl = encodeURIComponent(router.asPath)
+ toast({
+ title: 'Masuk Akun',
+ description: <>
+ Masuk akun untuk dapat menambahkan promo ke keranjang belanja. {' '}
+ <Link className='underline' href={`/login?next=${currentUrl}`}>Klik disini</Link>
+ </>,
+ status: 'error',
+ duration: 4000,
+ isClosable: true,
+ position: 'top',
+ })
+ return
+ }
if (status === 'success') return
setStatus('loading')
diff --git a/src-migrate/modules/product-promo/components/Card.tsx b/src-migrate/modules/product-promo/components/Card.tsx
index 2874c2cc..e894c143 100644
--- a/src-migrate/modules/product-promo/components/Card.tsx
+++ b/src-migrate/modules/product-promo/components/Card.tsx
@@ -5,11 +5,12 @@ import { InfoIcon, PlusIcon } from "lucide-react"
import { Skeleton, Tooltip } from '@chakra-ui/react'
import { motion } from "framer-motion"
+import { PROMO_CATEGORY } from "~/constants/promotion"
+import { getVariantById } from "~/services/variant"
+
import { IProductVariantPromo, IPromotion } from '~/common/types/promotion'
import formatCurrency from '~/common/libs/formatCurrency'
import clsxm from '~/common/libs/clsxm'
-import { PROMO_CATEGORY } from "~/constants/promotion"
-import { getVariantById } from "~/services/variant"
import ProductPromoItem from './Item'
import ProductPromoAddToCart from "./AddToCart"
@@ -82,7 +83,10 @@ const ProductPromoCard = ({ promotion }: Props) => {
{allProducts.map((product, index) => (
<>
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.6 }}>
- <ProductPromoItem variant={product} />
+ <ProductPromoItem
+ variant={product}
+ isFree={index + 1 > products.length && promotion.type.value === 'merchandise'}
+ />
</motion.div>
<motion.div initial={{ y: 30, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.5, delay: 0.1 }}>
{index + 1 < allProducts.length && (
diff --git a/src-migrate/modules/product-promo/components/Item.tsx b/src-migrate/modules/product-promo/components/Item.tsx
index 058b2f6c..15ca4878 100644
--- a/src-migrate/modules/product-promo/components/Item.tsx
+++ b/src-migrate/modules/product-promo/components/Item.tsx
@@ -6,15 +6,21 @@ import Image from 'next/image'
import { IProductVariantPromo } from '~/common/types/promotion'
type Props = {
- variant: IProductVariantPromo
+ variant: IProductVariantPromo,
+ isFree?: boolean
}
-const ProductPromoItem = ({ variant }: Props) => {
+const ProductPromoItem = ({
+ variant,
+ isFree = false
+}: Props) => {
return (
<div className={style.item}>
<div className={style.image}>
- <Image src={variant.image} alt={variant.display_name} width={320} height={320} />
- <div className={style.quantity}>{variant.qty} pcs</div>
+ <Image src={variant.image} alt={variant.display_name} width={120} height={120} quality={100} />
+ <div className={style.quantity}>
+ {variant.qty} pcs {isFree ? '(free)' : ''}
+ </div>
</div>
<div className={style.name}>{variant.name}</div>
</div>
diff --git a/src-migrate/modules/product-promo/components/ModalContent.tsx b/src-migrate/modules/product-promo/components/ModalContent.tsx
index 45995af6..90cf79e7 100644
--- a/src-migrate/modules/product-promo/components/ModalContent.tsx
+++ b/src-migrate/modules/product-promo/components/ModalContent.tsx
@@ -1,6 +1,5 @@
import { useQuery } from "react-query"
import { Skeleton } from "@chakra-ui/react"
-import { motion } from "framer-motion"
import { getVariantPromoByCategory } from "~/services/variant"
@@ -22,10 +21,15 @@ const ProductPromoModalContent = () => {
const promotions = promotionsQuery.data
return (
- <Skeleton isLoaded={!promotionsQuery.isLoading} className='min-h-[60vh] grid grid-cols-1'>
- {promotions?.data.map((promo) => (
- <ProductPromoCard key={promo.id} promotion={promo} />
- ))}
+ <Skeleton isLoaded={!promotionsQuery.isLoading} className='min-h-[70vh] max-h-[70vh]'>
+ <div className="grid grid-cols-1 gap-y-6 pb-6">
+ {promotions?.data.map((promo) => (
+ <ProductPromoCard key={promo.id} promotion={promo} />
+ ))}
+ {promotions?.data.length === 0 && (
+ <div className="py-10 rounded-lg h-fit text-center text-body-1 font-semibold text-gray-800 bg-gray-200">Belum ada promo pada kategori ini</div>
+ )}
+ </div>
</Skeleton>
)
}
diff --git a/src-migrate/modules/product-promo/styles/item.module.css b/src-migrate/modules/product-promo/styles/item.module.css
index 86127836..b6a8b2ef 100644
--- a/src-migrate/modules/product-promo/styles/item.module.css
+++ b/src-migrate/modules/product-promo/styles/item.module.css
@@ -1,9 +1,9 @@
.item {
- @apply min-w-[110px] max-w-[110px];
+ @apply w-[100px] h-[100px];
}
.image {
- @apply relative border border-gray_r-6 p-2.5 rounded-lg mb-3;
+ @apply w-full h-[100px] relative border border-gray_r-6 p-2.5 rounded-lg mb-3;
}
.fillDesc {
diff --git a/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx b/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx
index b1207c5e..745f9944 100644
--- a/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx
+++ b/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx
@@ -10,8 +10,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (req.method === 'GET') {
const queryParams = new URLSearchParams({
q: `product_ids:${productId}`,
- fq: `type_value_s:${category}`,
- rows: '1'
+ fq: `type_value_s:${category}`
})
const response = await fetch(`${SOLR_HOST}/solr/promotion_program_lines/select?${queryParams.toString()}`)
diff --git a/src/core/utils/googleTag.js b/src/core/utils/googleTag.js
index 6d7476bd..cc6d1283 100644
--- a/src/core/utils/googleTag.js
+++ b/src/core/utils/googleTag.js
@@ -2,7 +2,7 @@ const mapVariants = (variants) => {
return variants.map((variant) => {
const res = {
item_id: variant.id,
- item_name: variant.parent.name,
+ item_name: variant.name,
discount: variant.price.price - variant.price.priceDiscount,
item_brand: variant.manufacture?.name,
item_variant: variant.code || variant.id,
diff --git a/src/lib/cart/components/Cartheader.jsx b/src/lib/cart/components/Cartheader.jsx
index 580dfc8c..19f79bc9 100644
--- a/src/lib/cart/components/Cartheader.jsx
+++ b/src/lib/cart/components/Cartheader.jsx
@@ -1,14 +1,9 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { getCartApi } from '../api/CartApi'
-import currencyFormat from '@/core/utils/currencyFormat'
-import Image from '@/core/components/elements/Image/Image'
-import { createSlug } from '@/core/utils/slug'
import useAuth from '@/core/hooks/useAuth'
import { useRouter } from 'next/router'
import odooApi from '@/core/api/odooApi'
import { useProductCartContext } from '@/contexts/ProductCartContext'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import { AnimatePresence, motion } from 'framer-motion'
const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline')
const { default: Link } = require('next/link')
@@ -114,182 +109,6 @@ const Cardheader = (cartCount) => {
</span>
</Link>
</div>
-
- <AnimatePresence>
- {isHovered && (
- <>
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- exit={{ opacity: 0 }}
- transition={{ duration: 0.15 }}
- className='fixed top-[155px] left-0 w-full h-full bg-black/50 z-10'
- />
-
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1, transition: { duration: 0.2 } }}
- exit={{ opacity: 0, transition: { duration: 0.3 } }}
- className='absolute z-10 left-0 w-96'
- onMouseEnter={handleMouseEnter}
- onMouseLeave={handleMouseLeave}
- >
- <motion.div
- initial={{ height: 0 }}
- animate={{ height: 'auto' }}
- exit={{ height: 0 }}
- className='w-full max-w-md p-2 bg-white border border-gray-200 rounded-lg shadow overflow-hidden'
- >
- <div className='p-2 flex justify-between items-center'>
- <h5 className='text-base font-semibold leading-none'>Keranjang Belanja</h5>
- <Link href='/shop/cart' class='text-sm font-medium text-red-600 underline'>
- Lihat Semua
- </Link>
- </div>
- <hr className='mt-3 mb-3 border border-gray-100' />
- <div className='flow-root max-h-[250px] overflow-y-auto'>
- {!auth && (
- <div className='justify-center p-4'>
- <p className='text-gray-500 text-center '>
- Silahkan{' '}
- <Link href='/login' className='text-red-600 underline leading-6'>
- Login
- </Link>{' '}
- Untuk Melihat Daftar Keranjang Belanja Anda
- </p>
- </div>
- )}
- {isLoading &&
- itemLoading.map((item) => (
- <div key={item} role='status' className='max-w-sm animate-pulse'>
- <div className='flex items-center space-x-4 mb- 2'>
- <div className='flex-shrink-0'>
- <PhotoIcon className='h-16 w-16 text-gray-500' />
- </div>
- <div className='flex-1 min-w-0'>
- <div className='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4'></div>
- <div className='h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5'></div>
- <div className='h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5'></div>
- </div>
- </div>
- </div>
- ))}
- {auth && products.length === 0 && !isLoading && (
- <div className='justify-center p-4'>
- <p className='text-gray-500 text-center '>
- Tidak Ada Produk di Keranjang Belanja Anda
- </p>
- </div>
- )}
- {auth && products.length > 0 && !isLoading && (
- <>
- <ul role='list' className='divide-y divide-gray-200 dark:divide-gray-700'>
- {products &&
- products?.map((product, index) => (
- <>
- <li className='py-1 sm:py-2'>
- <div className='flex items-center space-x-4'>
- <div className='flex-shrink-0'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
- >
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-16 w-16 rounded-md'
- />
- </Link>
- </div>
- <div className='flex-1 min-w-0'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
- >
- {' '}
- <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
- {product.parent.name}
- </p>
- </Link>
-
- {product?.hasFlashsale && (
- <div className='flex gap-x-1 items-center mb-2 mt-1'>
- <div className='badge-solid-red'>
- {product?.price?.discountPercentage}%
- </div>
- <div className='text-gray_r-11 line-through text-caption-2'>
- {currencyFormat(product?.price?.price)}
- </div>
- </div>
- )}
- <div className='flex justify-between items-center'>
- <div className='font-semibold text-sm text-red-600'>
- {product?.price?.priceDiscount > 0 ? (
- currencyFormat(product?.price?.priceDiscount)
- ) : (
- <span className='text-gray_r-12/90 font-normal text-caption-1'>
- <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'
- >
- Call For Price
- </a>
- </span>
- )}
- </div>
- </div>
- </div>
- </div>
- </li>
- </>
- ))}
- </ul>
- <hr />
- </>
- )}
- </div>
- {auth && products.length > 0 && !isLoading && (
- <>
- <div className='mt-3'>
- <span className='text-gray-400 text-caption-2'>Subtotal Sebelum PPN : </span>
- <span className='font-semibold text-red-600'>{currencyFormat(subTotal)}</span>
- </div>
- <div className='mt-5 mb-2'>
- <button
- type='button'
- className='btn-solid-red rounded-lg w-full'
- onClick={handleCheckout}
- disabled={buttonLoading}
- >
- {buttonLoading ? 'Loading...' : 'Lanjutkan Ke Pembayaran'}
- </button>
- </div>
- </>
- )}
- </motion.div>
- </motion.div>
- </>
- )}
- </AnimatePresence>
</div>
)
}
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 9a799010..eb31b9dc 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -1,191 +1,199 @@
-import Alert from '@/core/components/elements/Alert/Alert'
-import Divider from '@/core/components/elements/Divider/Divider'
-import Link from '@/core/components/elements/Link/Link'
-import useAuth from '@/core/hooks/useAuth'
-import { getItemAddress } from '@/core/utils/address'
-import addressesApi from '@/lib/address/api/addressesApi'
+import Alert from '@/core/components/elements/Alert/Alert';
+import Divider from '@/core/components/elements/Divider/Divider';
+import Link from '@/core/components/elements/Link/Link';
+import useAuth from '@/core/hooks/useAuth';
+import { getItemAddress } from '@/core/utils/address';
+import addressesApi from '@/lib/address/api/addressesApi';
import {
BanknotesIcon,
ChevronLeftIcon,
ClockIcon,
- ExclamationCircleIcon
-} from '@heroicons/react/24/outline'
-import React, { useEffect, useRef, useState } from 'react'
-import _ from 'lodash'
-import { deleteItemCart, getCartApi } from '@/core/utils/cart'
-import currencyFormat from '@/core/utils/currencyFormat'
-import { toast } from 'react-hot-toast'
-import getFileBase64 from '@/core/utils/getFileBase64'
-import { useRouter } from 'next/router'
-import VariantGroupCard from '@/lib/variant/components/VariantGroupCard'
-import axios from 'axios'
-import Image from '@/core/components/elements/Image/Image'
-import MobileView from '@/core/components/views/MobileView'
-import DesktopView from '@/core/components/views/DesktopView'
-import ExpedisiList from '../api/ExpedisiList'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import { useQuery } from 'react-query'
-import { gtagPurchase } from '@/core/utils/googleTag'
-import { findVoucher, getVoucher } from '../api/getVoucher'
-import CardProdcuctsList from '@/core/components/elements/Product/cartProductsList'
-import { Spinner } from '@chakra-ui/react'
-import { AnimatePresence, motion } from 'framer-motion'
-
-const SELF_PICKUP_ID = 32
-
-const { checkoutApi } = require('../api/checkoutApi')
-const { getProductsCheckout } = require('../api/checkoutApi')
+ ExclamationCircleIcon,
+} from '@heroicons/react/24/outline';
+import React, { useEffect, useRef, useState } from 'react';
+import _ from 'lodash';
+import { deleteItemCart } from '@/core/utils/cart';
+import currencyFormat from '@/core/utils/currencyFormat';
+import { toast } from 'react-hot-toast';
+import getFileBase64 from '@/core/utils/getFileBase64';
+import { useRouter } from 'next/router';
+import axios from 'axios';
+import Image from '@/core/components/elements/Image/Image';
+import MobileView from '@/core/components/views/MobileView';
+import DesktopView from '@/core/components/views/DesktopView';
+import ExpedisiList from '../api/ExpedisiList';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import { useQuery } from 'react-query';
+import { gtagPurchase } from '@/core/utils/googleTag';
+import { findVoucher, getVoucher } from '../api/getVoucher';
+import { Spinner } from '@chakra-ui/react';
+import { AnimatePresence, motion } from 'framer-motion';
+import snakecaseKeys from 'snakecase-keys';
+import CartItem from '~/modules/cart/components/Item.tsx';
+
+const SELF_PICKUP_ID = 32;
+
+const { checkoutApi } = require('../api/checkoutApi');
+const { getProductsCheckout } = require('../api/checkoutApi');
const Checkout = () => {
- const router = useRouter()
- const query = router.query.source ?? null
- const auth = useAuth()
+ const router = useRouter();
+ const query = router.query.source ?? null;
+ const auth = useAuth();
- const [activeVoucher, SetActiveVoucher] = useState(null)
+ const [activeVoucher, SetActiveVoucher] = useState(null);
const { data: cartCheckout } = useQuery('cartCheckout-' + activeVoucher, () =>
getProductsCheckout(activeVoucher, query)
- )
+ );
const [selectedAddress, setSelectedAddress] = useState({
shipping: null,
- invoicing: null
- })
- const [addresses, setAddresses] = useState(null)
+ invoicing: null,
+ });
+ const [addresses, setAddresses] = useState(null);
useEffect(() => {
- if (!auth) return
+ if (!auth) return;
const getAddresses = async () => {
- const dataAddresses = await addressesApi()
- setAddresses(dataAddresses)
- }
+ const dataAddresses = await addressesApi();
+ setAddresses(dataAddresses);
+ };
- getAddresses()
- }, [auth])
+ getAddresses();
+ }, [auth]);
useEffect(() => {
- if (!addresses) return
+ if (!addresses) return;
const matchAddress = (key) => {
- const addressToMatch = getItemAddress(key)
- const foundAddress = addresses.filter((address) => address.id == addressToMatch)
+ const addressToMatch = getItemAddress(key);
+ const foundAddress = addresses.filter(
+ (address) => address.id == addressToMatch
+ );
if (foundAddress.length > 0) {
- return foundAddress[0]
+ return foundAddress[0];
}
- return addresses[0]
- }
+ return addresses[0];
+ };
setSelectedAddress({
shipping: matchAddress('shipping'),
- invoicing: matchAddress('invoicing')
- })
- }, [addresses])
-
- const [products, setProducts] = useState(null)
- const [totalWeight, setTotalWeight] = useState(0)
- const [priceCheck, setPriceCheck] = useState(false)
- const [listExpedisi, setExpedisi] = useState([])
- const [listserviceExpedisi, setListServiceExpedisi] = useState([])
- const [selectedExpedisi, setSelectedExpedisi] = useState(0)
- const [selectedCarrierId, setselectedCarrierId] = useState(0)
- const [selectedCarrier, setselectedCarrier] = useState(0)
- const [biayaKirim, setBiayaKirim] = useState(0)
- const [checkWeigth, setCheckWeight] = useState(false)
- const [selectedServiceType, setSelectedServiceType] = useState(null)
- const [selectedExpedisiService, setselectedExpedisiService] = useState(null)
- const [etd, setEtd] = useState(null)
- const [etdFix, setEtdFix] = useState(null)
- const [bottomPopup, SetBottomPopup] = useState(null)
- const [bottomPopupTnC, SetBottomPopupTnC] = useState(null)
- const [itemTnC, setItemTnC] = useState(null)
- const [listVouchers, SetListVoucher] = useState(null)
- const [discountVoucher, SetDiscountVoucher] = useState(0)
- const [codeVoucher, SetCodeVoucher] = useState(null)
- const [findCodeVoucher, SetFindVoucher] = useState(null)
- const [selisihHargaCode, SetSelisihHargaCode] = useState(null)
- const [buttonTerapkan, SetButtonTerapkan] = useState(false)
- const [checkoutValidation, setCheckoutValidation] = useState(false)
- const [loadingVoucher, setLoadingVoucher] = useState(true)
- const [loadingRajaOngkir, setLoadingRajaOngkir] = useState(false)
-
- const expedisiValidation = useRef(null)
+ invoicing: matchAddress('invoicing'),
+ });
+ }, [addresses]);
+
+ const [products, setProducts] = useState(null);
+ const [totalWeight, setTotalWeight] = useState(0);
+ const [priceCheck, setPriceCheck] = useState(false);
+ const [listExpedisi, setExpedisi] = useState([]);
+ const [listserviceExpedisi, setListServiceExpedisi] = useState([]);
+ const [selectedExpedisi, setSelectedExpedisi] = useState(0);
+ const [selectedCarrierId, setselectedCarrierId] = useState(0);
+ const [selectedCarrier, setselectedCarrier] = useState(0);
+ const [biayaKirim, setBiayaKirim] = useState(0);
+ const [checkWeigth, setCheckWeight] = useState(false);
+ const [selectedServiceType, setSelectedServiceType] = useState(null);
+ const [selectedExpedisiService, setselectedExpedisiService] = useState(null);
+ const [etd, setEtd] = useState(null);
+ const [etdFix, setEtdFix] = useState(null);
+ const [bottomPopup, SetBottomPopup] = useState(null);
+ const [bottomPopupTnC, SetBottomPopupTnC] = useState(null);
+ const [itemTnC, setItemTnC] = useState(null);
+ const [listVouchers, SetListVoucher] = useState(null);
+ const [discountVoucher, SetDiscountVoucher] = useState(0);
+ const [codeVoucher, SetCodeVoucher] = useState(null);
+ const [findCodeVoucher, SetFindVoucher] = useState(null);
+ const [selisihHargaCode, SetSelisihHargaCode] = useState(null);
+ const [buttonTerapkan, SetButtonTerapkan] = useState(false);
+ const [checkoutValidation, setCheckoutValidation] = useState(false);
+ const [loadingVoucher, setLoadingVoucher] = useState(true);
+ const [loadingRajaOngkir, setLoadingRajaOngkir] = useState(false);
+
+ const expedisiValidation = useRef(null);
const voucher = async () => {
if (!listVouchers) {
try {
- let dataVoucher = await getVoucher(auth?.id, query)
- SetListVoucher(dataVoucher)
+ let dataVoucher = await getVoucher(auth?.id, query);
+ SetListVoucher(dataVoucher);
} finally {
- setLoadingVoucher(false)
+ setLoadingVoucher(false);
}
}
- }
+ };
const VoucherCode = async (code) => {
- let dataVoucher = await findVoucher(code, auth.id, query)
+ let dataVoucher = await findVoucher(code, auth.id, query);
if (dataVoucher.length <= 0) {
- SetFindVoucher(1)
- return
+ SetFindVoucher(1);
+ return;
}
- let addNewLine = dataVoucher[0]
- let checkList = listVouchers.findIndex((voucher) => voucher.code == addNewLine.code)
+ let addNewLine = dataVoucher[0];
+ let checkList = listVouchers.findIndex(
+ (voucher) => voucher.code == addNewLine.code
+ );
if (checkList >= 0) {
if (listVouchers[checkList].canApply) {
- ToggleSwitch(code)
- SetCodeVoucher(null)
+ ToggleSwitch(code);
+ SetCodeVoucher(null);
} else {
- SetSelisihHargaCode(listVouchers[checkList].differenceToApply)
- SetFindVoucher(2)
+ SetSelisihHargaCode(listVouchers[checkList].differenceToApply);
+ SetFindVoucher(2);
}
- return
+ return;
}
if (cartCheckout?.subtotal < addNewLine.minPurchaseAmount) {
- SetSelisihHargaCode(currencyFormat(addNewLine.minPurchaseAmount - cartCheckout?.subtotal))
- SetFindVoucher(2)
- return
+ SetSelisihHargaCode(
+ currencyFormat(addNewLine.minPurchaseAmount - cartCheckout?.subtotal)
+ );
+ SetFindVoucher(2);
+ return;
} else {
- SetFindVoucher(3)
- SetButtonTerapkan(true)
+ SetFindVoucher(3);
+ SetButtonTerapkan(true);
}
- SetListVoucher((prevList) => [addNewLine, ...prevList])
- SetActiveVoucher(addNewLine.code)
- }
+ SetListVoucher((prevList) => [addNewLine, ...prevList]);
+ SetActiveVoucher(addNewLine.code);
+ };
useEffect(() => {
- SetFindVoucher(null)
- }, [bottomPopup])
+ SetFindVoucher(null);
+ }, [bottomPopup]);
useEffect(() => {
const loadExpedisi = async () => {
- let dataExpedisi = await ExpedisiList()
+ let dataExpedisi = await ExpedisiList();
dataExpedisi = dataExpedisi.map((expedisi) => ({
value: expedisi.id,
label: expedisi.name,
- carrierId: expedisi.deliveryCarrierId
- }))
- setExpedisi(dataExpedisi)
- }
- loadExpedisi()
+ carrierId: expedisi.deliveryCarrierId,
+ }));
+ setExpedisi(dataExpedisi);
+ };
+ loadExpedisi();
const handlePopState = () => {
- router.push('/shop/cart')
- }
+ router.push('/shop/cart');
+ };
- window.onpopstate = handlePopState
+ window.onpopstate = handlePopState;
return () => {
- window.onpopstate = null
- }
+ window.onpopstate = null;
+ };
// voucher()
- }, [])
+ }, []);
const hitungDiscountVoucher = (code) => {
- let dataVoucherIndex = listVouchers.findIndex((voucher) => voucher.code == code)
- let dataActiveVoucher = listVouchers[dataVoucherIndex]
+ let dataVoucherIndex = listVouchers.findIndex(
+ (voucher) => voucher.code == code
+ );
+ let dataActiveVoucher = listVouchers[dataVoucherIndex];
- let countDiscount = dataActiveVoucher.discountVoucher
+ let countDiscount = dataActiveVoucher.discountVoucher;
/*if (dataActiveVoucher.discountType === 'percentage') {
countDiscount = cartCheckout?.subtotal * (dataActiveVoucher.discountAmount / 100)
@@ -199,116 +207,122 @@ const Checkout = () => {
countDiscount = dataActiveVoucher.discountAmount
}*/
- return countDiscount
- }
+ return countDiscount;
+ };
useEffect(() => {
- if (!listVouchers) return
- if (!activeVoucher) return
+ if (!listVouchers) return;
+ if (!activeVoucher) return;
- const countDiscount = hitungDiscountVoucher(activeVoucher)
+ const countDiscount = hitungDiscountVoucher(activeVoucher);
- SetDiscountVoucher(countDiscount)
- }, [activeVoucher, listVouchers])
+ SetDiscountVoucher(countDiscount);
+ }, [activeVoucher, listVouchers]);
useEffect(() => {
- setProducts(cartCheckout?.products)
- setCheckWeight(cartCheckout?.hasProductWithoutWeight)
- setTotalWeight(cartCheckout?.totalWeight.g)
- }, [cartCheckout])
+ setProducts(cartCheckout?.products);
+ setCheckWeight(cartCheckout?.hasProductWithoutWeight);
+ setTotalWeight(cartCheckout?.totalWeight.g);
+ }, [cartCheckout]);
useEffect(() => {
- setCheckoutValidation(false)
+ setCheckoutValidation(false);
const loadServiceRajaOngkir = async () => {
- setLoadingRajaOngkir(true)
+ setLoadingRajaOngkir(true);
const body = {
origin: 2127,
destination: selectedAddress.shipping.rajaongkirCityId,
weight: totalWeight,
courier: selectedCarrier,
originType: 'subdistrict',
- destinationType: 'subdistrict'
- }
- setBiayaKirim(0)
- const dataService = await axios('/api/rajaongkir-service?body=' + JSON.stringify(body))
- setLoadingRajaOngkir(false)
- setListServiceExpedisi(dataService.data[0].costs)
+ destinationType: 'subdistrict',
+ };
+ setBiayaKirim(0);
+ const dataService = await axios(
+ '/api/rajaongkir-service?body=' + JSON.stringify(body)
+ );
+ setLoadingRajaOngkir(false);
+ setListServiceExpedisi(dataService.data[0].costs);
if (dataService.data[0].costs[0]) {
- setBiayaKirim(dataService.data[0].costs[0]?.cost[0].value)
+ setBiayaKirim(dataService.data[0].costs[0]?.cost[0].value);
setselectedExpedisiService(
- dataService.data[0].costs[0]?.description + '-' + dataService.data[0].costs[0]?.service
- )
- setEtd(dataService.data[0].costs[0]?.cost[0].etd)
- toast.success('Harap pilih tipe layanan pengiriman')
+ dataService.data[0].costs[0]?.description +
+ '-' +
+ dataService.data[0].costs[0]?.service
+ );
+ setEtd(dataService.data[0].costs[0]?.cost[0].etd);
+ toast.success('Harap pilih tipe layanan pengiriman');
} else {
- toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.')
+ toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.');
}
- }
+ };
if (selectedCarrier != 0 && selectedCarrier != 1 && totalWeight > 0) {
- loadServiceRajaOngkir()
+ loadServiceRajaOngkir();
} else {
- setListServiceExpedisi()
- setBiayaKirim(0)
- setselectedExpedisiService()
- setEtd()
+ setListServiceExpedisi();
+ setBiayaKirim(0);
+ setselectedExpedisiService();
+ setEtd();
}
- }, [selectedCarrier, selectedAddress, totalWeight])
+ }, [selectedCarrier, selectedAddress, totalWeight]);
useEffect(() => {
if (selectedServiceType) {
- let serviceType = selectedServiceType.split(',')
- setBiayaKirim(serviceType[0])
- setselectedExpedisiService(serviceType[1])
- setEtd(serviceType[2])
+ let serviceType = selectedServiceType.split(',');
+ setBiayaKirim(serviceType[0]);
+ setselectedExpedisiService(serviceType[1]);
+ setEtd(serviceType[2]);
}
- }, [selectedServiceType])
+ }, [selectedServiceType]);
useEffect(() => {
- if (etd) setEtdFix(calculateEstimatedArrival(etd))
- }, [etd])
+ if (etd) setEtdFix(calculateEstimatedArrival(etd));
+ }, [etd]);
useEffect(() => {
if (selectedExpedisi) {
- let serviceType = selectedExpedisi.split(',')
- if (serviceType[0] === 0) return
+ let serviceType = selectedExpedisi.split(',');
+ if (serviceType[0] === 0) return;
- setselectedCarrier(serviceType[0])
- setselectedCarrierId(serviceType[1])
- setListServiceExpedisi([])
+ setselectedCarrier(serviceType[0]);
+ setselectedCarrierId(serviceType[1]);
+ setListServiceExpedisi([]);
}
- }, [selectedExpedisi])
+ }, [selectedExpedisi]);
- const poNumber = useRef(null)
- const poFile = useRef(null)
+ const poNumber = useRef(null);
+ const poFile = useRef(null);
- const [isLoading, setIsLoading] = useState(false)
+ const [isLoading, setIsLoading] = useState(false);
const checkout = async () => {
- const file = poFile.current.files[0]
+ const file = poFile.current.files[0];
if (typeof file !== 'undefined' && file.size > 5000000) {
- toast.error('Maksimal ukuran file adalah 5MB', { position: 'bottom-center' })
- return
+ toast.error('Maksimal ukuran file adalah 5MB', {
+ position: 'bottom-center',
+ });
+ return;
}
if (selectedExpedisi === 0) {
- setCheckoutValidation(true)
+ setCheckoutValidation(true);
if (expedisiValidation.current) {
- const position = expedisiValidation.current.getBoundingClientRect()
+ const position = expedisiValidation.current.getBoundingClientRect();
window.scrollTo({
top: position.top - 300 + window.pageYOffset,
- behavior: 'smooth'
- })
+ behavior: 'smooth',
+ });
}
- return
+ return;
}
if (selectedCarrier != 1 && biayaKirim == 0) {
- toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.')
- return
+ toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.');
+ return;
}
- setIsLoading(true)
+ setIsLoading(true);
const productOrder = products.map((product) => ({
product_id: product.id,
- quantity: product.quantity
- }))
+ quantity: product.quantity,
+ }));
let data = {
partner_shipping_id: auth.partnerId,
partner_invoice_id: auth.partnerId,
@@ -319,80 +333,82 @@ const Checkout = () => {
estimated_arrival_days: splitDuration(etd),
delivery_service_type: selectedExpedisiService,
voucher: activeVoucher,
- type: 'sale_order'
- }
+ type: 'sale_order',
+ };
if (query) {
- data.source = 'buy'
+ data.source = 'buy';
}
- if (poNumber.current.value) data.po_number = poNumber.current.value
- if (typeof file !== 'undefined') data.po_file = await getFileBase64(file)
+ if (poNumber.current.value) data.po_number = poNumber.current.value;
+ if (typeof file !== 'undefined') data.po_file = await getFileBase64(file);
- const isCheckouted = await checkoutApi({ data })
+ const isCheckouted = await checkoutApi({ data });
if (!isCheckouted?.id) {
- toast.error('Gagal melakukan transaksi, terjadi kesalahan internal')
- return
+ toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
+ return;
}
- gtagPurchase(products, biayaKirim, isCheckouted.name)
+ gtagPurchase(products, biayaKirim, isCheckouted.name);
const midtrans = async () => {
- for (const product of products) deleteItemCart({ productId: product.id })
+ for (const product of products) deleteItemCart({ productId: product.id });
const payment = await axios.post(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/midtrans-payment?transactionId=${isCheckouted.id}`
- )
- setIsLoading(false)
- window.location.href = payment.data.redirectUrl
- }
+ );
+ setIsLoading(false);
+ window.location.href = payment.data.redirectUrl;
+ };
gtag('event', 'conversion', {
send_to: 'AW-954540379/nDymCL3BhaQYENvClMcD',
- value: cartCheckout?.grandTotal + Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000,
+ value:
+ cartCheckout?.grandTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000,
currency: 'IDR',
transaction_id: isCheckouted.id,
- event_callback: midtrans
- })
- }
+ event_callback: midtrans,
+ });
+ };
const handlingActivateCode = async () => {
- VoucherCode(codeVoucher)
- }
+ VoucherCode(codeVoucher);
+ };
const handleUseVoucher = async (code, isCheck) => {
if (isCheck) {
if (code === activeVoucher) {
- SetActiveVoucher(null)
- SetDiscountVoucher(0)
+ SetActiveVoucher(null);
+ SetDiscountVoucher(0);
} else {
- SetActiveVoucher(code)
- SetFindVoucher(null)
- document.getElementById('uniqCode').value = ''
- SetButtonTerapkan(false)
+ SetActiveVoucher(code);
+ SetFindVoucher(null);
+ document.getElementById('uniqCode').value = '';
+ SetButtonTerapkan(false);
}
} else {
- SetActiveVoucher(code)
- SetFindVoucher(null)
- document.getElementById('uniqCode').value = ''
- SetButtonTerapkan(false)
+ SetActiveVoucher(code);
+ SetFindVoucher(null);
+ document.getElementById('uniqCode').value = '';
+ SetButtonTerapkan(false);
}
- }
+ };
const onChangeCodeVoucher = async (e) => {
- SetCodeVoucher(e.target.value)
- SetButtonTerapkan(false)
- }
+ SetCodeVoucher(e.target.value);
+ SetButtonTerapkan(false);
+ };
- const [isChecked, setIsChecked] = useState(false)
+ const [isChecked, setIsChecked] = useState(false);
const ToggleSwitch = (code) => {
- setIsChecked(!isChecked)
- handleUseVoucher(code, !isChecked)
- }
+ setIsChecked(!isChecked);
+ handleUseVoucher(code, !isChecked);
+ };
const handlingTnC = async (item) => {
- setItemTnC(item)
- SetBottomPopupTnC(true)
- }
+ setItemTnC(item);
+ SetBottomPopupTnC(true);
+ };
// const taxTotal = (totalAmount - totalDiscountAmount - discountVoucher) * 0.11
return (
@@ -401,13 +417,17 @@ const Checkout = () => {
className='w-full md:!w-[40%] !min-h-[90vh]'
active={bottomPopupTnC}
close={() => {
- SetBottomPopupTnC(false)
- SetBottomPopup(false)
+ SetBottomPopupTnC(false);
+ SetBottomPopup(false);
}}
title={
<div>
- <button className='flex gap-x-2 items-center' onClick={() => SetBottomPopupTnC(false)}>
- <ChevronLeftIcon class='h- w-5 text-black' /> <span className='text-lg'>Voucher</span>
+ <button
+ className='flex gap-x-2 items-center'
+ onClick={() => SetBottomPopupTnC(false)}
+ >
+ <ChevronLeftIcon class='h- w-5 text-black' />{' '}
+ <span className='text-lg'>Voucher</span>
</button>{' '}
</div>
}
@@ -420,13 +440,17 @@ const Checkout = () => {
<span className='text-sm'>
{' '}
Berakhir dalam :{' '}
- <span className='text-sm text-red-500'>{itemTnC?.remainingTime} lagi</span>
+ <span className='text-sm text-red-500'>
+ {itemTnC?.remainingTime} lagi
+ </span>
</span>
</div>
<div className='flex items-center gap-x-1'>
<BanknotesIcon class='h-6 w-6 text-green-500' />
<span className='text-sm'> Kode Voucher : </span>
- <span className='text-red-500 font-semibold'>{itemTnC?.code}</span>
+ <span className='text-red-500 font-semibold'>
+ {itemTnC?.code}
+ </span>
</div>
</div>
<div>
@@ -441,6 +465,7 @@ const Checkout = () => {
</div>
</div>
</BottomPopup>
+
<BottomPopup
className='w-full md:!w-[40%] !min-h-[350px]'
active={bottomPopup}
@@ -448,8 +473,8 @@ const Checkout = () => {
title='Gunakan Promo'
>
<div className='row'>
- <div className='flex justify-between items-center'>
- <div className='flex md:w-[70%]'>
+ <div className='flex justify-between items-center gap-x-4'>
+ <div className='flex flex-1 md:w-[70%]'>
<input
type='text'
id='uniqCode'
@@ -481,15 +506,16 @@ const Checkout = () => {
{findCodeVoucher === 1 && (
<div className='mt-2'>
<span className='text-caption-1 mt-2 text-red-600'>
- Kode voucher salah / sudah tidak berlaku lagi. Coba voucher lainnya, ya.
+ Kode voucher salah / sudah tidak berlaku lagi. Coba voucher
+ lainnya, ya.
</span>
</div>
)}
{findCodeVoucher === 2 && (
<div className='mt-2'>
<span className='text-caption-1 mt-2 text-red-600'>
- Tambah <span className='text-red-600'>{selisihHargaCode}</span> untuk pakai promo
- ini
+ Tambah <span className='text-red-600'>{selisihHargaCode}</span>{' '}
+ untuk pakai promo ini
</span>
</div>
)}
@@ -500,15 +526,21 @@ const Checkout = () => {
<div className='flex items-center justify-center mt-4 mb-4'>
<div className='text-center'>
<h1 className='font-bold mb-4'>Tidak ada voucher tersedia</h1>
- <p className='text-gray-500'>Maaf, saat ini tidak ada voucher yang tersedia.</p>
+ <p className='text-gray-500'>
+ Maaf, saat ini tidak ada voucher yang tersedia.
+ </p>
</div>
</div>
) : (
- <h3 className='font-semibold mb-4'>Promo Khusus Untuk {auth?.name}</h3>
+ <h3 className='font-semibold mb-4'>
+ Promo Khusus Untuk {auth?.name}
+ </h3>
)}
{loadingVoucher && (
<>
- <div className={`border border-solid w-full hover:cursor-pointer p-2`}>
+ <div
+ className={`border border-solid w-full hover:cursor-pointer p-2`}
+ >
<div class='flex items-center space-x-3'>
<div class='flex items-center justify-center h-28 w-48 mb-4 bg-gray-300 rounded dark:bg-gray-700'>
<svg
@@ -529,7 +561,9 @@ const Checkout = () => {
</div>
</div>
</div>
- <div className={`border border-solid w-full hover:cursor-pointer p-2`}>
+ <div
+ className={`border border-solid w-full hover:cursor-pointer p-2`}
+ >
<div class='flex items-center space-x-3'>
<div class='flex items-center justify-center h-28 w-48 mb-4 bg-gray-300 rounded dark:bg-gray-700'>
<svg
@@ -579,7 +613,9 @@ const Checkout = () => {
>
<p>
Voucher tidak bisa di gunakan,{' '}
- <span className='text-red font-bold'>Baca Selengkapnya !</span>
+ <span className='text-red font-bold'>
+ Baca Selengkapnya !
+ </span>
</p>
</div>
)}
@@ -589,14 +625,20 @@ const Checkout = () => {
<div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0 z-50' />
)}
<div className='hidden md:w-[250px] md:block'>
- <Image src={item.image} alt={item.name} className={`object-cover`} />
+ <Image
+ src={item.image}
+ alt={item.name}
+ className={`object-cover`}
+ />
</div>
<div className='w-full'>
<div className='flex justify-between gap-x-2 mb-1 items-center'>
<div className=''>
<h3 className='font-semibold'>{item.name}</h3>
<div className='mt-1'>
- <span className='text-sm line-clamp-3'>{item.description} </span>
+ <span className='text-sm line-clamp-3'>
+ {item.description}{' '}
+ </span>
</div>
</div>
<div className='flex justify-end'>
@@ -605,7 +647,9 @@ const Checkout = () => {
type='checkbox'
value=''
class='sr-only peer'
- checked={activeVoucher === item.code ? true : false}
+ checked={
+ activeVoucher === item.code ? true : false
+ }
onChange={() => ToggleSwitch(item.code)}
/>
<div class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-green-300 dark:peer-focus:ring-green-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600"></div>
@@ -616,11 +660,15 @@ const Checkout = () => {
<div className='flex justify-between items-center'>
<p className='text-justify text-sm md:text-xs'>
Kode Voucher :{' '}
- <span className='text-red-500 font-semibold'>{item.code}</span>
+ <span className='text-red-500 font-semibold'>
+ {item.code}
+ </span>
</p>
<p className='text-sm md:text-xs'>
{activeVoucher === item.code && (
- <span className=' text-green-600'>Voucher digunakan </span>
+ <span className=' text-green-600'>
+ Voucher digunakan{' '}
+ </span>
)}
</p>
</div>
@@ -642,7 +690,10 @@ const Checkout = () => {
<div className='flex justify-between items-center'>
<div className='text-left ml-3 text-sm '>
Berakhir dalam{' '}
- <span className='text-red-600'>{item.remainingTime}</span> lagi,{' '}
+ <span className='text-red-600'>
+ {item.remainingTime}
+ </span>{' '}
+ lagi,{' '}
</div>
<div
className='text-sm ml-2 text-red-600'
@@ -670,6 +721,7 @@ const Checkout = () => {
</div>
</div>
</BottomPopup>
+
<MobileView>
<div className='p-4'>
<Alert type='info' className='text-caption-2 flex gap-x-3'>
@@ -677,8 +729,8 @@ const Checkout = () => {
<ExclamationCircleIcon className='w-7 text-blue-700' />
</div>
<span className='leading-5'>
- Jika mengalami kesulitan dalam melakukan pembelian di website Indoteknik. Hubungi kami
- disini
+ Jika mengalami kesulitan dalam melakukan pembelian di website
+ Indoteknik. Hubungi kami disini
</span>
</Alert>
</div>
@@ -701,14 +753,17 @@ const Checkout = () => {
</svg>
<span class='sr-only'>Info</span>
<div className='text-justify'>
- Fitur Self Pickup, hanya berlaku untuk customer di area jakarta. Apa bila memilih
- fitur ini, anda akan dihubungi setelah barang siap diambil.
+ Fitur Self Pickup, hanya berlaku untuk customer di area jakarta.
+ Apa bila memilih fitur ini, anda akan dihubungi setelah barang
+ siap diambil.
</div>
</div>
</div>
)}
- {selectedCarrierId == SELF_PICKUP_ID && <PickupAddress label='Alamat Pickup' />}
+ {selectedCarrierId == SELF_PICKUP_ID && (
+ <PickupAddress label='Alamat Pickup' />
+ )}
{selectedCarrierId != SELF_PICKUP_ID && (
<>
<SectionAddress
@@ -742,7 +797,10 @@ const Checkout = () => {
/>
<div className='p-4 flex flex-col gap-y-4'>
- {products && <VariantGroupCard openOnClick={false} variants={products} />}
+ {!!products &&
+ snakecaseKeys(products).map((item, index) => (
+ <CartItem key={index} item={item} editable={false} />
+ ))}
</div>
<Divider />
@@ -750,7 +808,6 @@ const Checkout = () => {
<div className='p-4'>
<div className='flex justify-between items-center'>
<div className='font-medium'>Ringkasan Pesanan</div>
- <div className='text-gray_r-11 text-caption-1'>{products?.length} Barang</div>
</div>
<hr className='my-4 border-gray_r-6' />
{!cartCheckout ? (
@@ -804,7 +861,9 @@ const Checkout = () => {
{activeVoucher && (
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>Diskon Voucher</div>
- <div className='text-danger-500'>- {currencyFormat(discountVoucher)}</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discountVoucher)}
+ </div>
</div>
)}
<div className='flex gap-x-2 justify-between'>
@@ -819,7 +878,11 @@ const Checkout = () => {
<div className='text-gray_r-11'>
Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}</div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
</div>
</div>
)}
@@ -840,7 +903,8 @@ const Checkout = () => {
<div>Grand Total</div>
<div className='font-semibold text-gray_r-12'>
{currencyFormat(
- cartCheckout?.grandTotal + Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ cartCheckout?.grandTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
)}
</div>
</div>
@@ -852,8 +916,8 @@ const Checkout = () => {
<button
type='button'
onClick={() => {
- SetBottomPopup(true)
- voucher()
+ SetBottomPopup(true);
+ voucher();
}}
className='text-gray-900 p-4 flex items-center justify-between rounded-lg bg-white border font-medium border-gray-300 hover:bg-gray-100 py-2.5 h-[50px] w-[100%]'
>
@@ -886,7 +950,8 @@ const Checkout = () => {
</div>
{/* <p className='text-caption-2 text-gray_r-10 mb-2'>*) Belum termasuk biaya pengiriman</p> */}
<p className='text-caption-2 text-gray_r-10 leading-5'>
- Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '}
+ Dengan melakukan pembelian melalui website Indoteknik, saya
+ menyetujui{' '}
<Link href='/syarat-ketentuan' className='inline font-normal'>
Syarat & Ketentuan
</Link>{' '}
@@ -911,10 +976,16 @@ const Checkout = () => {
</div>
<div className='w-6/12'>
<label className='form-label font-normal'>Nomor PO</label>
- <input type='text' className='form-input mt-2 h-12' ref={poNumber} />
+ <input
+ type='text'
+ className='form-input mt-2 h-12'
+ ref={poNumber}
+ />
</div>
</div>
- <p className='text-caption-2 text-gray_r-11 mt-2'>Ukuran dokumen PO Maksimal 5MB</p>
+ <p className='text-caption-2 text-gray_r-11 mt-2'>
+ Ukuran dokumen PO Maksimal 5MB
+ </p>
</div>
<Divider />
@@ -923,7 +994,9 @@ const Checkout = () => {
<button
className='flex-1 btn-yellow'
onClick={checkout}
- disabled={isLoading || !products || products?.length == 0 || priceCheck}
+ disabled={
+ isLoading || !products || products?.length == 0 || priceCheck
+ }
>
{isLoading ? 'Loading...' : 'Lanjut Pembayaran'}
</button>
@@ -957,8 +1030,9 @@ const Checkout = () => {
</svg>
<span class='sr-only'>Info</span>
<div>
- Fitur Self Pickup, hanya berlaku untuk customer di area jakarta. Apa bila memilih
- fitur ini, anda akan dihubungi setelah barang siap diambil.
+ Fitur Self Pickup, hanya berlaku untuk customer di area jakarta.
+ Apa bila memilih fitur ini, anda akan dihubungi setelah barang
+ siap diambil.
</div>
</div>
)}
@@ -966,7 +1040,9 @@ const Checkout = () => {
<div className='flex'>
{' '}
<div className='w-3/4 border border-gray_r-6 rounded bg-white'>
- {selectedCarrierId == SELF_PICKUP_ID && <PickupAddress label='Alamat Pickup' />}
+ {selectedCarrierId == SELF_PICKUP_ID && (
+ <PickupAddress label='Alamat Pickup' />
+ )}
{selectedCarrierId != SELF_PICKUP_ID && (
<>
<SectionAddress
@@ -1000,170 +1076,13 @@ const Checkout = () => {
/>
<div className='p-4'>
- <div className='font-medium'>Detail Pesanan</div>
- <CardProdcuctsList isLoading={isLoading} products={products} />
-
- {/* <table className='table-checkout'>
- <thead>
- <tr>
- <th>Nama Produk</th>
- <th>Jumlah</th>
- <th>Harga</th>
- <th>Subtotal</th>
- </tr>
- </thead>
- <tbody>
- {!products ? (
- <tr>
- <td colSpan={4}>
- <div className='container my-4'>
- <div class='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4'></div>
- <div class='h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5'></div>
- <div class='h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5'></div>
- <div class='h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
- </div>
- </td>
- </tr>
- ) : (
- products?.map((product) => (
- <>
- <tr
- key={product.id}
- className={`${product.program ? '!border-t-0 !border-b-0' : ''}`}
- >
- <td className='flex'>
- <div className='w-[20%] flex-shrink-0'>
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md'
- />
- </div>
- <div className='px-2 text-left'>
- <div className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'>
- {product?.parent?.name}
- </div>
- <div className='text-gray_r-11 mt-2'>
- {product?.code}{' '}
- {product?.attributes.length > 0
- ? `| ${product?.attributes.join(', ')}`
- : ''}
- </div>
- <div className='text-gray_r-11 mt-2'>
- Berat item : {product?.weight} Kg
- </div>
- </div>
- </td>
- <td>
- <input
- className='form-input w-16 py-2 text-center bg-gray_r-1'
- type='number'
- value={product?.quantity}
- disabled
- />
- </td>
- <td>
- {product?.hasFlashsale ? (
- <>
- <div className='flex gap-x-1 items-center justify-center mt-3'>
- <div className='text-gray_r-11 line-through text-caption-1'>
- {currencyFormat(product?.price?.price)}
- </div>
- <div className='badge-solid-red'>
- {product?.price?.discountPercentage}%
- </div>
- </div>
- <div className='font-normal mt-1'>
- {currencyFormat(product?.price?.priceDiscount)}
- </div>
- </>
- ) : (
- <div className='font-normal mt-1'>
- {product.price.priceDiscount > 0
- ? currencyFormat(product?.price?.priceDiscount)
- : 'Call for Inquiry'}
- </div>
- )}
- </td>
- <td>
- <div className='text-danger-500 font-medium'>
- {product.price.priceDiscount > 0 ? (
- currencyFormat(product?.price?.priceDiscount * product?.quantity)
- ) : (
- <a
- href={whatsappUrl('product', {
- name: product.name,
- url: createSlug(
- '/shop/product/',
- product.name,
- product.id,
- true
- )
- })}
- className='underline'
- >
- Call for Inquiry{' '}
- </a>
- )}
- </div>
- </td>
- </tr>
- {product.program &&
- product.program.items &&
- product.program.items.map((item) => (
- <>
- <tr key={product?.program?.id} className='!border-t-0'>
- <td className='flex'>
- <div className='w-[20%] flex-shrink-0'>
- <Image
- src={item.parent.image}
- alt={item.name}
- className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md'
- />
- </div>
- <div className='px-2 text-left'>
- <div className=''>
- <span className='border border-solid border-red-600 rounded-md p-1 text-red-600'>
- {product.program.type.label}
- </span>
- </div>
- <div className='mt-2 line-clamp-2 leading-6'>{item.name}</div>
- </div>
- </td>
- <td>
- <input
- className='form-input w-16 py-2 text-center bg-gray_r-1'
- type='number'
- value={1}
- disabled
- />
- </td>
- <td>
- {item?.price?.discountPercentage > 0 && (
- <div className='flex gap-x-1 items-center justify-center mt-3'>
- <div className='text-gray_r-11 line-through text-caption-1'>
- {currencyFormat(product?.price?.price)}
- </div>
- </div>
- )}
- <div className='font-normal mt-1'>
- {item?.price.priceDiscount > 0 ? 'Gratis' : ''}
- </div>
- </td>
- <td>
- <div className='text-danger-500 font-medium'>
- {item.price.priceDiscount > 0 ? 'Gratis' : ''}
- </div>
- </td>
- <td></td>
- </tr>
- </>
- ))}
- </>
- ))
- )}
- </tbody>
- </table> */}
+ <div className='font-medium mb-6'>Detail Pesanan</div>
+ <div className='flex flex-col gap-y-8 border-t border-gray-300 pt-8'>
+ {!!products &&
+ snakecaseKeys(products).map((item, index) => (
+ <CartItem key={index} item={item} editable={false} />
+ ))}
+ </div>
</div>
</div>
<div className='w-1/4 pl-4'>
@@ -1171,7 +1090,8 @@ const Checkout = () => {
<div className='flex justify-between items-center'>
<div className='font-medium'>Ringkasan Pesanan</div>
<div className='text-gray_r-11 text-caption-1'>
- {products?.length} Barang - {cartCheckout?.totalWeight.kg} Kg
+ {cartCheckout?.totalWeight.kg}{' '}
+ Kg
</div>
</div>
@@ -1227,7 +1147,9 @@ const Checkout = () => {
{activeVoucher && (
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>Diskon Voucher</div>
- <div className='text-danger-500'>- {currencyFormat(discountVoucher)}</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discountVoucher)}
+ </div>
</div>
)}
<div className='flex gap-x-2 justify-between'>
@@ -1244,7 +1166,9 @@ const Checkout = () => {
<p className='text-xs mt-3'>{etdFix}</p>
</div>
<div>
- {currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
</div>
</div>
</div>
@@ -1279,8 +1203,8 @@ const Checkout = () => {
<button
type='button'
onClick={() => {
- SetBottomPopup(true)
- voucher()
+ SetBottomPopup(true);
+ voucher();
}}
className='text-gray-900 p-3 flex items-center justify-between rounded-lg bg-white border font-medium border-gray-300 hover:bg-gray-100 py-2.5 h-[50px] w-[100%]'
>
@@ -1312,7 +1236,8 @@ const Checkout = () => {
</div>
<p className='text-caption-2 text-gray_r-11 leading-5'>
- Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '}
+ Dengan melakukan pembelian melalui website Indoteknik, saya
+ menyetujui{' '}
<Link href='/syarat-ketentuan' className='inline font-normal'>
Syarat & Ketentuan
</Link>{' '}
@@ -1322,7 +1247,8 @@ const Checkout = () => {
<hr className='my-4 border-gray_r-6' />
<div className='font-medium mt-4'>
- Purchase Order <span className='font-normal text-gray_r-11'>(Opsional)</span>
+ Purchase Order{' '}
+ <span className='font-normal text-gray_r-11'>(Opsional)</span>
</div>
<div className='mt-4 flex gap-x-3'>
@@ -1337,17 +1263,28 @@ const Checkout = () => {
</div>
<div className='w-6/12'>
<label className='form-label font-normal'>Nomor PO</label>
- <input type='text' className='form-input mt-2 h-12' ref={poNumber} />
+ <input
+ type='text'
+ className='form-input mt-2 h-12'
+ ref={poNumber}
+ />
</div>
</div>
- <p className='text-caption-2 text-gray_r-11 mt-2'>Ukuran dokumen PO Maksimal 5MB</p>
+ <p className='text-caption-2 text-gray_r-11 mt-2'>
+ Ukuran dokumen PO Maksimal 5MB
+ </p>
<hr className='my-4 border-gray_r-6' />
<button
className='w-full btn-yellow mt-4'
onClick={checkout}
- disabled={isLoading || !products || products?.length == 0 || priceCheck}
+ disabled={
+ isLoading ||
+ !products ||
+ products?.length == 0 ||
+ priceCheck
+ }
>
{isLoading ? 'Loading...' : 'Lanjut Pembayaran'}
</button>
@@ -1367,8 +1304,8 @@ const Checkout = () => {
</div>
</DesktopView>
</>
- )
-}
+ );
+};
const SectionAddress = ({ address, label, url }) => (
<div className='p-4'>
@@ -1382,7 +1319,9 @@ const SectionAddress = ({ address, label, url }) => (
{address && (
<div className='mt-4 text-caption-1'>
<div className='badge-red mb-2'>
- {address.type.charAt(0).toUpperCase() + address.type.slice(1) + ' Address'}
+ {address.type.charAt(0).toUpperCase() +
+ address.type.slice(1) +
+ ' Address'}
</div>
<p className='font-medium'>{address.name}</p>
<p className='mt-2 text-gray_r-11'>{address.mobile}</p>
@@ -1392,7 +1331,7 @@ const SectionAddress = ({ address, label, url }) => (
</div>
)}
</div>
-)
+);
const SectionValidation = ({ address }) =>
address?.rajaongkirCityId == 0 && (
@@ -1409,7 +1348,7 @@ const SectionValidation = ({ address }) =>
</Link>
</div>
</BottomPopup>
- )
+ );
const SectionExpedisi = ({
address,
@@ -1418,7 +1357,7 @@ const SectionExpedisi = ({
checkWeigth,
checkoutValidation,
expedisiValidation,
- loadingRajaOngkir
+ loadingRajaOngkir,
}) =>
address?.rajaongkirCityId > 0 && (
<div className='p-4' ref={expedisiValidation}>
@@ -1427,7 +1366,9 @@ const SectionExpedisi = ({
<div className='w-[250px]'>
<div className='flex items-center gap-x-4'>
<select
- className={`form-input ${checkoutValidation ? 'border-red-500 shake' : ''}`}
+ className={`form-input ${
+ checkoutValidation ? 'border-red-500 shake' : ''
+ }`}
onChange={(e) => setSelectedExpedisi(e.target.value)}
required
>
@@ -1453,7 +1394,7 @@ const SectionExpedisi = ({
animate={{ opacity: 1, width: '28px' }}
exit={{ opacity: 0, width: 0 }}
transition={{
- duration: 0.25
+ duration: 0.25,
}}
className='overflow-hidden'
>
@@ -1463,7 +1404,9 @@ const SectionExpedisi = ({
</AnimatePresence>
</div>
{checkoutValidation && (
- <span className='text-sm text-red-500'>*silahkan pilih expedisi</span>
+ <span className='text-sm text-red-500'>
+ *silahkan pilih expedisi
+ </span>
)}
</div>
<style jsx>{`
@@ -1474,8 +1417,9 @@ const SectionExpedisi = ({
</div>
{checkWeigth == true && (
<p className='mt-4 text-gray_r-11 leading-6'>
- Mohon maaf, pengiriman hanya tersedia untuk self pickup karena terdapat barang yang belum
- diatur beratnya. Mohon atur berat barang dengan menghubungi admin melalui{' '}
+ Mohon maaf, pengiriman hanya tersedia untuk self pickup karena
+ terdapat barang yang belum diatur beratnya. Mohon atur berat barang
+ dengan menghubungi admin melalui{' '}
<a
className='text-danger-500 inline'
href='https://api.whatsapp.com/send?phone=628128080622'
@@ -1485,7 +1429,7 @@ const SectionExpedisi = ({
</p>
)}
</div>
- )
+ );
const SectionListService = ({ listserviceExpedisi, setSelectedServiceType }) =>
listserviceExpedisi?.length > 0 && (
@@ -1494,7 +1438,10 @@ const SectionListService = ({ listserviceExpedisi, setSelectedServiceType }) =>
<div className='flex justify-between items-center'>
<div className='font-medium'>Tipe Layanan Ekspedisi: </div>
<div>
- <select className='form-input' onChange={(e) => setSelectedServiceType(e.target.value)}>
+ <select
+ className='form-input'
+ onChange={(e) => setSelectedServiceType(e.target.value)}
+ >
{listserviceExpedisi.map((service) => (
<option
value={
@@ -1511,7 +1458,9 @@ const SectionListService = ({ listserviceExpedisi, setSelectedServiceType }) =>
{' '}
{service.description} - {service.service.toUpperCase()}
{extractDuration(service.cost[0].etd) &&
- ` (Estimasi Tiba ${extractDuration(service.cost[0].etd)} Hari)`}
+ ` (Estimasi Tiba ${extractDuration(
+ service.cost[0].etd
+ )} Hari)`}
</option>
))}
</select>
@@ -1520,73 +1469,73 @@ const SectionListService = ({ listserviceExpedisi, setSelectedServiceType }) =>
</div>
<Divider />
</>
- )
+ );
function addDays(date, days) {
- const result = new Date(date)
- result.setDate(result.getDate() + days)
- return result
+ const result = new Date(date);
+ result.setDate(result.getDate() + days);
+ return result;
}
function formatDate(date) {
- const day = date.getDate()
- const month = date.toLocaleString('default', { month: 'short' })
- return `${day} ${month}`
+ const day = date.getDate();
+ const month = date.toLocaleString('default', { month: 'short' });
+ return `${day} ${month}`;
}
function calculateEstimatedArrival(duration) {
if (duration) {
- let estimationDate = duration.split('-')
- estimationDate[0] = parseInt(estimationDate[0])
- estimationDate[1] = parseInt(estimationDate[1])
- const from = addDays(new Date(), estimationDate[0] + 3)
- const to = addDays(new Date(), estimationDate[1] + 3)
+ let estimationDate = duration.split('-');
+ estimationDate[0] = parseInt(estimationDate[0]);
+ estimationDate[1] = parseInt(estimationDate[1]);
+ const from = addDays(new Date(), estimationDate[0] + 3);
+ const to = addDays(new Date(), estimationDate[1] + 3);
- let etdText = `*Estimasi tiba ${formatDate(from)}`
+ let etdText = `*Estimasi tiba ${formatDate(from)}`;
if (estimationDate[1] > estimationDate[0]) {
- etdText += ` - ${formatDate(to)}`
+ etdText += ` - ${formatDate(to)}`;
}
- return etdText
+ return etdText;
}
- return ''
+ return '';
}
function splitDuration(duration) {
if (duration) {
- let estimationDate = null
+ let estimationDate = null;
if (duration.includes('-')) {
- estimationDate = duration.split('-')
- estimationDate = parseInt(estimationDate[1])
+ estimationDate = duration.split('-');
+ estimationDate = parseInt(estimationDate[1]);
} else {
- estimationDate = parseInt(duration)
+ estimationDate = parseInt(duration);
}
- return estimationDate
+ return estimationDate;
}
- return ''
+ return '';
}
const extractDuration = (text) => {
- const matches = text.match(/\d+(?:-\d+)?/g)
+ const matches = text.match(/\d+(?:-\d+)?/g);
if (matches && matches.length === 1) {
- const parts = matches[0].split('-')
- const min = parseInt(parts[0])
- const max = parseInt(parts[1])
+ const parts = matches[0].split('-');
+ const min = parseInt(parts[0]);
+ const max = parseInt(parts[1]);
if (min === max) {
- return min.toString()
+ return min.toString();
}
- return matches[0]
+ return matches[0];
}
- return ''
-}
+ return '';
+};
const PickupAddress = ({ label }) => (
<div className='p-4'>
@@ -1596,13 +1545,14 @@ const PickupAddress = ({ label }) => (
<div className='mt-4 text-caption-1'>
<p className='font-medium'>Indoteknik</p>
<p className='mt-2 mb-2 text-gray_r-11 leading-6'>
- Jl. Bandengan Utara Raya No.85, RT.3/RW.16, Penjaringan, Kec. Penjaringan, Kota Jkt Utara,
- Daerah Khusus Ibukota Jakarta, Indonesia Kodepos : 14440
+ Jl. Bandengan Utara Raya No.85, RT.3/RW.16, Penjaringan, Kec.
+ Penjaringan, Kota Jkt Utara, Daerah Khusus Ibukota Jakarta, Indonesia
+ Kodepos : 14440
</p>
<p className='mt-1 text-gray_r-11'>Telp : 021-2933 8828/29</p>
<p className='mt-1 text-gray_r-11'>Mobile : 0813 9000 7430</p>
</div>
</div>
-)
+);
-export default Checkout
+export default Checkout;
diff --git a/src/lib/home/api/categoryHomeApi.js b/src/lib/home/api/categoryHomeApi.js
index 9e7d1402..e5def608 100644
--- a/src/lib/home/api/categoryHomeApi.js
+++ b/src/lib/home/api/categoryHomeApi.js
@@ -1,11 +1,10 @@
-import odooApi from '@/core/api/odooApi'
-import axios from 'axios'
+import axios from 'axios';
const categoryHomeIdApi = async ({ id }) => {
- // const dataCategoryHomeIdO = await odooApi('GET', `/api/v1/product/category-homepage?id=${id}`)
- // console.log('ini adalah odoo', dataCategoryHomeIdO)
- const dataCategoryHomeId = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/product-homepage?id=` + id)
- return dataCategoryHomeId.data
-}
+ const dataCategoryHomeId = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/product-homepage?id=` + id
+ );
+ return dataCategoryHomeId.data;
+};
-export default categoryHomeIdApi
+export default categoryHomeIdApi;
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index 5f034c09..4074f243 100644
--- a/src/lib/product/components/Product/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -1,416 +1,441 @@
-import Image from '@/core/components/elements/Image/Image'
-import Link from '@/core/components/elements/Link/Link'
-import DesktopView from '@/core/components/views/DesktopView'
-import currencyFormat from '@/core/utils/currencyFormat'
-import { HeartIcon } from '@heroicons/react/24/outline'
-import { useCallback, useEffect, useRef, useState } from 'react'
-import LazyLoad from 'react-lazy-load'
-import ProductSimilar from '../ProductSimilar'
-import { toast } from 'react-hot-toast'
-import { updateItemCart } from '@/core/utils/cart'
-import { useRouter } from 'next/router'
-import { createSlug } from '@/core/utils/slug'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import ProductCard from '../ProductCard'
-import productSimilarApi from '../../api/productSimilarApi'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import odooApi from '@/core/api/odooApi'
-import PromotionType from '@/lib/promotinProgram/components/PromotionType'
-import useAuth from '@/core/hooks/useAuth'
-import ImageNext from 'next/image'
-import CountDown2 from '@/core/components/elements/CountDown/CountDown2'
-import { LazyLoadComponent } from 'react-lazy-load-image-component'
-import ColumnsSLA from './ColumnsSLA'
-import { useProductCartContext } from '@/contexts/ProductCartContext'
-import { Box, Skeleton, Tooltip } from '@chakra-ui/react'
-import { Info } from 'lucide-react'
-import Breadcrumb from './Breadcrumb'
-import { sellingProductFormat } from '@/core/utils/formatValue'
+import { useEffect, useRef, useState } from 'react';
+import ImageNext from 'next/image';
+import { LazyLoadComponent } from 'react-lazy-load-image-component';
+import { Box, Skeleton, Tooltip } from '@chakra-ui/react';
+import { HeartIcon } from '@heroicons/react/24/outline';
+import { Info } from 'lucide-react';
+import LazyLoad from 'react-lazy-load';
+import { toast } from 'react-hot-toast';
+import { useRouter } from 'next/router';
+
+import Image from '@/core/components/elements/Image/Image';
+import Link from '@/core/components/elements/Link/Link';
+import DesktopView from '@/core/components/views/DesktopView';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import CountDown2 from '@/core/components/elements/CountDown/CountDown2';
+
+import currencyFormat from '@/core/utils/currencyFormat';
+import { updateItemCart } from '@/core/utils/cart';
+import { createSlug } from '@/core/utils/slug';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { sellingProductFormat } from '@/core/utils/formatValue';
+
+import odooApi from '@/core/api/odooApi';
+import useAuth from '@/core/hooks/useAuth';
+
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+
+import PromotionType from '@/lib/promotinProgram/components/PromotionType';
+
+import ProductSimilar from '../ProductSimilar';
+import ProductCard from '../ProductCard';
+import productSimilarApi from '../../api/productSimilarApi';
+import ColumnsSLA from './ColumnsSLA';
+import Breadcrumb from './Breadcrumb';
+
+import ProductPromoSection from '~/modules/product-promo/components/Section';
const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
- const router = useRouter()
- const auth = useAuth()
- const { slug } = router.query
+ const router = useRouter();
+ const auth = useAuth();
+ const { slug } = router.query;
- const [quantityActive, setQuantity] = useState(null)
- const [lowestPrice, setLowestPrice] = useState(null)
- const [product, setProducts] = useState(products)
+ const [quantityActive, setQuantity] = useState(null);
+ const [lowestPrice, setLowestPrice] = useState(null);
+ const [product, setProducts] = useState(products);
- const [addCartAlert, setAddCartAlert] = useState(false)
- const [isLoadingSLA, setIsLoadingSLA] = useState(true)
- const [promotionType, setPromotionType] = useState(false)
- const [promotionActiveId, setPromotionActiveId] = useState(null)
- const [selectVariantPromoActive, setSelectVariantPromoActive] = useState(null)
- const [backgorundFlashSale, setBackgorundFlashSale] = useState(null)
+ const [addCartAlert, setAddCartAlert] = useState(false);
+ const [isLoadingSLA, setIsLoadingSLA] = useState(true);
+ const [promotionType, setPromotionType] = useState(false);
+ const [promotionActiveId, setPromotionActiveId] = useState(null);
+ const [selectVariantPromoActive, setSelectVariantPromoActive] =
+ useState(null);
+ const [backgorundFlashSale, setBackgorundFlashSale] = useState(null);
- const { setRefreshCart, refreshCart } = useProductCartContext()
+ const { setRefreshCart, refreshCart } = useProductCartContext();
useEffect(() => {
- setLowestPrice({ price: product?.lowestPrice })
- }, [product])
+ setLowestPrice({ price: product?.lowestPrice });
+ }, [product]);
useEffect(() => {
const getBackgound = async () => {
- const get = await odooApi('GET', '/api/v1/banner?type=flash-sale-background-banner')
- setBackgorundFlashSale(get[0].image)
- }
- getBackgound()
- }, [])
+ const get = await odooApi(
+ 'GET',
+ '/api/v1/banner?type=flash-sale-background-banner'
+ );
+ setBackgorundFlashSale(get[0].image);
+ };
+ getBackgound();
+ }, []);
- const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
+ const [informationTab, setInformationTab] = useState(
+ informationTabOptions[0].value
+ );
- const variantQuantityRefs = useRef([])
+ const variantQuantityRefs = useRef([]);
const setVariantQuantityRef = (variantId) => (element) => {
if (element) {
- let variantIndex = product.variants.findIndex((varian) => varian.id == variantId)
- product.variants[variantIndex].quantity = element?.value
+ let variantIndex = product.variants.findIndex(
+ (varian) => varian.id == variantId
+ );
+ product.variants[variantIndex].quantity = element?.value;
}
- variantQuantityRefs.current[variantId] = element
- }
+ variantQuantityRefs.current[variantId] = element;
+ };
const validQuantity = (quantity) => {
- let isValid = true
+ let isValid = true;
if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) {
- toast.error('Jumlah barang minimal 1')
- isValid = false
+ toast.error('Jumlah barang minimal 1');
+ isValid = false;
}
- return isValid
- }
+ return isValid;
+ };
const updateCart = (variantId, quantity, source) => {
let dataUpdate = {
productId: variantId,
quantity,
selected: true,
- source: source === 'buy' ? 'buy' : null
- }
+ source: source === 'buy' ? 'buy' : null,
+ };
if (product.variants.length > 1) {
- let variantIndex = product.variants.findIndex((varian) => varian.id == variantId)
- dataUpdate['programLineId'] = product.variants[variantIndex].programActive
+ let variantIndex = product.variants.findIndex(
+ (varian) => varian.id == variantId
+ );
+ dataUpdate['programLineId'] =
+ product.variants[variantIndex].programActive;
} else {
- dataUpdate['programLineId'] = promotionActiveId
+ dataUpdate['programLineId'] = promotionActiveId;
}
- updateItemCart(dataUpdate)
- }
+ updateItemCart(dataUpdate);
+ };
const redirectToLogin = (action, variantId, quantity) => {
- const nextURL = `/shop/product/${slug}?action=${action}&variantId=${variantId}&qty=${quantity}`
- router.push(`/login?next=${encodeURIComponent(nextURL)}`)
- return true
- }
+ const nextURL = `/shop/product/${slug}?action=${action}&variantId=${variantId}&qty=${quantity}`;
+ router.push(`/login?next=${encodeURIComponent(nextURL)}`);
+ return true;
+ };
const handleAddToCart = (variantId) => {
- const quantity = variantQuantityRefs.current[variantId].value
+ const quantity = variantQuantityRefs.current[variantId].value;
- if (!validQuantity(quantity)) return
+ if (!validQuantity(quantity)) return;
if (!auth) {
- return redirectToLogin('add_to_cart', variantId, quantity)
+ return redirectToLogin('add_to_cart', variantId, quantity);
}
- let source = 'cart'
- updateCart(variantId, quantity, source)
- setRefreshCart(true)
- setAddCartAlert(true)
- }
+ let source = 'cart';
+ updateCart(variantId, quantity, source);
+ setRefreshCart(true);
+ setAddCartAlert(true);
+ };
const handleQuantityChange = (variantId) => (event) => {
- const { value } = event.target
- const variantIndex = product.variants.findIndex((variant) => variant.id === variantId)
+ const { value } = event.target;
+ const variantIndex = product.variants.findIndex(
+ (variant) => variant.id === variantId
+ );
if (variantIndex !== -1) {
- product.variants[variantIndex].quantity = parseInt(value, 10) // Pastikan untuk mengubah ke tipe number jika diperlukan
+ product.variants[variantIndex].quantity = parseInt(value, 10); // Pastikan untuk mengubah ke tipe number jika diperlukan
// Lakukan sesuatu jika nilai quantity diubah
}
- }
+ };
const handleBuy = (variant) => {
- const quantity = variantQuantityRefs.current[variant].value
- if (!validQuantity(quantity)) return
+ const quantity = variantQuantityRefs.current[variant].value;
+ if (!validQuantity(quantity)) return;
if (!auth) {
- return redirectToLogin('buy', variant, quantity)
+ return redirectToLogin('buy', variant, quantity);
}
- let source = 'buy'
- updateCart(variant, quantity, source)
- router.push(`/shop/checkout?source=buy`)
- }
+ let source = 'buy';
+ updateCart(variant, quantity, source);
+ router.push(`/shop/checkout?source=buy`);
+ };
- const variantSectionRef = useRef(null)
+ const variantSectionRef = useRef(null);
const goToVariantSection = () => {
if (variantSectionRef.current) {
- const position = variantSectionRef.current.getBoundingClientRect()
+ const position = variantSectionRef.current.getBoundingClientRect();
window.scrollTo({
top: position.top - 120 + window.pageYOffset,
- behavior: 'smooth'
- })
+ behavior: 'smooth',
+ });
}
- }
+ };
const handlePromoClick = (variantId) => {
- setSelectVariantPromoActive(variantId)
- setPromotionType(true)
- }
+ setSelectVariantPromoActive(variantId);
+ setPromotionType(true);
+ };
const productSimilarQuery = [
product?.name,
`fq=-product_id_i:${product.id}`,
- `fq=-manufacture_id_i:${product.manufacture?.id || 0}`
- ].join('&')
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`,
+ ].join('&');
- const [productSimilarInBrand, setProductSimilarInBrand] = useState(null)
+ const [productSimilarInBrand, setProductSimilarInBrand] = useState(null);
useEffect(() => {
const loadProductSimilarInBrand = async () => {
- const productSimilarQuery = [product?.name, `fq=-product_id_i:${product.id}`].join('&')
- const source = 'right'
- const dataProductSimilar = await productSimilarApi({ query: productSimilarQuery, source })
- setProductSimilarInBrand(dataProductSimilar.products)
- }
- if (!productSimilarInBrand) loadProductSimilarInBrand()
- }, [product, productSimilarInBrand])
+ const productSimilarQuery = [
+ product?.name,
+ `fq=-product_id_i:${product.id}`,
+ ].join('&');
+ const source = 'right';
+ const dataProductSimilar = await productSimilarApi({
+ query: productSimilarQuery,
+ source,
+ });
+ setProductSimilarInBrand(dataProductSimilar.products);
+ };
+ if (!productSimilarInBrand) loadProductSimilarInBrand();
+ }, [product, productSimilarInBrand]);
useEffect(() => {
const fetchData = async () => {
const promises = product.variants.map(async (variant) => {
- const dataSLA = await odooApi('GET', `/api/v1/product_variant/${variant.id}/stock`)
+ const dataSLA = await odooApi(
+ 'GET',
+ `/api/v1/product_variant/${variant.id}/stock`
+ );
return {
...variant,
- sla: dataSLA
- }
- })
- const variantData = await Promise.all(promises)
- product.variants = variantData
+ sla: dataSLA,
+ };
+ });
+ const variantData = await Promise.all(promises);
+ product.variants = variantData;
- setIsLoadingSLA(false)
- }
- if (product.variantTotal == 1) fetchData()
- }, [product])
+ setIsLoadingSLA(false);
+ };
+ if (product.variantTotal == 1) fetchData();
+ }, [product]);
return (
<DesktopView>
<div className='container mx-auto pt-10'>
<Breadcrumb productId={product.id} productName={product.name} />
- <div className='flex'>
- <div className='w-full flex flex-wrap'>
- <div className='w-5/12'>
- <div className='relative mb-2'>
- {product?.flashSale?.remainingTime > 0 &&
- lowestPrice?.price.discountPercentage > 0 && (
- <div className={`absolute bottom-0 w-full`}>
- <div className='absolute bottom-0 w-full h-full'>
- <ImageNext
- src={backgorundFlashSale || '/images/GAMBAR-BG-FLASH-SALE.jpg'}
- width={1000}
- height={100}
- />
- </div>
- <div className='relative'>
- <div className='flex gap-x-2 items-center p-2'>
- <div className='bg-yellow-400 rounded-full p-1 h-9 w-20 flex items-center justify-center '>
- <span className='text-lg font-bold'>
- {Math.floor(product.lowestPrice.discountPercentage)}%
- </span>
- </div>
- <div
- className={`bg-red-600 border border-solid border-yellow-400 rounded-full h-9 p-2 flex w-[50%] items-center justify-center gap-x-4`}
- >
- <ImageNext
- src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg'
- width={17}
- height={10}
- />
- <span className='text-white text-lg font-semibold'>
- {product?.flashSale?.tag != 'false' || product?.flashSale?.tag
- ? product?.flashSale?.tag
- : 'FLASH SALE'}
- </span>
- </div>
- <div>
- <CountDown2 initialTime={product.flashSale.remainingTime} />
- </div>
+
+ <div className='w-full flex flex-wrap'>
+ <div className='w-3/12'>
+ <div className='relative mb-2'>
+ {product?.flashSale?.remainingTime > 0 &&
+ lowestPrice?.price.discountPercentage > 0 && (
+ <div className={`absolute bottom-0 w-full`}>
+ <div className='absolute bottom-0 w-full h-full'>
+ <ImageNext
+ src={
+ backgorundFlashSale ||
+ '/images/GAMBAR-BG-FLASH-SALE.jpg'
+ }
+ width={1000}
+ height={100}
+ />
+ </div>
+ <div className='relative'>
+ <div className='flex gap-x-2 items-center p-2'>
+ <div className='bg-yellow-400 rounded-full p-1 h-9 w-20 flex items-center justify-center '>
+ <span className='text-lg font-bold'>
+ {Math.floor(product.lowestPrice.discountPercentage)}
+ %
+ </span>
+ </div>
+ <div
+ className={`bg-red-600 border border-solid border-yellow-400 rounded-full h-9 p-2 flex w-[50%] items-center justify-center gap-x-4`}
+ >
+ <ImageNext
+ src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg'
+ width={17}
+ height={10}
+ />
+ <span className='text-white text-lg font-semibold'>
+ {product?.flashSale?.tag != 'false' ||
+ product?.flashSale?.tag
+ ? product?.flashSale?.tag
+ : 'FLASH SALE'}
+ </span>
+ </div>
+ <div>
+ <CountDown2
+ initialTime={product.flashSale.remainingTime}
+ />
</div>
</div>
</div>
- )}
- <Image
- src={product.image}
- alt={product.name}
- className='h-[430px] object-contain object-center w-full border border-gray_r-4'
- />
- </div>
- <div>
- <p className='text-justify text-xs leading-5'>
- <span className='font-semibold '>Keterangan : </span>Gambar atau foto berperan
- sebagai ilustrasi produk. Kadang tidak sesuai dengan kondisi terbaru dengan
- berbagai perubahan dan perbaikan. Hubungi tim sales kami untuk informasi yang
- lebih baik perihal gambar di 021-2933 8828.
- </p>
- </div>
- </div>
-
- <div className='w-7/12 px-4'>
- <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1>
- <div className='mt-10'>
- <div className='flex p-3'>
- <div className='w-4/12 text-gray_r-12/70'>Nomor SKU</div>
- <div className='w-8/12'>SKU-{product.id}</div>
- </div>
- <div className='flex p-3 bg-gray_r-4'>
- <div className='w-4/12 text-gray_r-12/70'>Part Number</div>
- <div className='w-8/12'>{product.code || '-'}</div>
- </div>
- <div className='flex p-3'>
- <div className='w-4/12 text-gray_r-12/70'>Manufacture</div>
- <div className='w-8/12'>
- {product.manufacture?.name ? (
- <Link
- href={createSlug(
- '/shop/brands/',
- product.manufacture?.name,
- product.manufacture?.id
- )}
- >
- {product.manufacture?.name}
- </Link>
- ) : (
- <div>-</div>
- )}
</div>
- </div>
- <div className='flex p-3 items-center bg-gray_r-4'>
- <div className='w-4/12 text-gray_r-12/70'>Persiapan Barang</div>
- <div className='w-8/12'>
- {product.variants.length > 1 && (
- <button
- type='button'
- onClick={goToVariantSection}
- className={`flex gap-x-1 items-center p-2 rounded-lg w-auto btn-light`}
- >
- <span className='text-red-600 text-sm'>Lihat Selengkapnya</span>
- </button>
- )}
+ )}
+ <Image
+ src={product.image}
+ alt={product.name}
+ className='h-[430px] object-contain object-center w-full border border-gray_r-4'
+ />
+ </div>
+ <div>
+ <p className='text-justify text-xs leading-5'>
+ <span className='font-semibold '>Keterangan : </span>Gambar atau
+ foto berperan sebagai ilustrasi produk. Kadang tidak sesuai
+ dengan kondisi terbaru dengan berbagai perubahan dan perbaikan.
+ Hubungi tim sales kami untuk informasi yang lebih baik perihal
+ gambar di 021-2933 8828.
+ </p>
+ </div>
+ </div>
- {product.variants.length === 1 && (
- <>
- {!product.variants[0]?.sla && <Skeleton width='20%' height='16px' />}
- {product.variants[0]?.sla && (
- <Tooltip
- placement='top'
- label={`Masa Persiapan Barang ${product.variants[0]?.sla?.slaDate}`}
- >
- <Box className='w-fit flex items-center gap-x-2'>
- {product.variants[0]?.sla?.slaDate}
- <Info size={16} />
- </Box>
- </Tooltip>
- )}
- </>
- )}
- </div>
+ <div className='w-6/12 px-6'>
+ <h1 className='text-title-md leading-10 font-medium'>
+ {product?.name}
+ </h1>
+ <div className='mt-10'>
+ <div className='flex p-3'>
+ <div className='w-4/12 text-gray_r-12/70'>Nomor SKU</div>
+ <div className='w-8/12'>SKU-{product.id}</div>
+ </div>
+ <div className='flex p-3 bg-gray_r-4'>
+ <div className='w-4/12 text-gray_r-12/70'>Part Number</div>
+ <div className='w-8/12'>{product.code || '-'}</div>
+ </div>
+ <div className='flex p-3'>
+ <div className='w-4/12 text-gray_r-12/70'>Manufacture</div>
+ <div className='w-8/12'>
+ {product.manufacture?.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture?.name,
+ product.manufacture?.id
+ )}
+ >
+ {product.manufacture?.name}
+ </Link>
+ ) : (
+ <div>-</div>
+ )}
</div>
+ </div>
+ <div className='flex p-3 items-center bg-gray_r-4'>
+ <div className='w-4/12 text-gray_r-12/70'>Persiapan Barang</div>
+ <div className='w-8/12'>
+ {product.variants.length > 1 && (
+ <button
+ type='button'
+ onClick={goToVariantSection}
+ className={`flex gap-x-1 items-center p-2 rounded-lg w-auto btn-light`}
+ >
+ <span className='text-red-600 text-sm'>
+ Lihat Selengkapnya
+ </span>
+ </button>
+ )}
- {product.variants.length === 1 && (
- <div className='flex p-3 '>
- <div className='w-4/12 text-gray_r-12/70'>Stock</div>
- <div className='w-8/12'>
- {!product.variants[0]?.sla && <Skeleton width='10%' height='16px' />}
- {product?.variants[0].sla?.qty > 0 && (
- <span>{product?.variants[0].sla?.qty}</span>
+ {product.variants.length === 1 && (
+ <>
+ {!product.variants[0]?.sla && (
+ <Skeleton width='20%' height='16px' />
)}
- {product?.variants[0].sla?.qty == 0 && (
- <a
- href={whatsappUrl('product', {
- name: product.name,
- manufacture: product?.manufacture?.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
- })}
- className='text-danger-500 font-medium'
+ {product.variants[0]?.sla && (
+ <Tooltip
+ placement='top'
+ label={`Masa Persiapan Barang ${product.variants[0]?.sla?.slaDate}`}
>
- Tanya Admin
- </a>
+ <Box className='w-fit flex items-center gap-x-2'>
+ {product.variants[0]?.sla?.slaDate}
+ <Info size={16} />
+ </Box>
+ </Tooltip>
)}
- </div>
- </div>
- )}
+ </>
+ )}
+ </div>
+ </div>
- <div className={`flex p-3 ${product.variants.length > 1 ? '' : 'bg-gray_r-4'} `}>
- <div className='w-4/12 text-gray_r-12/70'>Berat Barang</div>
+ {product.variants.length === 1 && (
+ <div className='flex p-3 '>
+ <div className='w-4/12 text-gray_r-12/70'>Stock</div>
<div className='w-8/12'>
- {product?.weight > 0 && <span>{product?.weight} KG</span>}
- {product?.weight == 0 && (
+ {!product.variants[0]?.sla && (
+ <Skeleton width='10%' height='16px' />
+ )}
+ {product?.variants[0].sla?.qty > 0 && (
+ <span>{product?.variants[0].sla?.qty}</span>
+ )}
+ {product?.variants[0].sla?.qty == 0 && (
<a
- href={whatsappUrl('productWeight', {
+ href={whatsappUrl('product', {
name: product.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
+ manufacture: product?.manufacture?.name,
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
})}
className='text-danger-500 font-medium'
>
- Tanya Berat
+ Tanya Admin
</a>
)}
</div>
</div>
- {product.variants.length <= 1 && (
- <div className='pt-3'>
- <div className='flex mt-1'>
- <PromotionType
- variantId={product.variants[0].id}
- setPromotionActiveId={setPromotionActiveId}
- promotionActiveId={promotionActiveId}
- quantity={quantityActive}
- product={product}
- ></PromotionType>
- </div>
- </div>
- )}
- </div>
- </div>
+ )}
- <div className='w-full'>
- <div className='mt-12'>
- <div className='text-h-lg font-semibold'>Informasi Produk</div>
- <div className='flex gap-x-4 mt-6 mb-4'>
- {informationTabOptions.map((option) => (
- <TabButton
- value={option.value}
- key={option.value}
- active={informationTab == option.value}
- onClick={() => setInformationTab(option.value)}
+ <div
+ className={`flex p-3 ${
+ product.variants.length > 1 ? '' : 'bg-gray_r-4'
+ } `}
+ >
+ <div className='w-4/12 text-gray_r-12/70'>Berat Barang</div>
+ <div className='w-8/12'>
+ {product?.weight > 0 && <span>{product?.weight} KG</span>}
+ {product?.weight == 0 && (
+ <a
+ href={whatsappUrl('productWeight', {
+ name: product.name,
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
+ })}
+ className='text-danger-500 font-medium'
>
- {option.label}
- </TabButton>
- ))}
+ Tanya Berat
+ </a>
+ )}
</div>
- <div className='flex'>
- <div className='w-3/4 leading-8 product__description'>
- <TabContent active={informationTab == 'description'}>
- <span
- dangerouslySetInnerHTML={{
- __html:
- product.description != ''
- ? product.description
- : 'Belum ada deskripsi produk.'
- }}
- />
- </TabContent>
-
- <TabContent active={informationTab == 'information'}>
- Belum ada informasi.
- </TabContent>
+ </div>
+ {product.variants.length <= 1 && (
+ <div className='pt-3'>
+ <div className='flex mt-1'>
+ <PromotionType
+ variantId={product.variants[0].id}
+ setPromotionActiveId={setPromotionActiveId}
+ promotionActiveId={promotionActiveId}
+ quantity={quantityActive}
+ product={product}
+ ></PromotionType>
+ <ProductPromoSection productId={product.variants[0].id} />
</div>
</div>
- </div>
+ )}
</div>
</div>
- <div className='w-[30%]'>
- {product.variants.length > 1 && product.lowestPrice.priceDiscount > 0 && (
- <div className='text-gray_r-12/80'>Harga mulai dari: </div>
- )}
+ <div className='w-3/12'>
+ {product.variants.length > 1 &&
+ product.lowestPrice.priceDiscount > 0 && (
+ <div className='text-gray_r-12/80'>Harga mulai dari: </div>
+ )}
{/* {lowestPrice?.discountPercentage > 0 && (
<div className='flex gap-x-1 items-center mt-2'>
@@ -441,7 +466,8 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
{sellingProductFormat(product?.qtySold) + ' Terjual'}
</div>
)}
- {product?.flashSale?.id && lowestPrice?.price.discountPercentage > 0 ? (
+ {product?.flashSale?.id &&
+ lowestPrice?.price.discountPercentage > 0 ? (
<>
<div className='flex gap-x-1 items-center mt-2'>
<div className='badge-solid-red text-caption-1'>
@@ -456,7 +482,10 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
</div>
<div className='text-gray_r-9 text-base font-normal mt-1'>
Termasuk PPN:{' '}
- {currencyFormat(lowestPrice?.price.priceDiscount * process.env.NEXT_PUBLIC_PPN)}
+ {currencyFormat(
+ lowestPrice?.price.priceDiscount *
+ process.env.NEXT_PUBLIC_PPN
+ )}
</div>
</>
) : (
@@ -466,7 +495,9 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
{currencyFormat(lowestPrice?.price.price)}
<div className='text-gray_r-9 text-base font-normal mt-1'>
Termasuk PPN:{' '}
- {currencyFormat(lowestPrice?.price.price * process.env.NEXT_PUBLIC_PPN)}
+ {currencyFormat(
+ lowestPrice?.price.price * process.env.NEXT_PUBLIC_PPN
+ )}
</div>
</>
) : (
@@ -476,7 +507,12 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
href={whatsappUrl('product', {
name: product.name,
manufacture: product.manufacture?.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
})}
className='text-danger-500 underline'
rel='noopener noreferrer'
@@ -524,7 +560,10 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
)}
<div className='flex mt-4'>
- <button className='flex items-center gap-x-1' onClick={toggleWishlist}>
+ <button
+ className='flex items-center gap-x-1'
+ onClick={toggleWishlist}
+ >
{wishlist.data?.productTotal > 0 ? (
<HeartIcon className='w-6 fill-danger-500 text-danger-500' />
) : (
@@ -538,7 +577,7 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
<div className='font-medium text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'>
Produk Serupa
</div>
- <div className='h-full divide-y divide-gray_r-6 max-h-96'>
+ <div className='h-full divide-y divide-gray_r-6 max-h-[550px]'>
{productSimilarInBrand &&
productSimilarInBrand?.map((product) => (
<div className='py-2' key={product.id}>
@@ -550,6 +589,42 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
</div>
</div>
+ <div className='w-full'>
+ <div className='mt-12'>
+ <div className='text-h-lg font-semibold'>Informasi Produk</div>
+ <div className='flex gap-x-4 mt-6 mb-4'>
+ {informationTabOptions.map((option) => (
+ <TabButton
+ value={option.value}
+ key={option.value}
+ active={informationTab == option.value}
+ onClick={() => setInformationTab(option.value)}
+ >
+ {option.label}
+ </TabButton>
+ ))}
+ </div>
+ <div className='flex'>
+ <div className='w-3/4 leading-8 product__description'>
+ <TabContent active={informationTab == 'description'}>
+ <span
+ dangerouslySetInnerHTML={{
+ __html:
+ product.description != ''
+ ? product.description
+ : 'Belum ada deskripsi produk.',
+ }}
+ />
+ </TabContent>
+
+ <TabContent active={informationTab == 'information'}>
+ Belum ada informasi.
+ </TabContent>
+ </div>
+ </div>
+ </div>
+ </div>
+
{product.variants.length > 1 && (
<div className='mt-12' ref={variantSectionRef}>
<div className='text-h-lg font-semibold mb-6'>Varian Produk</div>
@@ -571,7 +646,9 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
<tr key={variant.id}>
<td className='flex items-center justify-center gap-x-1'>
{variant.isFlashsale && (
- <span className='blink-color-flash-sale'>&#128498;</span>
+ <span className='blink-color-flash-sale'>
+ &#128498;
+ </span>
)}
{variant.code}
</td>
@@ -580,11 +657,13 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
<ColumnsSLA variant={variant} product={product} />
</LazyLoadComponent>
<td>
- {variant.isFlashsale && variant?.price?.discountPercentage > 0 ? (
+ {variant.isFlashsale &&
+ variant?.price?.discountPercentage > 0 ? (
<>
<div className='flex items-center gap-x-1 justify-center'>
<div className='badge-solid-red text-caption-1'>
- {Math.floor(variant?.price?.discountPercentage)}%
+ {Math.floor(variant?.price?.discountPercentage)}
+ %
</div>
<div className='line-through text-caption-1 text-gray_r-11'>
{currencyFormat(variant?.price?.price)}
@@ -596,7 +675,8 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
<div className=' text-caption-1 text-gray_r-11 mb-1'>
Inc. PPN:{' '}
{currencyFormat(
- variant.price.priceDiscount * process.env.NEXT_PUBLIC_PPN
+ variant.price.priceDiscount *
+ process.env.NEXT_PUBLIC_PPN
)}
</div>
</>
@@ -610,7 +690,8 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
<div className=' text-caption-1 text-gray_r-11 mb-1'>
Inc. PPN:{' '}
{currencyFormat(
- variant?.price?.price * process.env.NEXT_PUBLIC_PPN
+ variant?.price?.price *
+ process.env.NEXT_PUBLIC_PPN
)}
</div>
</>
@@ -619,7 +700,12 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
href={whatsappUrl('product', {
name: variant.name,
manufacture: product.manufacture?.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
})}
className='text-red_r-11'
rel='noopener noreferrer'
@@ -705,11 +791,14 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
)}
<div className='my-12'>
- <div className='text-h-lg font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
+ <div className='text-h-lg font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
<LazyLoad>
<ProductSimilar query={productSimilarQuery} />
</LazyLoad>
</div>
+
<BottomPopup
className=' !h-[75%]'
title='Pakai Promo'
@@ -728,6 +817,7 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
></PromotionType>
</div>
</BottomPopup>
+
<BottomPopup
className='!container'
title='Berhasil Ditambahkan'
@@ -742,16 +832,23 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
className='h-32 object-contain object-center w-full border border-gray_r-4'
/>
</div>
- <div className='ml-3 flex flex-1 items-center font-normal'>{product.name}</div>
+ <div className='ml-3 flex flex-1 items-center font-normal'>
+ {product.name}
+ </div>
<div className='ml-3 flex items-center font-normal'>
- <Link href='/shop/cart' className='flex-1 py-2 text-gray_r-12 btn-yellow'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
Lihat Keranjang
</Link>
</div>
</div>
<div className='mt-8 mb-4'>
- <div className='text-h-sm font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
<LazyLoad>
<ProductSimilar query={productSimilarQuery} />
</LazyLoad>
@@ -759,29 +856,33 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
</BottomPopup>
</div>
</DesktopView>
- )
-}
+ );
+};
const informationTabOptions = [
{ value: 'description', label: 'Deskripsi' },
- { value: 'information', label: 'Info Penting' }
-]
+ { value: 'information', label: 'Info Penting' },
+];
const TabButton = ({ children, active, ...props }) => {
const activeClassName = active
? 'text-danger-500 underline underline-offset-4'
- : 'text-gray_r-12/80'
+ : 'text-gray_r-12/80';
return (
- <button {...props} type='button' className={`font-medium ${activeClassName}`}>
+ <button
+ {...props}
+ type='button'
+ className={`font-medium ${activeClassName}`}
+ >
{children}
</button>
- )
-}
+ );
+};
const TabContent = ({ children, active, className = '', ...props }) => (
<div {...props} className={`${active ? 'block' : 'hidden'} ${className}`}>
{children}
</div>
-)
+);
-export default ProductDesktop
+export default ProductDesktop;
diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx
index e23e2fb9..e9e64469 100644
--- a/src/lib/product/components/Product/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -1,60 +1,66 @@
-import Divider from '@/core/components/elements/Divider/Divider'
-import Image from '@/core/components/elements/Image/Image'
-import Link from '@/core/components/elements/Link/Link'
-import currencyFormat from '@/core/utils/currencyFormat'
-import { useEffect, useState } from 'react'
-import Select from 'react-select'
-import ProductSimilar from '../ProductSimilar'
-import LazyLoad from 'react-lazy-load'
-import { updateItemCart } from '@/core/utils/cart'
-import { HeartIcon } from '@heroicons/react/24/outline'
-import { useRouter } from 'next/router'
-import MobileView from '@/core/components/views/MobileView'
-import { toast } from 'react-hot-toast'
-import { createSlug } from '@/core/utils/slug'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import PromotionType from '@/lib/promotinProgram/components/PromotionType'
-import { gtagAddToCart } from '@/core/utils/googleTag'
-import odooApi from '@/core/api/odooApi'
-import ImageNext from 'next/image'
-import CountDown2 from '@/core/components/elements/CountDown/CountDown2'
-import Breadcrumb from './Breadcrumb'
-import useAuth from '@/core/hooks/useAuth'
-import { sellingProductFormat } from '@/core/utils/formatValue'
+import Divider from '@/core/components/elements/Divider/Divider';
+import Image from '@/core/components/elements/Image/Image';
+import Link from '@/core/components/elements/Link/Link';
+import currencyFormat from '@/core/utils/currencyFormat';
+import { useEffect, useState } from 'react';
+import Select from 'react-select';
+import ProductSimilar from '../ProductSimilar';
+import LazyLoad from 'react-lazy-load';
+import { updateItemCart } from '@/core/utils/cart';
+import { HeartIcon } from '@heroicons/react/24/outline';
+import { useRouter } from 'next/router';
+import MobileView from '@/core/components/views/MobileView';
+import { toast } from 'react-hot-toast';
+import { createSlug } from '@/core/utils/slug';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import PromotionType from '@/lib/promotinProgram/components/PromotionType';
+import { gtagAddToCart } from '@/core/utils/googleTag';
+import odooApi from '@/core/api/odooApi';
+import ImageNext from 'next/image';
+import CountDown2 from '@/core/components/elements/CountDown/CountDown2';
+import Breadcrumb from './Breadcrumb';
+import useAuth from '@/core/hooks/useAuth';
+import { sellingProductFormat } from '@/core/utils/formatValue';
+import ProductPromoSection from '~/modules/product-promo/components/Section';
const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
- const router = useRouter()
- const auth = useAuth()
- const { slug } = router.query
-
- const [quantity, setQuantity] = useState('1')
- const [selectedVariant, setSelectedVariant] = useState(null)
- const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
- const [addCartAlert, setAddCartAlert] = useState(false)
-
- const [isLoadingSLA, setIsLoadingSLA] = useState(true)
- const [promotionType, setPromotionType] = useState(false)
- const [promotionActiveId, setPromotionActiveId] = useState(null)
- const [backgorundFlashSale, setBackgorundFlashSale] = useState(null)
+ const router = useRouter();
+ const auth = useAuth();
+ const { slug } = router.query;
+
+ const [quantity, setQuantity] = useState('1');
+ const [selectedVariant, setSelectedVariant] = useState(null);
+ const [informationTab, setInformationTab] = useState(
+ informationTabOptions[0].value
+ );
+ const [addCartAlert, setAddCartAlert] = useState(false);
+
+ const [isLoadingSLA, setIsLoadingSLA] = useState(true);
+ const [promotionType, setPromotionType] = useState(false);
+ const [promotionActiveId, setPromotionActiveId] = useState(null);
+ const [backgorundFlashSale, setBackgorundFlashSale] = useState(null);
const getLowestPrice = () => {
- const prices = product.variants.map((variant) => variant.price)
+ const prices = product.variants.map((variant) => variant.price);
const lowest = prices.reduce((lowest, price) => {
- return price.priceDiscount < lowest.priceDiscount ? price : lowest
- }, prices[0])
- return lowest
- }
+ return price.priceDiscount < lowest.priceDiscount ? price : lowest;
+ }, prices[0]);
+ return lowest;
+ };
useEffect(() => {
const getBackgound = async () => {
- const get = await odooApi('GET', '/api/v1/banner?type=flash-sale-background-banner')
+ const get = await odooApi(
+ 'GET',
+ '/api/v1/banner?type=flash-sale-background-banner'
+ );
if (get.length > 0) {
- setBackgorundFlashSale(get[0].image)
+ setBackgorundFlashSale(get[0].image);
}
- }
- getBackgound()
- }, [])
+ };
+ getBackgound();
+ }, []);
const [activeVariant, setActiveVariant] = useState({
id: null,
@@ -64,40 +70,44 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
stock: product.stockTotal,
weight: product.weight,
hasProgram: false,
- qtySold: product.qtySold
- })
+ qtySold: product.qtySold,
+ });
const variantOptions = product.variants?.map((variant) => {
- let label = []
+ let label = [];
if (variant.isFlashsale) {
- label.push("<span class='blink-color-flash-sale'>&#128498;</span>")
+ label.push("<span class='blink-color-flash-sale'>&#128498;</span>");
}
if (variant.code) {
- label.push(`[${variant.code}]`)
+ label.push(`[${variant.code}]`);
}
if (variant.attributes.length > 0) {
- label.push(variant.attributes.join(', '))
+ label.push(variant.attributes.join(', '));
} else {
- label.push(product.name)
+ label.push(product.name);
}
return {
value: variant.id,
- label: label.join(' ')
- }
- })
+ label: label.join(' '),
+ };
+ });
useEffect(() => {
if (!selectedVariant && variantOptions.length == 1) {
- setSelectedVariant(variantOptions[0])
+ setSelectedVariant(variantOptions[0]);
}
- }, [selectedVariant, variantOptions])
+ }, [selectedVariant, variantOptions]);
useEffect(() => {
if (selectedVariant) {
- const variant = product.variants.find((variant) => variant.id == selectedVariant.value)
+ const variant = product.variants.find(
+ (variant) => variant.id == selectedVariant.value
+ );
const variantAttributes =
- variant.attributes.length > 0 ? ' - ' + variant.attributes.join(', ') : ''
+ variant.attributes.length > 0
+ ? ' - ' + variant.attributes.join(', ')
+ : '';
const newActiveVariant = {
id: variant.id,
@@ -108,60 +118,63 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
weight: variant.weight,
hasProgram: variant.hasProgram,
isFlashsale: variant.isFlashsale,
- qtySold: variant.qtySold
- }
+ qtySold: variant.qtySold,
+ };
- setActiveVariant(newActiveVariant)
+ setActiveVariant(newActiveVariant);
const fetchSLA = async () => {
- const dataSLA = await odooApi('GET', `/api/v1/product_variant/${variant.id}/stock`)
- setActiveVariant({ ...newActiveVariant, sla: dataSLA })
- }
- fetchSLA()
+ const dataSLA = await odooApi(
+ 'GET',
+ `/api/v1/product_variant/${variant.id}/stock`
+ );
+ setActiveVariant({ ...newActiveVariant, sla: dataSLA });
+ };
+ fetchSLA();
}
- }, [selectedVariant, product])
+ }, [selectedVariant, product]);
const validAction = () => {
- let isValid = true
+ let isValid = true;
if (!selectedVariant) {
- toast.error('Pilih varian terlebih dahulu')
- isValid = false
+ toast.error('Pilih varian terlebih dahulu');
+ isValid = false;
}
if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) {
- toast.error('Jumlah barang minimal 1')
- isValid = false
+ toast.error('Jumlah barang minimal 1');
+ isValid = false;
}
- return isValid
- }
+ return isValid;
+ };
const redirectToLogin = (action) => {
- const nextURL = `/shop/product/${slug}?action=${action}&variantId=${activeVariant.id}&qty=${quantity}`
- router.push(`/login?next=${encodeURIComponent(nextURL)}`)
- return true
- }
+ const nextURL = `/shop/product/${slug}?action=${action}&variantId=${activeVariant.id}&qty=${quantity}`;
+ router.push(`/login?next=${encodeURIComponent(nextURL)}`);
+ return true;
+ };
const handleClickCart = () => {
- if (!validAction()) return
- gtagAddToCart(activeVariant, quantity)
+ if (!validAction()) return;
+ gtagAddToCart(activeVariant, quantity);
if (!auth) {
- return redirectToLogin('add_to_cart')
+ return redirectToLogin('add_to_cart');
}
updateItemCart({
productId: activeVariant.id,
quantity,
programLineId: promotionActiveId,
- selected: true
- })
- setAddCartAlert(true)
- }
+ selected: true,
+ });
+ setAddCartAlert(true);
+ };
const handleClickBuy = () => {
- if (!validAction()) return
+ if (!validAction()) return;
if (!auth) {
- return redirectToLogin('buy')
+ return redirectToLogin('buy');
}
updateItemCart({
@@ -169,58 +182,62 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
quantity,
programLineId: promotionActiveId,
selected: true,
- source: 'buy'
- })
- router.push(`/shop/checkout?source=buy`)
- }
+ source: 'buy',
+ });
+ router.push(`/shop/checkout?source=buy`);
+ };
const productSimilarQuery = [
product?.name,
`fq=-product_id_i:${product.id}`,
- `fq=-manufacture_id_i:${product.manufacture?.id || 0}`
- ].join('&')
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`,
+ ].join('&');
return (
<MobileView>
<Breadcrumb productId={product.id} productName={product.name} />
<div className='relative'>
- {product?.flashSale?.remainingTime > 0 && activeVariant?.price.discountPercentage > 0 && (
- <div className={`absolute bottom-0 w-full`}>
- <div className='absolute bottom-0 w-full'>
- <ImageNext
- src={backgorundFlashSale || '/images/GAMBAR-BG-FLASH-SALE.jpg'}
- width={1000}
- height={100}
- />
- </div>
- <div className='relative'>
- <div className='flex gap-x-2 items-center p-2'>
- <div className='bg-yellow-400 rounded-full p-1 h-9 w-20 flex items-center justify-center '>
- <span className='text-lg font-bold'>
- {Math.floor(product.lowestPrice.discountPercentage)}%
- </span>
- </div>
- <div
- className={`bg-red-600 border border-solid border-yellow-400 rounded-full h-9 p-2 flex w-[50%] items-center justify-center gap-x-4`}
- >
- <ImageNext
- src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg'
- width={17}
- height={10}
- />
- <span className='text-white text-lg font-semibold'>
- {product?.flashSale?.tag != 'false' || product?.flashSale?.tag
- ? product?.flashSale?.tag
- : 'FLASH SALE'}
- </span>
- </div>
- <div>
- <CountDown2 initialTime={product.flashSale.remainingTime} />
+ {product?.flashSale?.remainingTime > 0 &&
+ activeVariant?.price.discountPercentage > 0 && (
+ <div className={`absolute bottom-0 w-full`}>
+ <div className='absolute bottom-0 w-full'>
+ <ImageNext
+ src={
+ backgorundFlashSale || '/images/GAMBAR-BG-FLASH-SALE.jpg'
+ }
+ width={1000}
+ height={100}
+ />
+ </div>
+ <div className='relative'>
+ <div className='flex gap-x-2 items-center p-2'>
+ <div className='bg-yellow-400 rounded-full p-1 h-9 w-20 flex items-center justify-center '>
+ <span className='text-lg font-bold'>
+ {Math.floor(product.lowestPrice.discountPercentage)}%
+ </span>
+ </div>
+ <div
+ className={`bg-red-600 border border-solid border-yellow-400 rounded-full h-9 p-2 flex w-[50%] items-center justify-center gap-x-4`}
+ >
+ <ImageNext
+ src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg'
+ width={17}
+ height={10}
+ />
+ <span className='text-white text-lg font-semibold'>
+ {product?.flashSale?.tag != 'false' ||
+ product?.flashSale?.tag
+ ? product?.flashSale?.tag
+ : 'FLASH SALE'}
+ </span>
+ </div>
+ <div>
+ <CountDown2 initialTime={product.flashSale.remainingTime} />
+ </div>
</div>
</div>
</div>
- </div>
- )}
+ )}
<Image
src={product.image}
alt={product.name}
@@ -232,7 +249,11 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
<div className='flex items-end mb-2'>
{product.manufacture?.name ? (
<Link
- href={createSlug('/shop/brands/', product.manufacture?.name, product.manufacture?.id)}
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture?.name,
+ product.manufacture?.id
+ )}
>
{product.manufacture?.name}
</Link>
@@ -249,18 +270,25 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
</div>
<h1 className='leading-6 font-medium mb-3'>{activeVariant?.name}</h1>
{product?.qtySold > 0 && (
- <div className='text-gray_r-9'>{sellingProductFormat(activeVariant?.qtySold) + ' Terjual'}</div>
+ <div className='text-gray_r-9'>
+ {sellingProductFormat(activeVariant?.qtySold) + ' Terjual'}
+ </div>
)}
{product.variants.length > 1 &&
activeVariant.price.priceDiscount > 0 &&
!selectedVariant && (
- <div className='text-gray_r-12/80 text-caption-2 mt-2 mb-1'>Harga mulai dari: </div>
+ <div className='text-gray_r-12/80 text-caption-2 mt-2 mb-1'>
+ Harga mulai dari:{' '}
+ </div>
)}
- {activeVariant.isFlashsale && activeVariant?.price?.discountPercentage > 0 ? (
+ {activeVariant.isFlashsale &&
+ activeVariant?.price?.discountPercentage > 0 ? (
<>
<div className='flex gap-x-1 items-center'>
- <div className='badge-solid-red'>{Math.floor(activeVariant?.price?.discountPercentage)}%</div>
+ <div className='badge-solid-red'>
+ {Math.floor(activeVariant?.price?.discountPercentage)}%
+ </div>
<div className='text-gray_r-11 line-through text-caption-1'>
{currencyFormat(activeVariant?.price?.price)}
</div>
@@ -270,7 +298,9 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
</div>
<div className='text-gray_r-9 text-base font-normal mt-1'>
Termasuk PPN:{' '}
- {currencyFormat(activeVariant?.price.priceDiscount * process.env.NEXT_PUBLIC_PPN)}
+ {currencyFormat(
+ activeVariant?.price.priceDiscount * process.env.NEXT_PUBLIC_PPN
+ )}
</div>
</>
) : (
@@ -280,7 +310,9 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
{currencyFormat(activeVariant?.price?.price)}
<div className='text-gray_r-9 text-base font-normal mt-1'>
Termasuk PPN:{' '}
- {currencyFormat(activeVariant?.price.price * process.env.NEXT_PUBLIC_PPN)}
+ {currencyFormat(
+ activeVariant?.price.price * process.env.NEXT_PUBLIC_PPN
+ )}
</div>
</>
) : (
@@ -289,7 +321,12 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
<a
href={whatsappUrl('product', {
name: product.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
})}
className='text-danger-500 underline'
>
@@ -307,13 +344,17 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
<div>
<label className='flex justify-between'>
Pilih Varian:
- <span className='text-gray_r-11'>{product?.variantTotal} Varian</span>
+ <span className='text-gray_r-11'>
+ {product?.variantTotal} Varian
+ </span>
</label>
<Select
name='variant'
classNamePrefix='form-select'
options={variantOptions}
- formatOptionLabel={({ label }) => <div dangerouslySetInnerHTML={{ __html: label }} />}
+ formatOptionLabel={({ label }) => (
+ <div dangerouslySetInnerHTML={{ __html: label }} />
+ )}
className='mt-2'
value={selectedVariant}
onChange={(option) => setSelectedVariant(option)}
@@ -342,15 +383,27 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
onChange={(e) => setQuantity(e.target.value)}
/>
</div>
- <button type='button' className='btn-yellow flex-1' onClick={handleClickCart}>
+ <button
+ type='button'
+ className='btn-yellow flex-1'
+ onClick={handleClickCart}
+ >
Keranjang
</button>
- <button type='button' className='btn-solid-red flex-1' onClick={handleClickBuy}>
+ <button
+ type='button'
+ className='btn-solid-red flex-1'
+ onClick={handleClickBuy}
+ >
Beli
</button>
</div>
+
+ <div className='h-4' />
</div>
+ <ProductPromoSection productId={activeVariant.id} />
+
<Divider />
<div className='p-4'>
@@ -380,12 +433,16 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
type='button'
title={`Masa Persiapan Barang ${activeVariant?.sla?.slaDate}`}
className={`flex gap-x-1 items-center p-2 h-8 rounded-lg w-full ${
- activeVariant?.sla?.slaDate === 'indent' ? 'bg-indigo-900' : 'btn-light'
+ activeVariant?.sla?.slaDate === 'indent'
+ ? 'bg-indigo-900'
+ : 'btn-light'
}`}
>
<div
className={`flex-1 text-sm ${
- activeVariant?.sla?.slaDate === 'indent' ? 'text-white' : ''
+ activeVariant?.sla?.slaDate === 'indent'
+ ? 'text-white'
+ : ''
}`}
>
{activeVariant?.sla?.slaDate}
@@ -397,7 +454,9 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
stroke='currentColor'
stroke-width='1.5'
className={`w-7 h-7 text-sm ${
- activeVariant?.sla?.slaDate === 'indent' ? 'text-white' : ''
+ activeVariant?.sla?.slaDate === 'indent'
+ ? 'text-white'
+ : ''
}`}
>
<path
@@ -436,7 +495,12 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
<a
href={whatsappUrl('product', {
name: product.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
})}
className='text-danger-500 font-medium'
>
@@ -445,12 +509,19 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
)}
</SpecificationContent>
<SpecificationContent label='Berat Barang'>
- {activeVariant?.weight > 0 && <span>{activeVariant?.weight} KG</span>}
+ {activeVariant?.weight > 0 && (
+ <span>{activeVariant?.weight} KG</span>
+ )}
{activeVariant?.weight == 0 && (
<a
href={whatsappUrl('productWeight', {
name: product.name,
- url: createSlug('/shop/product/', product.name, product.id, true)
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ ),
})}
className='text-danger-500 font-medium'
>
@@ -464,7 +535,10 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
active={informationTab == 'description'}
className='leading-6 text-gray_r-11'
dangerouslySetInnerHTML={{
- __html: product.description != '' ? product.description : 'Belum ada deskripsi produk.'
+ __html:
+ product.description != ''
+ ? product.description
+ : 'Belum ada deskripsi produk.',
}}
/>
</div>
@@ -491,50 +565,63 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
className='h-20 object-contain object-center w-full border border-gray_r-4'
/>
</div>
- <div className='ml-3 flex flex-1 items-center text-sm font-normal'>{product.name}</div>
+ <div className='ml-3 flex flex-1 items-center text-sm font-normal'>
+ {product.name}
+ </div>
<div className='ml-3 flex items-center text-sm font-normal'>
- <Link href='/shop/cart' className='flex-1 py-2 text-gray_r-12 btn-yellow'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
Lihat Keranjang
</Link>
</div>
</div>
<div className='mt-8 mb-4'>
- <div className='text-h-sm font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
<LazyLoad>
<ProductSimilar query={productSimilarQuery} />
</LazyLoad>
</div>
</BottomPopup>
</MobileView>
- )
-}
+ );
+};
const informationTabOptions = [
{ value: 'specification', label: 'Spesifikasi' },
{ value: 'description', label: 'Deskripsi' },
- { value: 'information', label: 'Info Penting' }
-]
+ { value: 'information', label: 'Info Penting' },
+];
const TabButton = ({ children, active, ...props }) => {
- const activeClassName = active ? 'text-danger-500 underline underline-offset-4' : 'text-gray_r-11'
+ const activeClassName = active
+ ? 'text-danger-500 underline underline-offset-4'
+ : 'text-gray_r-11';
return (
- <button {...props} type='button' className={`font-medium pb-1 ${activeClassName}`}>
+ <button
+ {...props}
+ type='button'
+ className={`font-medium pb-1 ${activeClassName}`}
+ >
{children}
</button>
- )
-}
+ );
+};
const TabContent = ({ children, active, className, ...props }) => (
<div {...props} className={`${active ? 'block' : 'hidden'} ${className}`}>
{children}
</div>
-)
+);
const SpecificationContent = ({ children, label }) => (
<div className='flex justify-between p-3 items-center'>
<span className='text-gray_r-11'>{label}</span>
{children}
</div>
-)
+);
-export default ProductMobile
+export default ProductMobile;
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 29bb987e..e2b961f1 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -1,124 +1,141 @@
-import { useEffect, useMemo, useState } from 'react'
-import useProductSearch from '../hooks/useProductSearch'
-import ProductCard from './ProductCard'
-import Pagination from '@/core/components/elements/Pagination/Pagination'
-import { toQuery } from 'lodash-contrib'
-import _ from 'lodash'
-import ProductSearchSkeleton from './Skeleton/ProductSearchSkeleton'
-import ProductFilter from './ProductFilter'
-import useActive from '@/core/hooks/useActive'
-import MobileView from '@/core/components/views/MobileView'
-import DesktopView from '@/core/components/views/DesktopView'
-import NextImage from 'next/image'
-import ProductFilterDesktop from './ProductFilterDesktop'
-import { useRouter } from 'next/router'
-import searchSpellApi from '@/core/api/searchSpellApi'
-import Link from '@/core/components/elements/Link/Link'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react'
-import odooApi from '@/core/api/odooApi'
-import { formatCurrency } from '@/core/utils/formatValue'
-import axios from 'axios'
-import Skeleton from 'react-loading-skeleton'
-import { createSlug } from '@/core/utils/slug'
-
-const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null }) => {
- const router = useRouter()
- const { page = 1 } = query
- const [q, setQ] = useState(query?.q || '*')
- const [search, setSearch] = useState(query?.q || '*')
- const [limit, setLimit] = useState(query?.limit || 30)
- const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular')
- if (defaultBrand) query.brand = defaultBrand.toLowerCase()
+import { useEffect, useMemo, useState } from 'react';
+import useProductSearch from '../hooks/useProductSearch';
+import ProductCard from './ProductCard';
+import Pagination from '@/core/components/elements/Pagination/Pagination';
+import { toQuery } from 'lodash-contrib';
+import _ from 'lodash';
+import ProductSearchSkeleton from './Skeleton/ProductSearchSkeleton';
+import ProductFilter from './ProductFilter';
+import useActive from '@/core/hooks/useActive';
+import MobileView from '@/core/components/views/MobileView';
+import DesktopView from '@/core/components/views/DesktopView';
+import NextImage from 'next/image';
+import ProductFilterDesktop from './ProductFilterDesktop';
+import { useRouter } from 'next/router';
+import searchSpellApi from '@/core/api/searchSpellApi';
+import Link from '@/core/components/elements/Link/Link';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react';
+import odooApi from '@/core/api/odooApi';
+import { formatCurrency } from '@/core/utils/formatValue';
+import axios from 'axios';
+import { createSlug } from '@/core/utils/slug';
+
+const ProductSearch = ({
+ query,
+ prefixUrl,
+ defaultBrand = null,
+ brand = null,
+}) => {
+ const router = useRouter();
+ const { page = 1 } = query;
+ const [q, setQ] = useState(query?.q || '*');
+ const [search, setSearch] = useState(query?.q || '*');
+ const [limit, setLimit] = useState(query?.limit || 30);
+ const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular');
+ if (defaultBrand) query.brand = defaultBrand.toLowerCase();
const { productSearch } = useProductSearch({
query: { ...query, q, limit, orderBy },
- operation: 'AND'
- })
- const [products, setProducts] = useState(null)
- const [spellings, setSpellings] = useState(null)
- const [bannerPromotionHeader, setBannerPromotionHeader] = useState(null)
- const [bannerPromotionFooter, setBannerPromotionFooter] = useState(null)
- const [isBrand, setIsBrand] = useState(null)
- const popup = useActive()
- const numRows = [30, 50, 80, 100]
+ operation: 'AND',
+ });
+ const [products, setProducts] = useState(null);
+ const [spellings, setSpellings] = useState(null);
+ const [bannerPromotionHeader, setBannerPromotionHeader] = useState(null);
+ const [bannerPromotionFooter, setBannerPromotionFooter] = useState(null);
+ const [isBrand, setIsBrand] = useState(null);
+ const popup = useActive();
+ const numRows = [30, 50, 80, 100];
const [brandValues, setBrand] = useState(
- !router.pathname.includes('brands') ? (query.brand ? query.brand.split(',') : []) : []
- )
- const [categoryValues, setCategory] = useState(query?.category?.split(',') || [])
- const [priceFrom, setPriceFrom] = useState(query?.priceFrom || null)
- const [priceTo, setPriceTo] = useState(query?.priceTo || null)
-
- const pageCount = Math.ceil(productSearch.data?.response.numFound / limit)
- const productStart = productSearch.data?.responseHeader.params.start
- const productRows = limit
- const productFound = productSearch.data?.response.numFound
+ !router.pathname.includes('brands')
+ ? query.brand
+ ? query.brand.split(',')
+ : []
+ : []
+ );
+ const [categoryValues, setCategory] = useState(
+ query?.category?.split(',') || []
+ );
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom || null);
+ const [priceTo, setPriceTo] = useState(query?.priceTo || null);
+
+ const pageCount = Math.ceil(productSearch.data?.response.numFound / limit);
+ const productStart = productSearch.data?.responseHeader.params.start;
+ const productRows = limit;
+ const productFound = productSearch.data?.response.numFound;
useEffect(() => {
if (productFound == 0 && query.q && !spellings) {
searchSpellApi({ query: query.q }).then((response) => {
const oddIndexSuggestions = response.data.spellcheck.suggestions.filter(
(_, index) => index % 2 === 1
- )
+ );
const oddIndexCollations = response.data.spellcheck.collations.filter(
(_, index) => index % 2 === 1
- )
+ );
const dataSpellings = oddIndexSuggestions.reduce((acc, curr) => {
oddIndexCollations.forEach((collation) => {
- acc.push(collation.collationQuery)
- })
+ acc.push(collation.collationQuery);
+ });
curr.suggestion.forEach((s) => {
- if (!acc.includes(s.word)) acc.push(s.word)
- })
- return acc
- }, [])
+ if (!acc.includes(s.word)) acc.push(s.word);
+ });
+ return acc;
+ }, []);
if (dataSpellings.length > 0) {
- setQ(dataSpellings[0])
+ setQ(dataSpellings[0]);
}
- setSpellings(dataSpellings)
- })
+ setSpellings(dataSpellings);
+ });
}
- }, [productFound, query, spellings])
+ }, [productFound, query, spellings]);
useEffect(() => {
const checkIfBrand = async () => {
const brand = await axios(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/brands?params=search&q=${search}`
- )
- console.log('ini brand', brand)
+ );
+
if (brand.data.length > 0) {
- setIsBrand(brand?.data[0])
+ setIsBrand(brand?.data[0]);
} else {
- setIsBrand(null)
+ setIsBrand(null);
}
- }
+ };
if (router.pathname.includes('search')) {
- checkIfBrand()
+ checkIfBrand();
}
- }, [q])
+ }, [q]);
- const brands = []
+ const brands = [];
for (
let i = 0;
i < productSearch.data?.facetCounts?.facetFields?.manufactureNameS.length;
i += 2
) {
- const brand = productSearch.data?.facetCounts?.facetFields?.manufactureNameS[i]
- const qty = productSearch.data?.facetCounts?.facetFields?.manufactureNameS[i + 1]
+ const brand =
+ productSearch.data?.facetCounts?.facetFields?.manufactureNameS[i];
+ const qty =
+ productSearch.data?.facetCounts?.facetFields?.manufactureNameS[i + 1];
if (qty > 0) {
- brands.push({ brand, qty })
+ brands.push({ brand, qty });
}
}
- const categories = []
- for (let i = 0; i < productSearch.data?.facetCounts?.facetFields?.categoryName.length; i += 2) {
- const name = productSearch.data?.facetCounts?.facetFields?.categoryName[i]
- const qty = productSearch.data?.facetCounts?.facetFields?.categoryName[i + 1]
+ const categories = [];
+ for (
+ let i = 0;
+ i < productSearch.data?.facetCounts?.facetFields?.categoryName.length;
+ i += 2
+ ) {
+ const name = productSearch.data?.facetCounts?.facetFields?.categoryName[i];
+ const qty =
+ productSearch.data?.facetCounts?.facetFields?.categoryName[i + 1];
if (qty > 0) {
- categories.push({ name, qty })
+ categories.push({ name, qty });
}
}
@@ -126,46 +143,54 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
{ value: 'price-asc', label: 'Harga Terendah' },
{ value: 'price-desc', label: 'Harga Tertinggi' },
{ value: 'popular', label: 'Populer' },
- { value: 'stock', label: 'Ready Stock' }
- ]
+ { value: 'stock', label: 'Ready Stock' },
+ ];
const handleOrderBy = (e) => {
let params = {
...router.query,
- orderBy: e.target.value
- }
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
- router.push(`${prefixUrl}?${params}`)
- }
+ orderBy: e.target.value,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+ router.push(`${prefixUrl}?${params}`);
+ };
const handleLimit = (e) => {
let params = {
...router.query,
- limit: e.target.value
- }
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
- router.push(`${prefixUrl}?${params}`)
- }
+ limit: e.target.value,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+ router.push(`${prefixUrl}?${params}`);
+ };
const getBanner = async () => {
if (router.pathname.includes('search')) {
- const getBannerHeader = await odooApi('GET', '/api/v1/banner?type=promotion-header')
- const getBannerFooter = await odooApi('GET', '/api/v1/banner?type=promotion-footer')
- var randomIndex = Math.floor(Math.random() * getBannerHeader.length)
- var randomIndexFooter = Math.floor(Math.random() * getBannerFooter.length)
- setBannerPromotionHeader(getBannerHeader[randomIndex])
- setBannerPromotionFooter(getBannerFooter[randomIndexFooter])
+ const getBannerHeader = await odooApi(
+ 'GET',
+ '/api/v1/banner?type=promotion-header'
+ );
+ const getBannerFooter = await odooApi(
+ 'GET',
+ '/api/v1/banner?type=promotion-footer'
+ );
+ var randomIndex = Math.floor(Math.random() * getBannerHeader.length);
+ var randomIndexFooter = Math.floor(
+ Math.random() * getBannerFooter.length
+ );
+ setBannerPromotionHeader(getBannerHeader[randomIndex]);
+ setBannerPromotionFooter(getBannerFooter[randomIndexFooter]);
}
- }
+ };
useEffect(() => {
- getBanner()
- }, [])
+ getBanner();
+ }, []);
useEffect(() => {
- setProducts(productSearch.data?.response?.products)
- }, [productSearch])
+ setProducts(productSearch.data?.response?.products);
+ }, [productSearch]);
const SpellingComponent = useMemo(() => {
return (
@@ -182,8 +207,8 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
</Link>
))}
</>
- )
- }, [spellings])
+ );
+ }, [spellings]);
const handleDeleteFilter = async (source, value) => {
let params = {
@@ -192,41 +217,41 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
brand: brandValues.join(','),
category: categoryValues.join(','),
priceFrom,
- priceTo
- }
+ priceTo,
+ };
- let brands = brandValues
- let catagories = categoryValues
+ let brands = brandValues;
+ let catagories = categoryValues;
switch (source) {
case 'brands':
- brands = brandValues.filter((item) => item !== value)
- params.brand = brands.join(',')
- await setBrand(brands)
- break
+ brands = brandValues.filter((item) => item !== value);
+ params.brand = brands.join(',');
+ await setBrand(brands);
+ break;
case 'category':
- catagories = categoryValues.filter((item) => item !== value)
- params.category = catagories.join(',')
- await setCategory(catagories)
- break
+ catagories = categoryValues.filter((item) => item !== value);
+ params.category = catagories.join(',');
+ await setCategory(catagories);
+ break;
case 'price':
- params.priceFrom = null
- params.priceTo = null
- break
+ params.priceFrom = null;
+ params.priceTo = null;
+ break;
case 'delete':
params = {
q: router.query.q,
- orderBy: orderBy
- }
- break
+ orderBy: orderBy,
+ };
+ break;
}
- handleSubmitFilter(params)
- }
+ handleSubmitFilter(params);
+ };
const handleSubmitFilter = (params) => {
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
- router.push(`${prefixUrl}?${params}`)
- }
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+ router.push(`${prefixUrl}?${params}`);
+ };
return (
<>
@@ -235,8 +260,14 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
<div className='p-4 pt-0'>
{isBrand && isBrand.logo && (
<div className='mb-3'>
- <h1 className='mb-2 font-semibold text-h-sm'>Brand Pencarian {q}</h1>
- <Image src={isBrand?.logo} alt='' className='object-cover object-center h-[60px]' />
+ <h1 className='mb-2 font-semibold text-h-sm'>
+ Brand Pencarian {q}
+ </h1>
+ <Image
+ src={isBrand?.logo}
+ alt=''
+ className='object-cover object-center h-[60px]'
+ />
</div>
)}
<h1 className='mb-2 font-semibold text-h-sm'>Produk</h1>
@@ -255,7 +286,8 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
{pageCount > 1 ? (
<>
{productStart + 1}-
- {parseInt(productStart) + parseInt(productRows) > productFound
+ {parseInt(productStart) + parseInt(productRows) >
+ productFound
? productFound
: parseInt(productStart) + parseInt(productRows)}
&nbsp;dari&nbsp;
@@ -267,7 +299,8 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
&nbsp;produk{' '}
{query.q && (
<>
- untuk pencarian <span className='font-semibold'>{query.q}</span>
+ untuk pencarian{' '}
+ <span className='font-semibold'>{query.q}</span>
</>
)}
</>
@@ -279,7 +312,10 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
{productFound > 0 && (
<div className='flex items-center gap-x-2 mb-5 justify-between'>
<div>
- <button className='btn-light py-2 px-5 h-[40px]' onClick={popup.activate}>
+ <button
+ className='btn-light py-2 px-5 h-[40px]'
+ onClick={popup.activate}
+ >
Filter
</button>
</div>
@@ -303,7 +339,9 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
<div className='grid grid-cols-2 gap-3'>
{products &&
- products.map((product) => <ProductCard product={product} key={product.id} />)}
+ products.map((product) => (
+ <ProductCard product={product} key={product.id} />
+ ))}
</div>
<Pagination
@@ -329,7 +367,9 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
<div className='w-3/12'>
{brand && (
<div className='p-4'>
- <div className='text-caption-1 text-gray_r-11 mb-2'>Produk dari brand:</div>
+ <div className='text-caption-1 text-gray_r-11 mb-2'>
+ Produk dari brand:
+ </div>
{brand?.data?.logo && (
<Image
src={brand?.data?.logo}
@@ -365,12 +405,18 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
{isBrand && isBrand.logo && (
<div className='mb-3'>
- <h1 className='text-2xl mb-2 font-semibold'>Brand Pencarian {q}</h1>
+ <h1 className='text-2xl mb-2 font-semibold'>
+ Brand Pencarian {q}
+ </h1>
<Link
href={createSlug('/shop/brands/', isBrand.name, isBrand.id)}
className='inline'
>
- <Image src={isBrand?.logo} alt='' className='object-cover object-center h-24' />
+ <Image
+ src={isBrand?.logo}
+ alt=''
+ className='object-cover object-center h-24'
+ />
</Link>
</div>
)}
@@ -391,7 +437,8 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
{pageCount > 1 ? (
<>
{productStart + 1}-
- {parseInt(productStart) + parseInt(productRows) > productFound
+ {parseInt(productStart) + parseInt(productRows) >
+ productFound
? productFound
: parseInt(productStart) + parseInt(productRows)}
&nbsp;dari&nbsp;
@@ -403,7 +450,8 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
&nbsp;produk{' '}
{query.q && (
<>
- untuk pencarian <span className='font-semibold'>{query.q}</span>
+ untuk pencarian{' '}
+ <span className='font-semibold'>{query.q}</span>
</>
)}
</>
@@ -447,7 +495,9 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
{productSearch.isLoading && <ProductSearchSkeleton />}
<div className='grid grid-cols-5 gap-x-3 gap-y-6'>
{products &&
- products.map((product) => <ProductCard product={product} key={product.id} />)}
+ products.map((product) => (
+ <ProductCard product={product} key={product.id} />
+ ))}
</div>
<div className='flex justify-between items-center mt-6 mb-2'>
<div className='pt-2 pb-6 flex items-center gap-x-3'>
@@ -464,7 +514,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
href={
query?.q
? whatsappUrl('productSearch', {
- name: query.q
+ name: query.q,
})
: whatsappUrl()
}
@@ -496,40 +546,61 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null })
</div>
</DesktopView>
</>
- )
-}
+ );
+};
-export default ProductSearch
+export default ProductSearch;
const FilterChoicesComponent = ({
brandValues,
categoryValues,
priceFrom,
priceTo,
- handleDeleteFilter
+ handleDeleteFilter,
}) => (
<div className='flex items-center'>
<HStack spacing={2} className='flex-wrap'>
{brandValues?.map((value, index) => (
- <Tag size='lg' key={index} borderRadius='lg' variant='outline' colorScheme='gray'>
+ <Tag
+ size='lg'
+ key={index}
+ borderRadius='lg'
+ variant='outline'
+ colorScheme='gray'
+ >
<TagLabel>{value}</TagLabel>
<TagCloseButton onClick={() => handleDeleteFilter('brands', value)} />
</Tag>
))}
{categoryValues?.map((value, index) => (
- <Tag size='lg' key={index} borderRadius='lg' variant='outline' colorScheme='gray'>
+ <Tag
+ size='lg'
+ key={index}
+ borderRadius='lg'
+ variant='outline'
+ colorScheme='gray'
+ >
<TagLabel>{value}</TagLabel>
- <TagCloseButton onClick={() => handleDeleteFilter('category', value)} />
+ <TagCloseButton
+ onClick={() => handleDeleteFilter('category', value)}
+ />
</Tag>
))}
{priceFrom && priceTo && (
<Tag size='lg' borderRadius='lg' variant='outline' colorScheme='gray'>
- <TagLabel>{formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)}</TagLabel>
- <TagCloseButton onClick={() => handleDeleteFilter('price', priceFrom)} />
+ <TagLabel>
+ {formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)}
+ </TagLabel>
+ <TagCloseButton
+ onClick={() => handleDeleteFilter('price', priceFrom)}
+ />
</Tag>
)}
- {brandValues?.length > 0 || categoryValues?.length > 0 || priceFrom || priceTo ? (
+ {brandValues?.length > 0 ||
+ categoryValues?.length > 0 ||
+ priceFrom ||
+ priceTo ? (
<span>
<button
className='btn-transparent py-2 px-5 h-[40px] text-red-700'
@@ -543,4 +614,4 @@ const FilterChoicesComponent = ({
)}
</HStack>
</div>
-)
+);
diff --git a/src/pages/shop/cart.jsx b/src/pages/shop/cart.jsx
index 2da58c96..34cae86a 100644
--- a/src/pages/shop/cart.jsx
+++ b/src/pages/shop/cart.jsx
@@ -1,14 +1,14 @@
-import Seo from '@/core/components/Seo'
-import BasicLayout from '@/core/components/layouts/BasicLayout'
-import DesktopView from '@/core/components/views/DesktopView'
-import MobileView from '@/core/components/views/MobileView'
-import IsAuth from '@/lib/auth/components/IsAuth'
-import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react'
-import dynamic from 'next/dynamic'
-import Link from 'next/link'
+import Seo from '@/core/components/Seo';
+import BasicLayout from '@/core/components/layouts/BasicLayout';
+import DesktopView from '@/core/components/views/DesktopView';
+import MobileView from '@/core/components/views/MobileView';
+import IsAuth from '@/lib/auth/components/IsAuth';
+import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react';
+import dynamic from 'next/dynamic';
+import Link from 'next/link';
-const AppLayout = dynamic(() => import('@/core/components/layouts/AppLayout'))
-const CartComponent = dynamic(() => import('@/lib/cart/components/Cart'))
+const AppLayout = dynamic(() => import('@/core/components/layouts/AppLayout'));
+const CartDetail = dynamic(() => import('~/modules/cart/components/Detail'));
export default function Cart() {
return (
@@ -18,7 +18,9 @@ export default function Cart() {
<IsAuth>
<MobileView>
<AppLayout title='Keranjang' withFooter={false}>
- <CartComponent />
+ <div className='p-4'>
+ <CartDetail />
+ </div>
</AppLayout>
</MobileView>
@@ -27,20 +29,29 @@ export default function Cart() {
<div className='container mx-auto py-4 md:py-6 pb-0'>
<Breadcrumb>
<BreadcrumbItem>
- <BreadcrumbLink as={Link} href='/' className='!text-danger-500 whitespace-nowrap'>
+ <BreadcrumbLink
+ as={Link}
+ href='/'
+ className='!text-danger-500 whitespace-nowrap'
+ >
Home
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem isCurrentPage>
- <BreadcrumbLink className='whitespace-nowrap'>Keranjang</BreadcrumbLink>
+ <BreadcrumbLink className='whitespace-nowrap'>
+ Keranjang
+ </BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>
+
+ <div className='h-10' />
+
+ <CartDetail />
</div>
- <CartComponent />
</BasicLayout>
</DesktopView>
</IsAuth>
</>
- )
+ );
}
diff --git a/src/pages/shop/product/[slug].jsx b/src/pages/shop/product/[slug].jsx
index d8366d3c..667373b4 100644
--- a/src/pages/shop/product/[slug].jsx
+++ b/src/pages/shop/product/[slug].jsx
@@ -1,79 +1,83 @@
-import Seo from '@/core/components/Seo'
-import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'
-import { getIdFromSlug } from '@/core/utils/slug'
-import productApi from '@/lib/product/api/productApi'
-import PageNotFound from '@/pages/404'
-import dynamic from 'next/dynamic'
-import { useRouter } from 'next/router'
-import cookie from 'cookie'
-import axios from 'axios'
-import { useProductContext } from '@/contexts/ProductContext'
-import { useEffect } from 'react'
-import { updateItemCart } from '@/core/utils/cart'
+import Seo from '@/core/components/Seo';
+import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner';
+import { getIdFromSlug } from '@/core/utils/slug';
+import productApi from '@/lib/product/api/productApi';
+import PageNotFound from '@/pages/404';
+import dynamic from 'next/dynamic';
+import { useRouter } from 'next/router';
+import cookie from 'cookie';
+import axios from 'axios';
+import { useProductContext } from '@/contexts/ProductContext';
+import { useEffect } from 'react';
+import { updateItemCart } from '@/core/utils/cart';
-const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout'))
-const Product = dynamic(() => import('@/lib/product/components/Product/Product'))
+const BasicLayout = dynamic(() =>
+ import('@/core/components/layouts/BasicLayout')
+);
+const Product = dynamic(() =>
+ import('@/lib/product/components/Product/Product')
+);
export async function getServerSideProps(context) {
- const { slug } = context.query
- const cookies = context.req.headers.cookie
- const cookieObj = cookies ? cookie.parse(cookies) : {}
- const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {}
- const tier = auth.pricelist ? auth.pricelist : false
- const authToken = auth?.token || ''
+ const { slug } = context.query;
+ const cookies = context.req.headers.cookie;
+ const cookieObj = cookies ? cookie.parse(cookies) : {};
+ const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {};
+ const tier = auth.pricelist ? auth.pricelist : false;
let response = await axios(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/product-detail?id=` +
getIdFromSlug(slug) +
'&auth=' +
tier
- )
- let product = response.data
+ );
+ let product = response.data;
// let productSolr = await productApi({ id: getIdFromSlug(slug), headers: { Token: authToken } })
// let productSolr = null
if (product?.length == 1) {
- product = product[0]
+ product = product[0];
} else {
- product = null
+ product = null;
}
return {
- props: { product }
- }
+ props: { product },
+ };
}
export default function ProductDetail({ product }) {
- const router = useRouter()
- const { setProduct } = useProductContext()
+ const router = useRouter();
+ const { setProduct } = useProductContext();
useEffect(() => {
if (product) {
- setProduct(product)
+ setProduct(product);
}
- }, [product, setProduct])
+ }, [product, setProduct]);
useEffect(() => {
- const { action, variantId, qty } = router.query
+ const { action, variantId, qty } = router.query;
const addToCart = async () => {
const data = {
productId: variantId,
quantity: qty,
selected: true,
programLineId: null,
- source: action
- }
- console.log('data dr test', data)
- await updateItemCart(data)
- const redirectURL = action === 'buy' ? '/shop/checkout?source=buy' : '/shop/cart'
- router.push(redirectURL)
- }
+ source: action,
+ };
+
+ await updateItemCart(data);
+ const redirectURL =
+ action === 'buy' ? '/shop/checkout?source=buy' : '/shop/cart';
+ router.push(redirectURL);
+ };
if (action && variantId && qty) {
- addToCart()
+ addToCart();
}
- }, [router])
+ }, [router]);
- if (!product) return <PageNotFound />
+ if (!product) return <PageNotFound />;
return (
<BasicLayout>
@@ -87,16 +91,16 @@ export default function ProductDetail({ product }) {
url: product?.image,
width: 800,
height: 800,
- alt: product?.name
- }
+ alt: product?.name,
+ },
],
- type: 'product'
+ type: 'product',
}}
additionalMetaTags={[
{
name: 'keywords',
- content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`
- }
+ content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`,
+ },
]}
/>
{!product && (
@@ -106,5 +110,5 @@ export default function ProductDetail({ product }) {
)}
{product && <Product product={product} />}
</BasicLayout>
- )
+ );
}