summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/cart/components/Cart.jsx56
-rw-r--r--src/lib/checkout/api/getVoucher.js11
-rw-r--r--src/lib/checkout/components/Checkout.jsx454
-rw-r--r--src/lib/home/components/CategoryHomeId.jsx2
-rw-r--r--src/lib/home/components/PreferredBrand.jsx4
-rw-r--r--src/lib/product/api/variantApi.js9
-rw-r--r--src/lib/product/api/variantSearchApi.js11
-rw-r--r--src/lib/product/components/Product/Product.jsx41
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx14
-rw-r--r--src/lib/product/components/Product/ProductDesktopVariant.jsx349
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx2
-rw-r--r--src/lib/product/components/Product/ProductMobileVariant.jsx324
-rw-r--r--src/lib/review/components/CustomerReviews.jsx2
13 files changed, 1207 insertions, 72 deletions
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index 41efffe9..efbcf76b 100644
--- a/src/lib/cart/components/Cart.jsx
+++ b/src/lib/cart/components/Cart.jsx
@@ -21,6 +21,7 @@ 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()
@@ -205,6 +206,15 @@ const Cart = () => {
setPromotionActiveId(promoId)
}
+ const handleCheckout = () => {
+ gtagBeginCheckout(products)
+ router.push('/shop/checkout')
+ }
+
+ const totalOrder = totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount
+ const tax = totalOrder * 0.11
+ const totalPrice = totalOrder + tax
+
return (
<>
<BottomPopup
@@ -473,15 +483,19 @@ const Cart = () => {
))}
<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 className='flex flex-col gap-y-3 mb-4'>
+ <div className='text-gray_r-11 flex justify-between'>
+ <span>Total Pesanan</span>
+ <span className='text-gray_r-12'>{currencyFormat(totalOrder)}</span>
+ </div>
+ <div className='text-gray_r-11 flex justify-between'>
+ PPN 11%
+ <span className='text-gray_r-12'>{currencyFormat(tax)}</span>
+ </div>
+ <hr />
+ <div className='text-gray_r-12 flex font-semibold justify-between'>
+ Total Harga:
+ <span className='text-danger-500'>{currencyFormat(totalPrice)}</span>
</div>
</div>
<div className='flex gap-x-3'>
@@ -783,15 +797,19 @@ const Cart = () => {
<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 className='flex flex-col gap-y-3 mb-4'>
+ <div className='text-gray_r-11 flex justify-between'>
+ <span>Total Pesanan</span>
+ <span className='text-gray_r-12'>{currencyFormat(totalOrder)}</span>
+ </div>
+ <div className='text-gray_r-11 flex justify-between'>
+ PPN 11%
+ <span className='text-gray_r-12'>{currencyFormat(tax)}</span>
+ </div>
+ <hr />
+ <div className='text-gray_r-12 flex font-semibold justify-between'>
+ Total Harga:
+ <span className='text-danger-500'>{currencyFormat(totalPrice)}</span>
</div>
</div>
<div className='flex gap-x-3'>
@@ -807,7 +825,7 @@ const Cart = () => {
type='button'
className='btn-solid-red flex-1'
disabled={selectedProduct().length == 0}
- onClick={() => router.push('/shop/checkout')}
+ onClick={handleCheckout}
>
Checkout
</button>
diff --git a/src/lib/checkout/api/getVoucher.js b/src/lib/checkout/api/getVoucher.js
new file mode 100644
index 00000000..03ac3d6d
--- /dev/null
+++ b/src/lib/checkout/api/getVoucher.js
@@ -0,0 +1,11 @@
+import odooApi from '@/core/api/odooApi'
+
+export const getVoucher = async () => {
+ const dataVoucher = await odooApi('GET', `/api/v1/voucher`)
+ return dataVoucher
+}
+
+export const findVoucher = async (code) => {
+ const dataVoucher = await odooApi('GET', `/api/v1/voucher?code=${code}`)
+ return dataVoucher
+}
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index f5b27cfb..9c026372 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -16,14 +16,16 @@ 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 { Button, Modal } from 'flowbite-react'
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
@@ -84,7 +86,45 @@ 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 [activeVoucher, SetActiveVoucher] = 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 expedisiValidation = useRef(null)
+
+ const voucher = async () => {
+ let dataVoucher = await getVoucher()
+ SetListVoucher(dataVoucher)
+ }
+ const VoucherCode = async (code) => {
+ let dataVoucher = await findVoucher(code)
+ if (dataVoucher.length <= 0) {
+ SetFindVoucher(1)
+ return
+ }
+ let addNewLine = dataVoucher[0]
+ let checkList = listVouchers.findIndex((voucher) => voucher.code == addNewLine.code)
+ if (checkList >= 0) return
+ if (totalAmount - totalDiscountAmount < addNewLine.minPurchaseAmount) {
+ SetSelisihHargaCode(
+ currencyFormat(addNewLine.minPurchaseAmount - (totalAmount - totalDiscountAmount))
+ )
+ SetFindVoucher(2)
+ return
+ } else {
+ SetFindVoucher(3)
+ SetButtonTerapkan(true)
+ }
+ SetListVoucher((prevList) => [addNewLine, ...prevList])
+ SetActiveVoucher(addNewLine.code)
+ }
useEffect(() => {
const loadExpedisi = async () => {
let dataExpedisi = await ExpedisiList()
@@ -96,8 +136,39 @@ const Checkout = () => {
setExpedisi(dataExpedisi)
}
loadExpedisi()
+ voucher()
}, [])
+ const hitungDiscountVoucher = (code) => {
+ let dataVoucherIndex = listVouchers.findIndex((voucher) => voucher.code == code)
+ let dataActiveVoucher = listVouchers[dataVoucherIndex]
+
+ let countDiscount = 0
+
+ if (dataActiveVoucher.discountType === 'percentage') {
+ countDiscount = (totalAmount - totalDiscountAmount) * (dataActiveVoucher.discountAmount / 100)
+ if (
+ dataActiveVoucher.maxDiscountAmount > 0 &&
+ countDiscount > dataActiveVoucher.maxDiscountAmount
+ ) {
+ countDiscount = dataActiveVoucher.maxDiscountAmount
+ }
+ } else {
+ countDiscount = dataActiveVoucher.discountAmount
+ }
+
+ return countDiscount
+ }
+
+ useEffect(() => {
+ if (!listVouchers) return
+ if (!activeVoucher) return
+
+ const countDiscount = hitungDiscountVoucher(activeVoucher)
+
+ SetDiscountVoucher(countDiscount)
+ }, [activeVoucher, listVouchers])
+
useEffect(() => {
setProducts(cartCheckout?.products)
setCheckWeight(cartCheckout?.hasProductWithoutWeight)
@@ -105,6 +176,7 @@ const Checkout = () => {
}, [cartCheckout])
useEffect(() => {
+ setCheckoutValidation(false)
const loadServiceRajaOngkir = async () => {
const body = {
origin: 2127,
@@ -154,6 +226,9 @@ const Checkout = () => {
useEffect(() => {
if (selectedExpedisi) {
let serviceType = selectedExpedisi.split(',')
+ if (serviceType[0] === 0) {
+ setSelectedExpedisi(0)
+ }
setselectedCarrier(serviceType[0])
setselectedCarrierId(serviceType[1])
setListServiceExpedisi([])
@@ -171,6 +246,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,
@@ -184,6 +274,7 @@ const Checkout = () => {
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
@@ -195,16 +286,219 @@ const Checkout = () => {
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
+ gtagPurchase(products, biayaKirim, isCheckouted.name)
+
+ const midtrans = async () => {
+ 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
+ }
+
+ gtag('event', 'conversion', {
+ send_to: 'AW-954540379/nDymCL3BhaQYENvClMcD',
+ value:
+ totalAmount -
+ totalDiscountAmount +
+ taxTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000,
+ currency: 'IDR',
+ transaction_id: isCheckouted.id,
+ event_callback: midtrans
+ })
+ }
+
+ 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=''>
+ {!listVouchers ? (
+ <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>
+ )}
+ {listVouchers?.map((item) => (
+ <div key={item.id} className='relative'>
+ {totalAmount - totalDiscountAmount < item.minPurchaseAmount && (
+ <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 '>
+ {totalAmount - totalDiscountAmount < item.minPurchaseAmount
+ ? 'Tambah ' +
+ currencyFormat(
+ item.minPurchaseAmount - (totalAmount - totalDiscountAmount)
+ ) +
+ ' 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'>
@@ -243,6 +537,8 @@ const Checkout = () => {
listExpedisi={listExpedisi}
setSelectedExpedisi={setSelectedExpedisi}
checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
/>
<Divider />
<SectionListService
@@ -268,13 +564,15 @@ const Checkout = () => {
<div>{currencyFormat(cartCheckout?.totalPurchase)}</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(cartCheckout?.totalDiscount)}</div>
- </div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Total Hemat</div>
- <div className='text-danger-500'>- {currencyFormat(cartCheckout?.totalSavings)}</div>
+ <div className='text-gray_r-11'>Diskon Produk</div>
+ <div className='text-danger-500'>- {currencyFormat(totalDiscountAmount)}</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>
@@ -299,6 +597,42 @@ const Checkout = () => {
)}
</div>
</div>
+
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='mt-4 mb-4'>
+ <button
+ type='button'
+ onClick={() => SetBottomPopup(true)}
+ 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'>
Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '}
@@ -338,9 +672,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>
@@ -383,6 +715,8 @@ const Checkout = () => {
listExpedisi={listExpedisi}
setSelectedExpedisi={setSelectedExpedisi}
checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
/>
<Divider />
<SectionListService
@@ -549,17 +883,15 @@ const Checkout = () => {
<div>{currencyFormat(cartCheckout?.totalPurchase)}</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(cartCheckout?.totalDiscount)}
- </div>
+ <div className='text-gray_r-11'>Diskon Produk</div>
+ <div className='text-danger-500'>- {currencyFormat(cartCheckout?.totalDiscount)}</div>
</div>
- <div className='flex gap-x-2 justify-between'>
- <div className='text-gray_r-11'>Total Hemat</div>
- <div className='text-danger-500'>
- - {currencyFormat(cartCheckout?.totalSavings)}
+ {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>
+ )}
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>Subtotal</div>
<div>{currencyFormat(cartCheckout?.subtotal)}</div>
@@ -587,6 +919,42 @@ const Checkout = () => {
)}
</div>
</div>
+
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='mt-4 mb-4'>
+ <button
+ type='button'
+ onClick={() => SetBottomPopup(true)}
+ 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'>
@@ -623,14 +991,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>
@@ -693,13 +1054,24 @@ 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)}>
+ <div className='w-[250px]'>
+ <select
+ className={`form-input ${checkoutValidation ? 'border-red-500 shake' : ''}`}
+ onChange={(e) => setSelectedExpedisi(e.target.value)}
+ required
+ >
<option value='0,0'>Pengiriman</option>
<option value='1,32'>SELF PICKUP</option>
{checkWeigth != true &&
@@ -714,7 +1086,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/home/components/CategoryHomeId.jsx b/src/lib/home/components/CategoryHomeId.jsx
index 3707063c..71428e27 100644
--- a/src/lib/home/components/CategoryHomeId.jsx
+++ b/src/lib/home/components/CategoryHomeId.jsx
@@ -7,7 +7,7 @@ const CategoryHomeId = () => {
return (
<div>
- <div className='font-medium sm:text-h-lg mb-6 px-4 sm:px-0'>Kategori Pilihan</div>
+ <div className='font-semibold sm:text-h-lg mb-6 px-4 sm:px-0'>Kategori Pilihan</div>
<div className='flex flex-col gap-y-10'>
{categoryHomeIds.data?.map((id) => (
<LazyLoadComponent key={id}>
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index 34c50220..55abe0b7 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -12,9 +12,9 @@ const PreferredBrand = () => {
return (
<div className='px-4 sm:px-0'>
<div className='flex justify-between items-center mb-4'>
- <div className='font-medium sm:text-h-lg'>Brand Pilihan</div>
+ <div className='font-semibold sm:text-h-lg'>Brand Pilihan</div>
{isDesktop && (
- <Link href='/shop/brands' className='btn-yellow !text-gray_r-12'>
+ <Link href='/shop/brands' className='!text-red-500 font-semibold'>
Lihat Semua
</Link>
)}
diff --git a/src/lib/product/api/variantApi.js b/src/lib/product/api/variantApi.js
new file mode 100644
index 00000000..47273dd7
--- /dev/null
+++ b/src/lib/product/api/variantApi.js
@@ -0,0 +1,9 @@
+import odooApi from '@/core/api/odooApi'
+
+const variantApi = async ({ id, headers = {} }) => {
+ if (!id) return
+ const dataProduct = await odooApi('GET', `/api/v2/product_variant/${id}`, {}, headers)
+ return dataProduct
+}
+
+export default variantApi
diff --git a/src/lib/product/api/variantSearchApi.js b/src/lib/product/api/variantSearchApi.js
new file mode 100644
index 00000000..d7b05423
--- /dev/null
+++ b/src/lib/product/api/variantSearchApi.js
@@ -0,0 +1,11 @@
+import _ from 'lodash-contrib'
+import axios from 'axios'
+
+const variantSearchApi = async ({ query, operation = 'AND' }) => {
+ const dataProductSearch = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant?${query}&operation=${operation}`
+ )
+ return dataProductSearch.data
+}
+
+export default variantSearchApi
diff --git a/src/lib/product/components/Product/Product.jsx b/src/lib/product/components/Product/Product.jsx
index 9649fd21..0547c36e 100644
--- a/src/lib/product/components/Product/Product.jsx
+++ b/src/lib/product/components/Product/Product.jsx
@@ -5,8 +5,12 @@ import ProductDesktop from './ProductDesktop'
import useAuth from '@/core/hooks/useAuth'
import ProductMobile from './ProductMobile'
import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+import { gtagViewItem } from '@/core/utils/googleTag'
+import ProductDesktopVariant from './ProductDesktopVariant'
+import ProductMobileVariant from './ProductMobileVariant'
-const Product = ({ product }) => {
+const Product = ({ product, isVariant = false }) => {
const auth = useAuth()
const router = useRouter()
const { wishlist } = useWishlist({ productId: product?.id })
@@ -26,12 +30,35 @@ const Product = ({ product }) => {
wishlist.refetch()
}
- return (
- <>
- <ProductMobile product={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
- <ProductDesktop products={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
- </>
- )
+ useEffect(() => {
+ if (isVariant == false) {
+ gtagViewItem(product.variants)
+ }
+ }, [product, isVariant])
+
+ if (isVariant == true) {
+ return (
+ <>
+ <ProductDesktopVariant
+ product={product}
+ wishlist={wishlist}
+ toggleWishlist={toggleWishlist}
+ />
+ <ProductMobileVariant
+ product={product}
+ wishlist={wishlist}
+ toggleWishlist={toggleWishlist}
+ />
+ </>
+ )
+ } else {
+ return (
+ <>
+ <ProductMobile product={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
+ <ProductDesktop product={product} wishlist={wishlist} toggleWishlist={toggleWishlist} />
+ </>
+ )
+ }
}
export default Product
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index e15ea252..4e746877 100644
--- a/src/lib/product/components/Product/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -15,9 +15,13 @@ import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
import ProductCard from '../ProductCard'
import productSimilarApi from '../../api/productSimilarApi'
import whatsappUrl from '@/core/utils/whatsappUrl'
+<<<<<<< HEAD
import PromotionType from '@/lib/promotinProgram/components/PromotionType'
import useAuth from '@/core/hooks/useAuth'
import odooApi from '@/core/api/odooApi'
+=======
+import { gtagAddToCart } from '@/core/utils/googleTag'
+>>>>>>> Feature/voucher
const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
const router = useRouter()
@@ -96,10 +100,10 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
setAddCartAlert(true)
}
- const handleBuy = (variantId) => {
- const quantity = variantQuantityRefs.current[variantId].value
+ const handleBuy = (variant) => {
+ const quantity = variantQuantityRefs.current[variant].value
if (!validQuantity(quantity)) return
- router.push(`/shop/checkout?productId=${variantId}&quantity=${quantity}`)
+ router.push(`/shop/checkout?productId=${variant}&quantity=${quantity}`)
}
const variantSectionRef = useRef(null)
@@ -299,7 +303,7 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
/>
<button
type='button'
- onClick={() => handleAddToCart(product.variants[0].id)}
+ onClick={() => handleAddToCart(product.variants[0])}
className='flex-1 py-2 btn-yellow'
>
Keranjang
@@ -428,7 +432,7 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
)}
<button
type='button'
- onClick={() => handleAddToCart(variant.id)}
+ onClick={() => handleAddToCart(variant)}
className='flex-1 py-2 btn-yellow'
>
Keranjang
diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx
new file mode 100644
index 00000000..e0573357
--- /dev/null
+++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx
@@ -0,0 +1,349 @@
+import Image from '@/core/components/elements/Image/Image'
+import Link from '@/core/components/elements/Link/Link'
+import DesktopView from '@/core/components/views/DesktopView'
+import currencyFormat from '@/core/utils/currencyFormat'
+import { HeartIcon } from '@heroicons/react/24/outline'
+import { useCallback, useEffect, useRef, useState } from 'react'
+import LazyLoad from 'react-lazy-load'
+import ProductSimilar from '../ProductSimilar'
+import { toast } from 'react-hot-toast'
+import { updateItemCart } from '@/core/utils/cart'
+import { useRouter } from 'next/router'
+import { createSlug } from '@/core/utils/slug'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import ProductCard from '../ProductCard'
+import productSimilarApi from '../../api/productSimilarApi'
+import whatsappUrl from '@/core/utils/whatsappUrl'
+
+const ProductDesktopVariant = ({ product, wishlist, toggleWishlist, isVariant }) => {
+ const router = useRouter()
+
+ const [lowestPrice, setLowestPrice] = useState(null)
+
+ const [addCartAlert, setAddCartAlert] = useState(false)
+
+ const getLowestPrice = useCallback(() => {
+ const lowest = product.price
+ /* const lowest = prices.reduce((lowest, price) => {
+ return price.priceDiscount < lowest.priceDiscount ? price : lowest
+ }, prices[0])*/
+ return lowest
+ }, [product])
+
+ useEffect(() => {
+ const lowest = getLowestPrice()
+ setLowestPrice(lowest)
+ }, [getLowestPrice])
+
+ const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
+
+ const variantQuantityRefs = useRef([])
+
+ const setVariantQuantityRef = (variantId) => (element) => {
+ variantQuantityRefs.current[variantId] = element
+ }
+
+ const validQuantity = (quantity) => {
+ let isValid = true
+ if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) {
+ toast.error('Jumlah barang minimal 1')
+ isValid = false
+ }
+ return isValid
+ }
+
+ const handleAddToCart = (variant) => {
+ const quantity = variantQuantityRefs.current[variant].value
+ if (!validQuantity(quantity)) return
+ updateItemCart({
+ productId: variant,
+ quantity,
+ selected: true
+ })
+ setAddCartAlert(true)
+ }
+
+ const handleBuy = (variant) => {
+ const quantity = variantQuantityRefs.current[variant].value
+ if (!validQuantity(quantity)) return
+ router.push(`/shop/checkout?productId=${variant}&quantity=${quantity}`)
+ }
+
+ const variantSectionRef = useRef(null)
+ const goToVariantSection = () => {
+ if (variantSectionRef.current) {
+ const position = variantSectionRef.current.getBoundingClientRect()
+ window.scrollTo({
+ top: position.top - 120 + window.pageYOffset,
+ behavior: 'smooth'
+ })
+ }
+ }
+
+ const productSimilarQuery = [
+ product?.name,
+ `fq=-product_id_i:${product.id}`,
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`
+ ].join('&')
+
+ const [productSimilarInBrand, setProductSimilarInBrand] = useState(null)
+
+ useEffect(() => {
+ const loadProductSimilarInBrand = async () => {
+ const productSimilarQuery = [product?.name, `fq=-product_id_i:${product.id}`].join('&')
+ const dataProductSimilar = await productSimilarApi({ query: productSimilarQuery })
+ setProductSimilarInBrand(dataProductSimilar.products)
+ }
+ if (!productSimilarInBrand) loadProductSimilarInBrand()
+ }, [product, productSimilarInBrand])
+
+ return (
+ <DesktopView>
+ <div className='container mx-auto pt-10'>
+ <div className='flex'>
+ <div className='w-full flex flex-wrap'>
+ <div className='w-5/12'>
+ <Image
+ src={product.parent.image}
+ alt={product.name}
+ className='h-[430px] object-contain object-center w-full border border-gray_r-4'
+ />
+ </div>
+
+ <div className='w-7/12 px-4'>
+ <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1>
+ <div className='mt-10'>
+ <div className='flex p-3'>
+ <div className='w-1/4 text-gray_r-12/70'>Nomor SKU</div>
+ <div className='w-3/4'>SKU-{product.id}</div>
+ </div>
+ <div className='flex p-3 bg-gray_r-4'>
+ <div className='w-1/4 text-gray_r-12/70'>Part Number</div>
+ <div className='w-3/4'>{product.code || '-'}</div>
+ </div>
+ <div className='flex p-3'>
+ <div className='w-1/4 text-gray_r-12/70'>Manufacture</div>
+ <div className='w-3/4'>
+ {product.manufacture?.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture?.name,
+ product.manufacture?.id
+ )}
+ >
+ {product.manufacture?.name}
+ </Link>
+ ) : (
+ <div>-</div>
+ )}
+ </div>
+ </div>
+ <div className='flex p-3 bg-gray_r-4'>
+ <div className='w-1/4 text-gray_r-12/70'>Berat Barang</div>
+ <div className='w-3/4'>
+ {product?.weight > 0 && <span>{product?.weight} KG</span>}
+ {product?.weight == 0 && (
+ <a
+ href={whatsappUrl('productWeight', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 font-medium'
+ >
+ Tanya Berat
+ </a>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* <div className='w-full'>
+ <div className='mt-12'>
+ <div className='text-h-lg font-semibold'>Informasi Produk</div>
+ <div className='flex gap-x-4 mt-6 mb-4'>
+ {informationTabOptions.map((option) => (
+ <TabButton
+ value={option.value}
+ key={option.value}
+ active={informationTab == option.value}
+ onClick={() => setInformationTab(option.value)}
+ >
+ {option.label}
+ </TabButton>
+ ))}
+ </div>
+ <div className='flex'>
+ <div className='w-3/4 leading-7 product__description'>
+ <TabContent active={informationTab == 'description'}>
+ <span
+ dangerouslySetInnerHTML={{
+ __html:
+ product.description != ''
+ ? product.description
+ : 'Belum ada deskripsi produk.'
+ }}
+ />
+ </TabContent>
+
+ <TabContent active={informationTab == 'information'}>
+ Belum ada informasi.
+ </TabContent>
+ </div>
+ </div>
+ </div>
+ </div> */}
+ </div>
+ <div className='w-[25%]'>
+ {lowestPrice?.priceDiscount > 0 ? (
+ <>
+ <div className='flex gap-x-2 mb-3 items-center'>
+ <div className='flex'>
+ <span className='text-gray-400 text-md'>Harga Sebelum PPN : </span>
+ </div>
+ <div className='flex'>
+ <span className=' text-body-1 '>
+ {currencyFormat(lowestPrice?.priceDiscount)}
+ </span>
+ </div>
+ </div>
+ <span className='font-semibold'>Termasuk PPN :</span>
+ <div className='flex gap-x-1 items-center mt-2 '>
+ <div className='badge-solid-red text-caption-1'>
+ {lowestPrice?.discountPercentage}%
+ </div>
+ <div className='text-gray_r-11 line-through text-caption-1'>
+ {currencyFormat(lowestPrice?.price * 1.11)}
+ </div>
+ <h3 className='text-danger-500 font-semibold text-title-sm'>
+ {currencyFormat(lowestPrice?.priceDiscount * 1.11)}
+ </h3>
+ </div>
+ </>
+ ) : (
+ <span className='text-gray_r-12/90 font-normal text-h-sm'>
+ Hubungi kami untuk dapatkan harga terbaik,&nbsp;
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 underline'
+ >
+ klik disini
+ </a>
+ </span>
+ )}
+ <div className='flex gap-x-3 mt-4'>
+ <input
+ type='number'
+ className='form-input w-16 py-2 text-center bg-gray_r-1'
+ ref={setVariantQuantityRef(product.id)}
+ defaultValue={1}
+ />
+ <button
+ type='button'
+ onClick={() => handleAddToCart(product.id)}
+ className='flex-1 py-2 btn-yellow'
+ >
+ Keranjang
+ </button>
+ <button
+ type='button'
+ onClick={() => handleBuy(product.id)}
+ className='flex-1 py-2 btn-solid-red'
+ >
+ Beli
+ </button>
+ </div>
+ <div className='flex mt-4'>
+ <button className='flex items-center gap-x-1' onClick={toggleWishlist}>
+ {wishlist.data?.productTotal > 0 ? (
+ <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
+ ) : (
+ <HeartIcon className='w-6' />
+ )}
+ Wishlist
+ </button>
+ </div>
+ <div className='border border-gray_r-6 overflow-auto mt-4'>
+ <div className='font-medium text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'>
+ Produk Serupa
+ </div>
+ <div className='h-full divide-y divide-gray_r-6 max-h-96'>
+ {productSimilarInBrand &&
+ productSimilarInBrand?.map((product) => (
+ <div className='py-2' key={product.id}>
+ <ProductCard product={product} variant='horizontal' />
+ </div>
+ ))}
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className='my-12'>
+ <div className='text-h-lg font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+
+ <BottomPopup
+ className='!container'
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => setAddCartAlert(false)}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[10%]'>
+ <Image
+ src={product.image}
+ alt={product.name}
+ className='h-32 object-contain object-center w-full border border-gray_r-4'
+ />
+ </div>
+ <div className='ml-3 flex flex-1 items-center font-normal'>{product.name}</div>
+ <div className='ml-3 flex items-center font-normal'>
+ <Link href='/shop/cart' className='flex-1 py-2 text-gray_r-12 btn-yellow'>
+ Lihat Keranjang
+ </Link>
+ </div>
+ </div>
+
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
+ </div>
+ </DesktopView>
+ )
+}
+
+const informationTabOptions = [
+ { value: 'description', label: 'Deskripsi' },
+ { value: 'information', label: 'Info Penting' }
+]
+
+const TabButton = ({ children, active, ...props }) => {
+ const activeClassName = active
+ ? 'text-danger-500 underline underline-offset-4'
+ : 'text-gray_r-12/80'
+ return (
+ <button {...props} type='button' className={`font-medium ${activeClassName}`}>
+ {children}
+ </button>
+ )
+}
+
+const TabContent = ({ children, active, className = '', ...props }) => (
+ <div {...props} className={`${active ? 'block' : 'hidden'} ${className}`}>
+ {children}
+ </div>
+)
+
+export default ProductDesktopVariant
diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx
index f3afa938..fa1e2521 100644
--- a/src/lib/product/components/Product/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -15,6 +15,7 @@ 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 }) => {
const router = useRouter()
@@ -91,6 +92,7 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
const handleClickCart = () => {
if (!validAction()) return
+ gtagAddToCart(activeVariant, quantity)
updateItemCart({
productId: activeVariant.id,
quantity,
diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx
new file mode 100644
index 00000000..958b00cc
--- /dev/null
+++ b/src/lib/product/components/Product/ProductMobileVariant.jsx
@@ -0,0 +1,324 @@
+import Divider from '@/core/components/elements/Divider/Divider'
+import Image from '@/core/components/elements/Image/Image'
+import Link from '@/core/components/elements/Link/Link'
+import currencyFormat from '@/core/utils/currencyFormat'
+import { useEffect, useState } from 'react'
+import Select from 'react-select'
+import ProductSimilar from '../ProductSimilar'
+import LazyLoad from 'react-lazy-load'
+import { updateItemCart } from '@/core/utils/cart'
+import { HeartIcon } from '@heroicons/react/24/outline'
+import { useRouter } from 'next/router'
+import MobileView from '@/core/components/views/MobileView'
+import { toast } from 'react-hot-toast'
+import { createSlug } from '@/core/utils/slug'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import whatsappUrl from '@/core/utils/whatsappUrl'
+import { gtagAddToCart } from '@/core/utils/googleTag'
+
+const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
+ const router = useRouter()
+
+ const [quantity, setQuantity] = useState('1')
+ const [selectedVariant, setSelectedVariant] = useState(product.id)
+ const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
+ const [addCartAlert, setAddCartAlert] = useState(false)
+
+ const getLowestPrice = () => {
+ const lowest = product.price
+ return lowest
+ }
+
+ const [activeVariant, setActiveVariant] = useState({
+ id: null,
+ code: product.code,
+ name: product.name,
+ price: getLowestPrice(),
+ stock: product.stockTotal,
+ weight: product.weight
+ })
+
+ useEffect(() => {
+ if (selectedVariant) {
+ setActiveVariant({
+ id: product.id,
+ code: product.code,
+ name: product.name,
+ price: product.price,
+ stock: product.stock,
+ weight: product.weight
+ })
+ }
+ }, [selectedVariant, product])
+
+ const validAction = () => {
+ let isValid = true
+ if (!selectedVariant) {
+ toast.error('Pilih varian terlebih dahulu')
+ isValid = false
+ }
+ if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) {
+ toast.error('Jumlah barang minimal 1')
+ isValid = false
+ }
+ return isValid
+ }
+
+ const handleClickCart = () => {
+ if (!validAction()) return
+ gtagAddToCart(activeVariant, quantity)
+ updateItemCart({
+ productId: activeVariant.id,
+ quantity,
+ selected: true
+ })
+ setAddCartAlert(true)
+ }
+
+ const handleClickBuy = () => {
+ if (!validAction()) return
+ router.push(`/shop/checkout?productId=${activeVariant.id}&quantity=${quantity}`)
+ }
+
+ const productSimilarQuery = [
+ product?.name,
+ `fq=-product_id_i:${product.id}`,
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`
+ ].join('&')
+
+ return (
+ <MobileView>
+ <Image
+ src={product.parent.image}
+ alt={product.name}
+ className='h-72 object-contain object-center w-full border-b border-gray_r-4'
+ />
+
+ <div className='p-4'>
+ <div className='flex items-end mb-2'>
+ {product.manufacture?.name ? (
+ <Link
+ href={createSlug('/shop/brands/', product.manufacture?.name, product.manufacture?.id)}
+ >
+ {product.manufacture?.name}
+ </Link>
+ ) : (
+ <div>-</div>
+ )}
+ <button type='button' className='ml-auto' onClick={toggleWishlist}>
+ {wishlist.data?.productTotal > 0 ? (
+ <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
+ ) : (
+ <HeartIcon className='w-6' />
+ )}
+ </button>
+ </div>
+ <h1 className='leading-6 font-medium mb-3'>{activeVariant?.name}</h1>
+
+ {activeVariant?.price?.priceDiscount > 0 ? (
+ <>
+ <div className='flex gap-x-1 items-center'>
+ <div className='text-gray_r-11 text-caption-1'>Harga Sebelum PPN :</div>
+ <div className='text-gray_r-12 line-through text-caption-1'>
+ {' '}
+ {currencyFormat(activeVariant?.price?.priceDiscount)}
+ </div>
+ </div>
+ <div className='mt-2'>
+ <span className='font-semibold '>Termasuk PPN :</span>
+ <div className='flex gap-x-2 items-center mt-2'>
+ {activeVariant?.price?.discountPercentage > 0 && (
+ <>
+ <div className='badge-solid-red'>
+ {activeVariant?.price?.discountPercentage}%
+ </div>
+ <div className='text-gray_r-11 line-through text-caption-1'>
+ {currencyFormat(activeVariant?.price?.price * 1.11)}
+ </div>
+ </>
+ )}
+ <h3 className='text-danger-500 font-semibold'>
+ {currencyFormat(activeVariant?.price?.priceDiscount * 1.11)}
+ </h3>
+ </div>
+ </div>
+ </>
+ ) : (
+ <span className='text-gray_r-11 leading-6 font-normal'>
+ Hubungi kami untuk dapatkan harga terbaik,&nbsp;
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 underline'
+ >
+ klik disini
+ </a>
+ </span>
+ )}
+ </div>
+
+ <Divider />
+
+ <div className='p-4'>
+ <div className='mt-4 mb-2'>Jumlah</div>
+ <div className='flex gap-x-3'>
+ <div className='w-2/12'>
+ <input
+ name='quantity'
+ type='number'
+ className='form-input'
+ value={quantity}
+ onChange={(e) => setQuantity(e.target.value)}
+ />
+ </div>
+ <button type='button' className='btn-yellow flex-1' onClick={handleClickCart}>
+ Keranjang
+ </button>
+ <button type='button' className='btn-solid-red flex-1' onClick={handleClickBuy}>
+ Beli
+ </button>
+ </div>
+ </div>
+
+ <Divider />
+
+ <div className='p-4'>
+ <h2 className='font-semibold'>Informasi Produk</h2>
+ <div className='flex gap-x-4 mt-4 mb-3'>
+ {informationTabOptions.map((option) => (
+ <TabButton
+ value={option.value}
+ key={option.value}
+ active={informationTab == option.value}
+ onClick={() => setInformationTab(option.value)}
+ >
+ {option.label}
+ </TabButton>
+ ))}
+ </div>
+
+ <TabContent
+ active={informationTab == 'specification'}
+ className='rounded border border-gray_r-6 divide-y divide-gray_r-6'
+ >
+ <SpecificationContent label='Nomor SKU'>
+ <span>SKU-{product?.id}</span>
+ </SpecificationContent>
+ <SpecificationContent label='Part Number'>
+ <span>{activeVariant?.code || '-'}</span>
+ </SpecificationContent>
+ <SpecificationContent label='Stok'>
+ {activeVariant?.stock > 0 && (
+ <span className='flex gap-x-1.5'>
+ <div className='badge-solid-red'>Ready Stock</div>
+ <div className='badge-gray'>{activeVariant?.stock > 5 ? '> 5' : '< 5'}</div>
+ </span>
+ )}
+ {activeVariant?.stock == 0 && (
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 font-medium'
+ >
+ Tanya Stok
+ </a>
+ )}
+ </SpecificationContent>
+ <SpecificationContent label='Berat Barang'>
+ {activeVariant?.weight > 0 && <span>{activeVariant?.weight} KG</span>}
+ {activeVariant?.weight == 0 && (
+ <a
+ href={whatsappUrl('productWeight', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 font-medium'
+ >
+ Tanya Berat
+ </a>
+ )}
+ </SpecificationContent>
+ </TabContent>
+
+ <TabContent
+ active={informationTab == 'description'}
+ className='leading-6 text-gray_r-11'
+ dangerouslySetInnerHTML={{
+ __html: product.description != '' ? product.description : 'Belum ada deskripsi produk.'
+ }}
+ />
+ </div>
+
+ <Divider />
+
+ <div className='p-4'>
+ <h2 className='font-semibold mb-4'>Kamu Mungkin Juga Suka</h2>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+
+ <BottomPopup
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => setAddCartAlert(false)}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[15%]'>
+ <Image
+ src={product.image}
+ alt={product.name}
+ className='h-20 object-contain object-center w-full border border-gray_r-4'
+ />
+ </div>
+ <div className='ml-3 flex flex-1 items-center text-sm font-normal'>{product.name}</div>
+ <div className='ml-3 flex items-center text-sm font-normal'>
+ <Link href='/shop/cart' className='flex-1 py-2 text-gray_r-12 btn-yellow'>
+ Lihat Keranjang
+ </Link>
+ </div>
+ </div>
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
+ </MobileView>
+ )
+}
+
+const informationTabOptions = [
+ { value: 'specification', label: 'Spesifikasi' }
+ // { value: 'description', label: 'Deskripsi' },
+ // { value: 'information', label: 'Info Penting' }
+]
+
+const TabButton = ({ children, active, ...props }) => {
+ const activeClassName = active ? 'text-danger-500 underline underline-offset-4' : 'text-gray_r-11'
+ return (
+ <button {...props} type='button' className={`font-medium pb-1 ${activeClassName}`}>
+ {children}
+ </button>
+ )
+}
+
+const TabContent = ({ children, active, className, ...props }) => (
+ <div {...props} className={`${active ? 'block' : 'hidden'} ${className}`}>
+ {children}
+ </div>
+)
+
+const SpecificationContent = ({ children, label }) => (
+ <div className='flex justify-between p-3'>
+ <span className='text-gray_r-11'>{label}</span>
+ {children}
+ </div>
+)
+
+export default ProductMobileVariant
diff --git a/src/lib/review/components/CustomerReviews.jsx b/src/lib/review/components/CustomerReviews.jsx
index 5cc179e9..7cad52fb 100644
--- a/src/lib/review/components/CustomerReviews.jsx
+++ b/src/lib/review/components/CustomerReviews.jsx
@@ -12,7 +12,7 @@ const CustomerReviews = () => {
return (
<div className='px-4 sm:px-0'>
- <div className='font-medium sm:text-h-lg mb-4'>Ulasan Konsumen Kami</div>
+ <div className='font-semibold sm:text-h-lg mb-4'>Ulasan Konsumen Kami</div>
<DesktopView>
<Swiper slidesPerView={3.2} spaceBetween={16} {...swiperProps}>