diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-10-24 11:01:08 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-10-24 11:01:08 +0700 |
| commit | 9abd621285d739a9c502d661013db5ce96edec33 (patch) | |
| tree | 98ff4ae0bf8adcced77699de33aebe50616233dc /src-migrate | |
| parent | ca30c28dd0b19977eb771fc32ff5e520cdef1068 (diff) | |
| parent | e0aa9e04a473a4f7a21b389b42314d3fed06b43e (diff) | |
Merge branch 'new-release' into CR/new_product_detail
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/modules/page-content/index.tsx | 30 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/AddToCart.tsx | 312 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx | 17 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromoList.tsx | 79 | ||||
| -rw-r--r-- | src-migrate/pages/shop/promo/index.tsx | 41 |
5 files changed, 279 insertions, 200 deletions
diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx index edecb855..3423ca8b 100644 --- a/src-migrate/modules/page-content/index.tsx +++ b/src-migrate/modules/page-content/index.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; import { PageContentProps } from '~/types/pageContent'; import { getPageContent } from '~/services/pageContent'; @@ -8,18 +8,38 @@ type Props = { }; const PageContent = ({ path }: Props) => { + const [localData, setData] = useState<PageContentProps>(); + const [shouldFetch, setShouldFetch] = useState(false); + + useEffect(() => { + const localData = localStorage.getItem(`page-content:${path}`); + if (localData) { + setData(JSON.parse(localData)); + }else{ + setShouldFetch(true); + } + },[]) + const { data, isLoading } = useQuery<PageContentProps>( `page-content:${path}`, - async () => await getPageContent({ path }) + async () => await getPageContent({ path }), { + enabled: shouldFetch, + onSuccess: (data) => { + if (data) { + localStorage.setItem(`page-content:${path}`, JSON.stringify(data)); + setData(data); + } + }, + } ); const parsedContent = useMemo<string>(() => { - if (!data) return ''; - return data.content.replaceAll( + if (!localData) return ''; + return localData.content.replaceAll( 'src="/web/image', `src="${process.env.NEXT_PUBLIC_ODOO_API_HOST}/web/image` ); - }, [data]); + }, [localData]); if (isLoading) return <PageContentSkeleton />; return <div dangerouslySetInnerHTML={{ __html: parsedContent || '' }}></div>; diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx index a5284637..280e4a7a 100644 --- a/src-migrate/modules/product-detail/components/AddToCart.tsx +++ b/src-migrate/modules/product-detail/components/AddToCart.tsx @@ -1,51 +1,55 @@ -import BottomPopup from '@/core/components/elements/Popup/BottomPopup' +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; import style from '../styles/price-action.module.css'; -import { Button, Link, useToast } from '@chakra-ui/react' -import product from 'next-seo/lib/jsonld/product' -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' -import Image from '~/components/ui/image' -import { getAuth } from '~/libs/auth' -import { upsertUserCart } from '~/services/cart' +import { Button, Link, useToast } from '@chakra-ui/react'; +import product from 'next-seo/lib/jsonld/product'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import Image from '~/components/ui/image'; +import { getAuth } from '~/libs/auth'; +import { upsertUserCart } from '~/services/cart'; import LazyLoad from 'react-lazy-load'; import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar'; import { IProductDetail } from '~/types/product'; import ImageNext from 'next/image'; -import { useProductCartContext } from '@/contexts/ProductCartContext' -import { createSlug } from '~/libs/slug' -import formatCurrency from '~/libs/formatCurrency' +import { useProductCartContext } from '@/contexts/ProductCartContext'; +import { createSlug } from '~/libs/slug'; +import formatCurrency from '~/libs/formatCurrency'; import { useProductDetail } from '../stores/useProductDetail'; type Props = { - variantId: number | null, + variantId: number | null; quantity?: number; source?: 'buy' | 'add_to_cart'; - products : IProductDetail -} + products: IProductDetail; +}; -type Status = 'idle' | 'loading' | 'success' +type Status = 'idle' | 'loading' | 'success'; const AddToCart = ({ variantId, quantity = 1, source = 'add_to_cart', - products + products, }: Props) => { - const auth = getAuth() - const router = useRouter() + let auth = getAuth(); + const router = useRouter(); const toast = useToast({ position: 'top', - isClosable: true - }) + isClosable: true, + }); - const { - askAdminUrl, - } = useProductDetail(); + const { askAdminUrl } = useProductDetail(); const [product, setProducts] = useState(products); - const [status, setStatus] = useState<Status>('idle') - const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } = - useProductCartContext() + const [status, setStatus] = useState<Status>('idle'); + const { + productCart, + setRefreshCart, + setProductCart, + refreshCart, + isLoading, + setIsloading, + } = useProductCartContext(); const productSimilarQuery = [ product?.name, @@ -55,32 +59,48 @@ const AddToCart = ({ const [addCartAlert, setAddCartAlert] = useState(false); const handleButton = async () => { - if (typeof auth !== 'object') { - const currentUrl = encodeURIComponent(router.asPath) - router.push(`/login?next=${currentUrl}`) - return; + let isLoggedIn = typeof auth === 'object'; + + if (!isLoggedIn) { + const currentUrl = encodeURIComponent(router.asPath); + await router.push(`/login?next=${currentUrl}`); + + // Tunggu login berhasil, misalnya dengan memantau perubahan status auth. + const authCheckInterval = setInterval(() => { + const newAuth = getAuth(); + if (typeof newAuth === 'object') { + isLoggedIn = true; + auth = newAuth; // Update nilai auth setelah login + clearInterval(authCheckInterval); + } + }, 500); // Periksa status login setiap 500ms + + await new Promise((resolve) => { + const checkLogin = setInterval(() => { + if (isLoggedIn) { + clearInterval(checkLogin); + resolve(null); + } + }, 500); + }); } - - if ( - !variantId || - isNaN(quantity) || - typeof auth !== 'object' - ) return; - if (status === 'success') return - setStatus('loading') + + if (!variantId || isNaN(quantity) || typeof auth !== 'object') return; + if (status === 'success') return; + setStatus('loading'); await upsertUserCart({ userId: auth.id, - type: 'product', - id: variantId, - qty: quantity, - selected: true, - source: source, - qtyAppend: true - }) - setStatus('idle') + type: 'product', + id: variantId, + qty: quantity, + selected: true, + source: source, + qtyAppend: true, + }); + setStatus('idle'); setRefreshCart(true); setAddCartAlert(true); - + toast({ title: 'Tambah ke keranjang', description: 'Berhasil menambahkan barang ke keranjang belanja', @@ -88,120 +108,130 @@ const AddToCart = ({ duration: 3000, isClosable: true, position: 'top', - }) - + }); + if (source === 'buy') { - router.push('/shop/checkout?source=buy') + router.push('/shop/checkout?source=buy'); } - } + }; useEffect(() => { - if (status === 'success') setTimeout(() => { setStatus('idle') }, 3000) - }, [status]) + if (status === 'success') + setTimeout(() => { + setStatus('idle'); + }, 3000); + }, [status]); const btnConfig = { - 'add_to_cart': { + add_to_cart: { colorScheme: 'yellow', - text: 'Keranjang' + text: 'Keranjang', }, - 'buy': { + buy: { colorScheme: 'red', - text: 'Beli' - } - } + text: 'Beli', + }, + }; return ( <div className='w-full'> - <Button onClick={handleButton} colorScheme={btnConfig[source].colorScheme} className='w-full'> + <Button + onClick={handleButton} + colorScheme={btnConfig[source].colorScheme} + className='w-full' + > {btnConfig[source].text} </Button> <BottomPopup - className='!container' - title='Berhasil Ditambahkan' - active={addCartAlert} - close={() => { - setAddCartAlert(false); - }} - > - <div className='flex mt-4'> - <div className='w-[10%]'> - <ImageNext - src={product.image} - alt={product.name} - className='h-32 object-contain object-center w-full border border-gray_r-4' - width={80} - height={80} - /> - </div> - <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'> - {!!product.manufacture.name ? ( - <Link - href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())} - className=' hover:underline' - color={"red"} - > - {product.manufacture.name} - </Link> - ) : '-'} - <p className='text-ellipsis overflow-hidden'> - {product.name} - </p> - <p> - {product.code} - </p> - {!!product.lowest_price && product.lowest_price.price > 0 && ( + className='!container' + title='Berhasil Ditambahkan' + active={addCartAlert} + close={() => { + setAddCartAlert(false); + }} + > + <div className='flex mt-4'> + <div className='w-[10%]'> + <ImageNext + src={product.image} + alt={product.name} + className='h-32 object-contain object-center w-full border border-gray_r-4' + width={80} + height={80} + /> + </div> + <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'> + {!!product.manufacture.name ? ( + <Link + href={createSlug( + '/shop/brands/', + product.manufacture.name, + product.manufacture.id.toString() + )} + className=' hover:underline' + color={'red'} + > + {product.manufacture.name} + </Link> + ) : ( + '-' + )} + <p className='text-ellipsis overflow-hidden'>{product.name}</p> + <p>{product.code}</p> + {!!product.lowest_price && product.lowest_price.price > 0 && ( + <> + <div className='flex items-end gap-x-2'> + {product.lowest_price.discount_percentage > 0 && ( <> - <div className='flex items-end gap-x-2'> - {product.lowest_price.discount_percentage > 0 && ( - <> - <div className='badge-solid-red'> - {Math.floor(product.lowest_price.discount_percentage)}% - </div> - <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'> - Rp {formatCurrency(product.lowest_price.price || 0)} - </div> - </> - )} - <div className='text-danger-500 font-semibold'> - Rp {formatCurrency(product.lowest_price.price_discount || 0)} - </div> + <div className='badge-solid-red'> + {Math.floor(product.lowest_price.discount_percentage)}% + </div> + <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'> + Rp {formatCurrency(product.lowest_price.price || 0)} </div> </> )} + <div className='text-danger-500 font-semibold'> + Rp{' '} + {formatCurrency(product.lowest_price.price_discount || 0)} + </div> + </div> + </> + )} - {!!product.lowest_price && product.lowest_price.price === 0 && ( - <span> - Hubungi kami untuk dapatkan harga terbaik,{' '} - <Link - href={askAdminUrl} - target='_blank' - className='font-medium underline' - color={'red'} - > - klik disini - </Link> - </span> - )} - </div> - <div className='ml-3 flex items-center font-normal'> - <Link - href='/shop/cart' - className='flex-1 py-2 text-gray_r-12 btn-yellow' - > - Lihat Keranjang - </Link> - </div> + {!!product.lowest_price && product.lowest_price.price === 0 && ( + <span> + Hubungi kami untuk dapatkan harga terbaik,{' '} + <Link + href={askAdminUrl} + target='_blank' + className='font-medium underline' + color={'red'} + > + klik disini + </Link> + </span> + )} + </div> + <div className='ml-3 flex items-center font-normal'> + <Link + href='/shop/cart' + className='flex-1 py-2 text-gray_r-12 btn-yellow' + > + Lihat Keranjang + </Link> </div> - <div className='mt-8 mb-4'> - <div className='text-h-sm font-semibold mb-6'> - Kamu Mungkin Juga Suka - </div> - <LazyLoad> - <ProductSimilar query={productSimilarQuery} /> - </LazyLoad> + </div> + <div className='mt-8 mb-4'> + <div className='text-h-sm font-semibold mb-6'> + Kamu Mungkin Juga Suka </div> - </BottomPopup> + <LazyLoad> + <ProductSimilar query={productSimilarQuery} /> + </LazyLoad> + </div> + </BottomPopup> </div> - ) -} + ); +}; -export default AddToCart
\ No newline at end of file +export default AddToCart; diff --git a/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx b/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx new file mode 100644 index 00000000..5685b83a --- /dev/null +++ b/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx @@ -0,0 +1,17 @@ +import dynamic from 'next/dynamic'; +import React from 'react'; +import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton'; +const FlashSaleNonDisplay = dynamic( + () => import('@/lib/flashSale/components/FlashSaleNonDisplay'), + { + loading: () => <FlashSaleSkeleton />, + } +); +const FlashSalePromo = () => { + return ( + <> + <FlashSaleNonDisplay /> + </> + ); +}; +export default FlashSalePromo; diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx index d59d1867..9f808718 100644 --- a/src-migrate/modules/promo/components/PromoList.tsx +++ b/src-migrate/modules/promo/components/PromoList.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { Button, Skeleton } from '@chakra-ui/react' -import clsxm from "~/libs/clsxm" +import { Button, Skeleton } from '@chakra-ui/react'; +import clsxm from '~/libs/clsxm'; import ProductPromoCard from '../../product-promo/components/Card'; import { fetchPromoItemsSolr } from '../../../../src/api/promoApi'; import { Swiper, SwiperSlide } from 'swiper/react'; @@ -8,7 +8,7 @@ import SwiperCore, { Navigation, Pagination } from 'swiper'; import useDevice from '@/core/hooks/useDevice'; import LogoSpinner from '../../../../src/core/components/elements/Spinner/LogoSpinner'; import usePromoStore from './promoStore'; -import Link from "next/link" +import Link from 'next/link'; import { IPromotion } from '~/types/promotion'; interface PromoListProps { selectedPromo: string; // Tipe selectedPromo ditetapkan sebagai string @@ -32,11 +32,11 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => { const swiperBanner = { modules: [Navigation], - className: 'h-[400px] w-full', + className: 'h-full w-full', slidesPerView: isMobile ? 1.1 : 3.25, spaceBetween: 10, - navigation:isMobile? true : false, - allowTouchMove:isMobile? false : true, + navigation: isMobile ? true : false, + allowTouchMove: isMobile ? false : true, }; useEffect(() => { @@ -56,7 +56,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => { const fetchPromotions = async () => { setIsLoading(true); try { - const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10); + const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10); setPromoItems(items); const promoDataPromises = items?.map(async (item) => { @@ -69,9 +69,11 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => { }); const promoDataArray = await Promise.all(promoDataPromises); - const mergedPromoData = promoDataArray?.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); + const mergedPromoData = promoDataArray?.reduce( + (accumulator, currentValue) => accumulator.concat(currentValue), + [] + ); setPromoData(mergedPromoData); - } catch (error) { console.error('Error fetching promo items:', error); } finally { @@ -92,44 +94,49 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => { <div className='flex justify-between items-center'> <h1 className='text-h-sm md:text-h-lg font-semibold py-4'>{title}</h1> <div> - <Link href={`/shop/promo/${slug}`} className='!text-red-500 font-semibold'> + <Link + href={`/shop/promo/${slug}`} + className='!text-red-500 font-semibold' + > Lihat Semua </Link> </div> </div> {isLoading ? ( - <div className="loading-spinner flex justify-center"> + <div className='loading-spinner flex justify-center'> <LogoSpinner width={48} height={48} /> </div> ) : ( <Skeleton - isLoaded={!isLoading} - className={clsxm( - "flex gap-x-4 overflow-x-auto px-4 md:px-0", { - "min-h-[340px]": promoData[0] && promoData?.length > 0 - })} - > - {isDesktop && ( - <Swiper {...swiperBanner}> - {promoData?.map((promotion: IPromotion) => ( - <SwiperSlide key={promotion.id}> - <div className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full"> - <ProductPromoCard product={promoItems} promotion={promotion} /> - </div> - </SwiperSlide> - ))} - </Swiper> - )} - {isMobile && (promoData?.map((promotion: IPromotion) => ( - <div key={promotion.id} className="min-w-[400px] max-w-[400px]"> - <ProductPromoCard product={promoItems} promotion={promotion} /> - </div> - )))} - - </Skeleton> + isLoaded={!isLoading} + className={clsxm('flex gap-x-4 overflow-x-auto px-4 md:px-0', { + 'min-h-[340px]': promoData[0] && promoData?.length > 0, + })} + > + {isDesktop && ( + <Swiper {...swiperBanner}> + {promoData?.map((promotion: IPromotion) => ( + <SwiperSlide key={promotion.id}> + <div className='min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full'> + <ProductPromoCard + product={promoItems} + promotion={promotion} + /> + </div> + </SwiperSlide> + ))} + </Swiper> + )} + {isMobile && + promoData?.map((promotion: IPromotion) => ( + <div key={promotion.id} className='min-w-[400px] max-w-[400px]'> + <ProductPromoCard product={promoItems} promotion={promotion} /> + </div> + ))} + </Skeleton> )} </div> ); }; -export default PromoList;
\ No newline at end of file +export default PromoList; diff --git a/src-migrate/pages/shop/promo/index.tsx b/src-migrate/pages/shop/promo/index.tsx index febe31a4..689c2537 100644 --- a/src-migrate/pages/shop/promo/index.tsx +++ b/src-migrate/pages/shop/promo/index.tsx @@ -1,13 +1,14 @@ -import dynamic from 'next/dynamic' -import React, { useState } from 'react' -import { LazyLoadComponent } from 'react-lazy-load-image-component' -import Hero from '~/modules/promo/components/Hero' -import PromotionProgram from '~/modules/promo/components/PromotinProgram' -import Voucher from '~/modules/promo/components/Voucher' -import FlashSale from '../../../modules/promo/components/FlashSale' -const PromoList = dynamic(() => import('../../../modules/promo/components/PromoList')); - - +import dynamic from 'next/dynamic'; +import React, { useState } from 'react'; +import { LazyLoadComponent } from 'react-lazy-load-image-component'; +import Hero from '~/modules/promo/components/Hero'; +import PromotionProgram from '~/modules/promo/components/PromotinProgram'; +import Voucher from '~/modules/promo/components/Voucher'; +import FlashSale from '../../../modules/promo/components/FlashSale'; +import FlashSaleNonDisplay from '../../../modules/promo/components/FlashSaleNonDisplay'; +const PromoList = dynamic( + () => import('../../../modules/promo/components/PromoList') +); const PromoPage = () => { const [selectedPromo, setSelectedPromo] = useState('Bundling'); @@ -17,22 +18,26 @@ const PromoPage = () => { <Hero /> </LazyLoadComponent> <LazyLoadComponent> - <PromotionProgram - selectedPromo={selectedPromo} - onSelectPromo={setSelectedPromo} - /> + <PromotionProgram + selectedPromo={selectedPromo} + onSelectPromo={setSelectedPromo} + /> <PromoList selectedPromo={selectedPromo} /> </LazyLoadComponent> - + <LazyLoadComponent> <FlashSale /> </LazyLoadComponent> <h1 className='h-1'></h1> <LazyLoadComponent> + <FlashSaleNonDisplay /> + </LazyLoadComponent> + <h1 className='h-1'></h1> + <LazyLoadComponent> <Voucher /> </LazyLoadComponent> </> - ) -} + ); +}; -export default PromoPage
\ No newline at end of file +export default PromoPage; |
