diff options
| author | Rafi Zadanly <zadanlyr@gmail.com> | 2023-05-22 11:58:05 +0700 |
|---|---|---|
| committer | Rafi Zadanly <zadanlyr@gmail.com> | 2023-05-22 11:58:05 +0700 |
| commit | 33da0fcb718335eb1d077af4321ac65e0146a2d6 (patch) | |
| tree | d87dab8d58c3e148e3e541a88b1ef7d2fd6d0ba7 /src | |
| parent | c65d7f6b82a7f2f80b1fe43a0bd06144d2ca64ff (diff) | |
Refactoring hero banner feature
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/BannerApi.js | 5 | ||||
| -rw-r--r-- | src/components/skeleton/BannerSkeleton.jsx | 20 | ||||
| -rw-r--r-- | src/components/ui/HeroBanner.jsx | 64 | ||||
| -rw-r--r-- | src/components/ui/HeroBannerSecondary.jsx | 35 | ||||
| -rw-r--r-- | src/lib/home/components/HeroBanner.jsx | 70 | ||||
| -rw-r--r-- | src/lib/home/hooks/useHeroBanner.js | 13 | ||||
| -rw-r--r-- | src/pages/index.jsx | 25 | ||||
| -rw-r--r-- | src/utils/getRandomInt.js | 3 |
8 files changed, 132 insertions, 103 deletions
diff --git a/src/api/BannerApi.js b/src/api/BannerApi.js new file mode 100644 index 00000000..8ebecd26 --- /dev/null +++ b/src/api/BannerApi.js @@ -0,0 +1,5 @@ +import odooApi from '@/core/api/odooApi' + +export const BannerApi = ({ type }) => { + return async () => await odooApi('GET', `/api/v1/banner?type=${type}`) +} diff --git a/src/components/skeleton/BannerSkeleton.jsx b/src/components/skeleton/BannerSkeleton.jsx new file mode 100644 index 00000000..3de9c027 --- /dev/null +++ b/src/components/skeleton/BannerSkeleton.jsx @@ -0,0 +1,20 @@ +const HeroBannerSkeleton = () => { + return ( + <div role='status' className='animate-pulse h-full min-h-[460px]'> + <div className='flex justify-center items-center h-full w-full mb-4 bg-gray-300 rounded' aria-busy> + <svg + className='w-20 h-20 text-gray-100' + xmlns='http://www.w3.org/2000/svg' + aria-hidden='true' + fill='currentColor' + viewBox='0 0 640 512' + > + <path d='M480 80C480 35.82 515.8 0 560 0C604.2 0 640 35.82 640 80C640 124.2 604.2 160 560 160C515.8 160 480 124.2 480 80zM0 456.1C0 445.6 2.964 435.3 8.551 426.4L225.3 81.01C231.9 70.42 243.5 64 256 64C268.5 64 280.1 70.42 286.8 81.01L412.7 281.7L460.9 202.7C464.1 196.1 472.2 192 480 192C487.8 192 495 196.1 499.1 202.7L631.1 419.1C636.9 428.6 640 439.7 640 450.9C640 484.6 612.6 512 578.9 512H55.91C25.03 512 .0006 486.1 .0006 456.1L0 456.1z' /> + </svg> + </div> + <span className='sr-only'>Loading...</span> + </div> + ) +} + +export { HeroBannerSkeleton } diff --git a/src/components/ui/HeroBanner.jsx b/src/components/ui/HeroBanner.jsx new file mode 100644 index 00000000..1b5bf165 --- /dev/null +++ b/src/components/ui/HeroBanner.jsx @@ -0,0 +1,64 @@ +import DesktopView from '@/core/components/views/DesktopView' +import MobileView from '@/core/components/views/MobileView' +import { Swiper, SwiperSlide } from 'swiper/react' +import { Pagination, Autoplay } from 'swiper' +import 'swiper/css' +import 'swiper/css/pagination' +import 'swiper/css/autoplay' +import { useMemo } from 'react' +import Link from '@/core/components/elements/Link/Link' +import Image from '@/core/components/elements/Image/Image' +import { useQuery } from 'react-query' +import { BannerApi } from '@/api/BannerApi' +import { HeroBannerSkeleton } from '../skeleton/BannerSkeleton' + +const swiperBanner = { + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + modules: [Pagination, Autoplay], + loop: true, + className: 'border border-gray_r-6', + slidesPerView: 1 +} + +const HeroBanner = () => { + const heroBanner = useQuery('heroBanner', BannerApi({ type: 'index-a-1' })) + + const swiperBannerMobile = { + ...swiperBanner, + pagination: { dynamicBullets: true, clickable: true } + } + + const swiperBannerDesktop = { + ...swiperBanner, + pagination: { dynamicBullets: false, clickable: true } + } + + const BannerComponent = useMemo(() => { + return heroBanner.data?.map((banner, index) => ( + <SwiperSlide key={index}> + <Link href={banner.url} className='w-full h-auto'> + <Image src={banner.image} alt={banner.name} className='w-full h-auto' /> + </Link> + </SwiperSlide> + )) + }, [heroBanner.data]) + + if (heroBanner.isLoading) return <HeroBannerSkeleton /> + + return ( + <> + <MobileView> + <Swiper {...swiperBannerMobile}>{BannerComponent}</Swiper> + </MobileView> + + <DesktopView> + <Swiper {...swiperBannerDesktop}>{BannerComponent}</Swiper> + </DesktopView> + </> + ) +} + +export default HeroBanner diff --git a/src/components/ui/HeroBannerSecondary.jsx b/src/components/ui/HeroBannerSecondary.jsx new file mode 100644 index 00000000..a3227002 --- /dev/null +++ b/src/components/ui/HeroBannerSecondary.jsx @@ -0,0 +1,35 @@ +import Link from '@/core/components/elements/Link/Link' +import { getRandomInt } from '@/utils/getRandomInt' +import Image from 'next/image' +import { useMemo } from 'react' +import { useQuery } from 'react-query' +import { HeroBannerSkeleton } from '../skeleton/BannerSkeleton' +import { BannerApi } from '@/api/BannerApi' + +const HeroBannerSecondary = () => { + const heroBannerSecondary = useQuery('heroBannerSecondary', BannerApi({ type: 'index-a-2' })) + + const randomIndex = useMemo(() => { + if (!heroBannerSecondary.data) return null + const length = heroBannerSecondary.data?.length + return getRandomInt(length) + }, [heroBannerSecondary.data]) + + if (heroBannerSecondary.isLoading) return <HeroBannerSkeleton /> + + return ( + heroBannerSecondary.data && ( + <Link href={heroBannerSecondary.data[randomIndex].url} className='h-full'> + <Image + src={heroBannerSecondary.data[randomIndex].image} + width={512} + height={1024} + alt={heroBannerSecondary.data[randomIndex].name} + className='object-cover object-center h-full' + /> + </Link> + ) + ) +} + +export default HeroBannerSecondary diff --git a/src/lib/home/components/HeroBanner.jsx b/src/lib/home/components/HeroBanner.jsx deleted file mode 100644 index 889bb2ce..00000000 --- a/src/lib/home/components/HeroBanner.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import ImageSkeleton from '@/core/components/elements/Skeleton/ImageSkeleton' -import useHeroBanner from '../hooks/useHeroBanner' -import Image from '@/core/components/elements/Image/Image' - -// Swiper -import { Swiper, SwiperSlide } from 'swiper/react' -import { Pagination, Autoplay } from 'swiper' -import 'swiper/css' -import 'swiper/css/pagination' -import 'swiper/css/autoplay' -import MobileView from '@/core/components/views/MobileView' -import DesktopView from '@/core/components/views/DesktopView' -import Link from '@/core/components/elements/Link/Link' - -const HeroBanner = () => { - const { heroBanners } = useHeroBanner() - - const swiperBanner = { - autoplay: { - delay: 6000, - disableOnInteraction: false - }, - modules: [Pagination, Autoplay], - loop: true - } - - const swiperBannerMobile = { - ...swiperBanner, - pagination: { dynamicBullets: true, clickable: true } - } - - const swiperBannerDesktop = { - ...swiperBanner, - pagination: { dynamicBullets: false, clickable: true } - } - - return ( - <div className='min-h-[200px]'> - {heroBanners.isLoading && <ImageSkeleton />} - {!heroBanners.isLoading && ( - <> - <MobileView> - <Swiper slidesPerView={1} className='border border-gray_r-6' {...swiperBannerMobile}> - {heroBanners.data?.map((banner, index) => ( - <SwiperSlide key={index}> - <Link href={banner.url || ''} className='w-full h-auto'> - <Image src={banner.image} alt={banner.name} className='w-full h-auto' /> - </Link> - </SwiperSlide> - ))} - </Swiper> - </MobileView> - <DesktopView> - <Swiper slidesPerView={1} className='border border-gray_r-6' {...swiperBannerDesktop}> - {heroBanners.data?.map((banner, index) => ( - <SwiperSlide key={index}> - <Link href={banner.url || ''} className='w-full h-auto'> - <Image src={banner.image} alt={banner.name} className='w-full h-auto' /> - </Link> - </SwiperSlide> - ))} - </Swiper> - </DesktopView> - </> - )} - </div> - ) -} - -export default HeroBanner diff --git a/src/lib/home/hooks/useHeroBanner.js b/src/lib/home/hooks/useHeroBanner.js deleted file mode 100644 index 5d2b0512..00000000 --- a/src/lib/home/hooks/useHeroBanner.js +++ /dev/null @@ -1,13 +0,0 @@ -import heroBannerApi from '../api/heroBannerApi' -import { useQuery } from 'react-query' - -const useHeroBanner = () => { - const fetchHeroBanner = async () => await heroBannerApi() - const { isLoading, data } = useQuery('heroBanner', fetchHeroBanner) - - return { - heroBanners: { data, isLoading } - } -} - -export default useHeroBanner diff --git a/src/pages/index.jsx b/src/pages/index.jsx index d83bc990..ea4949b2 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -5,17 +5,15 @@ import MobileView from '@/core/components/views/MobileView' import DesktopView from '@/core/components/views/DesktopView' import { useRef } from 'react' import Seo from '@/core/components/Seo' -import { useQuery } from 'react-query' -import odooApi from '@/core/api/odooApi' -import Image from 'next/image' import DelayRender from '@/core/components/elements/DelayRender/DelayRender' -import Link from '@/core/components/elements/Link/Link' +import HeroBannerSecondary from '@/components/ui/HeroBannerSecondary' +import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton' const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout')) const FlashSale = dynamic(() => import('@/lib/flashSale/components/FlashSale')) const BannerSection = dynamic(() => import('@/lib/home/components/BannerSection')) -const HeroBanner = dynamic(() => import('@/lib/home/components/HeroBanner'), { - loading: () => <ImageSkeleton /> +const HeroBanner = dynamic(() => import('@/components/ui/HeroBanner'), { + loading: () => <HeroBannerSkeleton /> }) const PreferredBrand = dynamic(() => import('@/lib/home/components/PreferredBrand'), { loading: () => <PopularProductSkeleton /> @@ -29,9 +27,6 @@ const CategoryHomeId = dynamic(() => import('@/lib/home/components/CategoryHomeI const CustomerReviews = dynamic(() => import('@/lib/review/components/CustomerReviews')) export default function Home() { - const fetchSecondHeroBanner = async () => await odooApi('GET', '/api/v1/banner?type=index-a-2') - const secondHeroBanner = useQuery('secondHeroBanner', fetchSecondHeroBanner) - const bannerRef = useRef(null) const wrapperRef = useRef(null) @@ -58,17 +53,7 @@ export default function Home() { <div className='container mx-auto'> <div className='flex h-[360px]' ref={wrapperRef} onLoad={handleOnLoad}> <div className='w-2/12'> - {secondHeroBanner.isFetched && ( - <Link href={secondHeroBanner.data[0].url}> - <Image - src={secondHeroBanner.data[0].image} - width={512} - height={1024} - alt={secondHeroBanner.data[0].name} - className='object-cover object-center h-full' - /> - </Link> - )} + <HeroBannerSecondary /> </div> <div className='w-7/12 px-1' ref={bannerRef}> <HeroBanner /> diff --git a/src/utils/getRandomInt.js b/src/utils/getRandomInt.js new file mode 100644 index 00000000..900e55f8 --- /dev/null +++ b/src/utils/getRandomInt.js @@ -0,0 +1,3 @@ +export const getRandomInt = (max) => { + return Math.floor(Math.random() * max); +}
\ No newline at end of file |
