summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortrisusilo <tri.susilo@altama.co.id>2023-09-22 03:25:33 +0000
committertrisusilo <tri.susilo@altama.co.id>2023-09-22 03:25:33 +0000
commit8b12b43c0c3f9dd2d2743c83c23ed2a3f30fdae0 (patch)
tree6ce4958f4000e3db72ceddebe7ffb468eefe395b /src
parent74b4e3a9b86f1d3b102ad3f907237f7da1b05009 (diff)
parentbda91439b6ef4605a579bde8bef603b551aab3dd (diff)
Merged in Feature/popup_cart (pull request #73)
Feature/popup cart
Diffstat (limited to 'src')
-rw-r--r--src/contexts/ProductCartContext.js21
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx18
-rw-r--r--src/lib/cart/components/Cart.jsx4
-rw-r--r--src/lib/cart/components/Cartheader.jsx278
-rw-r--r--src/lib/product/api/productSimilarApi.js12
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx4
-rw-r--r--src/pages/_app.jsx77
7 files changed, 363 insertions, 51 deletions
diff --git a/src/contexts/ProductCartContext.js b/src/contexts/ProductCartContext.js
new file mode 100644
index 00000000..06e97563
--- /dev/null
+++ b/src/contexts/ProductCartContext.js
@@ -0,0 +1,21 @@
+import { createContext, useCallback, useContext, useEffect, useState } from 'react'
+
+const ProductCartContext = createContext()
+
+export const ProductCartProvider = ({ children }) => {
+ const [productCart, setProductCart] = useState(null)
+ const [refreshCart, setRefreshCart] = useState(false)
+ const [isLoading, setIsloading] = useState(false)
+
+ return (
+ <ProductCartContext.Provider
+ value={{ productCart, setProductCart, refreshCart, setRefreshCart, isLoading, setIsloading }}
+ >
+ {children}
+ </ProductCartContext.Provider>
+ )
+}
+
+export const useProductCartContext = () => {
+ return useContext(ProductCartContext)
+}
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 2e4b25fc..3aba55c9 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -10,19 +10,17 @@ import DesktopView from '../../views/DesktopView'
import dynamic from 'next/dynamic'
import IndoteknikLogo from '@/images/logo.png'
import Category from '@/lib/category/components/Category'
-import { useContext, useEffect, useState } from 'react'
+import { useCallback, useContext, useEffect, useState } from 'react'
import useAuth from '@/core/hooks/useAuth'
import NavbarUserDropdown from './NavbarUserDropdown'
-import { getCountCart } from '@/core/utils/cart'
+import { getCartApi, getCountCart } from '@/core/utils/cart'
import whatsappUrl from '@/core/utils/whatsappUrl'
import { useRouter } from 'next/router'
import { getAuth, setAuth } from '@/core/utils/auth'
import { createSlug, getIdFromSlug } from '@/core/utils/slug'
-import productApi from '@/lib/product/api/productApi'
-import { useSession } from 'next-auth/react'
-import { AuthContext } from '@/pages/_app'
import { TopBannerSkeleton } from '../Skeleton/TopBannerSkeleton'
import { useProductContext } from '@/contexts/ProductContext'
+import Cardheader from '@/lib/cart/components/Cartheader'
const Search = dynamic(() => import('./Search'))
const TopBanner = dynamic(() => import('./TopBanner'), {
@@ -31,7 +29,6 @@ const TopBanner = dynamic(() => import('./TopBanner'), {
const NavbarDesktop = () => {
const [isOpenCategory, setIsOpenCategory] = useState(false)
- const { authenticated } = useContext(AuthContext)
const auth = useAuth()
const [cartCount, setCartCount] = useState(0)
@@ -43,7 +40,7 @@ const NavbarDesktop = () => {
const router = useRouter()
const { product } = useProductContext()
-
+
useEffect(() => {
if (router.pathname === '/shop/product/[slug]') {
setPayloadWa({
@@ -101,7 +98,7 @@ const NavbarDesktop = () => {
<div className='flex-1 flex items-center'>
<Search />
</div>
- <div className='flex gap-x-4'>
+ <div className='flex gap-x-4 items-center'>
<Link
href='/my/transactions'
target='_blank'
@@ -113,7 +110,8 @@ const NavbarDesktop = () => {
<br />
Quotation
</Link>
- <Link
+ <Cardheader cartCount={cartCount}/>
+ {/* <Link
href='/shop/cart'
target='_blank'
rel='noreferrer'
@@ -132,7 +130,7 @@ const NavbarDesktop = () => {
<br />
Belanja
</span>
- </Link>
+ </Link> */}
<Link
target='_blank'
rel='noreferrer'
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index efbcf76b..b5976a1b 100644
--- a/src/lib/cart/components/Cart.jsx
+++ b/src/lib/cart/components/Cart.jsx
@@ -22,6 +22,7 @@ import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'
import { getPromotionProgram } from '@/lib/promotinProgram/api/homepageApi'
import PromotionType from '@/lib/promotinProgram/components/PromotionType'
import { gtagBeginCheckout } from '@/core/utils/googleTag'
+import { useProductCartContext } from '@/contexts/ProductCartContext'
const Cart = () => {
const router = useRouter()
@@ -31,6 +32,8 @@ const Cart = () => {
const [cart, setCart] = useState(null)
+ const {setRefreshCart} = useProductCartContext()
+
useEffect(() => {
if (!auth) return
}, [auth])
@@ -196,6 +199,7 @@ const Cart = () => {
deleteItemCart({ productId })
setDeleteConfirmation(null)
setProducts([...productsToUpdate])
+ setRefreshCart(true)
toast.success('Berhasil menghapus barang dari keranjang')
}
diff --git a/src/lib/cart/components/Cartheader.jsx b/src/lib/cart/components/Cartheader.jsx
new file mode 100644
index 00000000..dd6c276e
--- /dev/null
+++ b/src/lib/cart/components/Cartheader.jsx
@@ -0,0 +1,278 @@
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { getCartApi } from '../api/CartApi'
+import currencyFormat from '@/core/utils/currencyFormat'
+import Image from '@/core/components/elements/Image/Image'
+import { createSlug } from '@/core/utils/slug'
+import useAuth from '@/core/hooks/useAuth'
+import { useRouter } from 'next/router'
+import odooApi from '@/core/api/odooApi'
+import { useProductCartContext } from '@/contexts/ProductCartContext'
+import whatsappUrl from '@/core/utils/whatsappUrl'
+
+const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline')
+const { default: Link } = require('next/link')
+
+const Cardheader = (cartCount) => {
+ const router = useRouter()
+ const [subTotal, setSubTotal] = useState(null)
+ const [buttonLoading, SetButtonTerapkan] = useState(false)
+ const itemLoading = [1, 2, 3]
+ const auth = useAuth()
+ const [countCart, setCountCart] = useState(null)
+ const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
+ useProductCartContext()
+
+ const [isHovered, setIsHovered] = useState(false)
+
+ const products = useMemo(() => {
+ return productCart?.products || []
+ }, [productCart])
+
+ const handleMouseEnter = () => {
+ setIsHovered(true)
+ getCart()
+ }
+
+ const handleMouseLeave = () => {
+ setIsHovered(false)
+ }
+
+ const getCart = () => {
+ if (!productCart && auth) {
+ refreshCartf()
+ }
+ }
+ const refreshCartf = useCallback(async () => {
+ setIsloading(true)
+ let cart = await getCartApi()
+ setProductCart(cart)
+ setCountCart(cart.productTotal)
+ setIsloading(false)
+ }, [setProductCart])
+
+ useEffect(() => {
+ if (!products) return
+
+ let calculateTotalPriceBeforeTax = 0
+ let calculateTotalTaxAmount = 0
+ let calculateTotalDiscountAmount = 0
+ for (const product of products) {
+ if (product.quantity == '') continue
+
+ let priceBeforeTax = product.price.price / 1.11
+ calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity
+ calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity
+ calculateTotalDiscountAmount +=
+ (product.price.price - product.price.priceDiscount) * product.quantity
+ }
+ let subTotal =
+ calculateTotalPriceBeforeTax - calculateTotalDiscountAmount + calculateTotalTaxAmount
+ setSubTotal(subTotal)
+ }, [products])
+
+ useEffect(() => {
+ if (refreshCart) {
+ refreshCartf()
+ }
+ setRefreshCart(false)
+ }, [refreshCart, refreshCartf, setRefreshCart])
+
+ useEffect(() => {
+ setCountCart(cartCount.cartCount)
+ }, [cartCount])
+
+ const handleCheckout = async () => {
+ SetButtonTerapkan(true)
+ let checkoutAll = await odooApi('POST', `/api/v1/user/${auth.id}/cart/select-all`)
+ router.push('/shop/checkout')
+ }
+
+ return (
+ <div className='relative group'>
+ <div>
+ <Link
+ href='/shop/cart'
+ target='_blank'
+ rel='noreferrer'
+ className='flex items-center gap-x-2 !text-gray_r-12/80'
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+ <div className={`relative ${countCart > 0 && 'mr-2'}`}>
+ <ShoppingCartIcon className='w-7' />
+ {countCart > 0 && (
+ <span className='absolute -top-2 -right-2 badge-solid-red rounded-full w-5 h-5 flex items-center justify-center'>
+ {countCart}
+ </span>
+ )}
+ </div>
+ <span>
+ Keranjang
+ <br />
+ Belanja
+ </span>
+ </Link>
+ </div>
+ <div
+ className={` ${
+ isHovered ? 'block' : 'hidden'
+ } fixed top-[155px] left-0 w-full h-full bg-black opacity-50 z-10`}
+ ></div>
+ <div
+ className='hidden group-hover:block absolute z-10 left-0 w-96'
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+ <div className='w-full max-w-md p-2 bg-white border border-gray-200 rounded-lg shadow'>
+ <div className='p-2 flex justify-between items-center'>
+ <h5 class='text-base font-semibold leading-none'>Keranjang Belanja</h5>
+ <Link href='/shop/cart' class='text-sm font-medium text-red-600 underline'>
+ Lihat Semua
+ </Link>
+ </div>
+ <hr className='mt-3 mb-3 border border-gray-100' />
+ <div className='flow-root max-h-[250px] overflow-y-auto'>
+ {!auth && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Silahkan{' '}
+ <Link href='/login' className='text-red-600 underline leading-6'>
+ Login
+ </Link>{' '}
+ Untuk Melihat Daftar Keranjang Belanja Anda
+ </p>
+ </div>
+ )}
+ {isLoading &&
+ itemLoading.map((item) => (
+ <div key={item} role='status' class='max-w-sm animate-pulse'>
+ <div class='flex items-center space-x-4 mb- 2'>
+ <div class='flex-shrink-0'>
+ <PhotoIcon class='h-16 w-16 text-gray-500' />
+ </div>
+ <div class='flex-1 min-w-0'>
+ <div class='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4'></div>
+ <div class='h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5'></div>
+ <div class='h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5'></div>
+ </div>
+ </div>
+ </div>
+ ))}
+ {products.length === 0 && !isLoading && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Tidak Ada Produk di Keranjang Belanja Anda
+ </p>
+ </div>
+ )}
+ {products.length > 0 && !isLoading && (
+ <>
+ <ul role='list' class='divide-y divide-gray-200 dark:divide-gray-700'>
+ {products &&
+ products?.map((product, index) => (
+ <>
+ <li class='py-1 sm:py-2'>
+ <div class='flex items-center space-x-4'>
+ <div class='flex-shrink-0'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-16 w-16 rounded-md'
+ />
+ </Link>
+ </div>
+ <div class='flex-1 min-w-0'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {' '}
+ <p class='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
+ {product.parent.name}
+ </p>
+ </Link>
+
+ {product?.price?.discountPercentage > 0 && (
+ <div className='flex gap-x-1 items-center mb-2 mt-1'>
+ <div className='badge-solid-red'>
+ {product?.price?.discountPercentage}%
+ </div>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {currencyFormat(product?.price?.price)}
+ </div>
+ </div>
+ )}
+ <div className='flex justify-between items-center'>
+ <div className='font-semibold text-sm text-red-600'>
+ {product?.price?.priceDiscount > 0 ? (
+ currencyFormat(product?.price?.priceDiscount)
+ ) : (
+ <span className='text-gray_r-12/90 font-normal text-caption-1'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ manufacture: product.manufacture?.name,
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ )
+ })}
+ className='text-danger-500 underline'
+ rel='noopener noreferrer'
+ target='_blank'
+ >
+ Call For Price
+ </a>
+ </span>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </>
+ ))}
+ </ul>
+ <hr />
+ </>
+ )}
+ </div>
+ {products.length > 0 && !isLoading && (
+ <>
+ <div className='mt-3'>
+ <span className='text-gray-400 text-caption-2'>Sub Total Sebelum PPN : </span>
+ <span className='font-semibold text-red-600'>Rp. {currencyFormat(subTotal)}</span>
+ </div>
+ <div className='mt-5 mb-2'>
+ <button
+ type='button'
+ className='btn-solid-red rounded-lg w-full'
+ onClick={handleCheckout}
+ disabled={buttonLoading}
+ >
+ {buttonLoading ? 'Loading...' : 'Lanjutkan Ke Pembayaran'}
+ </button>
+ </div>
+ </>
+ )}
+ </div>
+ </div>
+ </div>
+ )
+}
+
+export default Cardheader
diff --git a/src/lib/product/api/productSimilarApi.js b/src/lib/product/api/productSimilarApi.js
index c1bccd59..a008ce5d 100644
--- a/src/lib/product/api/productSimilarApi.js
+++ b/src/lib/product/api/productSimilarApi.js
@@ -21,10 +21,14 @@ const productSimilarApi = async ({ query, source }) => {
const dataProductSimilar = await axios(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=${query}&page=1&orderBy=popular-weekly&operation=OR`
)
- dataProductSimilar.data.response.products = [
- ...dataflashSale,
- ...dataProductSimilar.data.response.products,
- ];
+ if (dataflashSale) {
+ dataProductSimilar.data.response.products = [
+ ...dataflashSale,
+ ...dataProductSimilar.data.response.products
+ ]
+ } else {
+ dataProductSimilar.data.response.products = [...dataProductSimilar.data.response.products]
+ }
return dataProductSimilar.data.response
}
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index 937f2746..6da289bc 100644
--- a/src/lib/product/components/Product/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -22,6 +22,7 @@ import ImageNext from 'next/image'
import CountDown2 from '@/core/components/elements/CountDown/CountDown2'
import { LazyLoadComponent } from 'react-lazy-load-image-component'
import ColumnsSLA from './ColumnsSLA'
+import { useProductCartContext } from '@/contexts/ProductCartContext'
const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
const router = useRouter()
@@ -39,6 +40,8 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
const [selectVariantPromoActive, setSelectVariantPromoActive] = useState(null)
const [backgorundFlashSale, setBackgorundFlashSale] = useState(null)
+ const {setRefreshCart , refreshCart} = useProductCartContext()
+
const getLowestPrice = useCallback(() => {
const prices = product.variants.map((variant) => variant.price)
const lowest = prices.reduce((lowest, price) => {
@@ -116,6 +119,7 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => {
let source = 'cart'
updateCart(variantId, quantity, source)
+ setRefreshCart(true)
setAddCartAlert(true)
}
diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx
index ef737794..0062f7fc 100644
--- a/src/pages/_app.jsx
+++ b/src/pages/_app.jsx
@@ -11,12 +11,13 @@ import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'
import { SessionProvider } from 'next-auth/react'
import { getAuth } from '@/core/utils/auth'
import { ProductProvider } from '@/contexts/ProductContext'
+import { ProductCartProvider } from '@/contexts/ProductCartContext'
const queryClient = new QueryClient()
export const AuthContext = createContext({
- authenticated : false,
- setAuthenticated : (auth) => {}
+ authenticated: false,
+ setAuthenticated: (auth) => {}
})
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
@@ -63,43 +64,45 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
// <AuthContext.Provider value={{authenticated, setAuthenticated}}>
- <SessionProvider session={session}>
- <AnimatePresence>
- {animateLoader && (
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- exit={{ opacity: 0 }}
- transition={{
- duration: 0.1
- }}
- className='fixed w-screen h-screen z-[500] bg-white flex justify-center items-center'
- >
- <LogoSpinner />
- </motion.div>
- )}
- </AnimatePresence>
- <Toaster
- position='top-center'
- containerStyle={toasterStyle}
- toastOptions={{
- duration: 3000,
- className: 'border border-gray_r-8'
- }}
- />
- <NextProgress color='#F01C21' options={{ showSpinner: false }} />
- <QueryClientProvider client={queryClient}>
- <AnimatePresence
- mode='popLayout'
- initial={false}
- onExitComplete={() => window.scrollTo(0, 0)}
+ <SessionProvider session={session}>
+ <AnimatePresence>
+ {animateLoader && (
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ exit={{ opacity: 0 }}
+ transition={{
+ duration: 0.1
+ }}
+ className='fixed w-screen h-screen z-[500] bg-white flex justify-center items-center'
>
- <ProductProvider>
+ <LogoSpinner />
+ </motion.div>
+ )}
+ </AnimatePresence>
+ <Toaster
+ position='top-center'
+ containerStyle={toasterStyle}
+ toastOptions={{
+ duration: 3000,
+ className: 'border border-gray_r-8'
+ }}
+ />
+ <NextProgress color='#F01C21' options={{ showSpinner: false }} />
+ <QueryClientProvider client={queryClient}>
+ <AnimatePresence
+ mode='popLayout'
+ initial={false}
+ onExitComplete={() => window.scrollTo(0, 0)}
+ >
+ <ProductProvider>
+ <ProductCartProvider>
{!animateLoader && <Component {...pageProps} key={router.asPath} />}
- </ProductProvider>
- </AnimatePresence>
- </QueryClientProvider>
- </SessionProvider>
+ </ProductCartProvider>
+ </ProductProvider>
+ </AnimatePresence>
+ </QueryClientProvider>
+ </SessionProvider>
// </AuthContext.Provider>
)
}