summaryrefslogtreecommitdiff
path: root/src-migrate/modules/product-detail/components
diff options
context:
space:
mode:
authorRafi Zadanly <zadanlyr@gmail.com>2024-01-13 10:35:22 +0700
committerRafi Zadanly <zadanlyr@gmail.com>2024-01-13 10:35:22 +0700
commitf62b2345f463695ef0f8f79830cd76b6e0332821 (patch)
treec06ff12a8312e3a02b0203f588db0f4da044c911 /src-migrate/modules/product-detail/components
parentee0b5893ac039ab05fe8247647364a923d707da3 (diff)
Refactor src migrate folder
Diffstat (limited to 'src-migrate/modules/product-detail/components')
-rw-r--r--src-migrate/modules/product-detail/components/AddToCart.tsx79
-rw-r--r--src-migrate/modules/product-detail/components/AddToWishlist.tsx17
-rw-r--r--src-migrate/modules/product-detail/components/Image.tsx37
-rw-r--r--src-migrate/modules/product-detail/components/Information.tsx84
-rw-r--r--src-migrate/modules/product-detail/components/PriceAction.tsx53
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx126
-rw-r--r--src-migrate/modules/product-detail/components/SimilarBottom.tsx21
-rw-r--r--src-migrate/modules/product-detail/components/SimilarSide.tsx34
-rw-r--r--src-migrate/modules/product-detail/components/VariantList.tsx85
9 files changed, 536 insertions, 0 deletions
diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx
new file mode 100644
index 00000000..4accab17
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/AddToCart.tsx
@@ -0,0 +1,79 @@
+import React from 'react'
+import { Button, useToast } from '@chakra-ui/react'
+import { getAuth } from '~/libs/auth'
+import { useRouter } from 'next/router'
+import Link from 'next/link'
+import { upsertUserCart } from '~/services/cart'
+
+type Props = {
+ variantId: number | null,
+ quantity?: number;
+ source?: 'buy' | 'add_to_cart';
+}
+
+const AddToCart = ({
+ variantId,
+ quantity = 1,
+ source = 'add_to_cart'
+}: Props) => {
+ const auth = getAuth()
+ const router = useRouter()
+ const toast = useToast({
+ position: 'top',
+ isClosable: true
+ })
+
+ const handleClick = async () => {
+ if (typeof auth !== 'object') {
+ const currentUrl = encodeURIComponent(router.asPath)
+ toast({
+ title: 'Masuk Akun',
+ description: <>
+ Masuk akun untuk dapat menambahkan barang ke keranjang belanja. {' '}
+ <Link className='underline' href={`/login?next=${currentUrl}`}>Klik disini</Link>
+ </>,
+ status: 'error',
+ duration: 4000,
+ })
+ return;
+ }
+
+ if (
+ !variantId ||
+ isNaN(quantity) ||
+ typeof auth !== 'object'
+ ) return;
+
+ toast.promise(
+ upsertUserCart(auth.id, 'product', variantId, quantity, true, source),
+ {
+ loading: { title: 'Menambahkan ke keranjang', description: 'Mohon tunggu...' },
+ success: { title: 'Menambahkan ke keranjang', description: 'Berhasil menambahkan ke keranjang belanja' },
+ error: { title: 'Menambahkan ke keranjang', description: 'Gagal menambahkan ke keranjang belanja' },
+ }
+ )
+
+ if (source === 'buy') {
+ router.push('/shop/checkout?source=buy')
+ }
+ }
+
+ const btnConfig = {
+ 'add_to_cart': {
+ colorScheme: 'yellow',
+ text: 'Keranjang'
+ },
+ 'buy': {
+ colorScheme: 'red',
+ text: 'Beli'
+ }
+ }
+
+ return (
+ <Button onClick={handleClick} colorScheme={btnConfig[source].colorScheme} className='w-full'>
+ {btnConfig[source].text}
+ </Button>
+ )
+}
+
+export default AddToCart \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/AddToWishlist.tsx b/src-migrate/modules/product-detail/components/AddToWishlist.tsx
new file mode 100644
index 00000000..eab3c7be
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/AddToWishlist.tsx
@@ -0,0 +1,17 @@
+import { Button } from '@chakra-ui/react'
+import { HeartIcon } from 'lucide-react'
+import React from 'react'
+
+const AddToWishlist = () => {
+ return (
+ <Button
+ variant='link'
+ className='!text-gray-500 !font-medium'
+ leftIcon={<HeartIcon size={18} />}
+ >
+ Wishlist
+ </Button>
+ )
+}
+
+export default AddToWishlist \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/Image.tsx b/src-migrate/modules/product-detail/components/Image.tsx
new file mode 100644
index 00000000..361580ea
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/Image.tsx
@@ -0,0 +1,37 @@
+import React from 'react'
+import { InfoIcon } from 'lucide-react'
+import { Tooltip } from '@chakra-ui/react'
+
+import { IProductDetail } from '~/types/product'
+import ImageUI from '~/components/ui/image'
+
+type Props = {
+ product: IProductDetail
+}
+
+const Image = ({ product }: Props) => {
+ return (
+ <div className='h-[340px] border border-gray-200 rounded-lg p-2 relative'>
+ <ImageUI
+ src={product.image || '/images/noimage.jpeg'}
+ alt={product.name}
+ width={512}
+ height={512}
+ className='object-contain object-center h-full'
+ classNames={{ wrapper: 'h-full' }}
+ />
+ <div className='absolute hidden md:block top-4 right-4'>
+ <Tooltip
+ placement='bottom-end'
+ label='Gambar atau foto berperan sebagai ilustrasi produk. Kadang tidak sesuai dengan kondisi terbaru dengan berbagai perubahan dan perbaikan. Hubungi admin kami untuk informasi yang lebih baik perihal gambar.'
+ >
+ <div className="text-gray-600">
+ <InfoIcon size={20} />
+ </div>
+ </Tooltip>
+ </div>
+ </div>
+ )
+}
+
+export default Image \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx
new file mode 100644
index 00000000..fd0e0b3c
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/Information.tsx
@@ -0,0 +1,84 @@
+import style from '../styles/information.module.css'
+
+import React from 'react'
+import dynamic from 'next/dynamic'
+import Link from 'next/link'
+import { useQuery } from 'react-query'
+
+import { IProductDetail } from '~/types/product'
+import { IProductVariantSLA } from '~/types/productVariant'
+import { createSlug } from '~/libs/slug'
+import { getVariantSLA } from '~/services/productVariant'
+
+const Skeleton = dynamic(() => import('@chakra-ui/react').then((mod) => mod.Skeleton))
+
+type Props = {
+ product: IProductDetail
+}
+
+const Information = ({ product }: Props) => {
+ const querySLA = useQuery<IProductVariantSLA>({
+ queryKey: ['variant-sla', product.variants[0].id],
+ queryFn: () => getVariantSLA(product.variants[0].id),
+ enabled: product.variant_total === 1
+ })
+
+ const sla = querySLA?.data
+
+ return (
+ <div className={style['wrapper']}>
+ <div className={style['row']}>
+ <div className={style['label']}>SKU Number</div>
+ <div className={style['value']}>SKU-{product.id}</div>
+ </div>
+ {/* <div className={style['row']}>
+ <div className={style['label']}>Part Number</div>
+ <div className={style['value']}>{product.code || '-'}</div>
+ </div> */}
+ <div className={style['row']}>
+ <div className={style['label']}>Manufacture</div>
+ <div className={style['value']}>
+ {!!product.manufacture.name ? (
+ <Link
+ href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())}
+ className='text-danger-500 hover:underline'
+ >
+ {product.manufacture.name}
+ </Link>
+ ) : '-'}
+ </div>
+ </div>
+ {/* <div className={style['row']}>
+ <div className={style['label']}>Preparation Time</div>
+ <div className={style['value']}>
+ {product.variant_total > 1 && 'Lihat Variant'}
+ {product.variant_total === 1 && (
+ <Skeleton isLoaded={querySLA.isSuccess} w={querySLA.isSuccess ? '100%' : '40px'} h='100%'>
+ {sla?.sla_date}
+ </Skeleton>
+ )}
+ </div>
+ </div>
+ <div className={style['row']}>
+ <div className={style['label']}>Stock</div>
+ <div className={style['value']}>
+ {product.variant_total > 1 && 'Lihat Variant'}
+ {product.variant_total === 1 && (
+ <Skeleton isLoaded={querySLA.isSuccess} w={querySLA.isSuccess ? '100%' : '10px'} h='100%'>
+ {sla?.qty && sla.qty > 0 ? sla?.qty : '-'}
+ </Skeleton>
+ )}
+ </div>
+ </div>
+ <div className={style['row']}>
+ <div className={style['label']}>Weight</div>
+ <div className={style['value']}>
+ {product.variant_total > 1 && 'Lihat Variant'}
+ {product.variant_total === 1 && (product.weight > 0 ? `${product.weight} kg` : '-')}
+ </div>
+ </div> */}
+ </div>
+ )
+}
+
+export default Information \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx
new file mode 100644
index 00000000..8189e5bd
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/PriceAction.tsx
@@ -0,0 +1,53 @@
+import style from '../styles/price-action.module.css'
+
+import React, { useEffect } from 'react'
+import formatCurrency from '~/libs/formatCurrency'
+import { formatToShortText } from '~/libs/formatNumber'
+import { IProductDetail } from '~/types/product'
+import { useProductDetail } from '../stores/useProductDetail'
+import AddToCart from './AddToCart'
+
+type Props = {
+ product: IProductDetail
+}
+
+const PriceAction = ({ product }: Props) => {
+ const { activePrice, setActive, activeVariantId, quantityInput, setQuantityInput } = useProductDetail()
+
+ useEffect(() => {
+ setActive(product.variants[0])
+ }, [product, setActive]);
+
+ return (
+ <div className='block md:sticky top-[150px] bg-white py-0 md:py-6 z-10'>
+ {product.qty_sold > 0 && (
+ <div className={style['secondary-text']}>
+ {formatToShortText(product.qty_sold)} Terjual
+ </div>
+ )}
+ <div className='h-2' />
+ <div className={style['main-price']}>
+ Rp {formatCurrency(activePrice?.price || 0)}
+ </div>
+ <div className='h-1' />
+ <div className={style['secondary-text']}>
+ {!!activePrice && (
+ <>
+ Termasuk PPN: {' '}
+ Rp {formatCurrency(Math.round(activePrice?.price * 1.11))}
+ </>
+ )}
+ </div>
+
+ <div className='h-4' />
+
+ <div className={style['action-wrapper']}>
+ <input type='number' value={quantityInput} onChange={(e) => setQuantityInput(e.target.value)} className={style['quantity-input']} />
+ <AddToCart variantId={activeVariantId} quantity={Number(quantityInput)} />
+ <AddToCart source='buy' variantId={activeVariantId} quantity={Number(quantityInput)} />
+ </div>
+ </div>
+ )
+}
+
+export default PriceAction \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
new file mode 100644
index 00000000..b752a138
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -0,0 +1,126 @@
+import style from '../styles/product-detail.module.css'
+
+import React from 'react'
+import Link from 'next/link'
+import { MessageCircleIcon } from 'lucide-react'
+import { Button } from '@chakra-ui/react'
+
+import { IProductDetail } from '~/types/product'
+
+import ProductImage from './Image'
+import Information from './Information'
+import AddToWishlist from './AddToWishlist'
+import VariantList from './VariantList'
+import SimilarSide from './SimilarSide'
+import SimilarBottom from './SimilarBottom'
+import useDevice from '@/core/hooks/useDevice'
+import PriceAction from './PriceAction'
+
+type Props = {
+ product: IProductDetail
+}
+
+const ProductDetail = ({ product }: Props) => {
+ const { isDesktop, isMobile } = useDevice()
+
+ return (
+ <>
+ <div className='md:flex md:flex-wrap'>
+ <div className='md:w-9/12 md:flex md:flex-col md:pr-4'>
+ <div className='md:flex md:flex-wrap'>
+ <div className="md:w-4/12">
+ <ProductImage product={product} />
+ </div>
+
+ <div className='md:w-8/12 px-4 md:pl-6'>
+ <div className='h-6 md:h-0' />
+
+ <h1 className={style['title']}>
+ {product.name}
+ </h1>
+
+ <div className='h-6 md:h-8' />
+
+ <Information product={product} />
+
+ <div className='h-4' />
+
+ <Button
+ as={Link}
+ href=''
+ variant='link'
+ colorScheme='red'
+ leftIcon={<MessageCircleIcon size={18} />}
+ >
+ Ask Admin
+ </Button>
+ </div>
+ </div>
+
+ <div className='h-full'>
+ {isMobile && (
+ <div className='px-4 pt-6'>
+ <PriceAction product={product} />
+ </div>
+ )}
+
+ <div className='h-4 md:h-10'></div>
+
+ <div className={style['section-card']}>
+ <h2 className={style['heading']}>
+ Variant ({product.variant_total})
+ </h2>
+ <div className='h-4' />
+ <VariantList variants={product.variants} />
+ </div>
+
+ <div className='h-0 md:h-6'></div>
+
+ <div className={style['section-card']}>
+ <h2 className={style['heading']}>
+ Informasi Produk
+ </h2>
+ <div className='h-4' />
+ <div
+ className={style['description']}
+ dangerouslySetInnerHTML={{ __html: !product.description || product.description == '<p><br></p>' ? 'Belum ada deskripsi' : product.description }}
+ />
+ </div>
+ </div>
+ </div>
+
+ {isDesktop && (
+ <div className="md:w-3/12">
+ <PriceAction product={product} />
+
+ <AddToWishlist />
+
+ <div className='h-8' />
+
+ <div className={style['heading']}>
+ Produk Serupa
+ </div>
+
+ <div className='h-4' />
+
+ <SimilarSide product={product} />
+ </div>
+ )}
+
+ <div className='md:w-full py-0 md:py-10 px-4 md:px-0'>
+ <div className={style['heading']}>
+ Kamu Mungkin Juga Suka
+ </div>
+
+ <div className='h-6' />
+
+ <SimilarBottom product={product} />
+ </div>
+
+ <div className='h-6 md:h-0' />
+ </div>
+ </>
+ )
+}
+
+export default ProductDetail \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/SimilarBottom.tsx b/src-migrate/modules/product-detail/components/SimilarBottom.tsx
new file mode 100644
index 00000000..9a12a6ef
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/SimilarBottom.tsx
@@ -0,0 +1,21 @@
+import React from 'react'
+import useProductSimilar from '~/modules/product-similar/hooks/useProductSimilar'
+import ProductSlider from '~/modules/product-slider'
+import { IProductDetail } from '~/types/product'
+
+type Props = {
+ product: IProductDetail
+}
+
+const SimilarBottom = ({ product }: Props) => {
+ const productSimilar = useProductSimilar({
+ name: product.name,
+ except: { productId: product.id }
+ })
+
+ const products = productSimilar.data?.products || []
+
+ return <ProductSlider products={products} productLayout='vertical' />;
+}
+
+export default SimilarBottom \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/SimilarSide.tsx b/src-migrate/modules/product-detail/components/SimilarSide.tsx
new file mode 100644
index 00000000..646a1c51
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/SimilarSide.tsx
@@ -0,0 +1,34 @@
+import style from '../styles/side-similar.module.css'
+
+import React from 'react'
+
+import ProductCard from '~/modules/product-card'
+import useProductSimilar from '~/modules/product-similar/hooks/useProductSimilar'
+import { IProductDetail } from '~/types/product'
+
+type Props = {
+ product: IProductDetail
+}
+
+const SimilarSide = ({ product }: Props) => {
+ const productSimilar = useProductSimilar({
+ name: product.name,
+ except: { productId: product.id, manufactureId: product.manufacture.id },
+ })
+
+ const products = productSimilar.data?.products || []
+
+ return (
+ <div className={style['wrapper']}>
+ {products.map((product) => (
+ <ProductCard
+ key={product.id}
+ product={product}
+ layout='horizontal'
+ />
+ ))}
+ </div>
+ )
+}
+
+export default SimilarSide \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/VariantList.tsx b/src-migrate/modules/product-detail/components/VariantList.tsx
new file mode 100644
index 00000000..d07e6b23
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/VariantList.tsx
@@ -0,0 +1,85 @@
+import style from '../styles/variant-list.module.css'
+
+import React from 'react'
+import { Button, Skeleton } from '@chakra-ui/react'
+
+import formatCurrency from '~/libs/formatCurrency'
+import clsxm from '~/libs/clsxm'
+import { IProductVariantDetail, IProductVariantSLA } from '~/types/productVariant'
+import { useProductDetail } from '../stores/useProductDetail'
+import { LazyLoadComponent } from 'react-lazy-load-image-component';
+import { getVariantSLA } from '~/services/productVariant'
+import { useQuery } from 'react-query'
+
+type Props = {
+ variants: IProductVariantDetail[]
+}
+
+const VariantList = ({ variants }: Props) => {
+ return (
+ <div className='overflow-auto'>
+ <div className={style['wrapper']}>
+ <div className={style['header']}>
+ <div className="w-2/12 sticky left-0 bg-gray-200">Part Number</div>
+ <div className="w-2/12">Variant</div>
+ <div className="w-1/12">Stock</div>
+ <div className="w-2/12">Time</div>
+ <div className="w-1/12">Weight</div>
+ <div className="w-2/12">Price</div>
+ </div>
+ {variants.map((variant) => (
+ <LazyLoadComponent key={variant.id}>
+ <Row variant={variant} />
+ </LazyLoadComponent>
+ ))}
+ </div>
+ </div>
+ )
+}
+
+const Row = ({ variant }: { variant: IProductVariantDetail }) => {
+ const { activeVariantId, setActive } = useProductDetail()
+ const querySLA = useQuery<IProductVariantSLA>({
+ queryKey: ['variant-sla', variant.id],
+ queryFn: () => getVariantSLA(variant.id),
+ })
+
+ const sla = querySLA?.data
+
+ return (
+ <div className={style['row']}>
+ <div className='w-2/12 sticky left-0 bg-white'>{variant.code}</div>
+ <div className='w-2/12'>{variant.attributes.join(', ')}</div>
+ <div className='w-1/12'>
+ <Skeleton isLoaded={querySLA.isSuccess} h='21px' w={16}>
+ {sla?.qty}
+ </Skeleton>
+ </div>
+ <div className='w-2/12'>
+ <Skeleton isLoaded={querySLA.isSuccess} h='21px' w={16}>
+ {sla?.sla_date}
+ </Skeleton>
+ </div>
+ <div className='w-1/12'>
+ {variant.weight > 0 ? `${variant.weight} Kg` : '-'}
+ </div>
+ <div className='w-2/12'>
+ Rp {formatCurrency(variant.price.price)}
+ </div>
+ <div className='w-2/12'>
+ <Button
+ onClick={() => setActive(variant)}
+ size='sm'
+ w='100%'
+ className={clsxm(style['select-btn'], {
+ [style['select-btn--active']]: variant.id === activeVariantId
+ })}
+ >
+ Pilih
+ </Button>
+ </div>
+ </div>
+ )
+}
+
+export default VariantList \ No newline at end of file