diff options
Diffstat (limited to 'src')
23 files changed, 337 insertions, 231 deletions
diff --git a/src/components/ui/HeroBanner.jsx b/src/components/ui/HeroBanner.jsx index 2eea5915..d5ff6247 100644 --- a/src/components/ui/HeroBanner.jsx +++ b/src/components/ui/HeroBanner.jsx @@ -7,9 +7,7 @@ import { Swiper, SwiperSlide } from 'swiper/react'; import Image from 'next/image'; import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { bannerApi } from '@/api/bannerApi'; import Link from '@/core/components/elements/Link/Link'; import DesktopView from '@/core/components/views/DesktopView'; import MobileView from '@/core/components/views/MobileView'; @@ -52,28 +50,21 @@ const HeroBanner = () => { pagination: { dynamicBullets: false, clickable: true }, }; - const customLoader = ({ src }) => { - return src; // Loader yang mengembalikan URL gambar asli - }; - const BannerComponent = useMemo(() => { if (!heroBanner) return null; return heroBanner.map((banner, index) => ( <SwiperSlide key={index}> - <Link href={banner.url} className='w-full h-auto'> + <Link href={banner?.url} aria-label={banner.name}> <Image - loader={customLoader} src={banner.image} alt={banner.name} width={1152} height={768} - className='w-full h-auto' - priority={index === 0} - loading={index === 0 ? 'eager' : 'lazy'} placeholder='blur' blurDataURL='/images/indoteknik-placeholder.png' sizes='(max-width: 768px) 100vw, 50vw' + priority={true} /> </Link> </SwiperSlide> diff --git a/src/components/ui/HeroBannerSecondary.jsx b/src/components/ui/HeroBannerSecondary.jsx index 6074c9a6..0daf9c61 100644 --- a/src/components/ui/HeroBannerSecondary.jsx +++ b/src/components/ui/HeroBannerSecondary.jsx @@ -1,10 +1,8 @@ import Link from '@/core/components/elements/Link/Link'; import { getRandomInt } from '@/utils/getRandomInt'; import Image from 'next/image'; -import { useMemo, useEffect, useState } from 'react'; -import { useQuery } from 'react-query'; +import { useEffect, useMemo, useState } from 'react'; import { HeroBannerSkeleton } from '../skeleton/BannerSkeleton'; -import { bannerApi } from '@/api/bannerApi'; const HeroBannerSecondary = () => { const [heroBannerSecondary, setHeroBannerSecondary] = useState([]); @@ -45,7 +43,7 @@ const HeroBannerSecondary = () => { height={1024} alt={heroBannerSecondary[randomIndex]?.name} className='object-cover object-center h-full' - loading='lazy' + loading='eager' placeholder='blur' blurDataURL='/images/indoteknik-placeholder.png' sizes='(max-width: 768px) 100vw, 50vw' diff --git a/src/core/components/elements/Navbar/Navbar.jsx b/src/core/components/elements/Navbar/Navbar.jsx index 57904498..59f743a2 100644 --- a/src/core/components/elements/Navbar/Navbar.jsx +++ b/src/core/components/elements/Navbar/Navbar.jsx @@ -3,10 +3,12 @@ import dynamic from 'next/dynamic' const NavbarDesktop = dynamic(() => import('./NavbarDesktop')) const NavbarMobile = dynamic(() => import('./NavbarMobile')) -const Navbar = () => { +const Navbar = ({isMobile} ) => { + + if(isMobile) return <NavbarMobile /> + return ( <> - <NavbarMobile /> <NavbarDesktop /> </> ) diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx index fa3df5bf..253a2b03 100644 --- a/src/core/components/elements/Navbar/NavbarDesktop.jsx +++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx @@ -12,12 +12,9 @@ import { MenuButton, MenuItem, MenuList, - useDisclosure + useDisclosure, } from '@chakra-ui/react'; -import { - ChevronDownIcon, - HeartIcon -} from '@heroicons/react/24/outline'; +import { ChevronDownIcon, HeartIcon } from '@heroicons/react/24/outline'; import dynamic from 'next/dynamic'; import { default as Image, default as NextImage } from 'next/image'; import { useRouter } from 'next/router'; @@ -81,21 +78,17 @@ const NavbarDesktop = () => { }; window.addEventListener('scroll', handleScroll); + return () => { window.removeEventListener('scroll', handleScroll); }; }, []); useEffect(() => { - const handleScroll = () => { - setIsTop(window.scrollY < 100); - }; - - window.addEventListener('scroll', handleScroll); - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, []); + if (auth) { + loadCart(auth.id); + } + }, [auth]); useEffect(() => { setPendingTransactions(data); @@ -115,20 +108,22 @@ const NavbarDesktop = () => { }, [product, router]); useEffect(() => { - const handleCartChange = () => { - const cart = async () => { - const listCart = await getCountCart(); - setCartCount(listCart); - }; - cart(); - }; - handleCartChange(); - - window.addEventListener('localStorageChange', handleCartChange); - - return () => { - window.removeEventListener('localStorageChange', handleCartChange); - }; + // const handleCartChange = () => { + // const cart = async () => { + // const listCart = await getCountCart(); + // setCartCount(listCart); + // }; + // cart(); + // }; + // handleCartChange(); + + setCartCount(cart?.products?.length) + + // window.addEventListener('localStorageChange', handleCartChange); + + // return () => { + // window.removeEventListener('localStorageChange', handleCartChange); + // }; }, [transactions.data, cart]); useEffect(() => { @@ -197,12 +192,14 @@ const NavbarDesktop = () => { <nav className='pt-6 sticky top-0 z-50 bg-white border-b-2 border-danger-500'> <div className='container mx-auto flex gap-x-6'> - <Link href='/'> + <Link href='/' aria-label='Indoteknik Logo'> <Image src={IndoteknikLogo} alt='Indoteknik Logo' width={210} - height={210 / 3} + height={70} + loading='eager' + priority={true} /> </Link> <div className='flex-1 flex items-center'> @@ -220,6 +217,7 @@ const NavbarDesktop = () => { target='_blank' rel='noreferrer' href='/my/wishlist' + aria-label='Wishlist' className='flex items-center gap-x-2 !text-gray_r-12/80' > <HeartIcon className='w-7' /> @@ -227,6 +225,7 @@ const NavbarDesktop = () => { </Link> <a href={whatsappUrl(templateWA, payloadWA, urlPath)} + aria-label='Whatsapp' target='_blank' rel='noreferrer' className='flex items-center gap-x-1 !text-gray_r-12/80' @@ -236,6 +235,7 @@ const NavbarDesktop = () => { alt='Whatsapp' width={48} height={48} + loading='eager' /> <div> <div className='font-semibold'>Whatsapp</div> @@ -268,6 +268,7 @@ const NavbarDesktop = () => { <div className='w-6/12 flex px-1 divide-x divide-gray_r-6'> <Link href='/shop/promo' + aria-label='Promo' className={`${ router.asPath === '/shop/promo' && 'bg-gray_r-3' } flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group relative`} // Added relative position @@ -284,6 +285,7 @@ const NavbarDesktop = () => { quality={100} // className={`fixed ${isTop ? 'md:top-[145px] lg:top-[160px] ' : 'lg:top-[85px] top-[80px]'} rounded-3xl md:left-1/4 lg:left-1/4 xl:left-1/4 left-2/3 w-40 h-12 p-2 z-50 transition-all duration-300 animate-pulse`} className={`inline-block relative -top-8 transition-all duration-300 z-20 animate-pulse`} + loading='eager' /> </div> )} @@ -301,6 +303,7 @@ const NavbarDesktop = () => { <Link href='/shop/brands' + aria-label='Brand' className={`${ router.asPath === '/shop/brands' && 'bg-gray_r-3' } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group`} @@ -313,6 +316,7 @@ const NavbarDesktop = () => { </Link> <Link href='/shop/search?orderBy=stock' + aria-label='Ready Stock' className={`${ router.asPath.includes('/shop/search?orderBy=stock') && 'bg-gray_r-3' @@ -326,6 +330,7 @@ const NavbarDesktop = () => { </Link> <Link href='https://blog.indoteknik.com/' + aria-label='Blog Indoteknik' className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group' target='_blank' rel='noreferrer noopener' @@ -349,12 +354,14 @@ const NavbarDesktop = () => { <> <Link href='/login' + aria-label='Login' className='flex-1 flex justify-center items-center bg-danger-500 !text-gray_r-1 rounded-none rounded-t-xl' > Masuk </Link> <Link href='/register' + aria-label='Register' className='flex-1 flex justify-center items-center bg-danger-500 !text-gray_r-1 rounded-none rounded-t-xl' > Daftar @@ -363,7 +370,7 @@ const NavbarDesktop = () => { )} {auth && ( <> - <div href='/' className='navbar-user-dropdown-button'> + <div href='/' className='navbar-user-dropdown-button' aria-label='User'> <span>Halo, {auth?.name}</span> <div className='ml-auto'> <ChevronDownIcon className='w-6' /> @@ -388,24 +395,28 @@ const SocialMedias = () => ( target='_blank' rel='noreferrer' href='https://www.youtube.com/@indoteknikcom' + aria-label='Youtube - Indoteknik.com' > <NextImage src='/images/socials/youtube.webp' - // alt='Youtube - Indoteknik.com' + alt='Ytube' width={24} height={24} + loading='eager' /> </a> <a target='_blank' rel='noreferrer' href='https://www.tiktok.com/@indoteknikcom' + aria-label='TikTok - Indoteknik.com' > <NextImage src='/images/socials/tiktok.png' - // alt='TikTok - Indoteknik.com' + alt='TikTok' width={24} height={24} + loading='eager' /> </a> {/* <a target='_blank' rel='noreferrer' href={whatsappUrl(null)}> @@ -420,48 +431,56 @@ const SocialMedias = () => ( target='_blank' rel='noreferrer' href='https://www.facebook.com/indoteknikcom' + aria-label='Facebook - Indoteknik.com' > <NextImage src='/images/socials/Facebook.png' - // alt='Facebook - Indoteknik.com' + alt='FB' width={24} height={24} + loading='eager' /> </a> <a target='_blank' rel='noreferrer' href='https://www.instagram.com/indoteknikcom/' + aria-label='Instagram - Indoteknik.com' > <NextImage src='/images/socials/Instagram.png' - // alt='Instagram - Indoteknik.com' + alt='IG' width={24} height={24} + loading='eager' /> </a> <a target='_blank' rel='noreferrer' href='https://www.linkedin.com/company/pt-indoteknik-dotcom-gemilang/' + aria-label='Linkedin - Indoteknik.com' > <NextImage src='/images/socials/Linkedin.png' - // alt='Linkedin - Indoteknik.com' + alt='Linkedin' width={24} height={24} + loading='eager' /> </a> <a target='_blank' rel='noreferrer' href='https://goo.gl/maps/GF8EmDjpQTHZPsJ1A' + aria-label='Maps - Indoteknik.com' > <NextImage src='/images/socials/g_maps.png' - // alt='Maps - Indoteknik.com' + alt='Maps' width={24} height={24} + loading='eager' /> </a> </div> diff --git a/src/core/components/elements/Navbar/NavbarMobile.jsx b/src/core/components/elements/Navbar/NavbarMobile.jsx index 90314671..47182a47 100644 --- a/src/core/components/elements/Navbar/NavbarMobile.jsx +++ b/src/core/components/elements/Navbar/NavbarMobile.jsx @@ -12,30 +12,44 @@ import { useEffect, useState } from 'react'; import MobileView from '../../views/MobileView'; import Link from '../Link/Link'; import TopBanner from './TopBanner'; +import { useCartStore } from '~/modules/cart/stores/useCartStore'; +import useAuth from '@/core/hooks/useAuth'; const Search = dynamic(() => import('./Search')); const NavbarMobile = () => { const { Sidebar, open } = useSidebar(); + const auth = useAuth(); const [cartCount, setCartCount] = useState(0); + const { loadCart, cart, summary, updateCartItem } = useCartStore(); - useEffect(() => { - const handleCartChange = () => { - const cart = async () => { - const listCart = await getCountCart(); - setCartCount(listCart); - }; - cart(); - }; - handleCartChange(); + // useEffect(() => { + // const handleCartChange = () => { + // const cart = async () => { + // const listCart = await getCountCart(); + // setCartCount(listCart); + // }; + // cart(); + // }; + // handleCartChange(); + + // window.addEventListener('localStorageChange', handleCartChange); - window.addEventListener('localStorageChange', handleCartChange); + // return () => { + // window.removeEventListener('localStorageChange', handleCartChange); + // }; + // }, []); - return () => { - window.removeEventListener('localStorageChange', handleCartChange); - }; - }, []); + useEffect(() => { + if(auth){ + loadCart(auth?.id); + } + }, [auth]); + + useEffect(() => { + setCartCount(cart?.products?.length); + }, [cart]); return ( <MobileView> @@ -48,6 +62,7 @@ const NavbarMobile = () => { alt='Indoteknik Logo' width={120} height={40} + loading='eager' /> </Link> <div className='flex gap-x-3'> diff --git a/src/core/components/elements/Navbar/TopBanner.jsx b/src/core/components/elements/Navbar/TopBanner.jsx index 709495ce..7d44846c 100644 --- a/src/core/components/elements/Navbar/TopBanner.jsx +++ b/src/core/components/elements/Navbar/TopBanner.jsx @@ -50,6 +50,7 @@ const TopBanner = ({ onLoad = () => {} }) => { <Link href={data?.url} className='block bg-cover bg-center h-3 md:h-6 lg:h-[36px]' + aria-label='panduan pick up barang' style={{ backgroundImage: `url('${data?.image}')`, }} diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx index 1b62bf05..2998fa63 100644 --- a/src/core/components/layouts/BasicLayout.jsx +++ b/src/core/components/layouts/BasicLayout.jsx @@ -112,7 +112,7 @@ const BasicLayout = ({ children }) => { onAnimationEnd={() => setHighlight(false)} /> )} - <Navbar /> + <Navbar isMobile = {isMobile} /> <AnimationLayout> {children} <div @@ -149,6 +149,7 @@ const BasicLayout = ({ children }) => { className='block sm:hidden' width={36} height={36} + loading='eager' /> <Image src='/images/socials/WHATSAPP.svg' @@ -156,6 +157,7 @@ const BasicLayout = ({ children }) => { className='hidden sm:block' width={44} height={44} + loading='eager' /> </a> </div> diff --git a/src/lib/brand/components/BrandCard.jsx b/src/lib/brand/components/BrandCard.jsx index ebd41a67..dff61b24 100644 --- a/src/lib/brand/components/BrandCard.jsx +++ b/src/lib/brand/components/BrandCard.jsx @@ -11,6 +11,7 @@ const BrandCard = ({ brand }) => { className={`py-1 px-2 border-gray_r-6 flex justify-center items-center hover:scale-110 transition duration-500 ease-in-out ${ isMobile ? 'h-16' : 'h-24' }`} + aria-label={brand.name} > {brand.logo && ( <NextImage @@ -20,6 +21,7 @@ const BrandCard = ({ brand }) => { height={500} quality={85} className='h-full w-[122px] object-contain object-center' + loading='eager' /> )} {!brand.logo && ( diff --git a/src/lib/flashSale/components/FlashSale.jsx b/src/lib/flashSale/components/FlashSale.jsx index 6d90cad7..f4be279e 100644 --- a/src/lib/flashSale/components/FlashSale.jsx +++ b/src/lib/flashSale/components/FlashSale.jsx @@ -47,6 +47,7 @@ const FlashSale = () => { width={1080} height={192} className='w-full rounded mb-4 hidden sm:block' + loading='eager' /> <Image src={flashSale.bannerMobile} @@ -54,6 +55,7 @@ const FlashSale = () => { width={256} height={48} className='w-full rounded mb-4 block sm:hidden' + loading='eager' /> <FlashSaleProduct flashSaleId={flashSale.pricelistId} diff --git a/src/lib/home/components/BannerSection.jsx b/src/lib/home/components/BannerSection.jsx index 303b5c4b..898f1bf5 100644 --- a/src/lib/home/components/BannerSection.jsx +++ b/src/lib/home/components/BannerSection.jsx @@ -51,6 +51,7 @@ const BannerSection = () => { src={banner.image} alt={banner.name} className='h-auto w-full rounded' + loading='eager' /> </Link> ))} diff --git a/src/lib/home/components/CategoryDynamic.jsx b/src/lib/home/components/CategoryDynamic.jsx index cc4f42b7..b6994f60 100644 --- a/src/lib/home/components/CategoryDynamic.jsx +++ b/src/lib/home/components/CategoryDynamic.jsx @@ -9,6 +9,7 @@ import 'swiper/css'; import 'swiper/css/navigation'; import 'swiper/css/pagination'; import { Pagination } from 'swiper'; +import { LazyLoadComponent } from 'react-lazy-load-image-component'; const CategoryDynamic = () => { const [categoryManagement, setCategoryManagement] = useState([]); @@ -22,12 +23,17 @@ const CategoryDynamic = () => { if (data) { setCategoryManagement(data); } - setIsLoading(false); }; fetchCategoryData(); }, []); + useEffect(() => { + if (categoryManagement?.length > 0) { + setIsLoading(false); + } + }, [categoryManagement]); + const swiperBanner = { modules: [Pagination], classNames: 'mySwiper', @@ -44,95 +50,105 @@ const CategoryDynamic = () => { {categoryManagement && categoryManagement.map((category) => ( <Skeleton key={category.id} isLoaded={!isLoading}> - <div key={category.id}> - <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'> - <h1 className='font-semibold text-[14px] sm:text-h-lg mr-2'> - {category.name} - </h1> - <Link - href={createSlug( - '/shop/category/', - category?.name, - category?.category_id - )} - className='!text-red-500 font-semibold' - > - Lihat Semua - </Link> - </div> + <LazyLoadComponent key={category.id}> + <div key={category.id}> + <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'> + <h1 className='font-semibold text-[14px] sm:text-h-lg mr-2'> + {category.name} + </h1> + <Link + href={createSlug( + '/shop/category/', + category?.name, + category?.category_id + )} + className='!text-red-500 font-semibold' + > + Lihat Semua + </Link> + </div> - <Swiper {...swiperBanner}> - {category?.categories?.map((subCategory) => ( - <SwiperSlide key={subCategory.id}> - <div className='border rounded justify-start items-start '> - <div className='p-3'> - <div className='flex flex-row border rounded mb-2 justify-start items-center'> - <NextImage - src={ - subCategory.image - ? subCategory.image - : '/images/noimage.jpeg' - } - alt={subCategory.name} - width={90} - height={30} - className='object-fit p-4' - /> - <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'> - <h2 className='font-semibold text-lg mr-2'> - {subCategory?.name} - </h2> - <Link - href={createSlug( - '/shop/category/', - subCategory?.name, - subCategory?.id_level_2 - )} - className='!text-red-500 font-semibold' - > - Lihat Semua - </Link> - </div> - </div> - <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'> - {subCategory.child_frontend_id_i.map( - (childCategory) => ( - <div key={childCategory.id} className=''> + <Swiper {...swiperBanner}> + {category?.categories?.map((subCategory) => ( + <LazyLoadComponent key={subCategory.id}> + <SwiperSlide key={subCategory.id}> + <div className='border rounded justify-start items-start '> + <div className='p-3'> + <div className='flex flex-row border rounded mb-2 justify-start items-center'> + <NextImage + src={ + subCategory.image + ? subCategory.image + : '/images/noimage.jpeg' + } + alt={subCategory.name} + width={90} + height={30} + className='object-fit p-4' + loading='eager' + /> + <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'> + <h2 className='font-semibold text-lg mr-2'> + {subCategory?.name} + </h2> <Link href={createSlug( '/shop/category/', - childCategory?.name, - childCategory?.id_level_3 + subCategory?.name, + subCategory?.id_level_2 )} - className='flex flex-row gap-2 border rounded group hover:border-red-500' + className='!text-red-500 font-semibold' > - <NextImage - src={ - childCategory.image - ? childCategory.image - : '/images/noimage.jpeg' - } - alt={childCategory.name} - className='p-2 ml-1' - width={40} - height={40} - /> - <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'> - <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'> - {childCategory.name} - </h3> - </div> + Lihat Semua </Link> </div> - ) - )} + </div> + <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'> + {subCategory.child_frontend_id_i.map( + (childCategory) => ( + <LazyLoadComponent key={childCategory.id}> + <div key={childCategory.id} className=''> + <Link + href={createSlug( + '/shop/category/', + childCategory?.name, + childCategory?.id_level_3 + )} + className='flex flex-row gap-2 border rounded group hover:border-red-500' + > + <NextImage + src={ + childCategory.image + ? childCategory.image + : '/images/noimage.jpeg' + } + alt={childCategory.name} + className='p-2 ml-1' + width={40} + height={40} + placeholder='blur' + blurDataURL='/icon.jpg' + loading='eager' + /> + <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'> + <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'> + {childCategory.name} + </h3> + </div> + </Link> + </div> + </LazyLoadComponent> + ) + )} + </div> + </div> </div> - </div> - </div> - </SwiperSlide> - ))} - </Swiper> - </div> + </SwiperSlide> + </LazyLoadComponent> + ))} + </Swiper> + </div> + </LazyLoadComponent> </Skeleton> ))} </div> diff --git a/src/lib/home/components/CategoryDynamicMobile.jsx b/src/lib/home/components/CategoryDynamicMobile.jsx index 67ae6f5f..5d9e872c 100644 --- a/src/lib/home/components/CategoryDynamicMobile.jsx +++ b/src/lib/home/components/CategoryDynamicMobile.jsx @@ -90,6 +90,7 @@ const CategoryDynamicMobile = () => { width={30} height={30} className='' + loading='eager' /> <div className='bagian-judul flex flex-col justify-center items-start gap-1 ml-2'> <h2 className='font-semibold text-[10px] line-clamp-1'> @@ -123,6 +124,7 @@ const CategoryDynamicMobile = () => { width={40} height={40} className='p-2' + loading='eager' /> <div className='bagian-judul flex flex-col justify-center items-start gap-1 break-words line-clamp-2 group-hover:text-red-500'> <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-[10px]'> diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx index 562fa138..d8bf3edb 100644 --- a/src/lib/home/components/PromotionProgram.jsx +++ b/src/lib/home/components/PromotionProgram.jsx @@ -4,56 +4,35 @@ import { bannerApi } from '@/api/bannerApi'; import useDevice from '@/core/hooks/useDevice'; import { Swiper, SwiperSlide } from 'swiper/react'; import BannerPromoSkeleton from '../components/Skeleton/BannerPromoSkeleton'; + import { useEffect, useState } from 'react'; const { useQuery } = require('react-query'); const BannerSection = () => { const { isMobile, isDesktop } = useDevice(); const [data, setData] = useState(null); const [shouldFetch, setShouldFetch] = useState(false); + const [ isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchData = async () => { - const res = await fetch(`/api/hero-banner?type=banner-promotion`); - const { data } = await res.json(); - if (data) { - setData(data); + try { + const res = await fetch(`/api/hero-banner?type=banner-promotion`); + const { data } = await res.json(); + if (data) { + setData(data); + } + } catch (e) { + console.log(e); + } finally { + setIsLoading(false); } }; fetchData(); }, []); - // useEffect(() => { - // const localData = localStorage.getItem('Homepage_promotionProgram'); - // if (localData) { - // setData(JSON.parse(localData)); - // } else { - // setShouldFetch(true); - // } - // }, []); - - // const getPromotionProgram = useQuery( - // 'promotionProgram', - // bannerApi({ type: 'banner-promotion' }), - // { - // enabled: shouldFetch, - // onSuccess: (data) => { - // if (data) { - // localStorage.setItem( - // 'Homepage_promotionProgram', - // JSON.stringify(data) - // ); - // setData(data); - // } - // }, - // } - // ); - const promotionProgram = data; - // if (getPromotionProgram?.isLoading && !data) { - // return <BannerPromoSkeleton />; - // } - if (!data) { + if (isLoading) { return <BannerPromoSkeleton />; } @@ -90,7 +69,8 @@ const BannerSection = () => { quality={85} src={banner.image} alt={banner.name} - className='h-auto w-full rounded hover:scale-105 transition duration-500 ease-in-out' + className='rounded hover:scale-105 transition duration-500 ease-in-out' + loading='eager' /> </Link> ))} @@ -103,12 +83,13 @@ const BannerSection = () => { <SwiperSlide key={banner.id}> <Link key={banner.id} href={banner.url}> <Image - width={439} - height={150} - quality={85} + width={350} + height={100} + quality={70} src={banner.image} alt={banner.name} - className='h-auto w-full rounded ' + className='rounded ' + loading='eager' /> </Link> </SwiperSlide> diff --git a/src/lib/home/components/ServiceList.jsx b/src/lib/home/components/ServiceList.jsx index b3cc8fe5..6d03a587 100644 --- a/src/lib/home/components/ServiceList.jsx +++ b/src/lib/home/components/ServiceList.jsx @@ -18,6 +18,7 @@ const ServiceList = () => { src='/images/icon_service/ONE-STOP-SOLUTIONS.svg' alt='' className='h-20 w-20 rounded' + loading='eager' /> </div> <div className=''> @@ -43,6 +44,7 @@ const ServiceList = () => { src='/images/icon_service/WARRANTY.svg' alt='' className='h-20 w-20 rounded' + loading='eager' /> </div> <div> @@ -68,6 +70,7 @@ const ServiceList = () => { src='/images/icon_service/DUE-PAYMENT.svg' alt='' className='h-20 w-20 rounded' + loading='eager' /> </div> <div> @@ -93,6 +96,7 @@ const ServiceList = () => { src='/images/icon_service/TAX.svg' alt='' className='h-20 w-20 rounded' + loading='eager' /> </div> <div> diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx index 444ddd8e..19e76a2b 100644 --- a/src/lib/product/components/Product/ProductDesktop.jsx +++ b/src/lib/product/components/Product/ProductDesktop.jsx @@ -255,6 +255,7 @@ const ProductDesktop = ({ products, wishlist, toggleWishlist }) => { > <ImageNext src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg' + alt='Flash Sale' width={17} height={10} /> diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx index 113a1e42..4cfd3755 100644 --- a/src/lib/product/components/Product/ProductMobile.jsx +++ b/src/lib/product/components/Product/ProductMobile.jsx @@ -219,6 +219,7 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { > <ImageNext src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg' + alt='Flash Sale' width={17} height={10} /> diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx index 174e5cb1..a8ed90a4 100644 --- a/src/lib/product/components/ProductCard.jsx +++ b/src/lib/product/components/ProductCard.jsx @@ -74,7 +74,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { if (variant == 'vertical') { return ( <div className='rounded shadow-sm border border-gray_r-4 bg-white h-[330px] md:h-[380px]'> - <Link href={URL.product} className='border-b border-gray_r-4 relative'> + <Link href={URL.product} className='border-b border-gray_r-4 relative' aria-label='Produk'> <div className='relative'> <Image src={image} @@ -90,6 +90,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { className='w-4 h-5 object-contain object-top sm:h-6' width={50} height={50} + loading='eager' /> )} </div> @@ -101,6 +102,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { className='w-11 h-6 object-contain object-top ml-1 mr-1 sm:h-6' width={50} height={50} + loading='eager' /> )} </div> @@ -115,6 +117,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { className='h-full' width={1000} height={100} + loading='eager' /> </div> <div className='relative'> @@ -127,8 +130,10 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { <div className='bg-red-600 border border-solid border-yellow-400 p-2 rounded-full h-6 flex w-fit items-center justify-center gap-x-2'> <ImageNext src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg' + alt='flash sale' width={13} height={5} + loading='eager' /> <span className='text-white text-[9px] md:text-[10px] font-semibold'> {product?.flashSale?.tag != 'false' || @@ -150,26 +155,28 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { <div className='p-2 sm:p-3 pb-3 text-caption-2 sm:text-body-2 leading-5'> <div className='flex justify-between '> {product?.manufacture?.name ? ( - <Link href={URL.manufacture} className='mb-1 mt-1 truncate'> + <Link href={URL.manufacture} className='mb-1 mt-1 truncate' aria-label={product.manufacture.name}> {product.manufacture.name} </Link> ) : ( <div>-</div> )} {product?.isInBu && ( - <Link href='/panduan-pick-up-service' className='group'> + <Link href='/panduan-pick-up-service' className='group' aria-label='pickup now'> <Image src='/images/PICKUP-NOW.png' className='group-hover:scale-105 transition-transform duration-200' alt='pickup now' width={90} height={12} + loading='eager' /> </Link> )} </div> <Link href={URL.product} + aria-label={product?.name} className={`mb-2 !text-gray_r-12 leading-6 block line-clamp-3 h-[64px]`} title={product?.name} > @@ -194,6 +201,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { rel='noopener noreferrer' target='_blank' href={callForPriceWhatsapp} + aria-label='Call for Inquiry' > Call for Inquiry </a> @@ -217,6 +225,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { rel='noopener noreferrer' target='_blank' href={callForPriceWhatsapp} + aria-label='Call for Inquiry' > Call for Inquiry </a> @@ -251,7 +260,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { return ( <div className='flex bg-white'> <div className='w-4/12'> - <Link href={URL.product} className='relative'> + <Link href={URL.product} className='relative' aria-label={product?.name}> <div className='relative'> <Image src={image} @@ -267,6 +276,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { className='w-4 h-5 object-contain object-top sm:h-6' width={50} height={50} + loading='eager' /> )} </div> @@ -278,6 +288,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { className='w-11 h-6 object-contain object-top ml-1 sm:h-6' width={50} height={50} + loading='eager' /> )} </div> @@ -295,8 +306,10 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { <div className='bg-red-600 rounded-full mb-1 p-2 pl-3 pr-3 flex w-fit items-center gap-x-1'> <ImageNext src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg' + alt='flash sae' width={15} height={10} + loading='eager' /> <span className='text-white text-xs font-semibold'> {' '} @@ -308,7 +321,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { )} {product?.manufacture?.name ? ( <div className='flex justify-between'> - <Link href={URL.manufacture} className='mb-1'> + <Link href={URL.manufacture} className='mb-1' aria-label={product?.manufacture.name}> {product.manufacture.name} </Link> {/* {product?.is_in_bu && ( @@ -324,6 +337,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { )} <Link href={URL.product} + aria-label={product?.name} className={`mb-3 !text-gray_r-12 leading-6 line-clamp-3`} > {product?.name} @@ -350,6 +364,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { rel='noopener noreferrer' target='_blank' href={callForPriceWhatsapp} + aria-label='Call for Inquiry' > Call for Inquiry </a> @@ -373,6 +388,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { rel='noopener noreferrer' target='_blank' href={callForPriceWhatsapp} + aria-label='Call for Inquiry' > Call for Inquiry </a> diff --git a/src/lib/product/components/ProductSlider.jsx b/src/lib/product/components/ProductSlider.jsx index 54f209cc..91d199a6 100644 --- a/src/lib/product/components/ProductSlider.jsx +++ b/src/lib/product/components/ProductSlider.jsx @@ -35,7 +35,7 @@ const ProductSlider = ({ products, simpleTitle = false, bannerMode = false }) => <> {bannerMode && ( <SwiperSlide> - <Link href={products.banner.url} className='w-full h-full block'></Link> + <Link href={products.banner.url} className='w-full h-full block' aria-label={products.banner.name}></Link> </SwiperSlide> )} {products?.products?.map((product, index) => ( diff --git a/src/pages/api/flashsale-header.js b/src/pages/api/flashsale-header.js index 31f8efdd..21cb9c9d 100644 --- a/src/pages/api/flashsale-header.js +++ b/src/pages/api/flashsale-header.js @@ -15,23 +15,35 @@ export default async function handler(req, res) { try { await connectRedis(); const cacheKey = `flashsale_header`; - // await client.del(cacheKey); + + // Cek data yang ada di Redis let cachedData = await client.get(cacheKey); if (cachedData) { - const data = JSON.parse(cachedData); + const data = JSON.parse(cachedData); + + // Periksa apakah data adalah array dan panjangnya 0 + if (!data || (Array.isArray(data) && data.length === 0)) { + await client.del(cacheKey); // Hapus kunci jika data kosong + return res.status(200).json({ data: [] }); + } return res.status(200).json({ data }); } else { const flashSale = await odooApi('GET', `/api/v1/flashsale/header`); + if (flashSale.length === 0) { + return res.status(200).json({ data: [] }); + } else { + await client.set( + cacheKey, + JSON.stringify(flashSale), + 'EX', + flashSale.duration + ); - await client.set( - cacheKey, - JSON.stringify(flashSale), - 'EX', - flashSale.duration - ); - cachedData = await client.get(cacheKey); - return res.status(200).json({ data: cachedData }); + cachedData = await client.get(cacheKey); + const data = JSON.parse(cachedData); + return res.status(200).json({ data }); + } } } catch (error) { console.error('Error interacting with Redis or fetching data:', error); diff --git a/src/pages/api/hero-banner.js b/src/pages/api/hero-banner.js index 7a348cfa..b7f3c1c5 100644 --- a/src/pages/api/hero-banner.js +++ b/src/pages/api/hero-banner.js @@ -28,6 +28,8 @@ export default async function handler(req, res) { `/api/v1/banner?type=${type}` ); + if(!dataBannerSections) return res.status(200).json({ data: [] }); + // Simpan hasil fetch ke Redis dengan masa kadaluarsa 3 hari (259200 detik) await client.set( cacheKey, diff --git a/src/pages/api/search-flashsale.js b/src/pages/api/search-flashsale.js index d9e56c83..c00f6c64 100644 --- a/src/pages/api/search-flashsale.js +++ b/src/pages/api/search-flashsale.js @@ -23,20 +23,28 @@ export default async function handler(req, res) { if (cachedData) { const data = JSON.parse(cachedData); + // Periksa apakah data adalah array dan panjangnya 0 + if (!data || (Array.isArray(data) && data.length === 0)) { + await client.del(cacheKey); // Hapus kunci jika data kosong + return res.status(200).json({ data: [] }); + } return res.status(200).json({ data }); } else { const dataProductSearch = await axios( `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=${operation}]` ); - - await client.set( - cacheKey, - JSON.stringify(dataProductSearch.data), - 'EX', - duration - ); - cachedData = await client.get(cacheKey); - return res.status(200).json({ data: cachedData }); + if (dataProductSearch.data.length === 0) { + return res.status(200).json({ data: [] }); + } else { + await client.set( + cacheKey, + JSON.stringify(dataProductSearch.data), + 'EX', + duration + ); + cachedData = await client.get(cacheKey); + return res.status(200).json({ data: cachedData }); + } } } catch (error) { console.error('Error interacting with Redis or fetching data:', error); diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 2ec1231a..fce2202a 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,22 +1,26 @@ import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton'; import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton'; -import odooApi from '@/core/api/odooApi'; import Seo from '@/core/components/Seo'; import DelayRender from '@/core/components/elements/DelayRender/DelayRender'; import DesktopView from '@/core/components/views/DesktopView'; import MobileView from '@/core/components/views/MobileView'; -import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton'; -import BannerPromoSkeleton from '@/lib/home/components/Skeleton/BannerPromoSkeleton'; import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton'; import dynamic from 'next/dynamic'; -import { useEffect, useRef, useState } from 'react'; +import { useRef } from 'react'; import { getAuth } from '~/libs/auth'; -import PagePopupIformation from '~/modules/popup-information'; // need change to dynamic and ssr : false -import CategoryPilihan from '../lib/home/components/CategoryPilihan'; -// import { getAuth } from '~/libs/auth'; const BasicLayout = dynamic(() => - import('@/core/components/layouts/BasicLayout'),{ssr: false} + import('@/core/components/layouts/BasicLayout') +); + +const PagePopupIformation = dynamic(() => + import('~/modules/popup-information'), { + ssr: false + } +); + +const CategoryPilihan = dynamic(() => + import('../lib/home/components/CategoryPilihan') ); const HeroBanner = dynamic(() => import('@/components/ui/HeroBanner'), { loading: () => <HeroBannerSkeleton />, @@ -40,24 +44,17 @@ const PreferredBrand = dynamic( const FlashSale = dynamic( () => import('@/lib/flashSale/components/FlashSale'), - { - loading: () => <FlashSaleSkeleton />, - } ); const ProgramPromotion = dynamic( - () => import('@/lib/home/components/PromotionProgram'), - { - loading: () => <BannerPromoSkeleton />, - } + () => import('@/lib/home/components/PromotionProgram') ); const BannerSection = dynamic(() => import('@/lib/home/components/BannerSection') ); const CategoryHomeId = dynamic( - () => import('@/lib/home/components/CategoryHomeId'), - { ssr: false } + () => import('@/lib/home/components/CategoryHomeId') ); const CategoryDynamic = dynamic(() => diff --git a/src/pages/sitemap/homepage.xml.js b/src/pages/sitemap/homepage.xml.js new file mode 100644 index 00000000..08c52112 --- /dev/null +++ b/src/pages/sitemap/homepage.xml.js @@ -0,0 +1,33 @@ +import { create } from 'xmlbuilder'; + +export async function getServerSideProps({ res }) { + const links = [ + { label: 'Hubungi Kami', url: 'https://indoteknik.com/hubungi-kami' }, + { label: 'Tentang Kami', url: 'https://indoteknik.com/tentang-kami' }, + { label: 'Karir', url: 'https://indoteknik.com/karir' }, + { label: 'Daftar Tempo', url: 'https://indoteknik.com/my/pembayaran-tempo' }, + ]; + const sitemap = create('urlset', { encoding: 'utf-8' }).att( + 'xmlns', + 'http://www.sitemaps.org/schemas/sitemap/0.9' + ) + + const date = new Date() + links.forEach((link) => { + const url = sitemap.ele('url') + url.ele('loc', link.url) + url.ele('lastmod', date.toISOString().slice(0, 10)) + url.ele('changefreq', 'daily') + url.ele('priority', '1.0') + }) + + res.setHeader('Content-Type', 'text/xml') + res.write(sitemap.end()) + res.end() + + return { props: {} } +} + +export default function SitemapProducts() { + return null +} |
