summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/productApi.js21
-rw-r--r--src/components/ui/PopularProduct.jsx110
-rw-r--r--src/core/components/layouts/BasicLayout.jsx44
-rw-r--r--src/pages/index.jsx248
4 files changed, 179 insertions, 244 deletions
diff --git a/src/api/productApi.js b/src/api/productApi.js
index b5f47bcf..dc96a77e 100644
--- a/src/api/productApi.js
+++ b/src/api/productApi.js
@@ -1,6 +1,5 @@
import axios from 'axios';
-// CLIENT MODE → untuk useQuery
export const popularProductApi = () => {
return async () => {
const today = new Date();
@@ -8,25 +7,9 @@ export const popularProductApi = () => {
(today - new Date(today.getFullYear(), 0, 0)) / 86400000
);
const page = (dayOfYear % 24) + 1;
-
- const res = await axios(
+ const dataPopularProducts = await axios(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=*&page=${page}&orderBy=stock&priceFrom=1`
);
- return res.data.response;
+ return dataPopularProducts.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 0740798d..d3ae9e27 100644
--- a/src/components/ui/PopularProduct.jsx
+++ b/src/components/ui/PopularProduct.jsx
@@ -1,63 +1,61 @@
-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';
+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'
-export default function PopularProduct({ initialData }) {
- const query = useQuery(
- 'popularProducts',
- popularProductApi(),
- {
- initialData: initialData ? { products: initialData.products } : undefined,
- refetchOnMount: false
- }
- );
+const PopularProduct = () => {
+ const popularProduct = useQuery('popularProduct', popularProductApi())
- if (query.isLoading) return <PopularProductSkeleton />;
-
- const data = query.data;
-
- if (!data) return null;
+ if (popularProduct.isLoading) return <PopularProductSkeleton />
return (
- <>
- {/* 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>
+ 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 />
</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>
+ </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>
</div>
-
- <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>
- </>
- );
+ </DesktopView>
+ </>
+ )
+ )
}
+
+export default PopularProduct
diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx
index b13e807d..19e2c580 100644
--- a/src/core/components/layouts/BasicLayout.jsx
+++ b/src/core/components/layouts/BasicLayout.jsx
@@ -7,15 +7,12 @@ import { useProductContext } from '@/contexts/ProductContext';
import odooApi from '@/core/api/odooApi';
import whatsappUrl from '@/core/utils/whatsappUrl';
import Navbar from '../elements/Navbar/Navbar';
-import styles from './BasicLayout.module.css'; // Import modul CSS
+import styles from './BasicLayout.module.css';
import useDevice from '@/core/hooks/useDevice';
-const AnimationLayout = dynamic(() => import('./AnimationLayout'), {
- ssr: false,
-});
-const BasicFooter = dynamic(() => import('../elements/Footer/BasicFooter'), {
- ssr: false,
-});
+// ❌ JANGAN dynamic UNTUK LAYOUT
+import AnimationLayout from './AnimationLayout';
+import BasicFooter from '../elements/Footer/BasicFooter';
const BasicLayout = ({ children }) => {
const [templateWA, setTemplateWA] = useState(null);
@@ -27,7 +24,6 @@ const BasicLayout = ({ children }) => {
const [isProductPage, setIsProductPage] = useState(false);
const { isDesktop, isMobile } = useDevice();
-
const router = useRouter();
const buttonRef = useRef(null);
@@ -44,11 +40,10 @@ const BasicLayout = ({ children }) => {
) {
setPayloadWa({
name: product?.name,
- manufacture: product?.manufacture.name,
+ manufacture: product?.manufacture?.name,
url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath,
});
setTemplateWA('product');
-
setUrlPath(router.asPath);
}
if (router.pathname.includes('/shop/product/')) {
@@ -69,25 +64,17 @@ const BasicLayout = ({ children }) => {
};
window.addEventListener('mouseout', handleMouseOut);
-
- return () => {
- window.removeEventListener('mouseout', handleMouseOut);
- };
+ return () => window.removeEventListener('mouseout', handleMouseOut);
}, []);
useEffect(() => {
if (highlight) {
- // Set wobble animation after overlay highlight animation completes
- const timer = setTimeout(() => setWobble(true), 1000); // Adjust timing if needed
+ const timer = setTimeout(() => setWobble(true), 1000);
return () => clearTimeout(timer);
}
}, [highlight]);
- const recordActivity = async (pathname) => {
- const ONLY_ON_PATH = false;
- const recordedPath = [];
- if (ONLY_ON_PATH && !recordedPath.includes(pathname)) return;
-
+ const recordActivity = async () => {
const ip = await odooApi('GET', '/api/ip-address');
const data = new URLSearchParams({
page_title: document.title,
@@ -117,9 +104,12 @@ const BasicLayout = ({ children }) => {
onAnimationEnd={() => setHighlight(false)}
/>
)}
+
<Navbar isMobile={isMobile} />
+
<AnimationLayout>
{children}
+
{(!isProductPage || hasPrice) && (
<div
className={`fixed ${
@@ -142,6 +132,7 @@ const BasicLayout = ({ children }) => {
{isDesktop && 'Whatsapp'}
</span>
</a>
+
<a
href={whatsappUrl(templateWA, payloadWA, urlPath)}
className='elemen-whatsapp p-4 rounded-full bg-[#4FB84A] border border-green-300 flex items-center'
@@ -152,24 +143,15 @@ const BasicLayout = ({ children }) => {
<Image
src='/images/socials/WHATSAPP.svg'
alt='Whatsapp'
- className='block sm:hidden'
- width={36}
- height={36}
- loading='eager'
- />
- <Image
- src='/images/socials/WHATSAPP.svg'
- alt='Whatsapp'
- className='hidden sm:block'
width={44}
height={44}
- loading='eager'
/>
</a>
</div>
</div>
)}
</AnimationLayout>
+
<BasicFooter />
</>
);
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index fd960e8a..ba8c44af 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -1,6 +1,7 @@
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';
@@ -9,118 +10,77 @@ 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')
);
-// Popup
-const PagePopupIformation = dynamic(
- () => import('~/modules/popup-information'),
- { ssr: false }
-);
-
-// CATEGORY (SSR TRUE)
-const CategoryPilihan = dynamic(
- () => import('../lib/home/components/CategoryPilihan'),
- { ssr: true }
+const PagePopupIformation = dynamic(() =>
+ import('~/modules/popup-information'), {
+ ssr: false
+ }
);
-const CategoryDynamic = dynamic(
- () => import('@/lib/home/components/CategoryDynamic'),
- { ssr: true }
+const CategoryPilihan = dynamic(() =>
+ import('../lib/home/components/CategoryPilihan')
);
-
-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 HeroBanner = dynamic(() => import('@/components/ui/HeroBanner'), {
+ loading: () => <HeroBannerSkeleton />,
+});
+const HeroBannerSecondary = dynamic(
+ () => import('@/components/ui/HeroBannerSecondary'),
+ {
+ loading: () => <HeroBannerSkeleton />,
+ }
);
+const PopularProduct = dynamic(() => import('@/components/ui/PopularProduct'), {
+ loading: () => <PopularProductSkeleton />,
+});
const PreferredBrand = dynamic(
() => import('@/lib/home/components/PreferredBrand'),
- { loading: () => <PreferredBrandSkeleton />, ssr: true }
+ {
+ loading: () => <PreferredBrandSkeleton />,
+ }
);
const FlashSale = dynamic(
() => import('@/lib/flashSale/components/FlashSale'),
- { ssr: true }
);
const ProgramPromotion = dynamic(
- () => import('@/lib/home/components/PromotionProgram'),
- { ssr: true }
+ () => import('@/lib/home/components/PromotionProgram')
);
-// CSR ONLY COMPONENTS
-const HeroBanner = dynamic(
- () => import('@/components/ui/HeroBanner'),
- { loading: () => <HeroBannerSkeleton />, ssr: false }
+const BannerSection = dynamic(() =>
+ import('@/lib/home/components/BannerSection')
+);
+const CategoryHomeId = dynamic(
+ () => import('@/lib/home/components/CategoryHomeId')
);
-const HeroBannerSecondary = dynamic(
- () => import('@/components/ui/HeroBannerSecondary'),
- { loading: () => <HeroBannerSkeleton />, ssr: false }
+const CategoryDynamic = dynamic(() =>
+ import('@/lib/home/components/CategoryDynamic')
);
-const BannerSection = dynamic(
- () => import('@/lib/home/components/BannerSection'),
- { ssr: false }
+const CategoryDynamicMobile = dynamic(() =>
+ import('@/lib/home/components/CategoryDynamicMobile')
);
const CustomerReviews = dynamic(
() => import('@/lib/review/components/CustomerReviews'),
{ ssr: false }
-);
-
-const ServiceList = dynamic(
- () => import('@/lib/home/components/ServiceList'),
- { ssr: false }
-);
-
-
-/* =====================================
- * SERVER SIDE PROPS (HARUS DI LUAR)
- * ===================================== */
+); // need to ssr:false
+const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList'), {
+ ssr: false,
+}); // need to ssr: false
- 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 }) {
+export default function Home({ categoryId }) {
const bannerRef = useRef(null);
const wrapperRef = useRef(null);
+ const auth = getAuth();
+
const handleOnLoad = () => {
wrapperRef.current.style.height =
bannerRef.current?.querySelector(':first-child')?.clientHeight + 'px';
@@ -130,109 +90,121 @@ export default function Home({ auth, popularProducts }) {
<>
<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"
- }
+ '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"
- }
- ]
- }}
+ 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">
- <PopularProduct initialData={popularProducts} />
+ <div className='w-3/12'>
+ <DelayRender renderAfter={200}>
+ <PopularProduct />
+ </DelayRender>
</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 && (
<>
- <ProgramPromotion />
- <FlashSale />
+ <DelayRender renderAfter={200}>
+ <ProgramPromotion />
+ </DelayRender>
+ <DelayRender renderAfter={200}>
+ <FlashSale />
+ </DelayRender>
</>
)}
-
+ {/* <PromotinProgram /> */}
<CategoryPilihan />
<CategoryDynamic />
<CategoryHomeId />
-
<BannerSection />
<CustomerReviews />
</div>
</div>
</DesktopView>
-
- {/* MOBILE */}
<MobileView>
<PagePopupInformation />
- <HeroBanner />
-
- <div className="flex flex-col gap-y-4 my-6">
- <ServiceList />
- <MediaNews />
-
- <div id="flashsale">
- <PreferredBrand />
- </div>
-
+ <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>
{!auth?.feature?.soApproval && (
<>
- <ProgramPromotion />
- <FlashSale />
+ <DelayRender renderAfter={400}>
+ <ProgramPromotion />
+ </DelayRender>
+ <DelayRender renderAfter={600}>
+ <FlashSale />
+ </DelayRender>
</>
)}
-
- <CategoryPilihan />
- <CategoryDynamicMobile />
-
- <PopularProduct initialData={popularProducts} />
-
- <CategoryHomeId />
- <BannerSection />
-
- <CustomerReviews />
+ <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>
</div>
</MobileView>
</BasicLayout>