summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHATEC\SPVDEV001 <tri.susilo@altama.co.id>2023-06-19 15:46:03 +0700
committerHATEC\SPVDEV001 <tri.susilo@altama.co.id>2023-06-19 15:46:03 +0700
commit637c22f1886cecf7307ced88dc951134d466a3fa (patch)
tree7f00f63e13b3f5e56f3da06da1f98010937cd3cc
parente4b4f2c09ebd819acc204c2e58288fe9fc6294ea (diff)
checkout
-rw-r--r--public/images/noun-applied-check.svg31
-rw-r--r--public/images/noun-applied-check2.svg30
-rw-r--r--public/images/noun-check-5703372 2.svg11
-rw-r--r--public/images/noun-check-5703372.svg11
-rw-r--r--public/images/noun-discount-5796402.svg31
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx8
-rw-r--r--src/core/utils/cart.js27
-rw-r--r--src/lib/cart/components/Cart.jsx66
-rw-r--r--src/lib/checkout/api/checkoutApi.js9
-rw-r--r--src/lib/checkout/components/Checkout.jsx90
-rw-r--r--src/lib/checkout/components/CheckoutOld.jsx811
-rw-r--r--src/lib/invoice/components/Invoices.jsx2
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx47
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx7
-rw-r--r--src/lib/promotinProgram/components/HomePage.jsx7
-rw-r--r--src/styles/globals.css6
16 files changed, 1060 insertions, 134 deletions
diff --git a/public/images/noun-applied-check.svg b/public/images/noun-applied-check.svg
new file mode 100644
index 00000000..14e12e91
--- /dev/null
+++ b/public/images/noun-applied-check.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 500 161.7" style="enable-background:new 0 0 500 161.7;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#38B000;}
+ .st1{fill:#D00000;}
+ .st2{fill:#FFFFFF;}
+</style>
+<path class="st0" d="M-472.3-153.2c-129,0-234,105-234,234s105,234,234,234s234-105,234-234S-343.3-153.2-472.3-153.2z M-352,13.9
+ l-140.4,140.4c-3.1,3-7,4.5-11.1,4.5c-4.1,0-8-1.6-11.1-4.5L-577,91.9c-6.1-6.1-6.1-16.1,0-22.2s16.1-6.1,22.2,0l51.3,51.5
+ L-374.2-8.2c6.1-6.1,16.1-6.1,22.2,0S-346,7.8-352,13.9z"/>
+<g>
+ <rect x="19.5" y="17" class="st1" width="461" height="127.7"/>
+ <g>
+ <path class="st2" d="M76.9,44l23.7,75.6h-19l-4.7-15.6H52.4l-4.7,15.6H32.2L56.4,44H76.9z M56.7,90.1h16l-7.9-26.5L56.7,90.1z"/>
+ <path class="st2" d="M126.8,91.6v28.1h-18V44h29.8c6.6,0,11.7,0.7,15.3,2.1c3.5,1.4,6.7,4,9.4,7.6s4.2,8.1,4.2,13.5
+ c0,3.7-0.7,7.4-2.2,10.9c-1.5,3.5-3.6,6.3-6.3,8.4c-2.7,2-5.3,3.4-7.8,4c-2.5,0.7-6.5,1-11.9,1H126.8z M126.1,77.8h11.5
+ c4.4,0,7.5-1,9.3-3c1.8-2,2.7-4.4,2.7-7c0-3.1-1-5.5-3-7.3c-2-1.8-5-2.7-9-2.7h-11.5V77.8z"/>
+ <path class="st2" d="M195.3,91.6v28.1h-18V44h29.8c6.6,0,11.7,0.7,15.3,2.1c3.5,1.4,6.7,4,9.4,7.6s4.2,8.1,4.2,13.5
+ c0,3.7-0.7,7.4-2.2,10.9c-1.5,3.5-3.6,6.3-6.3,8.4c-2.7,2-5.3,3.4-7.8,4c-2.5,0.7-6.5,1-11.9,1H195.3z M194.6,77.8h11.5
+ c4.4,0,7.5-1,9.3-3c1.8-2,2.7-4.4,2.7-7c0-3.1-1-5.5-3-7.3c-2-1.8-5-2.7-9-2.7h-11.5V77.8z"/>
+ <path class="st2" d="M263.7,104.7h30.1v15h-48V44h18V104.7z"/>
+ <path class="st2" d="M320.6,44v75.6h-17.7V44H320.6z"/>
+ <path class="st2" d="M389.8,58.4h-36V74h28.3v14.2h-28.3v16.5h36v15h-54V44h54V58.4z"/>
+ <path class="st2" d="M400.4,44h23.1c9.4,0,16.4,1.3,21,3.9c4.6,2.6,8.7,6.7,12.4,12.4c3.7,5.6,5.5,12.8,5.5,21.4
+ c0,10.8-3.2,19.9-9.6,27.1c-6.4,7.3-15.2,10.9-26.4,10.9h-26V44z M417.8,57.9v47.5h7.3c6.5,0,11.2-2.4,14.4-7.2s4.7-10.5,4.7-17
+ c0-4.6-0.9-8.8-2.7-12.7c-1.8-3.9-4-6.6-6.7-8.2c-2.7-1.6-5.8-2.4-9.6-2.4H417.8z"/>
+ </g>
+</g>
+</svg>
diff --git a/public/images/noun-applied-check2.svg b/public/images/noun-applied-check2.svg
new file mode 100644
index 00000000..0b3b9137
--- /dev/null
+++ b/public/images/noun-applied-check2.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 500 161.7" style="enable-background:new 0 0 500 161.7;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#38B000;}
+ .st1{fill:none;stroke:#D00000;stroke-width:8;stroke-miterlimit:10;}
+ .st2{fill:#D00000;}
+</style>
+<path class="st0" d="M-472.3-153.2c-129,0-234,105-234,234s105,234,234,234s234-105,234-234S-343.3-153.2-472.3-153.2z M-352,13.9
+ l-140.4,140.4c-3.1,3-7,4.5-11.1,4.5c-4.1,0-8-1.6-11.1-4.5L-577,91.9c-6.1-6.1-6.1-16.1,0-22.2s16.1-6.1,22.2,0l51.3,51.5
+ L-374.2-8.2c6.1-6.1,16.1-6.1,22.2,0S-346,7.8-352,13.9z"/>
+<rect x="19.5" y="17" class="st1" width="461" height="127.7"/>
+<g>
+ <path class="st2" d="M77.4,43.6l23.9,76.4H82.1l-4.7-15.7H52.6l-4.8,15.7H32.2l24.5-76.4H77.4z M56.9,90.2h16.2l-8-26.8L56.9,90.2z
+ "/>
+ <path class="st2" d="M127.7,91.7v28.4h-18.2V43.6h30.1c6.7,0,11.9,0.7,15.4,2.2c3.6,1.4,6.7,4,9.5,7.7s4.2,8.2,4.2,13.6
+ c0,3.8-0.7,7.4-2.2,11c-1.5,3.6-3.6,6.4-6.3,8.5c-2.7,2.1-5.4,3.4-7.9,4.1c-2.6,0.7-6.6,1-12,1H127.7z M127.1,77.8h11.6
+ c4.4,0,7.6-1,9.3-3.1c1.8-2.1,2.7-4.4,2.7-7.1c0-3.1-1-5.6-3-7.4c-2-1.8-5-2.7-9.1-2.7h-11.6V77.8z"/>
+ <path class="st2" d="M196.9,91.7v28.4h-18.2V43.6h30.1c6.7,0,11.9,0.7,15.4,2.2c3.6,1.4,6.7,4,9.5,7.7s4.2,8.2,4.2,13.6
+ c0,3.8-0.7,7.4-2.2,11c-1.5,3.6-3.6,6.4-6.3,8.5c-2.7,2.1-5.4,3.4-7.9,4.1c-2.6,0.7-6.6,1-12,1H196.9z M196.3,77.8h11.6
+ c4.4,0,7.6-1,9.3-3.1c1.8-2.1,2.7-4.4,2.7-7.1c0-3.1-1-5.6-3-7.4c-2-1.8-5-2.7-9.1-2.7h-11.6V77.8z"/>
+ <path class="st2" d="M266,104.9h30.4v15.1h-48.5V43.6H266V104.9z"/>
+ <path class="st2" d="M323.4,43.6v76.4h-17.9V43.6H323.4z"/>
+ <path class="st2" d="M393.4,58.2H357v15.7h28.6v14.4H357v16.7h36.4v15.1h-54.6V43.6h54.6V58.2z"/>
+ <path class="st2" d="M404.1,43.6h23.3c9.5,0,16.6,1.3,21.3,3.9c4.7,2.6,8.8,6.8,12.5,12.5c3.7,5.7,5.5,12.9,5.5,21.6
+ c0,10.9-3.2,20.1-9.7,27.4c-6.4,7.3-15.3,11-26.7,11h-26.3V43.6z M421.7,57.7v48h7.4c6.5,0,11.4-2.4,14.5-7.3
+ c3.2-4.9,4.7-10.6,4.7-17.2c0-4.6-0.9-8.9-2.7-12.8c-1.8-3.9-4-6.7-6.7-8.3c-2.7-1.6-5.9-2.4-9.7-2.4H421.7z"/>
+</g>
+</svg>
diff --git a/public/images/noun-check-5703372 2.svg b/public/images/noun-check-5703372 2.svg
new file mode 100644
index 00000000..9b3bba6f
--- /dev/null
+++ b/public/images/noun-check-5703372 2.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#38B000;}
+</style>
+<path class="st0" d="M250,16C121,16,16,121,16,250s105,234,234,234s234-105,234-234S379,16,250,16z M370.3,183.1L229.9,323.5
+ c-3.1,3-7,4.5-11.1,4.5s-8-1.6-11.1-4.5l-62.4-62.4c-6.1-6.1-6.1-16.1,0-22.2c6.1-6.1,16.1-6.1,22.2,0l51.3,51.5l129.3-129.5
+ c6.1-6.1,16.1-6.1,22.2,0C376.4,167,376.4,177,370.3,183.1z"/>
+</svg>
diff --git a/public/images/noun-check-5703372.svg b/public/images/noun-check-5703372.svg
new file mode 100644
index 00000000..8a773f94
--- /dev/null
+++ b/public/images/noun-check-5703372.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#D00000;}
+</style>
+<path class="st0" d="M250,16C121,16,16,121,16,250s105,234,234,234s234-105,234-234S379,16,250,16z M370.3,183.1L229.9,323.5
+ c-3.1,3-7,4.5-11.1,4.5s-8-1.6-11.1-4.5l-62.4-62.4c-6.1-6.1-6.1-16.1,0-22.2c6.1-6.1,16.1-6.1,22.2,0l51.3,51.5l129.3-129.5
+ c6.1-6.1,16.1-6.1,22.2,0C376.4,167,376.4,177,370.3,183.1z"/>
+</svg>
diff --git a/public/images/noun-discount-5796402.svg b/public/images/noun-discount-5796402.svg
new file mode 100644
index 00000000..5850403c
--- /dev/null
+++ b/public/images/noun-discount-5796402.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns:serif="http://www.serif.com/"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 500 500"
+ style="enable-background:new 0 0 500 500;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#D00000;}
+</style>
+<g transform="matrix(1,0,0,1,-240,-48)">
+ <g>
+ <path class="st0" d="M478.7,69.3c2.9-3.2,7-5.1,11.3-5.1s8.4,1.8,11.3,5.1l23.4,26.4c4.5,5,10.3,8.6,16.8,10.4
+ c6.5,1.7,13.4,1.5,19.7-0.6l33.4-11.2c4.1-1.4,8.6-0.9,12.3,1.2c3.7,2.2,6.4,5.8,7.2,10l7,34.6c1.3,6.6,4.6,12.6,9.3,17.4
+ c4.8,4.8,10.8,8,17.4,9.3l34.6,7c4.2,0.9,7.9,3.5,10,7.2c2.1,3.7,2.6,8.2,1.2,12.3l-11.2,33.4c-2.1,6.4-2.3,13.2-0.6,19.7
+ c1.7,6.5,5.4,12.3,10.4,16.8l26.4,23.4c3.2,2.9,5.1,7,5.1,11.3s-1.8,8.4-5.1,11.3l-26.4,23.4c-5,4.5-8.6,10.3-10.4,16.8
+ c-1.7,6.5-1.5,13.4,0.6,19.7l11.2,33.4c1.4,4.1,0.9,8.6-1.2,12.3c-2.2,3.7-5.8,6.4-10,7.2l-34.6,7c-6.6,1.3-12.6,4.6-17.4,9.3
+ c-4.8,4.8-8,10.8-9.3,17.4l-7,34.6c-0.9,4.2-3.5,7.9-7.2,10c-3.7,2.1-8.2,2.6-12.3,1.2l-33.4-11.2c-6.4-2.1-13.2-2.3-19.7-0.6
+ c-6.5,1.7-12.3,5.4-16.8,10.4l-23.4,26.4c-2.9,3.2-7,5.1-11.3,5.1s-8.4-1.8-11.3-5.1l-23.4-26.4c-4.5-5-10.3-8.6-16.8-10.4
+ c-6.5-1.7-13.4-1.5-19.7,0.6l-33.4,11.2c-4.1,1.4-8.6,0.9-12.3-1.2c-3.7-2.2-6.4-5.8-7.2-10l-7-34.6c-1.3-6.6-4.6-12.6-9.3-17.4
+ c-4.8-4.8-10.8-8-17.4-9.3l-34.6-7c-4.2-0.9-7.9-3.5-10-7.2c-2.1-3.7-2.6-8.2-1.2-12.3l11.2-33.4c2.1-6.4,2.3-13.2,0.6-19.7
+ c-1.7-6.5-5.4-12.3-10.4-16.8l-26.4-23.4c-3.2-2.9-5.1-7-5.1-11.3s1.8-8.4,5.1-11.3l26.4-23.4c5-4.5,8.6-10.3,10.4-16.8
+ c1.7-6.5,1.5-13.4-0.6-19.7l-11.2-33.4c-1.4-4.1-0.9-8.6,1.2-12.3c2.2-3.7,5.8-6.4,10-7.2l34.6-7c6.6-1.3,12.6-4.6,17.4-9.3
+ c4.8-4.8,8-10.8,9.3-17.4l7-34.6c0.9-4.2,3.5-7.9,7.2-10c3.7-2.1,8.2-2.6,12.3-1.2l33.4,11.2c6.4,2.1,13.2,2.3,19.7,0.6
+ c6.5-1.7,12.3-5.4,16.8-10.4L478.7,69.3z M556.8,309.1c-30.7,0-55.7,24.9-55.7,55.7s24.9,55.7,55.7,55.7
+ c30.7,0,55.7-24.9,55.7-55.7S587.5,309.1,556.8,309.1z M386.5,417.2l222.6-222.6c4.3-4.3,4.3-11.4,0-15.7
+ c-4.3-4.3-11.4-4.3-15.7,0L370.8,401.5c-4.3,4.3-4.3,11.4,0,15.7C375.2,421.5,382.2,421.5,386.5,417.2z M556.8,331.4
+ c18.4,0,33.4,15,33.4,33.4s-15,33.4-33.4,33.4c-18.4,0-33.4-15-33.4-33.4S538.4,331.4,556.8,331.4z M423.2,175.5
+ c-30.7,0-55.7,24.9-55.7,55.7c0,30.7,24.9,55.7,55.7,55.7c30.7,0,55.7-24.9,55.7-55.7C478.9,200.5,453.9,175.5,423.2,175.5z
+ M423.2,197.8c18.4,0,33.4,15,33.4,33.4s-15,33.4-33.4,33.4c-18.4,0-33.4-15-33.4-33.4S404.8,197.8,423.2,197.8z"/>
+ </g>
+</g>
+</svg>
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 26edd5a4..733f5422 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -13,7 +13,7 @@ import Category from '@/lib/category/components/Category'
import { useEffect, useState } from 'react'
import useAuth from '@/core/hooks/useAuth'
import NavbarUserDropdown from './NavbarUserDropdown'
-import { getCart } from '@/core/utils/cart'
+import { getCountCart } from '@/core/utils/cart'
import TopBanner from './TopBanner'
import whatsappUrl from '@/core/utils/whatsappUrl'
@@ -27,7 +27,11 @@ const NavbarDesktop = () => {
useEffect(() => {
const handleCartChange = () => {
- setCartCount(Object.keys(getCart()).length)
+ const cart = async () => {
+ const listCart = await getCountCart()
+ setCartCount(listCart)
+ }
+ cart()
}
handleCartChange()
diff --git a/src/core/utils/cart.js b/src/core/utils/cart.js
index ad7894b5..6575f6d0 100644
--- a/src/core/utils/cart.js
+++ b/src/core/utils/cart.js
@@ -14,13 +14,6 @@ const getCart = () => {
return {}
}
-const getCartnew = async () => {
- const id = getAuth()?.id
- const cart = await odooApi('GET', `/api/v1/user/${id}/cart`)
-
- return cart
-}
-
/**
* Saves cart data to localStorage, if available.
*
@@ -51,6 +44,22 @@ const addCart = async (product_id, qty, selected) => {
)
}
+const getCartApi = async () => {
+ const id = getAuth()?.id
+ const cart = await odooApi('GET', `/api/v1/user/${id}/cart`)
+
+ return cart
+}
+
+const getCountCart = async () => {
+ const id = getAuth()?.id
+ if(id){
+ const cart = await odooApi('GET', `/api/v1/user/${id}/cart/count`)
+ return cart
+ }
+ return
+}
+
const deleteCart = async (product_id) => {
const id = getAuth()?.id
const cartDelete = await odooApi(
@@ -85,7 +94,7 @@ const updateItemCart = ({ productId, quantity, selected = false }) => {
quantity = parseInt(quantity)
cart[productId] = { productId, quantity, selected }
setCart(cart)
- addCart(productId, quantity)
+ addCart(productId, quantity, selected)
return true
}
@@ -104,4 +113,4 @@ const deleteItemCart = ({ productId }) => {
return true
}
-export { getCart, getItemCart, updateItemCart, deleteItemCart, getCartnew }
+export { getCart, getItemCart, updateItemCart, deleteItemCart, addCart, getCartApi, getCountCart}
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index 561a0064..31aa034d 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, getCart, getCartnew, getItemCart, updateItemCart } from '@/core/utils/cart'
+import { addCart, deleteItemCart, getCart, getCartApi, getCartnew, 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'
@@ -19,28 +19,34 @@ import productSearchApi from '@/lib/product/api/productSearchApi'
import whatsappUrl from '@/core/utils/whatsappUrl'
import useAuth from '@/core/hooks/useAuth'
-
-const { useQuery } = require('react-query')
-const { getCartApi } = require('../api/CartApi')
-
const Cart = () => {
const router = useRouter()
const [products, setProducts] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
const auth = useAuth()
- const { data: listCart } = useQuery('listCart', getCartApi)
+
+
+ const [cart, setCart] = useState(null)
useEffect(() => {
if (!auth) return
}, [auth])
useEffect(() => {
- if(listCart){
- setProducts(listCart.products)
+ const cart = async () => {
+ const listCart = await getCartApi()
+ setCart(listCart)
}
- }, [listCart])
+ cart()
+ })
+
+ useEffect(() => {
+ if(cart){
+ setProducts(cart.products)
+ setIsLoading(false)
+ }
+ }, [cart])
- console.log('product', products)
- const { cart } = useCart({ enabled: !products })
const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0)
const [totalTaxAmount, setTotalTaxAmount] = useState(0)
@@ -50,20 +56,6 @@ const Cart = () => {
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
@@ -73,11 +65,6 @@ 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
let priceBeforeTax = product.price.price / 1.11
@@ -127,8 +114,11 @@ 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)
}
const toggleSelected = (productId) => {
@@ -136,8 +126,12 @@ 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)
}
const selectedProduct = () => {
@@ -190,13 +184,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
@@ -335,7 +329,7 @@ const Cart = () => {
</tr>
</thead>
<tbody>
- {cart.isLoading && (
+ {isLoading && (
<tr>
<td colSpan={6}>
<div className='flex justify-center my-2'>
@@ -344,7 +338,7 @@ const Cart = () => {
</td>
</tr>
)}
- {!cart.isLoading && (!products || products?.length == 0) && (
+ {!isLoading && (!products || products?.length == 0) && (
<tr>
<td colSpan={6}>Keranjang belanja anda masih kosong</td>
</tr>
diff --git a/src/lib/checkout/api/checkoutApi.js b/src/lib/checkout/api/checkoutApi.js
index b76c9b7f..94de8721 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,9 @@ const checkoutApi = async ({ data }) => {
return dataCheckout
}
-export default checkoutApi
+export const getProductsCheckout = async () => {
+ const id = getAuth()?.id
+ const products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout`)
+
+ return products
+}
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 088b641b..09fdbd86 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -4,15 +4,14 @@ 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'
@@ -24,12 +23,18 @@ 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'
const SELF_PICKUP_ID = 32
+const { checkoutApi } = require('../api/checkoutApi')
+const { getProductsCheckout } = require('../api/checkoutApi')
+
const Checkout = () => {
const router = useRouter()
const auth = useAuth()
+ const { data: cartCheckout } = useQuery('cartCheckout', getProductsCheckout)
+
const [selectedAddress, setSelectedAddress] = useState({
shipping: null,
invoicing: null
@@ -66,8 +71,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([])
@@ -96,52 +99,10 @@ const Checkout = () => {
}, [])
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])
+ setProducts(cartCheckout?.products)
+ setCheckWeight(cartCheckout?.hasProductWithoutWeight)
+ setTotalWeight(cartCheckout?.totalWeight.g)
+ }, [cartCheckout])
useEffect(() => {
const loadServiceRajaOngkir = async () => {
@@ -240,7 +201,6 @@ const Checkout = () => {
setIsLoading(false)
window.location.href = payment.data.redirectUrl
}
- const taxTotal = (totalAmount - totalDiscountAmount) * 0.11
return (
<>
@@ -304,19 +264,19 @@ const Checkout = () => {
<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>{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(totalDiscountAmount)}</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'>Subtotal</div>
- <div>{currencyFormat(totalAmount - totalDiscountAmount)}</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(taxTotal)}</div>
+ <div>{currencyFormat(cartCheckout?.tax)}</div>
</div>
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
@@ -329,10 +289,7 @@ const Checkout = () => {
<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 +
+ {currencyFormat(cartCheckout?.grandTotal +
Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
)}
</div>
@@ -526,19 +483,19 @@ const Checkout = () => {
<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>{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(totalDiscountAmount)}</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'>Subtotal</div>
- <div>{currencyFormat(totalAmount - totalDiscountAmount)}</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(taxTotal)}</div>
+ <div>{currencyFormat(cartCheckout?.tax)}</div>
</div>
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
@@ -554,10 +511,7 @@ const Checkout = () => {
<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 +
+ {currencyFormat(cartCheckout?.grandTotal +
Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
)}
</div>
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/invoice/components/Invoices.jsx b/src/lib/invoice/components/Invoices.jsx
index b74af8c0..5edcfdbf 100644
--- a/src/lib/invoice/components/Invoices.jsx
+++ b/src/lib/invoice/components/Invoices.jsx
@@ -33,8 +33,6 @@ const Invoices = () => {
}
const { invoices } = useInvoices({ query })
- console.log('bangke', invoices)
-
const [inputQuery, setInputQuery] = useState(q)
const [toOthers, setToOthers] = useState(null)
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index f1a001a6..d9c95327 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'
@@ -58,7 +59,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
}
const handleAddToCart = (variantId) => {
- if(!auth) {
+ if (!auth) {
router.push(`/login?next=/shop/product/${slug}`)
return
}
@@ -165,11 +166,13 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
)}
</div>
</div>
- <div className='pt-3'>
- <div className='flex mt-1'>
- <PromotionType></PromotionType>
+ {product.variants.length <= 1 && (
+ <div className='pt-3'>
+ <div className='flex mt-1'>
+ <PromotionType></PromotionType>
+ </div>
</div>
- </div>
+ )}
</div>
</div>
@@ -328,7 +331,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>
@@ -367,6 +370,27 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
/>
</td>
<td className='flex gap-x-3'>
+ {index == 0 ? (
+ <ImageNext
+ src='/images/noun-applied-check2.svg'
+ alt=''
+ height={60}
+ width={60}
+ onClick={() => setPromotionType(true)}
+ className='cursor-pointer'
+ ></ImageNext>
+ ) : (
+ <div className='w-[60px] flex justify-center'>
+ <ImageNext
+ src='/images/noun-discount-5796402.svg'
+ alt=''
+ height={30}
+ width={30}
+ onClick={() => setPromotionType(true)}
+ className='cursor-pointer'
+ ></ImageNext>
+ </div>
+ )}
<button
type='button'
onClick={() => handleAddToCart(variant.id)}
@@ -396,7 +420,16 @@ 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}></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 1c2974b0..4da237ed 100644
--- a/src/lib/product/components/Product/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -171,9 +171,6 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
<Divider />
<div className='p-4'>
- <div className='mb-5'>
- <PromotionType></PromotionType>
- </div>
<div>
<label className='flex justify-between'>
Pilih Varian:
@@ -189,6 +186,10 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
isSearchable={product.variantTotal > 10}
/>
</div>
+ <div className='mt-5'>
+ <PromotionType></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/promotinProgram/components/HomePage.jsx b/src/lib/promotinProgram/components/HomePage.jsx
index 8d159899..c0968161 100644
--- a/src/lib/promotinProgram/components/HomePage.jsx
+++ b/src/lib/promotinProgram/components/HomePage.jsx
@@ -27,7 +27,6 @@ const HomePage = () => {
}
)
-
useEffect(() => {
if (titlePromotion && titlePromotion.length > 0) {
setActiveTab(titlePromotion[0].name)
@@ -36,11 +35,11 @@ const HomePage = () => {
setActiveBanner(titlePromotion[0].banner)
productPromotionRefetch()
}
- }, [titlePromotion])
+ }, [titlePromotion, productPromotionRefetch])
useEffect(() => {
if (productPromotion) {
- setparentPromotions(() => {
+ setparentPromotions((parentPromotions) => {
return parentPromotions.map((title) => {
if (title.id === activeId && title.products === undefined) {
return {
@@ -52,7 +51,7 @@ const HomePage = () => {
})
})
}
- }, [productPromotion, parentPromotions, activeId])
+ }, [productPromotion, activeId])
const { isMobile, isDesktop } = useDevice()
const handleTabClick = (id, label, banner) => {
diff --git a/src/styles/globals.css b/src/styles/globals.css
index aa707913..7d571e3f 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -396,11 +396,15 @@ button {
}
.table-specification > table {
- @apply table-fixed
+ @apply table-auto
border-collapse
w-full;
}
+.table-specification > table > thead > tr > th:last-child{
+ @apply w-3/12;
+}
+
.table-specification > table > thead {
@apply sticky top-0 border-b;
}