diff options
| author | HATEC\SPVDEV001 <tri.susilo@altama.co.id> | 2023-06-19 15:46:03 +0700 |
|---|---|---|
| committer | HATEC\SPVDEV001 <tri.susilo@altama.co.id> | 2023-06-19 15:46:03 +0700 |
| commit | 637c22f1886cecf7307ced88dc951134d466a3fa (patch) | |
| tree | 7f00f63e13b3f5e56f3da06da1f98010937cd3cc | |
| parent | e4b4f2c09ebd819acc204c2e58288fe9fc6294ea (diff) | |
checkout
| -rw-r--r-- | public/images/noun-applied-check.svg | 31 | ||||
| -rw-r--r-- | public/images/noun-applied-check2.svg | 30 | ||||
| -rw-r--r-- | public/images/noun-check-5703372 2.svg | 11 | ||||
| -rw-r--r-- | public/images/noun-check-5703372.svg | 11 | ||||
| -rw-r--r-- | public/images/noun-discount-5796402.svg | 31 | ||||
| -rw-r--r-- | src/core/components/elements/Navbar/NavbarDesktop.jsx | 8 | ||||
| -rw-r--r-- | src/core/utils/cart.js | 27 | ||||
| -rw-r--r-- | src/lib/cart/components/Cart.jsx | 66 | ||||
| -rw-r--r-- | src/lib/checkout/api/checkoutApi.js | 9 | ||||
| -rw-r--r-- | src/lib/checkout/components/Checkout.jsx | 90 | ||||
| -rw-r--r-- | src/lib/checkout/components/CheckoutOld.jsx | 811 | ||||
| -rw-r--r-- | src/lib/invoice/components/Invoices.jsx | 2 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktop.jsx | 47 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobile.jsx | 7 | ||||
| -rw-r--r-- | src/lib/promotinProgram/components/HomePage.jsx | 7 | ||||
| -rw-r--r-- | src/styles/globals.css | 6 |
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; } |
