summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/cart/api/CartApi.js12
-rw-r--r--src/lib/cart/components/Cart.jsx691
-rw-r--r--src/lib/cart/components/CartOld.jsx497
-rw-r--r--src/lib/checkout/api/checkoutApi.js14
-rw-r--r--src/lib/checkout/api/getVoucher.js11
-rw-r--r--src/lib/checkout/components/Checkout.jsx1010
-rw-r--r--src/lib/checkout/components/CheckoutOld.jsx811
-rw-r--r--src/lib/product/components/Product/Product.jsx2
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx129
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx22
-rw-r--r--src/lib/product/components/ProductCard.jsx2
-rw-r--r--src/lib/promotinProgram/api/homepageApi.js17
-rw-r--r--src/lib/promotinProgram/components/HomePage.jsx116
-rw-r--r--src/lib/promotinProgram/components/PromotionType.jsx304
-rw-r--r--src/lib/variant/components/VariantGroupCard.jsx43
15 files changed, 3288 insertions, 393 deletions
diff --git a/src/lib/cart/api/CartApi.js b/src/lib/cart/api/CartApi.js
index 7683a935..038479f8 100644
--- a/src/lib/cart/api/CartApi.js
+++ b/src/lib/cart/api/CartApi.js
@@ -1,6 +1,7 @@
import odooApi from '@/core/api/odooApi'
+import { getAuth } from '@/core/utils/auth'
-const CartApi = async ({ variantIds }) => {
+export const CartApi = async ({ variantIds }) => {
if (variantIds) {
const dataCart = await odooApi('GET', `/api/v2/product_variant/${variantIds}`)
return dataCart
@@ -8,4 +9,11 @@ const CartApi = async ({ variantIds }) => {
return null
}
-export default CartApi
+// export default CartApi
+
+export const getCartApi = async () => {
+ const id = getAuth()?.id
+ const cart = await odooApi('GET', `/api/v1/user/${id}/cart`)
+
+ return cart
+} \ No newline at end of file
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index c02f976f..efbcf76b 100644
--- a/src/lib/cart/components/Cart.jsx
+++ b/src/lib/cart/components/Cart.jsx
@@ -4,7 +4,7 @@ import Image from '@/core/components/elements/Image/Image'
import NextImage from 'next/image'
import currencyFormat from '@/core/utils/currencyFormat'
import { useEffect, useState } from 'react'
-import { deleteItemCart, getItemCart, updateItemCart } from '@/core/utils/cart'
+import { addCart, deleteItemCart, getCartApi, updateItemCart } from '@/core/utils/cart'
import { CheckIcon, TrashIcon } from '@heroicons/react/24/outline'
import { createSlug } from '@/core/utils/slug'
import { useRouter } from 'next/router'
@@ -17,12 +17,54 @@ import DesktopView from '@/core/components/views/DesktopView'
import ProductCard from '@/lib/product/components/ProductCard'
import productSearchApi from '@/lib/product/api/productSearchApi'
import whatsappUrl from '@/core/utils/whatsappUrl'
+import useAuth from '@/core/hooks/useAuth'
+import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'
+import { getPromotionProgram } from '@/lib/promotinProgram/api/homepageApi'
+import PromotionType from '@/lib/promotinProgram/components/PromotionType'
import { gtagBeginCheckout } from '@/core/utils/googleTag'
const Cart = () => {
const router = useRouter()
const [products, setProducts] = useState(null)
- const { cart } = useCart({ enabled: !products })
+ const [isLoading, setIsLoading] = useState(true)
+ const auth = useAuth()
+
+ const [cart, setCart] = useState(null)
+
+ useEffect(() => {
+ if (!auth) return
+ }, [auth])
+
+ const getCart = async () => {
+ const listCart = await getCartApi()
+ setCart(listCart)
+ }
+
+ useEffect(() => {
+ getCart()
+ }, [])
+
+ useEffect(() => {
+ if (cart) {
+ const processProducts = async () => {
+ const updatedProducts = await Promise.all(
+ cart.products.map(async (product) => {
+ const id = product.id
+ const program = await getPromotionProgram({ id })
+ const isPromo = program.length > 0 ? 1 : 0
+ return {
+ ...product,
+ isPromo
+ }
+ })
+ )
+ setProducts(updatedProducts)
+ }
+ // processProducts()
+ setProducts(cart.products)
+ setIsLoading(false)
+ }
+ }, [cart])
const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0)
const [totalTaxAmount, setTotalTaxAmount] = useState(0)
@@ -31,21 +73,10 @@ const Cart = () => {
const [deleteConfirmation, setDeleteConfirmation] = useState(null)
const [productRecomendation, setProductRecomendation] = useState(null)
-
- useEffect(() => {
- if (cart.data && !products) {
- const productsWithQuantity = cart.data.map((product) => {
- const productInCart = getItemCart({ productId: product.id })
- if (!productInCart) return
- return {
- ...product,
- quantity: productInCart.quantity,
- selected: productInCart.selected
- }
- })
- setProducts(productsWithQuantity)
- }
- }, [cart, products])
+ const [promotionType, setPromotionType] = useState(null)
+ const [variantId, setVariantId] = useState(null)
+ const [quantity, setQuantity] = useState(null)
+ const [promotionActiveId, setPromotionActiveId] = useState(null)
useEffect(() => {
if (!products) return
@@ -55,13 +86,12 @@ const Cart = () => {
let calculateTotalDiscountAmount = 0
for (const product of products) {
if (product.quantity == '') continue
- updateItemCart({
- productId: product.id,
- quantity: product.quantity,
- selected: product.selected
- })
if (!product.selected) continue
+
+ if (product.canBuy == false) {
+ toggleSelected(product.id)
+ }
let priceBeforeTax = product.price.price / 1.11
calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity
calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity
@@ -74,6 +104,24 @@ const Cart = () => {
}, [products])
useEffect(() => {
+ const updateData = () => {
+ updateItemCart({
+ productId: variantId,
+ quantity,
+ programLineId: promotionActiveId,
+ selected: true
+ }).then(() => {
+ getCart().then(() => {
+ if(promotionActiveId){
+ setPromotionType(false)
+ }
+ })
+ })
+ }
+ updateData()
+ }, [promotionActiveId])
+
+ useEffect(() => {
const LoadProductSimilar = async () => {
const randProductIndex = Math.floor(Math.random() * products.length)
const productLoad = await productSearchApi({
@@ -109,8 +157,16 @@ const Cart = () => {
quantity = value != '' && value < 1 ? 1 : value
break
}
- productsToUpdate[productIndex].quantity = quantity
+ let qty = quantity
+ productsToUpdate[productIndex].quantity = qty
+
setProducts([...productsToUpdate])
+ addCart(
+ productId,
+ qty,
+ productsToUpdate[productIndex].selected,
+ productsToUpdate[productIndex].program?.id
+ )
}
const toggleSelected = (productId) => {
@@ -118,8 +174,16 @@ const Cart = () => {
if (productIndex < 0) return
let productsToUpdate = products
- productsToUpdate[productIndex].selected = !productsToUpdate[productIndex].selected
+ let isSelected = !productsToUpdate[productIndex].selected
+ productsToUpdate[productIndex].selected = isSelected
+
setProducts([...productsToUpdate])
+ addCart(
+ productId,
+ productsToUpdate[productIndex].quantity,
+ isSelected,
+ productsToUpdate[productIndex].program?.id
+ )
}
const selectedProduct = () => {
@@ -135,6 +199,13 @@ const Cart = () => {
toast.success('Berhasil menghapus barang dari keranjang')
}
+ const handlePopUpPromo = (variantId, quantity, promoId = null) => {
+ setPromotionType(true)
+ setVariantId(variantId)
+ setQuantity(quantity)
+ setPromotionActiveId(promoId)
+ }
+
const handleCheckout = () => {
gtagBeginCheckout(products)
router.push('/shop/checkout')
@@ -173,6 +244,23 @@ const Cart = () => {
</div>
</BottomPopup>
+ <BottomPopup
+ className=' !h-[75%]'
+ title='Pakai Promo'
+ active={promotionType}
+ close={() => setPromotionType(false)}
+ >
+ <div className='flex mt-4'>
+ <PromotionType
+ isModal={true}
+ variantId={variantId}
+ setPromotionActiveId={setPromotionActiveId}
+ promotionActiveId={promotionActiveId}
+ quantity={quantity}
+ ></PromotionType>
+ </div>
+ </BottomPopup>
+
<MobileView>
<div className='pt-4'>
<div className='flex justify-between mb-4 px-4'>
@@ -181,13 +269,13 @@ const Cart = () => {
</div>
<div className='flex flex-col gap-y-4 h-screen'>
- {cart.isLoading && (
+ {isLoading && (
<div className='flex justify-center my-4'>
<Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
</div>
)}
- {!cart.isLoading && (!products || products?.length == 0) && (
+ {!isLoading && (!products || products?.length == 0) && (
<div className='px-4'>
<Alert className='text-center my-2' type='info'>
Keranjang belanja anda masih kosong
@@ -195,85 +283,204 @@ const Cart = () => {
</div>
)}
- {products?.map((product) => (
- <div key={product?.id} className='flex mx-4'>
- <input
- type='checkbox'
- onClick={() => toggleSelected(product.id)}
- checked={product?.selected}
- className='mr-2 accent-danger-500 w-4'
- />
-
- <Link
- href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
- className='w-[30%] 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'
- />
- </Link>
- <div className='flex-1 px-2 text-caption-2'>
- <Link
- href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
- >
- {product?.parent?.name}
- </Link>
- <div className='text-gray_r-11 mt-1'>
- {product?.code}{' '}
- {product?.attributes.length > 0 ? `| ${product?.attributes.join(', ')}` : ''}
- </div>
- {product?.price?.discountPercentage > 0 && (
- <div className='flex gap-x-1 items-center mt-3'>
- <div className='text-gray_r-11 line-through text-caption-2'>
- {currencyFormat(product?.price?.price)}
+ {products &&
+ products?.map((product) => (
+ <>
+ {product.hasProgram > 0 && (
+ <div className='flex gap-x-2 bg-yellow-100 p-2 items-center'>
+ {product.program ? (
+ <>
+ <div className='border border-solid border-red-600 rounded-md p-1 text-caption-2 whitespace-nowrap'>
+ <span className='text-red-600'>{product.program.type.label}</span>
+ </div>
+ <div className='text-caption-2'>
+ {product.program.type.value == 'merchandise' ? (
+ <>Selamat anda mendapatkan merchandise indoteknik.com</>
+ ) : (
+ <>
+ Selamat! Pembelian anda hemat
+ <span className='text-red-600 font-semibold text-caption-2'>
+ {' '}
+ {currencyFormat(product.program?.totalSavings)}
+ </span>
+ </>
+ )}
+ </div>
+ </>
+ ) : (
+ <>
+ <div className='flex border border-solid border-red-600 rounded-md p-1'>
+ <span className='text-red-600'>Promo</span>
+ </div>
+ <div className='flex'>Pilih Promo Yang Tersedia Bisa lebih Hemat</div>
+ </>
+ )}
+ <div
+ onClick={() =>
+ handlePopUpPromo(product.id, product.quantity, product.program?.id)
+ }
+ className='ml-auto text-red-500 flex gap-x-0 cursor-pointer'
+ >
+ <div className='font-semibold text-caption-2 whitespace-nowrap'>
+ {' '}
+ Cek Promo
+ </div>
+ <div>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 20 24'
+ className='h-5 w-5 font-semibold'
+ >
+ <path
+ d='M8.25 4.5l7.5 7.5-7.5 7.5'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ </div>
</div>
- <div className='badge-solid-red'>{product?.price?.discountPercentage}%</div>
</div>
)}
- <div className='font-normal mt-1'>
- {currencyFormat(product?.price?.priceDiscount)}
- </div>
- <div className='flex justify-between items-center mt-1'>
- <div className='text-danger-500 font-medium'>
- {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
- </div>
- <div className='flex gap-x-1'>
- <button
- type='button'
- className='btn-light px-2 py-1'
- onClick={() => updateQuantity(1, product?.id, 'MINUS')}
- disabled={product?.quantity == 1}
- >
- -
- </button>
- <input
- className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center'
- type='number'
- value={product?.quantity}
- onChange={(e) => updateQuantity(e.target.value, product?.id)}
- onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ <div key={product?.id} className='flex mx-4 relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <input
+ type='checkbox'
+ onClick={() => toggleSelected(product.id)}
+ checked={product?.selected}
+ className='mr-2 accent-danger-500 w-4'
+ />
+
+ <Link
+ href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
+ className='w-[30%] 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'
/>
- <button
- type='button'
- className='btn-light px-2 py-1'
- onClick={() => updateQuantity(1, product?.id, 'PLUS')}
- >
- +
- </button>
- <button
- className='btn-red p-1 ml-1'
- onClick={() => setDeleteConfirmation(product)}
+ </Link>
+ <div className='flex-1 px-2 text-caption-2'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
>
- <TrashIcon className='w-4' />
- </button>
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-1'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
+ {product?.price?.discountPercentage > 0 && (
+ <div className='flex gap-x-1 items-center mt-3'>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {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='flex justify-between items-center mt-1'>
+ <div className='text-danger-500 font-medium'>
+ {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
+ </div>
+ <div className='flex gap-x-1'>
+ <button
+ type='button'
+ className='btn-light px-2 py-1'
+ onClick={() => updateQuantity(1, product?.id, 'MINUS')}
+ disabled={product?.quantity == 1}
+ >
+ -
+ </button>
+ <input
+ className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center'
+ type='number'
+ value={product?.quantity}
+ onChange={(e) => updateQuantity(e.target.value, product?.id)}
+ onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ />
+ <button
+ type='button'
+ className='btn-light px-2 py-1'
+ onClick={() => updateQuantity(1, product?.id, 'PLUS')}
+ >
+ +
+ </button>
+ <button
+ className='btn-red p-1 ml-1'
+ onClick={() => setDeleteConfirmation(product)}
+ >
+ <TrashIcon className='w-4' />
+ </button>
+ </div>
+ </div>
</div>
</div>
- </div>
- </div>
- ))}
+ {product.program &&
+ product.program.items &&
+ product.program.items.map((item) => (
+ <div key={item.id} className='flex mx-4 relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <input className='mr-2 accent-danger-500 w-4' />
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[30%] 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'
+ />
+ </Link>
+ <div className='flex-1 px-2 text-caption-2'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ <div className='px-2 text-left mt-2'>
+ <div className='mb-2'>
+ <span className='border border-solid border-red-600 rounded-md p-1 text-red-600'>
+ {product.program.type.label}
+ </span>
+ </div>
+ <div>{item.name}</div>
+ </div>
+ </Link>
+ {item.price?.discountPercentage > 0 && (
+ <div className='flex gap-x-1 items-center mt-3 ml-3'>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {currencyFormat(item?.price?.price)}
+ </div>
+ </div>
+ )}
+ <div className='text-danger-500 font-medium mt-1 ml-3'>Gratis</div>
+ </div>
+ </div>
+ ))}
+ </>
+ ))}
<div className='sticky bottom-0 left-0 w-full p-4 mt-auto border-t border-gray_r-6 bg-white'>
<div className='flex flex-col gap-y-3 mb-4'>
@@ -330,105 +537,240 @@ const Cart = () => {
</tr>
</thead>
<tbody>
- {cart.isLoading && (
+ {isLoading && (
<tr>
<td colSpan={6}>
- <div className='flex justify-center my-2'>
- <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ <div className='container flex justify-center my-4'>
+ <LogoSpinner width={48} height={48} />
</div>
</td>
</tr>
)}
- {!cart.isLoading && (!products || products?.length == 0) && (
+ {!isLoading && (!products || products?.length == 0) && (
<tr>
<td colSpan={6}>Keranjang belanja anda masih kosong</td>
</tr>
)}
{products &&
products?.map((product) => (
- <tr key={product.id}>
- <td>
- <input
- type='checkbox'
- onClick={() => toggleSelected(product.id)}
- checked={product?.selected}
- className='accent-danger-500 w-4'
- />
- </td>
- <td className='flex'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- 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-28 w-full rounded-md'
+ <>
+ {product.hasProgram && (
+ <tr className='!border-b-0 pb-0'>
+ <td colSpan={6}>
+ <div className='flex gap-x-2 bg-yellow-100 p-2 items-center'>
+ {product.program ? (
+ <>
+ <div className='flex border border-solid border-red-600 rounded-md p-1'>
+ <span className='text-red-600'>
+ {product.program.type.label}
+ </span>
+ </div>
+ <div className='flex'>
+ {product.program.type.value == 'merchandise' ? (
+ <>Selamat anda mendapatkan merchandise indoteknik.com</>
+ ) : (
+ <>
+ Selamat! Pembelian anda lebih hemat
+ <span className='text-red-600 font-semibold ml-2'>
+ {' '}
+ {currencyFormat(product.program?.totalSavings)}
+ </span>
+ </>
+ )}
+ </div>
+ </>
+ ) : (
+ <>
+ <div className='flex border border-solid border-red-600 rounded-md p-1'>
+ <span className='text-red-600'>Promo</span>
+ </div>
+ <div className='flex'>
+ Pilih Promo Yang Tersedia Bisa lebih Hemat
+ </div>
+ </>
+ )}
+ <div
+ onClick={() =>
+ handlePopUpPromo(
+ product.id,
+ product.quantity,
+ product.program?.id
+ )
+ }
+ className='ml-auto text-red-500 flex gap-x-1 cursor-pointer'
+ >
+ <div className='font-semibold'> Cek Promo</div>
+ <div>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 20 24'
+ className='h-5 w-5 font-semibold'
+ >
+ <path
+ d='M8.25 4.5l7.5 7.5-7.5 7.5'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ )}
+ <tr
+ key={product.id}
+ className={`${product.hasProgram ? '!border-t-0 !border-b-0' : ''}`}
+ >
+ <td className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <input
+ type='checkbox'
+ onClick={() => toggleSelected(product.id)}
+ checked={product?.selected}
+ className='accent-danger-500 w-4'
/>
- </Link>
- <div className='px-2 text-left'>
+ </td>
+ <td className='flex relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
<Link
href={createSlug(
'/shop/product/',
product?.parent.name,
product?.parent.id
)}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ className='w-[20%] flex-shrink-0'
>
- {product?.parent?.name}
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md'
+ />
</Link>
- <div className='text-gray_r-11 mt-2'>
- {product?.code}{' '}
- {product?.attributes.length > 0
- ? `| ${product?.attributes.join(', ')}`
- : ''}
- </div>
- </div>
- </td>
- <td>
- <input
- className='form-input w-16 py-2 text-center bg-gray_r-1'
- type='number'
- value={product?.quantity}
- onChange={(e) => updateQuantity(e.target.value, product?.id)}
- onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
- />
- </td>
- <td>
- {product?.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 className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
</div>
- <div className='badge-solid-red'>
- {product?.price?.discountPercentage}%
+ </div>
+ </td>
+ <td className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <input
+ className='form-input w-16 py-2 text-center bg-gray_r-1'
+ type='number'
+ value={product?.quantity}
+ onChange={(e) => updateQuantity(e.target.value, product?.id)}
+ onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ />
+ </td>
+ <td className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ {product?.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 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'>
- {currencyFormat(product?.price?.priceDiscount)}
- </div>
- </td>
- <td>
- <div className='text-danger-500 font-medium'>
- {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
- </div>
- </td>
- <td>
- <div className='flex justify-center items-center h-full'>
- <button
- className='btn-red p-1 ml-1'
- onClick={() => setDeleteConfirmation(product)}
- >
- <TrashIcon className='w-4' />
- </button>
- </div>
- </td>
- </tr>
+ </td>
+ <td className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <div className='text-danger-500 font-medium'>
+ {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
+ </div>
+ </td>
+ <td>
+ <div className='flex justify-center items-center h-full'>
+ <button
+ className='btn-red p-1 ml-1'
+ onClick={() => setDeleteConfirmation(product)}
+ >
+ <TrashIcon className='w-4' />
+ </button>
+ </div>
+ </td>
+ </tr>
+ {product?.program?.items && (
+ <tr key={product.program.id} className='!border-t-0'>
+ {product.program.items.map((item) => (
+ <>
+ <td className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ </td>
+ <td className='flex relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <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-28 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 className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <input
+ className='form-input w-16 py-2 text-center bg-gray_r-1'
+ type='number'
+ value={1}
+ disabled
+ />
+ </td>
+ <td className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ {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 className='relative'>
+ <ComponentCanBuy canBuy={product.canBuy} />
+ <div className='text-danger-500 font-medium'>
+ {item.price.priceDiscount > 0 ? 'Gratis' : ''}
+ </div>
+ </td>
+ <td></td>
+ </>
+ ))}
+ </tr>
+ )}
+ </>
))}
</tbody>
</table>
@@ -506,4 +848,7 @@ const Cart = () => {
)
}
+const ComponentCanBuy = ({ canBuy }) =>
+ !canBuy && <div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0' />
+
export default Cart
diff --git a/src/lib/cart/components/CartOld.jsx b/src/lib/cart/components/CartOld.jsx
new file mode 100644
index 00000000..718541af
--- /dev/null
+++ b/src/lib/cart/components/CartOld.jsx
@@ -0,0 +1,497 @@
+import Link from '@/core/components/elements/Link/Link'
+import useCart from '../hooks/useCart'
+import Image from '@/core/components/elements/Image/Image'
+import NextImage from 'next/image'
+import currencyFormat from '@/core/utils/currencyFormat'
+import { useEffect, useState } from 'react'
+import { deleteItemCart, getItemCart, updateItemCart } from '@/core/utils/cart'
+import { CheckIcon, TrashIcon } from '@heroicons/react/24/outline'
+import { createSlug } from '@/core/utils/slug'
+import { useRouter } from 'next/router'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import { toast } from 'react-hot-toast'
+import Spinner from '@/core/components/elements/Spinner/Spinner'
+import Alert from '@/core/components/elements/Alert/Alert'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import ProductCard from '@/lib/product/components/ProductCard'
+import productSearchApi from '@/lib/product/api/productSearchApi'
+import whatsappUrl from '@/core/utils/whatsappUrl'
+import useAuth from '@/core/hooks/useAuth'
+
+const Cart = () => {
+ const router = useRouter()
+ const [products, setProducts] = useState(null)
+ const auth = useAuth()
+
+ useEffect(() => {
+ if (!auth) return
+ }, [auth])
+ const { cart } = useCart({ enabled: !products })
+
+ const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0)
+ const [totalTaxAmount, setTotalTaxAmount] = useState(0)
+ const [totalDiscountAmount, setTotalDiscountAmount] = useState(0)
+
+ const [deleteConfirmation, setDeleteConfirmation] = useState(null)
+
+ const [productRecomendation, setProductRecomendation] = useState(null)
+
+ useEffect(() => {
+ if (cart.data && !products) {
+ const productsWithQuantity = cart.data.map((product) => {
+ const productInCart = getItemCart({ productId: product.id })
+ if (!productInCart) return
+ return {
+ ...product,
+ quantity: productInCart.quantity,
+ selected: productInCart.selected
+ }
+ })
+ setProducts(productsWithQuantity)
+ }
+ }, [cart, products])
+
+ useEffect(() => {
+ if (!products) return
+
+ let calculateTotalPriceBeforeTax = 0
+ let calculateTotalTaxAmount = 0
+ let calculateTotalDiscountAmount = 0
+ for (const product of products) {
+ if (product.quantity == '') continue
+ updateItemCart({
+ productId: product.id,
+ quantity: product.quantity,
+ selected: product.selected
+ })
+
+ if (!product.selected) continue
+ let priceBeforeTax = product.price.price / 1.11
+ calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity
+ calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity
+ calculateTotalDiscountAmount +=
+ (product.price.price - product.price.priceDiscount) * product.quantity
+ }
+ setTotalPriceBeforeTax(calculateTotalPriceBeforeTax)
+ setTotalTaxAmount(calculateTotalTaxAmount)
+ setTotalDiscountAmount(calculateTotalDiscountAmount)
+ }, [products])
+
+ useEffect(() => {
+ const LoadProductSimilar = async () => {
+ const randProductIndex = Math.floor(Math.random() * products.length)
+ const productLoad = await productSearchApi({
+ query: `q=${products?.[randProductIndex].parent.name}&limit=10&operation=OR`
+ })
+
+ setProductRecomendation(productLoad)
+ }
+ if (products?.length > 0 && !productRecomendation) LoadProductSimilar()
+ }, [products, productRecomendation])
+
+ const updateQuantity = (value, productId, operation = '') => {
+ let productIndex = products.findIndex((product) => product.id == productId)
+ if (productIndex < 0) return
+
+ let productsToUpdate = products
+ let quantity = productsToUpdate[productIndex].quantity
+ if (value != '' && isNaN(parseInt(value))) return
+ value = value != '' ? parseInt(value) : ''
+ switch (operation) {
+ case 'PLUS':
+ quantity += value
+ break
+ case 'MINUS':
+ if (quantity - value < 1) return
+ quantity -= value
+ break
+ case 'BLUR':
+ if (value != '' && value > 0) return
+ quantity = 1
+ break
+ default:
+ quantity = value != '' && value < 1 ? 1 : value
+ break
+ }
+ productsToUpdate[productIndex].quantity = quantity
+ setProducts([...productsToUpdate])
+ }
+
+ const toggleSelected = (productId) => {
+ let productIndex = products.findIndex((product) => product.id == productId)
+ if (productIndex < 0) return
+
+ let productsToUpdate = products
+ productsToUpdate[productIndex].selected = !productsToUpdate[productIndex].selected
+ setProducts([...productsToUpdate])
+ }
+
+ const selectedProduct = () => {
+ if (!products) return []
+ return products?.filter((product) => product?.selected == true)
+ }
+
+ const deleteProduct = (productId) => {
+ const productsToUpdate = products.filter((product) => product.id != productId)
+ deleteItemCart({ productId })
+ setDeleteConfirmation(null)
+ setProducts([...productsToUpdate])
+ toast.success('Berhasil menghapus barang dari keranjang')
+ }
+
+ return (
+ <>
+ <BottomPopup
+ active={deleteConfirmation}
+ close={() => setDeleteConfirmation(null)}
+ title='Hapus dari Keranjang'
+ >
+ <div className='leading-7 text-gray_r-12/80'>
+ Apakah anda yakin menghapus barang{' '}
+ <span className='underline'>{deleteConfirmation?.name}</span> dari keranjang?
+ </div>
+ <div className='flex mt-6 gap-x-4 md:justify-end'>
+ <button
+ className='btn-solid-red flex-1 md:flex-none'
+ type='button'
+ onClick={() => deleteProduct(deleteConfirmation?.id)}
+ >
+ Ya, Hapus
+ </button>
+ <button
+ className='btn-light flex-1 md:flex-none'
+ type='button'
+ onClick={() => setDeleteConfirmation(null)}
+ >
+ Batal
+ </button>
+ </div>
+ </BottomPopup>
+
+ <MobileView>
+ <div className='pt-4'>
+ <div className='flex justify-between mb-4 px-4'>
+ <h1 className='font-semibold'>Keranjang</h1>
+ <Link href='/'>Cari Produk Lain</Link>
+ </div>
+
+ <div className='flex flex-col gap-y-4 h-screen'>
+ {cart.isLoading && (
+ <div className='flex justify-center my-4'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ )}
+
+ {!cart.isLoading && (!products || products?.length == 0) && (
+ <div className='px-4'>
+ <Alert className='text-center my-2' type='info'>
+ Keranjang belanja anda masih kosong
+ </Alert>
+ </div>
+ )}
+
+ {products?.map((product) => (
+ <div key={product?.id} className='flex mx-4'>
+ <input
+ type='checkbox'
+ onClick={() => toggleSelected(product.id)}
+ checked={product?.selected}
+ className='mr-2 accent-danger-500 w-4'
+ />
+
+ <Link
+ href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
+ className='w-[30%] 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'
+ />
+ </Link>
+ <div className='flex-1 px-2 text-caption-2'>
+ <Link
+ href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-1'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0 ? `| ${product?.attributes.join(', ')}` : ''}
+ </div>
+ {product?.price?.discountPercentage > 0 && (
+ <div className='flex gap-x-1 items-center mt-3'>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {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='flex justify-between items-center mt-1'>
+ <div className='text-danger-500 font-medium'>
+ {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
+ </div>
+ <div className='flex gap-x-1'>
+ <button
+ type='button'
+ className='btn-light px-2 py-1'
+ onClick={() => updateQuantity(1, product?.id, 'MINUS')}
+ disabled={product?.quantity == 1}
+ >
+ -
+ </button>
+ <input
+ className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center'
+ type='number'
+ value={product?.quantity}
+ onChange={(e) => updateQuantity(e.target.value, product?.id)}
+ onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ />
+ <button
+ type='button'
+ className='btn-light px-2 py-1'
+ onClick={() => updateQuantity(1, product?.id, 'PLUS')}
+ >
+ +
+ </button>
+ <button
+ className='btn-red p-1 ml-1'
+ onClick={() => setDeleteConfirmation(product)}
+ >
+ <TrashIcon className='w-4' />
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
+
+ <div className='sticky bottom-0 left-0 w-full p-4 mt-auto border-t border-gray_r-6 bg-white'>
+ <div className='flex justify-between mb-4'>
+ <div className='text-gray_r-11'>
+ Total:
+ <span className='text-danger-500 font-semibold'>
+ &nbsp;
+ {selectedProduct().length > 0
+ ? currencyFormat(totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount)
+ : '-'}
+ </span>
+ </div>
+ </div>
+ <div className='flex gap-x-3'>
+ <button
+ type='button'
+ className='btn-yellow flex-1'
+ disabled={selectedProduct().length == 0}
+ onClick={() => router.push('/shop/quotation')}
+ >
+ Quotation
+ </button>
+ <button
+ type='button'
+ className='btn-solid-red flex-1'
+ disabled={selectedProduct().length == 0}
+ onClick={() => router.push('/shop/checkout')}
+ >
+ Checkout
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto py-10 grid grid-cols-12'>
+ <div className='col-span-9 border border-gray_r-6 rounded bg-white p-4 pt-6'>
+ <h1 className='text-title-sm font-semibold mb-6'>Keranjang</h1>
+
+ <table className='table-cart'>
+ <thead>
+ <tr>
+ <th colSpan={2}>Nama Produk</th>
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ <th>Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ {cart.isLoading && (
+ <tr>
+ <td colSpan={6}>
+ <div className='flex justify-center my-2'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ </td>
+ </tr>
+ )}
+ {!cart.isLoading && (!products || products?.length == 0) && (
+ <tr>
+ <td colSpan={6}>Keranjang belanja anda masih kosong</td>
+ </tr>
+ )}
+ {products &&
+ products?.map((product) => (
+ <tr key={product.id}>
+ <td>
+ <input
+ type='checkbox'
+ onClick={() => toggleSelected(product.id)}
+ checked={product?.selected}
+ className='accent-danger-500 w-4'
+ />
+ </td>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ 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-28 w-full rounded-md'
+ />
+ </Link>
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
+ </div>
+ </td>
+ <td>
+ <input
+ className='form-input w-16 py-2 text-center bg-gray_r-1'
+ type='number'
+ value={product?.quantity}
+ onChange={(e) => updateQuantity(e.target.value, product?.id)}
+ onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ />
+ </td>
+ <td>
+ {product?.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 className='badge-solid-red'>
+ {product?.price?.discountPercentage}%
+ </div>
+ </div>
+ )}
+ <div className='font-normal mt-1'>
+ {currencyFormat(product?.price?.priceDiscount)}
+ </div>
+ </td>
+ <td>
+ <div className='text-danger-500 font-medium'>
+ {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
+ </div>
+ </td>
+ <td>
+ <div className='flex justify-center items-center h-full'>
+ <button
+ className='btn-red p-1 ml-1'
+ onClick={() => setDeleteConfirmation(product)}
+ >
+ <TrashIcon className='w-4' />
+ </button>
+ </div>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+
+ <div className='pt-2 pb-6 flex items-center gap-x-3'>
+ <NextImage
+ src='/images/logo-question.png'
+ alt='Logo Question Indoteknik'
+ width={60}
+ height={60}
+ />
+ <div className='text-gray_r-12/90'>
+ Tanya stock untuk pembelian anda sebelum melanjutkan pembayaran!
+ <span>
+ {' '}
+ <a href={whatsappUrl()} className='text-danger-500'>
+ Hubungi Kami
+ </a>
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div className='col-span-3 pl-4'>
+ <div className='sticky top-48 w-full p-4 rounded border border-gray_r-6 bg-white'>
+ <h1 className='text-title-sm font-semibold mb-6'>Ringkasan Belanja</h1>
+ <div className='flex justify-between mb-4'>
+ <div className='text-gray_r-11'>
+ Total:
+ <span className='text-danger-500 font-semibold'>
+ &nbsp;
+ {selectedProduct().length > 0
+ ? currencyFormat(totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount)
+ : '-'}
+ </span>
+ </div>
+ </div>
+ <div className='flex gap-x-3'>
+ <button
+ type='button'
+ className='btn-yellow flex-1'
+ disabled={selectedProduct().length == 0}
+ onClick={() => router.push('/shop/quotation')}
+ >
+ Quotation
+ </button>
+ <button
+ type='button'
+ className='btn-solid-red flex-1'
+ disabled={selectedProduct().length == 0}
+ onClick={() => router.push('/shop/checkout')}
+ >
+ Checkout
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <div className='col-span-9 pt-2 pb-6 mt-6'>
+ <h1 className='text-title-sm font-semibold mb-6'>Produk Yang Mungkin Kamu Suka</h1>
+ <div className='grid grid-cols-5 gap-x-3 gap-y-6'>
+ {productRecomendation &&
+ productRecomendation.response.products.map((product) => (
+ <ProductCard product={product} key={product.id} />
+ ))}
+ </div>
+ </div>
+ </div>
+ </DesktopView>
+ </>
+ )
+}
+
+export default Cart
diff --git a/src/lib/checkout/api/checkoutApi.js b/src/lib/checkout/api/checkoutApi.js
index b76c9b7f..0d0cc918 100644
--- a/src/lib/checkout/api/checkoutApi.js
+++ b/src/lib/checkout/api/checkoutApi.js
@@ -1,7 +1,7 @@
import odooApi from '@/core/api/odooApi'
import { getAuth } from '@/core/utils/auth'
-const checkoutApi = async ({ data }) => {
+export const checkoutApi = async ({ data }) => {
const auth = getAuth()
const dataCheckout = await odooApi(
'POST',
@@ -11,4 +11,14 @@ const checkoutApi = async ({ data }) => {
return dataCheckout
}
-export default checkoutApi
+export const getProductsCheckout = async (voucher) => {
+ const id = getAuth()?.id
+ let products
+ if(voucher){
+ products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}`)
+ }else{
+ products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout`)
+ }
+
+ return products
+}
diff --git a/src/lib/checkout/api/getVoucher.js b/src/lib/checkout/api/getVoucher.js
new file mode 100644
index 00000000..57d8acf5
--- /dev/null
+++ b/src/lib/checkout/api/getVoucher.js
@@ -0,0 +1,11 @@
+import odooApi from '@/core/api/odooApi'
+
+export const getVoucher = async (id) => {
+ const dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher`)
+ return dataVoucher
+}
+
+export const findVoucher = async (code, id) => {
+ const dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}`)
+ return dataVoucher
+}
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 07d9acb6..11a14e31 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -4,32 +4,44 @@ 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 CartApi from '@/lib/cart/api/CartApi'
import { ExclamationCircleIcon } from '@heroicons/react/24/outline'
import React, { useEffect, useRef, useState } from 'react'
import _ from 'lodash'
-import { deleteItemCart, getCart, getItemCart } from '@/core/utils/cart'
+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 checkoutApi from '../api/checkoutApi'
+// import checkoutApi from '../api/checkoutApi'
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 imageNext from 'next/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 { createSlug } from '@/core/utils/slug'
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'
const SELF_PICKUP_ID = 32
+const { checkoutApi } = require('../api/checkoutApi')
+const { getProductsCheckout } = require('../api/checkoutApi')
+
const Checkout = () => {
const router = useRouter()
const auth = useAuth()
+
+ const [activeVoucher, SetActiveVoucher] = useState(null)
+
+ const { data: cartCheckout } = useQuery('cartCheckout-' + activeVoucher, () =>
+ getProductsCheckout(activeVoucher)
+ )
+
const [selectedAddress, setSelectedAddress] = useState({
shipping: null,
invoicing: null
@@ -66,8 +78,6 @@ const Checkout = () => {
}, [addresses])
const [products, setProducts] = useState(null)
- const [totalAmount, setTotalAmount] = useState(0)
- const [totalDiscountAmount, setTotalDiscountAmount] = useState(0)
const [totalWeight, setTotalWeight] = useState(0)
const [priceCheck, setPriceCheck] = useState(false)
const [listExpedisi, setExpedisi] = useState([])
@@ -81,6 +91,60 @@ const Checkout = () => {
const [selectedExpedisiService, setselectedExpedisiService] = useState(null)
const [etd, setEtd] = useState(null)
const [etdFix, setEtdFix] = useState(null)
+ const [bottomPopup, SetBottomPopup] = 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 expedisiValidation = useRef(null)
+
+ const voucher = async () => {
+ try {
+ let dataVoucher = await getVoucher(auth?.id)
+ SetListVoucher(dataVoucher)
+ } finally {
+ setLoadingVoucher(false)
+ }
+ }
+ const VoucherCode = async (code) => {
+ let dataVoucher = await findVoucher(code, auth.id)
+ if (dataVoucher.length <= 0) {
+ SetFindVoucher(1)
+ return
+ }
+
+ let addNewLine = dataVoucher[0]
+ let checkList = listVouchers.findIndex((voucher) => voucher.code == addNewLine.code)
+ if (checkList >= 0) {
+ if (listVouchers[checkList].canApply) {
+ ToggleSwitch(code)
+ SetCodeVoucher(null)
+ } else {
+ SetSelisihHargaCode(listVouchers[checkList].differenceToApply)
+ SetFindVoucher(2)
+ }
+ return
+ }
+ if (cartCheckout?.subtotal < addNewLine.minPurchaseAmount) {
+ SetSelisihHargaCode(currencyFormat(addNewLine.minPurchaseAmount - cartCheckout?.subtotal))
+ SetFindVoucher(2)
+ return
+ } else {
+ SetFindVoucher(3)
+ SetButtonTerapkan(true)
+ }
+ SetListVoucher((prevList) => [addNewLine, ...prevList])
+ SetActiveVoucher(addNewLine.code)
+ }
+
+ useEffect(() => {
+ SetFindVoucher(null)
+ }, [bottomPopup])
useEffect(() => {
const loadExpedisi = async () => {
@@ -93,57 +157,47 @@ const Checkout = () => {
setExpedisi(dataExpedisi)
}
loadExpedisi()
+ // voucher()
}, [])
- useEffect(() => {
- const loadProducts = async () => {
- let variantIds = ''
- let { query } = router
- if (query?.productId) {
- variantIds = query.productId
- } else {
- const cart = getCart()
- variantIds = _.filter(cart, (o) => o.selected == true)
- .map((o) => o.productId)
- .join(',')
+ const hitungDiscountVoucher = (code) => {
+ let dataVoucherIndex = listVouchers.findIndex((voucher) => voucher.code == code)
+ let dataActiveVoucher = listVouchers[dataVoucherIndex]
+
+ let countDiscount = dataActiveVoucher.discountVoucher
+
+ /*if (dataActiveVoucher.discountType === 'percentage') {
+ countDiscount = cartCheckout?.subtotal * (dataActiveVoucher.discountAmount / 100)
+ if (
+ dataActiveVoucher.maxDiscountAmount > 0 &&
+ countDiscount > dataActiveVoucher.maxDiscountAmount
+ ) {
+ countDiscount = dataActiveVoucher.maxDiscountAmount
}
+ } else {
+ countDiscount = dataActiveVoucher.discountAmount
+ }*/
- const dataProducts = await CartApi({ variantIds })
- const productsWithQuantity = dataProducts?.map((product) => {
- if (product.price.priceDiscount == 0) setPriceCheck(true)
- return {
- ...product,
- quantity: query.quantity
- ? query.quantity
- : getItemCart({ productId: product.id }).quantity
- }
- })
- setProducts(productsWithQuantity)
- }
- loadProducts()
- }, [router])
+ return countDiscount
+ }
useEffect(() => {
- if (products) {
- let calculateTotalAmount = 0
- let calculateTotalDiscountAmount = 0
- let calcuateTotalWeight = 0
- products.forEach((product) => {
- calculateTotalAmount += product.price.price * product.quantity
- calculateTotalDiscountAmount +=
- (product.price.price - product.price.priceDiscount) * product.quantity
- calcuateTotalWeight += product.weight * product.quantity
- if (product.weight == 0) {
- setCheckWeight(true)
- }
- })
- setTotalAmount(calculateTotalAmount)
- setTotalDiscountAmount(calculateTotalDiscountAmount)
- setTotalWeight(calcuateTotalWeight * 1000)
- }
- }, [products])
+ if (!listVouchers) return
+ if (!activeVoucher) return
+
+ const countDiscount = hitungDiscountVoucher(activeVoucher)
+
+ SetDiscountVoucher(countDiscount)
+ }, [activeVoucher, listVouchers])
useEffect(() => {
+ setProducts(cartCheckout?.products)
+ setCheckWeight(cartCheckout?.hasProductWithoutWeight)
+ setTotalWeight(cartCheckout?.totalWeight.g)
+ }, [cartCheckout])
+
+ useEffect(() => {
+ setCheckoutValidation(false)
const loadServiceRajaOngkir = async () => {
const body = {
origin: 2127,
@@ -193,6 +247,9 @@ const Checkout = () => {
useEffect(() => {
if (selectedExpedisi) {
let serviceType = selectedExpedisi.split(',')
+ if (serviceType[0] === 0) {
+ setSelectedExpedisi(0)
+ }
setselectedCarrier(serviceType[0])
setselectedCarrierId(serviceType[1])
setListServiceExpedisi([])
@@ -210,6 +267,21 @@ const Checkout = () => {
toast.error('Maksimal ukuran file adalah 5MB', { position: 'bottom-center' })
return
}
+ if (selectedExpedisi === 0) {
+ setCheckoutValidation(true)
+ if (expedisiValidation.current) {
+ const position = expedisiValidation.current.getBoundingClientRect()
+ window.scrollTo({
+ top: position.top - 300 + window.pageYOffset,
+ behavior: 'smooth'
+ })
+ }
+ return
+ }
+ if (selectedCarrier != 1 && biayaKirim == 0) {
+ toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.')
+ return
+ }
setIsLoading(true)
const productOrder = products.map((product) => ({
product_id: product.id,
@@ -218,10 +290,12 @@ const Checkout = () => {
let data = {
partner_shipping_id: auth.partnerId,
partner_invoice_id: auth.partnerId,
+ user_id: auth.id,
order_line: JSON.stringify(productOrder),
delivery_amount: biayaKirim,
carrier_id: selectedCarrierId,
delivery_service_type: selectedExpedisiService,
+ voucher: activeVoucher,
type: 'sale_order'
}
if (poNumber.current.value) data.po_number = poNumber.current.value
@@ -246,20 +320,288 @@ const Checkout = () => {
gtag('event', 'conversion', {
send_to: 'AW-954540379/nDymCL3BhaQYENvClMcD',
- value:
- totalAmount -
- totalDiscountAmount +
- taxTotal +
- 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
})
}
- const taxTotal = (totalAmount - totalDiscountAmount) * 0.11
+
+ const handlingActivateCode = async () => {
+ VoucherCode(codeVoucher)
+ }
+
+ const handleUseVoucher = async (code, isCheck) => {
+ if (isCheck) {
+ if (code === activeVoucher) {
+ SetActiveVoucher(null)
+ SetDiscountVoucher(0)
+ } else {
+ SetActiveVoucher(code)
+ SetFindVoucher(null)
+ document.getElementById('uniqCode').value = ''
+ SetButtonTerapkan(false)
+ }
+ } else {
+ SetActiveVoucher(code)
+ SetFindVoucher(null)
+ document.getElementById('uniqCode').value = ''
+ SetButtonTerapkan(false)
+ }
+ }
+
+ const onChangeCodeVoucher = async (e) => {
+ SetCodeVoucher(e.target.value)
+ SetButtonTerapkan(false)
+ }
+
+ const [isChecked, setIsChecked] = useState(false)
+
+ const ToggleSwitch = (code) => {
+ setIsChecked(!isChecked)
+ handleUseVoucher(code, !isChecked)
+ }
+
+ // const taxTotal = (totalAmount - totalDiscountAmount - discountVoucher) * 0.11
return (
<>
+ <BottomPopup
+ className='w-full md:!w-[40%] !min-h-[350px]'
+ active={bottomPopup}
+ close={() => SetBottomPopup(false)}
+ title='Gunakan Promo'
+ >
+ <div className='row'>
+ <div className='flex justify-between items-center'>
+ <div className='flex md:w-[70%]'>
+ <input
+ type='text'
+ id='uniqCode'
+ name='uniqCode'
+ className='form-input w-full rounded-md'
+ placeholder='Kode Voucher'
+ autoCapitalize='true'
+ onChange={(e) => onChangeCodeVoucher(e)}
+ />
+ </div>
+ <div className='flex'>
+ <button
+ className='btn-solid-red flex-1 md:flex-none rounded-md'
+ type='button'
+ onClick={() => handlingActivateCode()}
+ disabled={buttonTerapkan}
+ >
+ Terapkan
+ </button>
+ </div>
+ </div>
+ {findCodeVoucher === 3 && activeVoucher === codeVoucher && (
+ <div className='mt-2'>
+ <span className='text-caption-1 mt-2 text-green-600'>
+ Voucher berhasil ditambahkan{' '}
+ </span>
+ </div>
+ )}
+ {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.
+ </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
+ </span>
+ </div>
+ )}
+
+ <hr className='mt-10 my-4 border-gray_r-10' />
+ <div className=''>
+ {!loadingVoucher && listVouchers.length === 0 ? (
+ <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>
+ </div>
+ </div>
+ ) : (
+ <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 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
+ class='w-10 h-10 text-gray-200 dark:text-gray-600'
+ aria-hidden='true'
+ xmlns='http://www.w3.org/2000/svg'
+ fill='currentColor'
+ viewBox='0 0 16 20'
+ >
+ <path d='M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z' />
+ <path d='M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z' />
+ </svg>
+ </div>
+ <div>
+ <div class='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-72 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>
+ </div>
+ </div>
+ <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
+ class='w-10 h-10 text-gray-200 dark:text-gray-600'
+ aria-hidden='true'
+ xmlns='http://www.w3.org/2000/svg'
+ fill='currentColor'
+ viewBox='0 0 16 20'
+ >
+ <path d='M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z' />
+ <path d='M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z' />
+ </svg>
+ </div>
+ <div>
+ <div class='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-72 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>
+ </div>
+ </div>
+ </>
+ )}
+ {!loadingVoucher &&
+ listVouchers?.map((item) => (
+ <div key={item.id} className='relative'>
+ {item.canApply === false && (
+ <div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0 z-50' />
+ )}
+
+ <div className={`border border-solid mb-5 w-full hover:cursor-pointer p-4 `}>
+ <div className={`flex gap-x-3`}>
+ <div className='hidden md:w-[250px] md:block'>
+ <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>
+ </div>
+ </div>
+ <div className='flex justify-end'>
+ <label class='relative inline-flex items-center cursor-pointer'>
+ <input
+ type='checkbox'
+ value=''
+ class='sr-only peer'
+ 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>
+ </label>
+ </div>
+ </div>
+ <hr className='mt-3 my-4 border-gray_r-8' />
+ <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>
+ </p>
+ <p className='text-sm md:text-xs'>
+ {activeVoucher === item.code && (
+ <span className=' text-green-600'>Voucher digunakan </span>
+ )}
+ </p>
+ </div>
+ </div>
+ </div>
+ <div className='mt-3'>
+ <p className='text-justify text-sm '>
+ {!item.canApply &&
+ item.applyStatus === 'MPA' &&
+ item.manufactureNames != '' && (
+ <p>
+ Tambah produk{' '}
+ <span className='text-red-500'>{item.manufactureNames}</span> senilai{' '}
+ <span className='text-red-500'>
+ {currencyFormat(item.differenceToApply)}
+ </span>{' '}
+ untuk pakai promo ini
+ </p>
+ )}
+ {!item.canApply &&
+ item.applyStatus === 'MPA' &&
+ item.manufactureNames === '' && (
+ <p>
+ Tambah{' '}
+ <span className='text-red-500'>
+ {currencyFormat(item.differenceToApply)}
+ </span>{' '}
+ untuk pakai promo ini{' '}
+ </p>
+ )}
+ {!item.canApply && item.applyStatus === 'UM' && (
+ <p>
+ Tambah produk{' '}
+ <span className='text-red-500'>{item.manufactureNames}</span> senilai{' '}
+ <span className='text-red-500'>
+ {currencyFormat(item.minPurchaseAmount)}
+ </span>{' '}
+ untuk pakai promo ini
+ </p>
+ )}
+ {item.canApply && (
+ <p>
+ Potensi potongan sebesar{' '}
+ <span className='text-red-500'>
+ {currencyFormat(item.discountVoucher)}
+ </span>
+ </p>
+ )}
+ {/* {item.canApply === false
+ ? 'Tambah ' +
+ currencyFormat(item.differenceToApply) +
+ ' untuk pakai promo ini'
+ : 'Potensi potongan sebesar ' +
+ currencyFormat(hitungDiscountVoucher(item.code))} */}
+ </p>
+ <hr className='mt-2 my-4 border-gray_r-8' />
+ <div className='flex items-center'>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='w-5 text-black'
+ >
+ <path
+ d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ <span className='text-left ml-3 text-sm '>
+ Berakhir dalam <span className='text-red-600'>{item.remainingTime}</span>{' '}
+ lagi{' '}
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ </BottomPopup>
<MobileView>
<div className='p-4'>
<Alert type='info' className='text-caption-2 flex gap-x-3'>
@@ -298,6 +640,8 @@ const Checkout = () => {
listExpedisi={listExpedisi}
setSelectedExpedisi={setSelectedExpedisi}
checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
/>
<Divider />
<SectionListService
@@ -317,41 +661,136 @@ const Checkout = () => {
<div className='text-gray_r-11 text-caption-1'>{products?.length} Barang</div>
</div>
<hr className='my-4 border-gray_r-6' />
- <div className='flex flex-col gap-y-4'>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Total Belanja</div>
- <div>{currencyFormat(totalAmount)}</div>
- </div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Total Diskon</div>
- <div className='text-danger-500'>- {currencyFormat(totalDiscountAmount)}</div>
+ {!cartCheckout ? (
+ <div
+ role='status'
+ class='max-w-md space-y-3 divide-y divide-gray-200 animate-pulse dark:divide-gray-700 dark:border-gray-700'
+ >
+ <div class='flex items-center justify-between'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Subtotal</div>
- <div>{currencyFormat(totalAmount - totalDiscountAmount)}</div>
+ ) : (
+ <div className='flex flex-col gap-y-4'>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Total Belanja</div>
+ <div>{currencyFormat(cartCheckout?.totalPurchase)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Produk</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(cartCheckout?.totalDiscount)}
+ </div>
+ </div>
+ {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>
+ )}
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Subtotal</div>
+ <div>{currencyFormat(cartCheckout?.subtotal)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>PPN 11%</div>
+ <div>{currencyFormat(cartCheckout?.tax)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <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>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>PPN 11%</div>
- <div>{currencyFormat(taxTotal)}</div>
+ )}
+
+ <hr className='my-4 border-gray_r-6' />
+ {!cartCheckout ? (
+ <div className='flex gap-x-2 justify-between mb-4'>
+ <div>
+ {' '}
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div className='font-semibold text-gray_r-12'>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>
- Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
+ ) : (
+ <div className='flex gap-x-2 justify-between mb-4'>
+ <div>Grand Total</div>
+ <div className='font-semibold text-gray_r-12'>
+ {currencyFormat(
+ cartCheckout?.grandTotal + Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}</div>
</div>
- </div>
+ )}
+
<hr className='my-4 border-gray_r-6' />
- <div className='flex gap-x-2 justify-between mb-4'>
- <div>Grand Total</div>
- <div className='font-semibold text-gray_r-12'>
- {currencyFormat(
- totalAmount -
- totalDiscountAmount +
- taxTotal +
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
- </div>
+
+ <div className='mt-4 mb-4'>
+ <button
+ type='button'
+ onClick={() => {
+ 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%]'
+ >
+ <div className='flex items-center justify-between gap-x-3'>
+ <span className='text-left text-gray_r-9'>
+ <Image
+ src={'/images/DISKONICON.svg'}
+ alt={''}
+ className='object-contain object-center h-6 rounded-md'
+ />
+ </span>
+ {activeVoucher ? (
+ <div className=''>
+ <div className='text-left text-sm text-black font-semibold'>
+ Potongan Senilai {currencyFormat(discountVoucher)}
+ </div>
+ <div className='text-left mt-1 text-green-600 text-xs'>
+ Voucher berhasil digunakan
+ </div>
+ </div>
+ ) : (
+ <span className='text-left text-sm text-gray_r-9'>
+ Hemat belanja dengan promo
+ </span>
+ )}
+ </div>
+
+ <span className='text-left ml-1 text-gray_r-9'>{'>'}</span>
+ </button>
</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'>
@@ -392,9 +831,7 @@ const Checkout = () => {
<button
className='flex-1 btn-yellow'
onClick={checkout}
- disabled={
- isLoading || !products || products?.length == 0 || priceCheck || selectedExpedisi == 0
- }
+ disabled={isLoading || !products || products?.length == 0 || priceCheck}
>
{isLoading ? 'Loading...' : 'Lanjut Pembayaran'}
</button>
@@ -437,6 +874,8 @@ const Checkout = () => {
listExpedisi={listExpedisi}
setSelectedExpedisi={setSelectedExpedisi}
checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
/>
<Divider />
<SectionListService
@@ -456,75 +895,149 @@ const Checkout = () => {
</tr>
</thead>
<tbody>
- {products?.map((product) => (
- <tr key={product.id}>
- <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'
- />
+ {!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>
- <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?.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)}
+ </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='badge-solid-red'>
- {product?.price?.discountPercentage}%
+ <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>
- </div>
- )}
- <div className='font-normal mt-1'>
- {product.price.priceDiscount > 0
- ? currencyFormat(product?.price?.priceDiscount)
- : 'Call For Price'}
- </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 Price{' '}
- </a>
- )}
- </div>
- </td>
- </tr>
- ))}
+ </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?.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 className='badge-solid-red'>
+ {product?.price?.discountPercentage}%
+ </div>
+ </div>
+ )}
+ <div className='font-normal mt-1'>
+ {product.price.priceDiscount > 0
+ ? currencyFormat(product?.price?.priceDiscount)
+ : 'Call For Price'}
+ </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 Price{' '}
+ </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>
@@ -538,46 +1051,141 @@ const Checkout = () => {
</div>
<hr className='my-4 border-gray_r-6' />
-
- <div className='flex flex-col gap-y-4'>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Total Belanja</div>
- <div>{currencyFormat(totalAmount)}</div>
- </div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Total Diskon</div>
- <div className='text-danger-500'>- {currencyFormat(totalDiscountAmount)}</div>
+ {!cartCheckout ? (
+ <div
+ role='status'
+ class='max-w-md space-y-3 divide-y divide-gray-200 animate-pulse dark:divide-gray-700 dark:border-gray-700'
+ >
+ <div class='flex items-center justify-between'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
+ <div class='flex items-center justify-between pt-4'>
+ <div>
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Subtotal</div>
- <div>{currencyFormat(totalAmount - totalDiscountAmount)}</div>
+ ) : (
+ <div className='flex flex-col gap-y-4'>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Total Belanja</div>
+ <div>{currencyFormat(cartCheckout?.totalPurchase)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Produk</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(cartCheckout?.totalDiscount)}
+ </div>
+ </div>
+ {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>
+ )}
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Subtotal</div>
+ <div>{currencyFormat(cartCheckout?.subtotal)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>PPN 11%</div>
+ <div>{currencyFormat(cartCheckout?.tax)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <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>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>PPN 11%</div>
- <div>{currencyFormat(taxTotal)}</div>
+ )}
+
+ <hr className='my-4 border-gray_r-6' />
+ {!cartCheckout ? (
+ <div className='flex gap-x-2 justify-between mb-4'>
+ <div>
+ {' '}
+ <div class='w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700'></div>
+ </div>
+ <div className='font-semibold text-gray_r-12'>
+ <div class='h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12'></div>
+ </div>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>
- Biaya Kirim
- <p className='text-xs mt-3'>{etdFix}</p>
+ ) : (
+ <div className='flex gap-x-2 justify-between mb-4'>
+ <div>Grand Total</div>
+ <div className='font-semibold text-gray_r-12'>
+ {currencyFormat(
+ cartCheckout?.grandTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}</div>
</div>
- </div>
+ )}
<hr className='my-4 border-gray_r-6' />
- <div className='flex gap-x-2 justify-between mb-4'>
- <div>Grand Total</div>
- <div className='font-semibold text-gray_r-12'>
- {currencyFormat(
- totalAmount -
- totalDiscountAmount +
- taxTotal +
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
- </div>
+ <div className='mt-4 mb-4'>
+ <button
+ type='button'
+ onClick={() => {
+ 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%]'
+ >
+ <div className='flex items-center justify-between gap-x-3'>
+ <span className='text-left text-gray_r-9'>
+ <Image
+ src={'/images/DISKONICON.svg'}
+ alt={''}
+ className='object-contain object-center h-6 w-full rounded-md'
+ />
+ </span>
+ {activeVoucher ? (
+ <div className=''>
+ <div className='text-left text-sm text-black font-semibold'>
+ Hemat {currencyFormat(discountVoucher)}
+ </div>
+ <div className='text-left mt-1 text-green-600 text-xs'>
+ Voucher berhasil digunakan
+ </div>
+ </div>
+ ) : (
+ <span className='text-left text-sm text-gray_r-9'>
+ Hemat belanja dengan promo
+ </span>
+ )}
+ </div>
+ <span className='text-left ml-1 text-gray_r-9'>{'>'}</span>
+ </button>
</div>
+
<p className='text-caption-2 text-gray_r-11 leading-5'>
Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '}
<Link href='/syarat-ketentuan' className='inline font-normal'>
@@ -614,14 +1222,7 @@ const Checkout = () => {
<button
className='w-full btn-yellow mt-4'
onClick={checkout}
- disabled={
- isLoading ||
- !products ||
- products?.length == 0 ||
- priceCheck ||
- selectedCarrier == 0 ||
- (selectedCarrier != 1 && biayaKirim == 0)
- }
+ disabled={isLoading || !products || products?.length == 0 || priceCheck}
>
{isLoading ? 'Loading...' : 'Lanjut Pembayaran'}
</button>
@@ -684,14 +1285,25 @@ const SectionValidation = ({ address }) =>
</BottomPopup>
)
-const SectionExpedisi = ({ address, listExpedisi, setSelectedExpedisi, checkWeigth }) =>
+const SectionExpedisi = ({
+ address,
+ listExpedisi,
+ setSelectedExpedisi,
+ checkWeigth,
+ checkoutValidation,
+ expedisiValidation
+}) =>
address?.rajaongkirCityId > 0 && (
- <div className='p-4'>
+ <div className='p-4' ref={expedisiValidation}>
<div className='flex justify-between items-center'>
<div className='font-medium'>Pilih Expedisi : </div>
- <div>
- <select className='form-input' onChange={(e) => setSelectedExpedisi(e.target.value)}>
- <option value='0,0'>Pengiriman</option>
+ <div className='w-[250px]'>
+ <select
+ className={`form-input ${checkoutValidation ? 'border-red-500 shake' : ''}`}
+ onChange={(e) => setSelectedExpedisi(e.target.value)}
+ required
+ >
+ <option value='0,0'>Pilih Pengiriman</option>
<option value='1,32'>SELF PICKUP</option>
{checkWeigth != true &&
listExpedisi.map((expedisi) => (
@@ -705,7 +1317,15 @@ const SectionExpedisi = ({ address, listExpedisi, setSelectedExpedisi, checkWeig
</option>
))}
</select>
+ {checkoutValidation && (
+ <span className='text-sm text-red-500'>*silahkan pilih expedisi</span>
+ )}
</div>
+ <style jsx>{`
+ .shake {
+ animation: shake 0.4s ease-in-out;
+ }
+ `}</style>
</div>
{checkWeigth == true && (
<p className='mt-4 text-gray_r-11 leading-6'>
diff --git a/src/lib/checkout/components/CheckoutOld.jsx b/src/lib/checkout/components/CheckoutOld.jsx
new file mode 100644
index 00000000..088b641b
--- /dev/null
+++ b/src/lib/checkout/components/CheckoutOld.jsx
@@ -0,0 +1,811 @@
+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 CartApi from '@/lib/cart/api/CartApi'
+import { ExclamationCircleIcon } from '@heroicons/react/24/outline'
+import React, { useEffect, useRef, useState } from 'react'
+import _ from 'lodash'
+import { deleteItemCart, getCart, getItemCart } from '@/core/utils/cart'
+import currencyFormat from '@/core/utils/currencyFormat'
+import { toast } from 'react-hot-toast'
+import getFileBase64 from '@/core/utils/getFileBase64'
+import checkoutApi from '../api/checkoutApi'
+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 { createSlug } from '@/core/utils/slug'
+import { Button, Modal } from 'flowbite-react'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+
+const SELF_PICKUP_ID = 32
+
+const Checkout = () => {
+ const router = useRouter()
+ const auth = useAuth()
+ const [selectedAddress, setSelectedAddress] = useState({
+ shipping: null,
+ invoicing: null
+ })
+ const [addresses, setAddresses] = useState(null)
+
+ useEffect(() => {
+ if (!auth) return
+
+ const getAddresses = async () => {
+ const dataAddresses = await addressesApi()
+ setAddresses(dataAddresses)
+ }
+
+ getAddresses()
+ }, [auth])
+
+ useEffect(() => {
+ if (!addresses) return
+
+ const matchAddress = (key) => {
+ const addressToMatch = getItemAddress(key)
+ const foundAddress = addresses.filter((address) => address.id == addressToMatch)
+ if (foundAddress.length > 0) {
+ return foundAddress[0]
+ }
+ return addresses[0]
+ }
+
+ setSelectedAddress({
+ shipping: matchAddress('shipping'),
+ invoicing: matchAddress('invoicing')
+ })
+ }, [addresses])
+
+ const [products, setProducts] = useState(null)
+ const [totalAmount, setTotalAmount] = useState(0)
+ const [totalDiscountAmount, setTotalDiscountAmount] = useState(0)
+ 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)
+
+ useEffect(() => {
+ const loadExpedisi = async () => {
+ let dataExpedisi = await ExpedisiList()
+ dataExpedisi = dataExpedisi.map((expedisi) => ({
+ value: expedisi.id,
+ label: expedisi.name,
+ carrierId: expedisi.deliveryCarrierId
+ }))
+ setExpedisi(dataExpedisi)
+ }
+ loadExpedisi()
+ }, [])
+
+ useEffect(() => {
+ const loadProducts = async () => {
+ let variantIds = ''
+ let { query } = router
+ if (query?.productId) {
+ variantIds = query.productId
+ } else {
+ const cart = getCart()
+ variantIds = _.filter(cart, (o) => o.selected == true)
+ .map((o) => o.productId)
+ .join(',')
+ }
+
+ const dataProducts = await CartApi({ variantIds })
+ const productsWithQuantity = dataProducts?.map((product) => {
+ if (product.price.priceDiscount == 0) setPriceCheck(true)
+ return {
+ ...product,
+ quantity: query.quantity
+ ? query.quantity
+ : getItemCart({ productId: product.id }).quantity
+ }
+ })
+ setProducts(productsWithQuantity)
+ }
+ loadProducts()
+ }, [router])
+
+ useEffect(() => {
+ if (products) {
+ let calculateTotalAmount = 0
+ let calculateTotalDiscountAmount = 0
+ let calcuateTotalWeight = 0
+ products.forEach((product) => {
+ calculateTotalAmount += product.price.price * product.quantity
+ calculateTotalDiscountAmount +=
+ (product.price.price - product.price.priceDiscount) * product.quantity
+ calcuateTotalWeight += product.weight * product.quantity
+ if (product.weight == 0) {
+ setCheckWeight(true)
+ }
+ })
+ setTotalAmount(calculateTotalAmount)
+ setTotalDiscountAmount(calculateTotalDiscountAmount)
+ setTotalWeight(calcuateTotalWeight * 1000)
+ }
+ }, [products])
+
+ useEffect(() => {
+ const loadServiceRajaOngkir = async () => {
+ 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))
+ setListServiceExpedisi(dataService.data[0].costs)
+ if (dataService.data[0].costs[0]) {
+ 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')
+ } else {
+ toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.')
+ }
+ }
+ if (selectedCarrier != 0 && selectedCarrier != 1 && totalWeight > 0) {
+ loadServiceRajaOngkir()
+ } else {
+ setListServiceExpedisi()
+ setBiayaKirim(0)
+ setselectedExpedisiService()
+ setEtd()
+ }
+ }, [selectedCarrier, selectedAddress, totalWeight])
+
+ useEffect(() => {
+ if (selectedServiceType) {
+ let serviceType = selectedServiceType.split(',')
+ setBiayaKirim(serviceType[0])
+ setselectedExpedisiService(serviceType[1])
+ setEtd(serviceType[2])
+ }
+ }, [selectedServiceType])
+
+ useEffect(() => {
+ if (etd) setEtdFix(calculateEstimatedArrival(etd))
+ }, [etd])
+
+ useEffect(() => {
+ if (selectedExpedisi) {
+ let serviceType = selectedExpedisi.split(',')
+ setselectedCarrier(serviceType[0])
+ setselectedCarrierId(serviceType[1])
+ setListServiceExpedisi([])
+ }
+ }, [selectedExpedisi])
+
+ const poNumber = useRef(null)
+ const poFile = useRef(null)
+
+ const [isLoading, setIsLoading] = useState(false)
+
+ const checkout = async () => {
+ const file = poFile.current.files[0]
+ if (typeof file !== 'undefined' && file.size > 5000000) {
+ toast.error('Maksimal ukuran file adalah 5MB', { position: 'bottom-center' })
+ return
+ }
+ setIsLoading(true)
+ const productOrder = products.map((product) => ({
+ product_id: product.id,
+ quantity: product.quantity
+ }))
+ let data = {
+ partner_shipping_id: auth.partnerId,
+ partner_invoice_id: auth.partnerId,
+ order_line: JSON.stringify(productOrder),
+ delivery_amount: biayaKirim,
+ carrier_id: selectedCarrierId,
+ delivery_service_type: selectedExpedisiService,
+ type: 'sale_order'
+ }
+ 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 })
+ if (!isCheckouted?.id) {
+ toast.error('Gagal melakukan transaksi, terjadi kesalahan internal')
+ return
+ }
+
+ 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
+ }
+ const taxTotal = (totalAmount - totalDiscountAmount) * 0.11
+
+ return (
+ <>
+ <MobileView>
+ <div className='p-4'>
+ <Alert type='info' className='text-caption-2 flex gap-x-3'>
+ <div>
+ <ExclamationCircleIcon className='w-7 text-blue-700' />
+ </div>
+ <span className='leading-5'>
+ Jika mengalami kesulitan dalam melakukan pembelian di website Indoteknik. Hubungi kami
+ disini
+ </span>
+ </Alert>
+ </div>
+
+ <Divider />
+
+ {selectedCarrierId == SELF_PICKUP_ID && <PickupAddress label='Alamat Pickup' />}
+ {selectedCarrierId != SELF_PICKUP_ID && (
+ <>
+ <SectionAddress
+ address={selectedAddress.shipping}
+ label='Alamat Pengiriman'
+ url='/my/address?select=shipping'
+ />
+ <Divider />
+ <SectionAddress
+ address={selectedAddress.invoicing}
+ label='Alamat Penagihan'
+ url='/my/address?select=invoice'
+ />
+ </>
+ )}
+ <Divider />
+ <SectionValidation address={selectedAddress.invoicing} />
+ <SectionExpedisi
+ address={selectedAddress.shipping}
+ listExpedisi={listExpedisi}
+ setSelectedExpedisi={setSelectedExpedisi}
+ checkWeigth={checkWeigth}
+ />
+ <Divider />
+ <SectionListService
+ listserviceExpedisi={listserviceExpedisi}
+ setSelectedServiceType={setSelectedServiceType}
+ />
+
+ <div className='p-4 flex flex-col gap-y-4'>
+ {products && <VariantGroupCard openOnClick={false} variants={products} />}
+ </div>
+
+ <Divider />
+
+ <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' />
+ <div className='flex flex-col gap-y-4'>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Total Belanja</div>
+ <div>{currencyFormat(totalAmount)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Total Diskon</div>
+ <div className='text-danger-500'>- {currencyFormat(totalDiscountAmount)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Subtotal</div>
+ <div>{currencyFormat(totalAmount - totalDiscountAmount)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>PPN 11%</div>
+ <div>{currencyFormat(taxTotal)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <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>
+ </div>
+ <hr className='my-4 border-gray_r-6' />
+ <div className='flex gap-x-2 justify-between mb-4'>
+ <div>Grand Total</div>
+ <div className='font-semibold text-gray_r-12'>
+ {currencyFormat(
+ totalAmount -
+ totalDiscountAmount +
+ taxTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
+ </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{' '}
+ <Link href='/syarat-ketentuan' className='inline font-normal'>
+ Syarat & Ketentuan
+ </Link>{' '}
+ yang berlaku
+ </p>
+ </div>
+
+ <Divider />
+
+ <div className='p-4'>
+ <div className='font-medium'>Purchase Order</div>
+
+ <div className='mt-4 flex gap-x-3'>
+ <div className='w-6/12'>
+ <label className='form-label font-normal'>Dokumen PO</label>
+ <input
+ type='file'
+ className='form-input mt-2 h-12'
+ accept='image/*,application/pdf'
+ ref={poFile}
+ />
+ </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} />
+ </div>
+ </div>
+ <p className='text-caption-2 text-gray_r-11 mt-2'>Ukuran dokumen PO Maksimal 5MB</p>
+ </div>
+
+ <Divider />
+
+ <div className='flex gap-x-3 p-4'>
+ <button
+ className='flex-1 btn-yellow'
+ onClick={checkout}
+ disabled={
+ isLoading || !products || products?.length == 0 || priceCheck || selectedExpedisi == 0
+ }
+ >
+ {isLoading ? 'Loading...' : 'Lanjut Pembayaran'}
+ </button>
+ </div>
+ {priceCheck && (
+ <div className='px-4 mb-4'>
+ <span className='text-danger-500'>
+ *) Terdapat produk yang belum memiliki harga,{' '}
+ <a href={whatsappUrl()} className='underline'>
+ Hubungi Kami untuk meminta harga.
+ </a>
+ </span>
+ </div>
+ )}
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto py-10 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 && (
+ <>
+ <SectionAddress
+ address={selectedAddress.shipping}
+ label='Alamat Pengiriman'
+ url='/my/address?select=shipping'
+ />
+ <Divider />
+ <SectionAddress
+ address={selectedAddress.invoicing}
+ label='Alamat Penagihan'
+ url='/my/address?select=invoice'
+ />
+ </>
+ )}
+ <Divider />
+ <SectionValidation address={selectedAddress.invoicing} />
+ <SectionExpedisi
+ address={selectedAddress.shipping}
+ listExpedisi={listExpedisi}
+ setSelectedExpedisi={setSelectedExpedisi}
+ checkWeigth={checkWeigth}
+ />
+ <Divider />
+ <SectionListService
+ listserviceExpedisi={listserviceExpedisi}
+ setSelectedServiceType={setSelectedServiceType}
+ />
+
+ <div className='p-4'>
+ <div className='font-medium'>Detail Pesanan</div>
+ <table className='table-checkout'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {products?.map((product) => (
+ <tr key={product.id}>
+ <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?.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 className='badge-solid-red'>
+ {product?.price?.discountPercentage}%
+ </div>
+ </div>
+ )}
+ <div className='font-normal mt-1'>
+ {product.price.priceDiscount > 0
+ ? currencyFormat(product?.price?.priceDiscount)
+ : 'Call For Price'}
+ </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 Price{' '}
+ </a>
+ )}
+ </div>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div className='w-1/4 pl-4'>
+ <div className='sticky top-48 border border-gray_r-6 bg-white rounded 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' />
+
+ <div className='flex flex-col gap-y-4'>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Total Belanja</div>
+ <div>{currencyFormat(totalAmount)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Total Diskon</div>
+ <div className='text-danger-500'>- {currencyFormat(totalDiscountAmount)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Subtotal</div>
+ <div>{currencyFormat(totalAmount - totalDiscountAmount)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>PPN 11%</div>
+ <div>{currencyFormat(taxTotal)}</div>
+ </div>
+ <div className='flex gap-x-2 justify-between'>
+ <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>
+ </div>
+
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-2 justify-between mb-4'>
+ <div>Grand Total</div>
+ <div className='font-semibold text-gray_r-12'>
+ {currencyFormat(
+ totalAmount -
+ totalDiscountAmount +
+ taxTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
+ </div>
+ <p className='text-caption-2 text-gray_r-11 leading-5'>
+ Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '}
+ <Link href='/syarat-ketentuan' className='inline font-normal'>
+ Syarat & Ketentuan
+ </Link>{' '}
+ yang berlaku
+ </p>
+
+ <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>
+ </div>
+
+ <div className='mt-4 flex gap-x-3'>
+ <div className='w-6/12'>
+ <label className='form-label font-normal'>Dokumen PO</label>
+ <input
+ type='file'
+ className='form-input mt-2 h-12'
+ accept='image/*,application/pdf'
+ ref={poFile}
+ />
+ </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} />
+ </div>
+ </div>
+ <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 ||
+ selectedCarrier == 0 ||
+ (selectedCarrier != 1 && biayaKirim == 0)
+ }
+ >
+ {isLoading ? 'Loading...' : 'Lanjut Pembayaran'}
+ </button>
+ {priceCheck && (
+ <div className='mt-4'>
+ <span className='text-danger-500'>
+ *) Terdapat produk yang belum memiliki harga,{' '}
+ <a href={whatsappUrl()} className='underline'>
+ Hubungi Kami untuk meminta harga.
+ </a>
+ </span>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ </DesktopView>
+ </>
+ )
+}
+
+const SectionAddress = ({ address, label, url }) => (
+ <div className='p-4'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>{label}</div>
+ <Link className='text-caption-1' href={url}>
+ Pilih Alamat Lain
+ </Link>
+ </div>
+
+ {address && (
+ <div className='mt-4 text-caption-1'>
+ <div className='badge-red mb-2'>
+ {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>
+ <p className='mt-1 text-gray_r-11'>
+ {address.street}, {address?.city?.name}
+ </p>
+ </div>
+ )}
+ </div>
+)
+
+const SectionValidation = ({ address }) =>
+ address?.rajaongkirCityId == 0 && (
+ <BottomPopup active={true} title='Update Alamat'>
+ <div className='leading-7 text-gray_r-12/80'>
+ Mohon untuk memperbarui alamat Anda dengan mengklik tombol di bawah ini.{' '}
+ </div>
+ <div className='flex justify-center mt-6 gap-x-4'>
+ <Link
+ className='btn-solid-red w-full md:w-fit text-white'
+ href={`/my/address/${address?.id}/edit`}
+ >
+ Update Alamat
+ </Link>
+ </div>
+ </BottomPopup>
+ )
+
+const SectionExpedisi = ({ address, listExpedisi, setSelectedExpedisi, checkWeigth }) =>
+ address?.rajaongkirCityId > 0 && (
+ <div className='p-4'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>Pilih Expedisi : </div>
+ <div>
+ <select className='form-input' onChange={(e) => setSelectedExpedisi(e.target.value)}>
+ <option value='0,0'>Pengiriman</option>
+ <option value='1,32'>SELF PICKUP</option>
+ {checkWeigth != true &&
+ listExpedisi.map((expedisi) => (
+ <option
+ disabled={checkWeigth}
+ value={expedisi.label + ',' + expedisi.carrierId}
+ key={expedisi.value}
+ >
+ {' '}
+ {expedisi.label.toUpperCase()}{' '}
+ </option>
+ ))}
+ </select>
+ </div>
+ </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{' '}
+ <a
+ className='text-danger-500 inline'
+ href='https://api.whatsapp.com/send?phone=628128080622'
+ >
+ tautan ini
+ </a>
+ </p>
+ )}
+ </div>
+ )
+
+const SectionListService = ({ listserviceExpedisi, setSelectedServiceType }) =>
+ listserviceExpedisi?.length > 0 && (
+ <>
+ <div className='p-4'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>Service Type Expedisi : </div>
+ <div>
+ <select className='form-input' onChange={(e) => setSelectedServiceType(e.target.value)}>
+ {listserviceExpedisi.map((service) => (
+ <option
+ value={
+ service.cost[0].value +
+ ',' +
+ service.description +
+ '-' +
+ service.service +
+ ',' +
+ extractDuration(service.cost[0].etd)
+ }
+ key={service.service}
+ >
+ {' '}
+ {service.description} - {service.service.toUpperCase()}
+ {extractDuration(service.cost[0].etd) &&
+ ` (Estimasi Tiba ${extractDuration(service.cost[0].etd)} Hari)`}
+ </option>
+ ))}
+ </select>
+ </div>
+ </div>
+ </div>
+ <Divider />
+ </>
+ )
+
+function addDays(date, days) {
+ 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}`
+}
+
+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 etdText = `*Estimasi tiba ${formatDate(from)}`
+
+ if (estimationDate[1] > estimationDate[0]) {
+ etdText += ` - ${formatDate(to)}`
+ }
+
+ return etdText
+ }
+
+ return ''
+}
+
+const extractDuration = (text) => {
+ 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])
+
+ if (min === max) {
+ return min.toString()
+ }
+
+ return matches[0]
+ }
+
+ return ''
+}
+
+const PickupAddress = ({ label }) => (
+ <div className='p-4'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>{label}</div>
+ </div>
+ <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
+ </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
diff --git a/src/lib/product/components/Product/Product.jsx b/src/lib/product/components/Product/Product.jsx
index 0547c36e..54490c26 100644
--- a/src/lib/product/components/Product/Product.jsx
+++ b/src/lib/product/components/Product/Product.jsx
@@ -55,7 +55,7 @@ const Product = ({ product, isVariant = false }) => {
return (
<>
<ProductMobile product={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
- <ProductDesktop product={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
+ <ProductDesktop products={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
</>
)
}
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index d8ea3c50..aef69ecd 100644
--- a/src/lib/product/components/Product/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -1,4 +1,5 @@
import Image from '@/core/components/elements/Image/Image'
+import ImageNext from 'next/image'
import Link from '@/core/components/elements/Link/Link'
import DesktopView from '@/core/components/views/DesktopView'
import currencyFormat from '@/core/utils/currencyFormat'
@@ -14,14 +15,22 @@ import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
import ProductCard from '../ProductCard'
import productSimilarApi from '../../api/productSimilarApi'
import whatsappUrl from '@/core/utils/whatsappUrl'
-import { gtagAddToCart } from '@/core/utils/googleTag'
+import PromotionType from '@/lib/promotinProgram/components/PromotionType'
+import useAuth from '@/core/hooks/useAuth'
-const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
+const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
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 [addCartAlert, setAddCartAlert] = useState(false)
+ const [promotionType, setPromotionType] = useState(false)
+ const [promotionActiveId, setPromotionActiveId] = useState(null)
+ const [selectVariantPromoActive, setSelectVariantPromoActive] = useState(null)
const getLowestPrice = useCallback(() => {
const prices = product.variants.map((variant) => variant.price)
@@ -41,9 +50,14 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
const variantQuantityRefs = useRef([])
const setVariantQuantityRef = (variantId) => (element) => {
+ if (element) {
+ let variantIndex = product.variants.findIndex((varian) => varian.id == variantId)
+ product.variants[variantIndex].quantity = element.value
+ }
variantQuantityRefs.current[variantId] = element
}
+
const validQuantity = (quantity) => {
let isValid = true
if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) {
@@ -53,22 +67,38 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
return isValid
}
- const handleAddToCart = (variant) => {
- const quantity = variantQuantityRefs.current[variant.id].value
+ const handleAddToCart = (variantId) => {
+ if (!auth) {
+ router.push(`/login?next=/shop/product/${slug}`)
+ return
+ }
+ const quantity = variantQuantityRefs.current[variantId].value
+
if (!validQuantity(quantity)) return
- gtagAddToCart(variant, quantity)
- updateItemCart({
- productId: variant.id,
- quantity,
- selected: true
- })
+ if(product.variants.length > 1){
+ let variantIndex = product.variants.findIndex((varian) => varian.id == variantId)
+ updateItemCart({
+ productId: variantId,
+ quantity,
+ programLineId: product.variants[variantIndex].programActive,
+ selected: true
+ })
+ }else{
+ updateItemCart({
+ productId: variantId,
+ quantity,
+ programLineId: promotionActiveId,
+ selected: true
+ })
+ }
+
setAddCartAlert(true)
}
const handleBuy = (variant) => {
- const quantity = variantQuantityRefs.current[variant.id].value
+ const quantity = variantQuantityRefs.current[variant].value
if (!validQuantity(quantity)) return
- router.push(`/shop/checkout?productId=${variant.id}&quantity=${quantity}`)
+ router.push(`/shop/checkout?productId=${variant}&quantity=${quantity}`)
}
const variantSectionRef = useRef(null)
@@ -82,6 +112,11 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
}
}
+ const handlePromoClick = (variantId) => {
+ setSelectVariantPromoActive(variantId)
+ setPromotionType(true)
+ }
+
const productSimilarQuery = [
product?.name,
`fq=-product_id_i:${product.id}`,
@@ -158,6 +193,19 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
)}
</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>
@@ -248,18 +296,19 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
type='number'
className='form-input w-16 py-2 text-center bg-gray_r-1'
ref={setVariantQuantityRef(product.variants[0].id)}
+ onChange={setVariantQuantityRef(product.variants[0].id)}
defaultValue={1}
/>
<button
type='button'
- onClick={() => handleAddToCart(product.variants[0])}
+ onClick={() => handleAddToCart(product.variants[0].id)}
className='flex-1 py-2 btn-yellow'
>
Keranjang
</button>
<button
type='button'
- onClick={() => handleBuy(product.variants[0])}
+ onClick={() => handleBuy(product.variants[0].id)}
className='flex-1 py-2 btn-solid-red'
>
Beli
@@ -309,7 +358,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
</tr>
</thead>
<tbody>
- {product.variants.map((variant) => (
+ {product.variants.map((variant, index) => (
<tr key={variant.id}>
<td>{variant.code}</td>
<td>{variant.attributes.join(', ') || '-'}</td>
@@ -347,10 +396,41 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
type='number'
className='form-input w-16 py-2 text-center bg-gray_r-1'
ref={setVariantQuantityRef(variant.id)}
+ onChange={setVariantQuantityRef(variant.id)}
defaultValue={1}
/>
</td>
<td className='flex gap-x-3'>
+ {/* {variant.programActive ? (
+ <ImageNext
+ src='/images/noun-applied-check2.svg'
+ alt=''
+ height={60}
+ width={60}
+ onClick={() => handlePromoClick(variant.id)}
+ className='cursor-pointer'
+ ></ImageNext>
+ ) : (
+ variant.hasProgram ? ( <div className='w-[60px] flex justify-center'>
+ <ImageNext
+ src='/images/noun-discount-5796402.svg'
+ alt=''
+ height={30}
+ width={30}
+ onClick={() => handlePromoClick(variant.id)}
+ className='cursor-pointer'
+ ></ImageNext>
+ </div>):( <div className='w-[60px] flex justify-center'>
+ <ImageNext
+ src='/images/noun-discount-57964023.svg'
+ alt=''
+ height={30}
+ width={30}
+ className='cursor-pointer'
+ ></ImageNext>
+ </div>)
+
+ )} */}
<button
type='button'
onClick={() => handleAddToCart(variant)}
@@ -380,7 +460,24 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
<ProductSimilar query={productSimilarQuery} />
</LazyLoad>
</div>
-
+ <BottomPopup
+ className=' !h-[75%]'
+ title='Pakai Promo'
+ active={promotionType}
+ close={() => setPromotionType(false)}
+ >
+ <div className='flex mt-4'>
+ <PromotionType
+ isModal={true}
+ variantId={selectVariantPromoActive}
+ setPromotionActiveId={setPromotionActiveId}
+ promotionActiveId={promotionActiveId}
+ quantity={quantityActive}
+ product={product}
+ setProducts={setProducts}
+ ></PromotionType>
+ </div>
+ </BottomPopup>
<BottomPopup
className='!container'
title='Berhasil Ditambahkan'
diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx
index 667a4ba1..fa1e2521 100644
--- a/src/lib/product/components/Product/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -14,6 +14,7 @@ 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'
const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
@@ -24,6 +25,9 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
const [addCartAlert, setAddCartAlert] = useState(false)
+ const [promotionType, setPromotionType] = useState(false)
+ const [promotionActiveId, setPromotionActiveId] = useState(null)
+
const getLowestPrice = () => {
const prices = product.variants.map((variant) => variant.price)
const lowest = prices.reduce((lowest, price) => {
@@ -38,7 +42,8 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
name: product.name,
price: getLowestPrice(),
stock: product.stockTotal,
- weight: product.weight
+ weight: product.weight,
+ hasProgram: false
})
const variantOptions = product.variants?.map((variant) => ({
@@ -66,7 +71,8 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
name: variant.parent.name + variantAttributes,
price: variant.price,
stock: variant.stock,
- weight: variant.weight
+ weight: variant.weight,
+ hasProgram: variant.hasProgram
})
}
}, [selectedVariant, product])
@@ -90,6 +96,7 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
updateItemCart({
productId: activeVariant.id,
quantity,
+ programLineId: promotionActiveId,
selected: true
})
setAddCartAlert(true)
@@ -187,6 +194,17 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
isSearchable={product.variantTotal > 10}
/>
</div>
+ {activeVariant.hasProgram && (
+ <div className='mt-5'>
+ <PromotionType
+ variantId={product.variants[0].id}
+ setPromotionActiveId={setPromotionActiveId}
+ promotionActiveId={promotionActiveId}
+ quantity={quantity}
+ ></PromotionType>
+ </div>
+ )}
+
<div className='mt-4 mb-2'>Jumlah</div>
<div className='flex gap-x-3'>
<div className='w-2/12'>
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index 8b48cf06..a8964310 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -12,7 +12,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
if (variant == 'vertical') {
return (
- <div className='rounded shadow-sm border border-gray_r-4 h-full bg-white'>
+ <div className='rounded shadow-sm border border-gray_r-4 bg-white h-[350px]'>
<Link
href={createSlug('/shop/product/', product?.name, product?.id)}
className='border-b border-gray_r-4 relative'
diff --git a/src/lib/promotinProgram/api/homepageApi.js b/src/lib/promotinProgram/api/homepageApi.js
new file mode 100644
index 00000000..496af9d6
--- /dev/null
+++ b/src/lib/promotinProgram/api/homepageApi.js
@@ -0,0 +1,17 @@
+import odooApi from "@/core/api/odooApi"
+
+export const getPromotionHome = async () => {
+ const response = await odooApi('GET', '/api/v1/promotion/home')
+ return response
+}
+
+export const getProductPromotionHome = async ({id}) => {
+ const response = await odooApi('GET', `/api/v1/promotion/home/${id}`)
+ return response
+}
+
+export const getPromotionProgram = async ({ id }) => {
+ const listProgram = await odooApi('GET', `/api/v1/product_variant/${id}/promotions`)
+
+ return listProgram
+} \ No newline at end of file
diff --git a/src/lib/promotinProgram/components/HomePage.jsx b/src/lib/promotinProgram/components/HomePage.jsx
new file mode 100644
index 00000000..c0968161
--- /dev/null
+++ b/src/lib/promotinProgram/components/HomePage.jsx
@@ -0,0 +1,116 @@
+import React, { use, useEffect, useState } from 'react'
+import { Swiper, SwiperSlide } from 'swiper/react'
+import 'swiper/swiper.min.css'
+import Image from '@/core/components/elements/Image/Image'
+import useDevice from '@/core/hooks/useDevice'
+import odooApi from '@/core/api/odooApi'
+import { LazyLoadComponent } from 'react-lazy-load-image-component'
+import ProductSlider from '@/lib/product/components/ProductSlider'
+import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton'
+
+const { useQuery } = require('react-query')
+const { getPromotionHome } = require('../api/homepageApi')
+const { getProductPromotionHome } = require('../api/homepageApi')
+
+const HomePage = () => {
+ const [activeTab, setActiveTab] = useState(null)
+ const [activeId, setActiveId] = useState(null)
+ const [activeBanner, setActiveBanner] = useState(null)
+ const [parentPromotions, setparentPromotions] = useState(null)
+
+ const { data: titlePromotion } = useQuery('titlePromotion', getPromotionHome)
+ const { data: productPromotion, refetch: productPromotionRefetch } = useQuery(
+ ['productPromotion', activeId],
+ async () => {
+ if (!activeId) return null
+ return await getProductPromotionHome({ id: activeId })
+ }
+ )
+
+ useEffect(() => {
+ if (titlePromotion && titlePromotion.length > 0) {
+ setActiveTab(titlePromotion[0].name)
+ setActiveId(titlePromotion[0].id)
+ setparentPromotions(titlePromotion)
+ setActiveBanner(titlePromotion[0].banner)
+ productPromotionRefetch()
+ }
+ }, [titlePromotion, productPromotionRefetch])
+
+ useEffect(() => {
+ if (productPromotion) {
+ setparentPromotions((parentPromotions) => {
+ return parentPromotions.map((title) => {
+ if (title.id === activeId && title.products === undefined) {
+ return {
+ ...title,
+ products: productPromotion
+ }
+ }
+ return title
+ })
+ })
+ }
+ }, [productPromotion, activeId])
+
+ const { isMobile, isDesktop } = useDevice()
+ const handleTabClick = (id, label, banner) => {
+ setActiveTab(label)
+ setActiveId(id)
+ setActiveBanner(banner)
+ }
+
+ return (
+ activeBanner && (
+ <div className='px-4 sm:px-0'>
+ <div className='flex justify-between items-center mb-4'>
+ <div className='font-medium sm:text-h-lg'>{activeTab}</div>
+ </div>
+ <div className='mb-4'>
+ <Image src={activeBanner} alt='' className='h-full w-full object-contain object-center' />
+ </div>
+ <Swiper slidesPerView={isMobile ? 3.5 : 7.5} spaceBetween={isMobile ? 12 : 20}>
+ {titlePromotion?.map((item, index) => (
+ <SwiperSlide key={index}>
+ <button
+ className={`py-1 px-2 rounded border flex justify-center items-center h-30 ${
+ activeTab === item.name ? 'border-red-500' : 'border-gray_r-6'
+ }`}
+ onClick={() => handleTabClick(item.id, item.name, item.banner)}
+ >
+ {' '}
+ <Image
+ src={item.icon}
+ alt=''
+ className='h-full w-full object-contain object-center'
+ />
+ </button>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ <div className='mt-4 relative min-h-[150px]'>
+ {parentPromotions &&
+ parentPromotions?.map((item, index) => (
+ <div
+ key={index}
+ className={`${activeId === item.id ? 'block' : 'hidden'} rounded-md`}
+ >
+ {item.products ? (
+ <ProductSlider
+ key={index}
+ products={{
+ products: item.products
+ }}
+ />
+ ) : (
+ <PopularProductSkeleton />
+ )}
+ </div>
+ ))}
+ </div>
+ </div>
+ )
+ )
+}
+
+export default HomePage
diff --git a/src/lib/promotinProgram/components/PromotionType.jsx b/src/lib/promotinProgram/components/PromotionType.jsx
new file mode 100644
index 00000000..ad7185e3
--- /dev/null
+++ b/src/lib/promotinProgram/components/PromotionType.jsx
@@ -0,0 +1,304 @@
+import React, { useEffect, useState } from 'react'
+import Image from '@/core/components/elements/Image/Image'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import CountDown2 from '@/core/components/elements/CountDown/CountDown2'
+import currencyFormat from '@/core/utils/currencyFormat'
+import { getPromotionProgram } from '../api/homepageApi'
+
+const PromotionType = ({
+ isModal = false,
+ variantId,
+ setPromotionActiveId,
+ promotionActiveId,
+ quantity,
+ product = null,
+ setProducts = null
+}) => {
+ const [selectedPromo, setSelectedPromo] = useState(null)
+ const [promotionType, setPromotionType] = useState(false)
+ const [promos, setPromotionList] = useState(null)
+ const [activeTitle, setActiveTitle] = useState(null)
+ const [quantitySet, setQuantity] = useState(null)
+
+ useEffect(() => {
+ const id = variantId
+ const listProgram = async () => {
+ const programs = await getPromotionProgram({ id })
+ if (programs.length > 0) {
+ setPromotionList(programs)
+ setActiveTitle(programs?.[0].type.value)
+ }
+ }
+ listProgram()
+ setSelectedPromo(promotionActiveId)
+ if (product) {
+ const variant = product.variants.find((variant) => variant.id === variantId)
+ setQuantity(variant.quantity)
+ }else{
+ setQuantity(quantity)
+ }
+ }, [])
+
+ const groupingData = promos?.reduce((groups, item) => {
+ const promoType = item.type.value
+ if (!groups[promoType]) {
+ groups[promoType] = []
+ }
+ groups[promoType].push(item)
+
+ return groups
+ }, {})
+
+ const handlePromoClick = (promoId, minQty) => {
+ if (quantitySet >= minQty) {
+ if (promoId == selectedPromo) {
+ setSelectedPromo(null)
+ setPromotionActiveId(null)
+ if (product) {
+ const updateProdcuts = () => {
+ let variantIndex = product.variants.findIndex((varian) => varian.id == variantId)
+ product.variants[variantIndex].programActive = null
+
+ setProducts(product)
+ }
+ updateProdcuts()
+ }
+ } else {
+ setSelectedPromo(promoId)
+ setPromotionActiveId(promoId)
+ if (product) {
+ const updateProdcuts = () => {
+ let variantIndex = product.variants.findIndex((varian) => varian.id == variantId)
+ product.variants[variantIndex].programActive = promoId
+
+ setProducts(product)
+ }
+ updateProdcuts()
+ }
+ }
+ }
+ }
+
+ const handlePopUp = () => {
+ if (isModal == false) {
+ setPromotionType(true)
+ }
+ }
+
+ return (
+ promos && (
+ <>
+ <div className='h-[50%] relative'>
+ <div className='relative rounded-lg border border-solid border-gray-300 mb-2 w-full'>
+ <Image src='https://placehold.co/537x50.png' alt='' layout='fill' objectFit='cover' />
+ <div className='h-full absolute top-0 left-0 w-full flex items-center justify-between p-2'>
+ <span className='font-semibold text-lg text-white'>Promo Tersedia</span>
+ <button type='button' onClick={() => handlePopUp()} className='py-2 btn-yellow'>
+ Lihat Semua
+ </button>
+ </div>
+ </div>
+ <div
+ className={`w-full ${
+ isModal == true ? '' : 'grid grid-cols-3 gap-1 bg-gray-200 '
+ } p-2 rounded-lg`}
+ >
+ {isModal === true ? (
+ <div>
+ <div className='flex gap-2 mb-3'>
+ {Object.keys(groupingData).map((index) => {
+ return (
+ <>
+ <button
+ onClick={() => setActiveTitle(index)}
+ className={`py-1 px-2 rounded-lg flex justify-center items-center text-sm ${
+ activeTitle === index ? 'badge-yellow text-black' : ''
+ } `}
+ >
+ {groupingData[index][0].type.label}
+ </button>
+ </>
+ )
+ })}
+ </div>
+ {activeTitle &&
+ groupingData[activeTitle].map((item, i) => (
+ <div
+ key={i}
+ onClick={() => handlePromoClick(item.id, item.minimumPurchaseQty)}
+ className={`border border-solid mb-5 w-full hover:cursor-pointer ${
+ selectedPromo
+ ? selectedPromo === item.id
+ ? 'opacity-100 border-red-500 bg-red-100'
+ : 'opacity-50 pointer-events-none'
+ : 'opacity-100'
+ } ${
+ quantitySet >= item.minimumPurchaseQty
+ ? ''
+ : 'opacity-50 pointer-events-none'
+ } `}
+ >
+ <div className={`flex`}>
+ <div className=''>
+ <Image
+ src={item.Image}
+ alt={item.name}
+ className={`flex-1 w-[170px] object-cover`}
+ />
+ </div>
+ <div className='p-2 w-full'>
+ <div className='flex justify-between mb-1'>
+ <div className='text-danger-500 font-semibold mb-1 mt-1'>
+ Waktu Tersisa
+ </div>
+ <div>
+ <CountDown2 initialTime={item.remainingTime}></CountDown2>
+ </div>
+ </div>
+ <p className='text-justify text-gray-500 line-clamp-3'>{item.name}</p>
+ <div className='mt-4'>
+ {/* {item.type.value === 'bundling' && (
+ <> */}
+ <div className='flex gap-x-2 mt-3 justify-between items-center'>
+ <div className='flex gap-x-2 items-center '>
+ <div className='text-gray_r-11 line-through text-caption-1 mt-1'>
+ {currencyFormat(item.totalSavings)}
+ </div>
+ <div className='text-danger-500 font-semibold '>Gratis</div>
+ </div>
+ <div className='text-danger-500 font-semibold '>
+ {quantitySet < item.minimumPurchaseQty
+ ? 'Tambah ' +
+ (parseInt(item.minimumPurchaseQty) -
+ parseInt(quantitySet)) +
+ ' lagi'
+ : ''}
+ </div>
+ </div>
+ {/* </>
+ )} */}
+ {/* {item.type.value === 'special_price' && (
+ <>
+ <div className='flex gap-x-2 mt-3 items-center'>
+ <div className='text-danger-500 font-semibold '> {currencyFormat(item.totalSavings)}</div>
+ </div>
+ </>
+ )}
+ {item.type.value === 'discount_loading' && (
+ <>
+ <div className='flex justify-between'>
+ <div className='text-danger-500 font-semibold '>
+ {currencyFormat(item.totalSavings)}
+ </div>
+ <div className='text-danger-500 font-semibold '>
+ {quantitySet < item.minimumPurchaseQty
+ ? 'Tambah ' +
+ (parseInt(item.minimumPurchaseQty) -
+ parseInt(quantitySet)) +
+ ' lagi'
+ : ''}
+ </div>
+ </div>
+ </>
+ )} */}
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ ) : (
+ promos.map((promo, index) => {
+ if (index > 2) {
+ return null
+ } else {
+ if (index === 2 && promos.length > 2) {
+ return (
+ <>
+ <div
+ onClick={() => setPromotionType(true)}
+ className={` border border-solid bg-white mb-5 w-full hover:cursor-pointer opacity-100 flex flex-col justify-center items-center`}
+ >
+ <div className='flex justify-center items-center'>
+ <div className='rounded-full shadow-lg w-10 h-10 flex justify-center items-center'>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='text-red-500 w-20 h-20'
+ >
+ <path
+ d='M12 6v12m6-6H6'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ </div>
+ </div>
+
+ <span className='mt-2 text-sm'>Lihat Promo Lainya</span>
+ </div>
+ </>
+ )
+ }
+ return (
+ <>
+ <div
+ key={promo.id}
+ onClick={() => setPromotionType(true)}
+ className={`border border-solid bg-white mb-5 w-full hover:cursor-pointer`}
+ >
+ <div className={`items-center`}>
+ <div className=''>
+ <Image
+ src={promo.Image}
+ alt={promo.name}
+ className={`flex-1 w-full object-cover`}
+ />
+ </div>
+ <div className='p-2'>
+ <div className='badge-yellow text-black mb-1'>{promo.type.label}</div>
+ <p className='text-justify line-clamp-2'>{promo.name}</p>
+ <div className='text-danger-500 font-semibold mb-1 mt-1'>
+ {/* {currencyFormat(promo.totalSavings)} */}
+ </div>
+ {/* <div className='w-full bg-yellow-200 rounded-full h-1.5 mb-2'>
+ <div className='bg-yellow-500 h-1.5 rounded-full w-[45%]'></div>
+ </div> */}
+ <div>
+ <CountDown2 initialTime={promo.remainingTime}></CountDown2>
+ </div>
+ </div>
+ </div>
+ </div>
+ </>
+ )
+ }
+ })
+ )}
+ <BottomPopup
+ className=' !h-[75%]'
+ title='Pakai Promo'
+ active={promotionType}
+ close={() => setPromotionType(false)}
+ >
+ <div className='flex mt-4'>
+ <PromotionType
+ isModal={true}
+ variantId={variantId}
+ setPromotionActiveId={setPromotionActiveId}
+ promotionActiveId={promotionActiveId}
+ quantity={quantitySet}
+ ></PromotionType>
+ </div>
+ </BottomPopup>
+ </div>
+ </div>
+ </>
+ )
+ )
+}
+
+export default PromotionType
diff --git a/src/lib/variant/components/VariantGroupCard.jsx b/src/lib/variant/components/VariantGroupCard.jsx
index 8cb1eec4..dd5983a9 100644
--- a/src/lib/variant/components/VariantGroupCard.jsx
+++ b/src/lib/variant/components/VariantGroupCard.jsx
@@ -1,14 +1,55 @@
import { useState } from 'react'
import VariantCard from './VariantCard'
+import Image from '@/core/components/elements/Image/Image'
+import currencyFormat from '@/core/utils/currencyFormat'
const VariantGroupCard = ({ variants, ...props }) => {
+ console.log('variant', variants)
const [showAll, setShowAll] = useState(false)
const variantsToShow = showAll ? variants : variants.slice(0, 2)
return (
<>
{variantsToShow?.map((variant, index) => (
- <VariantCard key={index} product={variant} {...props} />
+ <>
+ <VariantCard key={index} product={variant} {...props} />
+ {variant.program &&
+ variant.program.items &&
+ variant.program.items.map((item) => (
+ <div key={item.id}>
+ <div className='flex gap-x-3'>
+ <div className='w-4/12 flex items-center gap-x-2'>
+ <Image
+ src={item.parent.image}
+ alt={item.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ </div>
+ <div className='w-8/12 flex flex-col'>
+ <div className='mb-2'>
+ <span className='border border-solid border-red-600 rounded-md p-1 text-red-600'>
+ {variant.program.type.label}
+ </span>
+ </div>
+ <p className='product-card__title wrap-line-ellipsis-2'>{item.name}</p>
+ <p className='text-caption-2 text-gray_r-11 mt-1'>
+ Berat Item : {item.weight} Kg
+ </p>
+ <div className='flex flex-wrap gap-x-1 items-center mt-1'>
+ {item.price.discountPercentage > 0 && (
+ <>
+ <p className='text-caption-2 text-gray_r-11 line-through'>
+ {currencyFormat(item.price.price)}
+ </p>
+ </>
+ )}
+ </div>
+ <p className='text-caption-2 text-gray_r-12 font-bold mt-2'>Gratis</p>
+ </div>
+ </div>
+ </div>
+ ))}
+ </>
))}
{variants.length > 2 && (
<button