diff options
| -rw-r--r-- | src/core/components/elements/Navbar/NavbarDesktop.jsx | 4 | ||||
| -rw-r--r-- | src/lib/cart/components/Cart.jsx | 316 | ||||
| -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.jsx | 22 | ||||
| -rw-r--r-- | src/pages/shop/product/[slug].jsx | 2 | ||||
| -rw-r--r-- | src/styles/globals.css | 10 |
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'> + + {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'> - - {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 { |
