diff options
| author | Mqdd <ahmadmiqdad27@gmail.com> | 2025-11-26 13:47:31 +0700 |
|---|---|---|
| committer | Mqdd <ahmadmiqdad27@gmail.com> | 2025-11-26 13:47:31 +0700 |
| commit | c472261f34388a0b76c3e21fec494b8d5f304715 (patch) | |
| tree | c595ba6ff598ba49dfca8667d721306df5ac05b3 | |
| parent | 6ef04dbf392c484a2833d172534b6e7da0f8dcdd (diff) | |
<Miqdad> popular product now ssr
| -rw-r--r-- | src/api/productApi.js | 21 | ||||
| -rw-r--r-- | src/components/ui/PopularProduct.jsx | 110 | ||||
| -rw-r--r-- | src/pages/index.jsx | 248 |
3 files changed, 213 insertions, 166 deletions
diff --git a/src/api/productApi.js b/src/api/productApi.js index dc96a77e..b5f47bcf 100644 --- a/src/api/productApi.js +++ b/src/api/productApi.js @@ -1,5 +1,6 @@ import axios from 'axios'; +// CLIENT MODE → untuk useQuery export const popularProductApi = () => { return async () => { const today = new Date(); @@ -7,9 +8,25 @@ export const popularProductApi = () => { (today - new Date(today.getFullYear(), 0, 0)) / 86400000 ); const page = (dayOfYear % 24) + 1; - const dataPopularProducts = await axios( + + const res = await axios( `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=*&page=${page}&orderBy=stock&priceFrom=1` ); - return dataPopularProducts.data.response; + return res.data.response; }; }; + +// SERVER MODE → untuk SSR +export async function popularProductApiSSR() { + const today = new Date(); + const dayOfYear = Math.floor( + (today - new Date(today.getFullYear(), 0, 0)) / 86400000 + ); + const page = (dayOfYear % 24) + 1; + + const res = await axios( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=*&page=${page}&orderBy=stock&priceFrom=1` + ); + + return res.data.response || null; +} diff --git a/src/components/ui/PopularProduct.jsx b/src/components/ui/PopularProduct.jsx index 92b2a1b6..0740798d 100644 --- a/src/components/ui/PopularProduct.jsx +++ b/src/components/ui/PopularProduct.jsx @@ -1,61 +1,63 @@ -import { popularProductApi } from '@/api/productApi' -import MobileView from '@/core/components/views/MobileView' -import ProductSlider from '@/lib/product/components/ProductSlider' -import { useQuery } from 'react-query' -import { PopularProductSkeleton } from '../skeleton/PopularProductSkeleton' -import DesktopView from '@/core/components/views/DesktopView' -import ProductCard from '@/lib/product/components/ProductCard' -import Link from '@/core/components/elements/Link/Link' +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import ProductSlider from '@/lib/product/components/ProductSlider'; +import ProductCard from '@/lib/product/components/ProductCard'; +import Link from '@/core/components/elements/Link/Link'; +import { PopularProductSkeleton } from '../skeleton/PopularProductSkeleton'; +import { useQuery } from 'react-query'; +import { popularProductApi } from '@/api/productApi'; -const PopularProduct = () => { - const popularProduct = useQuery('popularProduct', popularProductApi()) +export default function PopularProduct({ initialData }) { + const query = useQuery( + 'popularProducts', + popularProductApi(), + { + initialData: initialData ? { products: initialData.products } : undefined, + refetchOnMount: false + } + ); - if (popularProduct.isLoading) return <PopularProductSkeleton /> + if (query.isLoading) return <PopularProductSkeleton />; + + const data = query.data; + + if (!data) return null; return ( - popularProduct.data && ( - <> - <MobileView> - <div className='px-4'> - <div className='font-semibold mb-4 flex justify-between items-center'><p> - Produk Ready Stock - </p> - <Link - href='/shop/search?orderBy=stock' - className='' - > - <p className='text-danger-500 font-semibold'>Lihat Semua</p> - </Link></div> - <ProductSlider products={popularProduct.data} simpleTitle /> + <> + {/* Mobile */} + <MobileView> + <div className="px-4"> + <div className="font-semibold mb-4 flex justify-between items-center"> + <p>Produk Ready Stock</p> + <Link href="/shop/search?orderBy=stock"> + <p className="text-danger-500 font-semibold">Lihat Semua</p> + </Link> </div> - </MobileView> - - <DesktopView> - <div className='border border-gray_r-6 h-full overflow-auto'> - <div className='font-semibold text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10 flex justify-between items-center'> - <p> - Produk Ready Stock - </p> - <Link - href='/shop/search?orderBy=stock' - className='' - > - <p className='text-danger-500 font-semibold'>Lihat Semua</p> - </Link> - </div> - <div className='h-full divide-y divide-gray_r-6'> - {popularProduct.data && - popularProduct.data.products.map((product) => ( - <div className='py-2' key={product.id}> - <ProductCard product={product} variant='horizontal' /> - </div> - ))} - </div> + + <ProductSlider products={data.products} simpleTitle /> + </div> + </MobileView> + + {/* Desktop */} + <DesktopView> + <div className="border border-gray_r-6 h-full overflow-auto"> + <div className="font-semibold text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10 flex justify-between items-center"> + <p>Produk Ready Stock</p> + <Link href="/shop/search?orderBy=stock"> + <p className="text-danger-500 font-semibold">Lihat Semua</p> + </Link> </div> - </DesktopView> - </> - ) - ) -} -export default PopularProduct + <div className="h-full divide-y divide-gray_r-6"> + {data.products.map((product) => ( + <div className="py-2" key={product.id}> + <ProductCard product={product} variant="horizontal" /> + </div> + ))} + </div> + </div> + </DesktopView> + </> + ); +} diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 28db4d3e..fd960e8a 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,7 +1,6 @@ import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton'; import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton'; 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 PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton'; @@ -10,77 +9,118 @@ import dynamic from 'next/dynamic'; import { useRef } from 'react'; import { getAuth } from '~/libs/auth'; import MediaNews from '../lib/home/components/MediaNews'; +import { popularProductApi, popularProductApiSSR } from '@/api/productApi'; +/* ============================ + * DYNAMIC IMPORT SETTINGS + * ============================ */ + +// Layout const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout') ); -const PagePopupIformation = dynamic(() => - import('~/modules/popup-information'), { - ssr: false - } +// Popup +const PagePopupIformation = dynamic( + () => import('~/modules/popup-information'), + { ssr: false } ); -const CategoryPilihan = dynamic(() => - import('../lib/home/components/CategoryPilihan') +// CATEGORY (SSR TRUE) +const CategoryPilihan = dynamic( + () => import('../lib/home/components/CategoryPilihan'), + { ssr: true } ); -const HeroBanner = dynamic(() => import('@/components/ui/HeroBanner'), { - loading: () => <HeroBannerSkeleton />, -}); -const HeroBannerSecondary = dynamic( - () => import('@/components/ui/HeroBannerSecondary'), - { - loading: () => <HeroBannerSkeleton />, - } + +const CategoryDynamic = dynamic( + () => import('@/lib/home/components/CategoryDynamic'), + { ssr: true } +); + +const CategoryDynamicMobile = dynamic( + () => import('@/lib/home/components/CategoryDynamicMobile'), + { ssr: true } +); + +const CategoryHomeId = dynamic( + () => import('@/lib/home/components/CategoryHomeId'), + { ssr: true } +); + +// PRODUCT (SSR TRUE) +const PopularProduct = dynamic( + () => import('@/components/ui/PopularProduct'), + { loading: () => <PopularProductSkeleton />, ssr: true } ); -const PopularProduct = dynamic(() => import('@/components/ui/PopularProduct'), { - loading: () => <PopularProductSkeleton />, -}); const PreferredBrand = dynamic( () => import('@/lib/home/components/PreferredBrand'), - { - loading: () => <PreferredBrandSkeleton />, - } + { loading: () => <PreferredBrandSkeleton />, ssr: true } ); const FlashSale = dynamic( () => import('@/lib/flashSale/components/FlashSale'), + { ssr: true } ); const ProgramPromotion = dynamic( - () => import('@/lib/home/components/PromotionProgram') + () => import('@/lib/home/components/PromotionProgram'), + { ssr: true } ); -const BannerSection = dynamic(() => - import('@/lib/home/components/BannerSection') -); -const CategoryHomeId = dynamic( - () => import('@/lib/home/components/CategoryHomeId') +// CSR ONLY COMPONENTS +const HeroBanner = dynamic( + () => import('@/components/ui/HeroBanner'), + { loading: () => <HeroBannerSkeleton />, ssr: false } ); -const CategoryDynamic = dynamic(() => - import('@/lib/home/components/CategoryDynamic') +const HeroBannerSecondary = dynamic( + () => import('@/components/ui/HeroBannerSecondary'), + { loading: () => <HeroBannerSkeleton />, ssr: false } ); -const CategoryDynamicMobile = dynamic(() => - import('@/lib/home/components/CategoryDynamicMobile') +const BannerSection = dynamic( + () => import('@/lib/home/components/BannerSection'), + { ssr: false } ); const CustomerReviews = dynamic( () => import('@/lib/review/components/CustomerReviews'), { ssr: false } -); // need to ssr:false -const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList'), { - ssr: false, -}); // need to ssr: false +); + +const ServiceList = dynamic( + () => import('@/lib/home/components/ServiceList'), + { ssr: false } +); + + +/* ===================================== + * SERVER SIDE PROPS (HARUS DI LUAR) + * ===================================== */ -export default function Home({ categoryId }) { + export async function getServerSideProps(context) { + const auth = getAuth(context.req); + + const popularProducts = await popularProductApiSSR(); + + return { + props: { + auth, + popularProducts + } + }; + } + + +/* ============================ + * MAIN PAGE COMPONENT + * ============================ */ + +export default function Home({ auth, popularProducts }) { const bannerRef = useRef(null); const wrapperRef = useRef(null); - const auth = getAuth(); - const handleOnLoad = () => { wrapperRef.current.style.height = bannerRef.current?.querySelector(':first-child')?.clientHeight + 'px'; @@ -90,121 +130,109 @@ export default function Home({ categoryId }) { <> <BasicLayout> <Seo - title='Indoteknik.com: B2B Industrial Supply & Solution' - description='Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.' + title="Indoteknik.com: B2B Industrial Supply & Solution" + description="Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan." additionalMetaTags={[ { - name: 'keywords', + name: "keywords", content: - 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air', - }, - ]} - openGraph={ - { - title : 'Indoteknik.com: B2B Industrial Supply & Solution', - description : 'Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.', - images: [ - { - url: 'https://indoteknik.com/icon.jpg', - width: 800, - height: 600, - alt: 'indoteknik.com', - }, - ], + "indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air" } - } + ]} + openGraph={{ + title: "Indoteknik.com: B2B Industrial Supply & Solution", + description: + "Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.", + images: [ + { + url: "https://indoteknik.com/icon.jpg", + width: 800, + height: 600, + alt: "indoteknik.com" + } + ] + }} /> <PagePopupIformation /> + {/* DESKTOP */} <DesktopView> <PagePopupInformation /> - <div className='container mx-auto'> + + <div className="container mx-auto"> <div - className='flex min-h-[400px] h-[460px]' + className="flex min-h-[400px] h-[460px]" ref={wrapperRef} onLoad={handleOnLoad} > - <div className='w-2/12'> + <div className="w-2/12"> <HeroBannerSecondary /> </div> - <div className='w-7/12 px-1' ref={bannerRef}> + + <div className="w-7/12 px-1" ref={bannerRef}> <HeroBanner /> </div> - <div className='w-3/12'> - <DelayRender renderAfter={200}> - <PopularProduct /> - </DelayRender> + + <div className="w-3/12"> + <PopularProduct initialData={popularProducts} /> </div> </div> - <div className='my-16 flex flex-col gap-y-8'> + <div className="my-16 flex flex-col gap-y-8"> <ServiceList /> <MediaNews /> - <div id='flashsale'> + + <div id="flashsale"> <PreferredBrand /> </div> + {!auth?.feature?.soApproval && ( <> - <DelayRender renderAfter={200}> - <ProgramPromotion /> - </DelayRender> - <DelayRender renderAfter={200}> - <FlashSale /> - </DelayRender> + <ProgramPromotion /> + <FlashSale /> </> )} - {/* <PromotinProgram /> */} + <CategoryPilihan /> <CategoryDynamic /> <CategoryHomeId /> + <BannerSection /> <CustomerReviews /> </div> </div> </DesktopView> + + {/* MOBILE */} <MobileView> <PagePopupInformation /> - <DelayRender renderAfter={200}> - <HeroBanner /> - </DelayRender> - <div className='flex flex-col gap-y-4 my-6'> - <DelayRender renderAfter={400}> - <ServiceList /> - <MediaNews /> - </DelayRender> - <DelayRender renderAfter={400}> - <div id='flashsale'> - <PreferredBrand /> - </div> - </DelayRender> + <HeroBanner /> + + <div className="flex flex-col gap-y-4 my-6"> + <ServiceList /> + <MediaNews /> + + <div id="flashsale"> + <PreferredBrand /> + </div> + {!auth?.feature?.soApproval && ( <> - <DelayRender renderAfter={400}> - <ProgramPromotion /> - </DelayRender> - <DelayRender renderAfter={600}> - <FlashSale /> - </DelayRender> + <ProgramPromotion /> + <FlashSale /> </> )} - <DelayRender renderAfter={600}> - {/* <PromotinProgram /> */} - </DelayRender> - <DelayRender renderAfter={600}> - <CategoryPilihan /> - <CategoryDynamicMobile /> - </DelayRender> - <DelayRender renderAfter={800}> - <PopularProduct /> - </DelayRender> - <DelayRender renderAfter={1000}> - <CategoryHomeId /> - <BannerSection /> - </DelayRender> - <DelayRender renderAfter={1200}> - <CustomerReviews /> - </DelayRender> + + <CategoryPilihan /> + <CategoryDynamicMobile /> + + <PopularProduct initialData={popularProducts} /> + + <CategoryHomeId /> + <BannerSection /> + + <CustomerReviews /> </div> </MobileView> </BasicLayout> |
