summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRafi Zadanly <zadanlyr@gmail.com>2023-03-20 17:14:16 +0700
committerRafi Zadanly <zadanlyr@gmail.com>2023-03-20 17:14:16 +0700
commitd178a520534af7d1cbcc03134034ad8a2327b461 (patch)
tree488606d5e19782d2c0942402ab7c6e7c8d16bc1c /src
parent833488811b4164d7fbdce9bd70e171f06d62bf8d (diff)
product detail and cart header
Diffstat (limited to 'src')
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx4
-rw-r--r--src/lib/cart/components/Cart.jsx316
-rw-r--r--src/lib/product/components/Product/Product.jsx (renamed from src/lib/product/components/Product.jsx)0
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx (renamed from src/lib/product/components/ProductDesktop.jsx)69
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx (renamed from src/lib/product/components/ProductMobile.jsx)2
-rw-r--r--src/pages/shop/cart.jsx22
-rw-r--r--src/pages/shop/product/[slug].jsx2
-rw-r--r--src/styles/globals.css10
8 files changed, 217 insertions, 208 deletions
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 3ee9dc1e..faba7013 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -136,12 +136,12 @@ const NavbarDesktop = () => {
)}
{auth && (
<>
- <Link href='/' className='navbar-user-dropdown-button'>
+ <div href='/' className='navbar-user-dropdown-button'>
<span>Halo, {auth?.name}</span>
<div className='ml-auto'>
<ChevronDownIcon className='w-6' />
</div>
- </Link>
+ </div>
<NavbarUserDropdown />
</>
)}
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index 962ef860..b48b41fc 100644
--- a/src/lib/cart/components/Cart.jsx
+++ b/src/lib/cart/components/Cart.jsx
@@ -11,6 +11,8 @@ import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
import { toast } from 'react-hot-toast'
import Spinner from '@/core/components/elements/Spinner/Spinner'
import Alert from '@/core/components/elements/Alert/Alert'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
const Cart = () => {
const router = useRouter()
@@ -115,174 +117,184 @@ const Cart = () => {
}
return (
- <div className='pt-4'>
- <div className='flex justify-between mb-4 px-4'>
- <h1 className='font-semibold'>Daftar Produk Belanja</h1>
- <Link href='/'>Cari Produk Lain</Link>
- </div>
-
- <div className='flex flex-col gap-y-4 h-screen'>
- {cart.isLoading && (
- <div className='flex justify-center my-4'>
- <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ <>
+ <MobileView>
+ <div className='pt-4'>
+ <div className='flex justify-between mb-4 px-4'>
+ <h1 className='font-semibold'>Daftar Produk Belanja</h1>
+ <Link href='/'>Cari Produk Lain</Link>
</div>
- )}
- {!cart.isLoading && (!products || products?.length == 0) && (
- <div className='px-4'>
- <Alert
- className='text-center my-2'
- type='info'
- >
- Keranjang belanja anda masih kosong
- </Alert>
- </div>
- )}
+ <div className='flex flex-col gap-y-4 h-screen'>
+ {cart.isLoading && (
+ <div className='flex justify-center my-4'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ )}
- {products?.map((product) => (
- <div
- key={product?.id}
- className='flex mx-4'
- >
- <button
- type='button'
- className='flex items-center mr-2'
- onClick={() => toggleSelected(product.id)}
- >
- {!product?.selected && <div className='w-5 h-5 border border-gray_r-11 rounded' />}
- {product?.selected && <CheckIcon className='border bg-red_r-10 w-5 text-white' />}
- </button>
- <Link
- href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
- className='w-[30%] flex-shrink-0'
- >
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md'
- />
- </Link>
- <div className='flex-1 px-2 text-caption-2'>
- <Link
- href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
- >
- {product?.parent?.name}
- </Link>
- <div className='text-gray_r-11 mt-1'>
- {product?.code}{' '}
- {product?.attributes.length > 0 ? `| ${product?.attributes.join(', ')}` : ''}
+ {!cart.isLoading && (!products || products?.length == 0) && (
+ <div className='px-4'>
+ <Alert className='text-center my-2' type='info'>
+ Keranjang belanja anda masih kosong
+ </Alert>
</div>
- {product?.price?.discountPercentage > 0 && (
- <div className='flex gap-x-1 items-center mt-3'>
- <div className='text-gray_r-11 line-through text-caption-2'>
- {currencyFormat(product?.price?.price)}
+ )}
+
+ {products?.map((product) => (
+ <div key={product?.id} className='flex mx-4'>
+ <button
+ type='button'
+ className='flex items-center mr-2'
+ onClick={() => toggleSelected(product.id)}
+ >
+ {!product?.selected && (
+ <div className='w-5 h-5 border border-gray_r-11 rounded' />
+ )}
+ {product?.selected && <CheckIcon className='border bg-red_r-10 w-5 text-white' />}
+ </button>
+ <Link
+ href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
+ className='w-[30%] flex-shrink-0'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-40 w-full rounded-md'
+ />
+ </Link>
+ <div className='flex-1 px-2 text-caption-2'>
+ <Link
+ href={createSlug('/shop/product/', product?.parent.name, product?.parent.id)}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-1'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0 ? `| ${product?.attributes.join(', ')}` : ''}
+ </div>
+ {product?.price?.discountPercentage > 0 && (
+ <div className='flex gap-x-1 items-center mt-3'>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {currencyFormat(product?.price?.price)}
+ </div>
+ <div className='badge-solid-red'>{product?.price?.discountPercentage}%</div>
+ </div>
+ )}
+ <div className='font-normal mt-1'>
+ {currencyFormat(product?.price?.priceDiscount)}
+ </div>
+ <div className='flex justify-between items-center mt-1'>
+ <div className='text-red_r-11 font-medium'>
+ {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
+ </div>
+ <div className='flex gap-x-1'>
+ <button
+ type='button'
+ className='btn-light px-2 py-1'
+ onClick={() => updateQuantity(1, product?.id, 'MINUS')}
+ disabled={product?.quantity == 1}
+ >
+ -
+ </button>
+ <input
+ className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center'
+ type='number'
+ value={product?.quantity}
+ onChange={(e) => updateQuantity(e.target.value, product?.id)}
+ onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ />
+ <button
+ type='button'
+ className='btn-light px-2 py-1'
+ onClick={() => updateQuantity(1, product?.id, 'PLUS')}
+ >
+ +
+ </button>
+ <button
+ className='btn-red p-1 ml-1'
+ onClick={() => setDeleteConfirmation(product)}
+ >
+ <TrashIcon className='w-4' />
+ </button>
+ </div>
</div>
- <div className='badge-solid-red'>{product?.price?.discountPercentage}%</div>
</div>
- )}
- <div className='font-normal mt-1'>
- {currencyFormat(product?.price?.priceDiscount)}
</div>
- <div className='flex justify-between items-center mt-1'>
- <div className='text-red_r-11 font-medium'>
- {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
- </div>
- <div className='flex gap-x-1'>
- <button
- type='button'
- className='btn-light px-2 py-1'
- onClick={() => updateQuantity(1, product?.id, 'MINUS')}
- disabled={product?.quantity == 1}
- >
- -
- </button>
- <input
- className='form-input w-6 border-0 border-b rounded-none py-1 px-0 text-center'
- type='number'
- value={product?.quantity}
- onChange={(e) => updateQuantity(e.target.value, product?.id)}
- onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
- />
- <button
- type='button'
- className='btn-light px-2 py-1'
- onClick={() => updateQuantity(1, product?.id, 'PLUS')}
- >
- +
- </button>
- <button
- className='btn-red p-1 ml-1'
- onClick={() => setDeleteConfirmation(product)}
- >
- <TrashIcon className='w-4' />
- </button>
+ ))}
+
+ <div className='sticky bottom-0 left-0 w-full p-4 mt-auto border-t border-gray_r-6 bg-white'>
+ <div className='flex justify-between mb-4'>
+ <div className='text-gray_r-11'>
+ Total:
+ <span className='text-red_r-11 font-semibold'>
+ &nbsp;
+ {selectedProduct().length > 0
+ ? currencyFormat(totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount)
+ : '-'}
+ </span>
</div>
</div>
+ <div className='flex gap-x-3'>
+ <button
+ type='button'
+ className='btn-yellow flex-1'
+ disabled={selectedProduct().length == 0}
+ onClick={() => router.push('/shop/quotation')}
+ >
+ Quotation
+ </button>
+ <button
+ type='button'
+ className='btn-solid-red flex-1'
+ disabled={selectedProduct().length == 0}
+ onClick={() => router.push('/shop/checkout')}
+ >
+ Checkout
+ </button>
+ </div>
</div>
</div>
- ))}
- <div className='sticky bottom-0 left-0 w-full p-4 mt-auto border-t border-gray_r-6 bg-white'>
- <div className='flex justify-between mb-4'>
- <div className='text-gray_r-11'>
- Total:
- <span className='text-red_r-11 font-semibold'>
- &nbsp;
- {selectedProduct().length > 0
- ? currencyFormat(totalPriceBeforeTax - totalDiscountAmount + totalTaxAmount)
- : '-'}
- </span>
+ <BottomPopup
+ active={deleteConfirmation}
+ close={() => setDeleteConfirmation(null)}
+ title='Hapus dari Keranjang'
+ >
+ <div className='leading-7 text-gray_r-12/80'>
+ Apakah anda yakin menghapus barang{' '}
+ <span className='underline'>{deleteConfirmation?.name}</span> dari keranjang?
</div>
- </div>
- <div className='flex gap-x-3'>
- <button
- type='button'
- className='btn-yellow flex-1'
- disabled={selectedProduct().length == 0}
- onClick={() => router.push('/shop/quotation')}
- >
- Quotation
- </button>
- <button
- type='button'
- className='btn-solid-red flex-1'
- disabled={selectedProduct().length == 0}
- onClick={() => router.push('/shop/checkout')}
- >
- Checkout
- </button>
- </div>
+ <div className='flex mt-6 gap-x-4'>
+ <button
+ className='btn-solid-red flex-1'
+ type='button'
+ onClick={() => deleteProduct(deleteConfirmation?.id)}
+ >
+ Ya, Hapus
+ </button>
+ <button
+ className='btn-light flex-1'
+ type='button'
+ onClick={() => setDeleteConfirmation(null)}
+ >
+ Batal
+ </button>
+ </div>
+ </BottomPopup>
</div>
- </div>
+ </MobileView>
- <BottomPopup
- active={deleteConfirmation}
- close={() => setDeleteConfirmation(null)}
- title='Hapus dari Keranjang'
- >
- <div className='leading-7 text-gray_r-12/80'>
- Apakah anda yakin menghapus barang{' '}
- <span className='underline'>{deleteConfirmation?.name}</span> dari keranjang?
- </div>
- <div className='flex mt-6 gap-x-4'>
- <button
- className='btn-solid-red flex-1'
- type='button'
- onClick={() => deleteProduct(deleteConfirmation?.id)}
- >
- Ya, Hapus
- </button>
- <button
- className='btn-light flex-1'
- type='button'
- onClick={() => setDeleteConfirmation(null)}
- >
- Batal
- </button>
+ <DesktopView>
+ <div className='container mx-auto pt-10'>
+ <div className='flex'>
+ <div className='w-9/12'>
+ <h1 className='text-title-sm font-semibold'>Daftar Produk Belanja</h1>
+ </div>
+ </div>
</div>
- </BottomPopup>
- </div>
+ </DesktopView>
+ </>
)
}
diff --git a/src/lib/product/components/Product.jsx b/src/lib/product/components/Product/Product.jsx
index 9521cbe4..9521cbe4 100644
--- a/src/lib/product/components/Product.jsx
+++ b/src/lib/product/components/Product/Product.jsx
diff --git a/src/lib/product/components/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index 55d44212..ac17ec6e 100644
--- a/src/lib/product/components/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -3,46 +3,24 @@ import Link from '@/core/components/elements/Link/Link'
import DesktopView from '@/core/components/views/DesktopView'
import currencyFormat from '@/core/utils/currencyFormat'
import { HeartIcon } from '@heroicons/react/24/outline'
-import { Fragment, useEffect, useRef, useState } from 'react'
+import { useRef, useState } from 'react'
import LazyLoad from 'react-lazy-load'
-import ProductSimilar from './ProductSimilar'
+import ProductSimilar from '../ProductSimilar'
import { toast } from 'react-hot-toast'
import { updateItemCart } from '@/core/utils/cart'
const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
- const [variantQuantity, setVariantQuantity] = useState(null)
const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
- useEffect(() => {
- const mapVariantQuantity = product.variants.reduce((acc, cur) => {
- acc[cur.id] = '1'
- return acc
- }, {})
- setVariantQuantity(mapVariantQuantity)
- }, [product])
+ const variantQuantityRefs = useRef([])
- const changeQuantity = (variantId, quantity) => {
- setVariantQuantity((variantQuantity) => ({ ...variantQuantity, [variantId]: quantity }))
+ const setVariantQuantityRef = (variantId) => (element) => {
+ variantQuantityRefs.current[variantId] = element
}
- const variantSectionRef = useRef(null)
- const goToVariantSection = () => {
- if (variantSectionRef.current) {
- const position = variantSectionRef.current.getBoundingClientRect()
- window.scrollTo({
- top: position.top - 120 + window.pageYOffset,
- behavior: 'smooth'
- })
- }
- }
-
- const validAction = (variantId) => {
+ const validQuantity = (quantity) => {
let isValid = true
- if (
- !variantQuantity[variantId] ||
- variantQuantity[variantId] < 1 ||
- isNaN(parseInt(variantQuantity[variantId]))
- ) {
+ if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) {
toast.error('Jumlah barang minimal 1')
isValid = false
}
@@ -50,14 +28,26 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
}
const handleAddToCart = (variantId) => {
- if (!validAction(variantId)) return
+ const quantity = variantQuantityRefs.current[variantId].value
+ if (!validQuantity(quantity)) return
updateItemCart({
productId: variantId,
- quantity: variantQuantity[variantId]
+ quantity
})
toast.success('Berhasil menambahkan ke keranjang')
}
+ const variantSectionRef = useRef(null)
+ const goToVariantSection = () => {
+ if (variantSectionRef.current) {
+ const position = variantSectionRef.current.getBoundingClientRect()
+ window.scrollTo({
+ top: position.top - 120 + window.pageYOffset,
+ behavior: 'smooth'
+ })
+ }
+ }
+
const productSimilarQuery = [
product?.name.replace(/[()/"&]/g, ''),
`fq=-product_id:${product.id}`,
@@ -66,16 +56,16 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
return (
<DesktopView>
- <div className='container mx-auto mt-10'>
+ <div className='container mx-auto pt-10'>
<div className='flex'>
- <div className='w-3/12'>
+ <div className='w-[30%]'>
<Image
src={product.image}
alt={product.name}
className='h-96 object-contain object-center w-full border border-gray_r-4'
/>
</div>
- <div className='w-6/12 px-4'>
+ <div className='w-[50%] px-4'>
<h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1>
<div className='mt-10'>
<div className='flex p-3'>
@@ -110,7 +100,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
</div>
</div>
- <div className='w-3/12'>
+ <div className='w-[20%]'>
{product.variants.length > 1 && product.lowestPrice.priceDiscount > 0 && (
<div className='text-gray_r-12/80'>Harga mulai dari: </div>
)}
@@ -187,8 +177,8 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
<input
type='number'
className='form-input w-16 py-2 text-center bg-gray_r-1'
- value={variantQuantity?.[variant.id]}
- onChange={(e) => changeQuantity(variant.id, e.target.value)}
+ ref={setVariantQuantityRef(variant.id)}
+ defaultValue={1}
/>
</td>
<td className='flex gap-x-3'>
@@ -212,8 +202,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
<div className='mt-12'>
<div className='text-h-lg font-semibold'>Informasi Produk</div>
- <div className='my-5 h-0.5 bg-gray_r-6' />
- <div className='flex gap-x-4 mb-5'>
+ <div className='flex gap-x-4 mt-6 mb-4'>
{informationTabOptions.map((option) => (
<TabButton
value={option.value}
@@ -237,7 +226,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
}}
/>
</TabContent>
-
+
<TabContent active={informationTab == 'information'}>Belum ada informasi.</TabContent>
</div>
</div>
diff --git a/src/lib/product/components/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx
index 07da876e..c572a58e 100644
--- a/src/lib/product/components/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -4,7 +4,7 @@ import Link from '@/core/components/elements/Link/Link'
import currencyFormat from '@/core/utils/currencyFormat'
import { useEffect, useState } from 'react'
import Select from 'react-select'
-import ProductSimilar from './ProductSimilar'
+import ProductSimilar from '../ProductSimilar'
import LazyLoad from 'react-lazy-load'
import { updateItemCart } from '@/core/utils/cart'
import { HeartIcon } from '@heroicons/react/24/outline'
diff --git a/src/pages/shop/cart.jsx b/src/pages/shop/cart.jsx
index 7e78f215..9566cfcb 100644
--- a/src/pages/shop/cart.jsx
+++ b/src/pages/shop/cart.jsx
@@ -1,3 +1,6 @@
+import BasicLayout from '@/core/components/layouts/BasicLayout'
+import DesktopView from '@/core/components/views/DesktopView'
+import MobileView from '@/core/components/views/MobileView'
import dynamic from 'next/dynamic'
const AppLayout = dynamic(() => import('@/core/components/layouts/AppLayout'))
@@ -5,11 +8,18 @@ const CartComponent = dynamic(() => import('@/lib/cart/components/Cart'))
export default function Cart() {
return (
- <AppLayout
- title='Keranjang'
- withFooter={false}
- >
- <CartComponent />
- </AppLayout>
+ <>
+ <MobileView>
+ <AppLayout title='Keranjang' withFooter={false}>
+ <CartComponent />
+ </AppLayout>
+ </MobileView>
+
+ <DesktopView>
+ <BasicLayout>
+ <CartComponent />
+ </BasicLayout>
+ </DesktopView>
+ </>
)
}
diff --git a/src/pages/shop/product/[slug].jsx b/src/pages/shop/product/[slug].jsx
index 83939291..e8084cbe 100644
--- a/src/pages/shop/product/[slug].jsx
+++ b/src/pages/shop/product/[slug].jsx
@@ -4,7 +4,7 @@ import productApi from '@/lib/product/api/productApi'
import dynamic from 'next/dynamic'
const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout'))
-const Product = dynamic(() => import('@/lib/product/components/Product'))
+const Product = dynamic(() => import('@/lib/product/components/Product/Product'))
export async function getServerSideProps(context) {
const { slug } = context.query
diff --git a/src/styles/globals.css b/src/styles/globals.css
index de40a21d..6e4d46e4 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -432,6 +432,7 @@ button {
p-4
items-center
bg-red_r-11
+ font-medium
!text-gray_r-1
rounded-none
rounded-tr-xl;
@@ -442,18 +443,15 @@ button {
}
.navbar-user-dropdown-wrapper a {
- @apply text-gray_r-12/80 hover:text-red_r-11 font-medium py-1;
+ @apply text-gray_r-12/80 hover:bg-gray_r-5 font-medium py-2 px-4;
}
.navbar-user-dropdown {
@apply bg-white
border
border-gray_r-6
- p-4
+ py-2
w-full
- flex
- flex-col
- gap-y-2
shadow;
}
@@ -497,7 +495,7 @@ button {
}
.category-mega-box > div:hover .category-mega-box__parent {
- @apply bg-yellow_r-4;
+ @apply bg-gray_r-5;
}
.category-mega-box > div:hover .category-mega-box__child-wrapper {