summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMqdd <ahmadmiqdad27@gmail.com>2025-11-26 13:47:31 +0700
committerMqdd <ahmadmiqdad27@gmail.com>2025-11-26 13:47:31 +0700
commitc472261f34388a0b76c3e21fec494b8d5f304715 (patch)
treec595ba6ff598ba49dfca8667d721306df5ac05b3
parent6ef04dbf392c484a2833d172534b6e7da0f8dcdd (diff)
<Miqdad> popular product now ssr
-rw-r--r--src/api/productApi.js21
-rw-r--r--src/components/ui/PopularProduct.jsx110
-rw-r--r--src/pages/index.jsx248
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 &amp; Alat Teknik untuk Perusahaan, UMKM &amp; Pemerintah dengan lengkap, mudah dan transparan.'
+ title="Indoteknik.com: B2B Industrial Supply & Solution"
+ description="Temukan pilihan produk B2B Industri &amp; Alat Teknik untuk Perusahaan, UMKM &amp; 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 &amp; Alat Teknik untuk Perusahaan, UMKM &amp; 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 &amp; Alat Teknik untuk Perusahaan, UMKM &amp; 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>