diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2024-01-19 02:32:43 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2024-01-19 02:32:43 +0000 |
| commit | 8bcadf6d43a44169c422305522784424c30c7b02 (patch) | |
| tree | 4666802b65784a949db4acad665a81de7297fc74 /src | |
| parent | 065396828266e2de42cb0182c81ea2d7a5b00e2b (diff) | |
| parent | 91086d8b1af2e1c0ca9db38d037f6331c9e6131a (diff) | |
Merged in Feature/perf/product-detail (pull request #127)
Feature/perf/product detail
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/components/elements/Navbar/NavbarDesktop.jsx | 124 | ||||
| -rw-r--r-- | src/core/components/elements/Navbar/NavbarMobile.jsx | 69 | ||||
| -rw-r--r-- | src/core/components/elements/Skeleton/TopBannerSkeleton.jsx | 24 | ||||
| -rw-r--r-- | src/core/components/layouts/AppLayout.jsx | 18 | ||||
| -rw-r--r-- | src/core/components/layouts/BasicLayout.jsx | 82 | ||||
| -rw-r--r-- | src/fonts/Inter/inter.css | 85 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSearch.jsx | 6 | ||||
| -rw-r--r-- | src/pages/_app.jsx | 108 | ||||
| -rw-r--r-- | src/pages/_document.jsx | 36 | ||||
| -rw-r--r-- | src/pages/shop/cart.jsx | 2 | ||||
| -rw-r--r-- | src/pages/shop/product/[slug].jsx | 118 | ||||
| -rw-r--r-- | src/utils/solrMapping.js | 94 |
12 files changed, 325 insertions, 441 deletions
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx index d9f5658e..9bcd4df2 100644 --- a/src/core/components/elements/Navbar/NavbarDesktop.jsx +++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx @@ -2,74 +2,72 @@ import { ChevronDownIcon, HeartIcon, ShoppingCartIcon, - DocumentCheckIcon -} from '@heroicons/react/24/outline' -import Link from '../Link/Link' -import Image from 'next/image' -import DesktopView from '../../views/DesktopView' -import dynamic from 'next/dynamic' -import IndoteknikLogo from '@/images/logo.png' -import Category from '@/lib/category/components/Category' -import { useCallback, useContext, useEffect, useState } from 'react' -import useAuth from '@/core/hooks/useAuth' -import NavbarUserDropdown from './NavbarUserDropdown' -import { getCartApi, getCountCart } from '@/core/utils/cart' -import whatsappUrl from '@/core/utils/whatsappUrl' -import { useRouter } from 'next/router' -import { getAuth, setAuth } from '@/core/utils/auth' -import { createSlug, getIdFromSlug } from '@/core/utils/slug' -import { TopBannerSkeleton } from '../Skeleton/TopBannerSkeleton' -import { useProductContext } from '@/contexts/ProductContext' -import Cardheader from '@/lib/cart/components/Cartheader' - -const Search = dynamic(() => import('./Search')) -const TopBanner = dynamic(() => import('./TopBanner'), { - loading: () => <TopBannerSkeleton /> -}) + DocumentCheckIcon, +} from '@heroicons/react/24/outline'; +import Link from '../Link/Link'; +import Image from 'next/image'; +import DesktopView from '../../views/DesktopView'; +import dynamic from 'next/dynamic'; +import IndoteknikLogo from '@/images/logo.png'; +import Category from '@/lib/category/components/Category'; +import { useCallback, useContext, useEffect, useState } from 'react'; +import useAuth from '@/core/hooks/useAuth'; +import NavbarUserDropdown from './NavbarUserDropdown'; +import { getCartApi, getCountCart } from '@/core/utils/cart'; +import whatsappUrl from '@/core/utils/whatsappUrl'; +import { useRouter } from 'next/router'; +import { getAuth, setAuth } from '@/core/utils/auth'; +import { createSlug, getIdFromSlug } from '@/core/utils/slug'; +import { TopBannerSkeleton } from '../Skeleton/TopBannerSkeleton'; +import { useProductContext } from '@/contexts/ProductContext'; +import Cardheader from '@/lib/cart/components/Cartheader'; + +const Search = dynamic(() => import('./Search'), { ssr: false }); +const TopBanner = dynamic(() => import('./TopBanner'), { ssr: false }); const NavbarDesktop = () => { - const [isOpenCategory, setIsOpenCategory] = useState(false) - const auth = useAuth() + const [isOpenCategory, setIsOpenCategory] = useState(false); + const auth = useAuth(); - const [cartCount, setCartCount] = useState(0) + const [cartCount, setCartCount] = useState(0); - const [templateWA, setTemplateWA] = useState(null) - const [payloadWA, setPayloadWa] = useState(null) - const [urlPath, setUrlPath] = useState(null) + const [templateWA, setTemplateWA] = useState(null); + const [payloadWA, setPayloadWa] = useState(null); + const [urlPath, setUrlPath] = useState(null); - const router = useRouter() + const router = useRouter(); - const { product } = useProductContext() + const { product } = useProductContext(); useEffect(() => { if (router.pathname === '/shop/product/[slug]') { setPayloadWa({ name: product?.name, manufacture: product?.manufacture.name, - url: createSlug('/shop/product/', product?.name, product?.id, true) - }) - setTemplateWA('product') + url: createSlug('/shop/product/', product?.name, product?.id, true), + }); + setTemplateWA('product'); - setUrlPath(router.asPath) + setUrlPath(router.asPath); } - }, [product, router]) + }, [product, router]); useEffect(() => { const handleCartChange = () => { const cart = async () => { - const listCart = await getCountCart() - setCartCount(listCart) - } - cart() - } - handleCartChange() + const listCart = await getCountCart(); + setCartCount(listCart); + }; + cart(); + }; + handleCartChange(); - window.addEventListener('localStorageChange', handleCartChange) + window.addEventListener('localStorageChange', handleCartChange); return () => { - window.removeEventListener('localStorageChange', handleCartChange) - } - }, []) + window.removeEventListener('localStorageChange', handleCartChange); + }; + }, []); return ( <DesktopView> @@ -93,7 +91,12 @@ 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='/'> - <Image src={IndoteknikLogo} alt='Indoteknik Logo' width={210} height={210 / 3} /> + <Image + src={IndoteknikLogo} + alt='Indoteknik Logo' + width={210} + height={210 / 3} + /> </Link> <div className='flex-1 flex items-center'> <Search /> @@ -128,7 +131,12 @@ const NavbarDesktop = () => { rel='noreferrer' className='flex items-center gap-x-1 !text-gray_r-12/80' > - <Image src='/images/socials/Whatsapp-2.png' alt='Whatsapp' width={48} height={48} /> + <Image + src='/images/socials/Whatsapp-2.png' + alt='Whatsapp' + width={48} + height={48} + /> <div> <div className='font-semibold'>Whatsapp</div> 0812 8080 622 (Chat) @@ -146,9 +154,15 @@ const NavbarDesktop = () => { className='w-3/12 p-4 font-semibold border border-gray_r-6 rounded-t-xl flex items-center relative' > <div>Kategori Produk</div> - <ChevronDownIcon className={`ml-auto w-6 ${isOpenCategory ? 'rotate-180' : ''}`} /> - - <div className={`category-mega-box-wrapper ${isOpenCategory ? 'show' : ''}`}> + <ChevronDownIcon + className={`ml-auto w-6 ${isOpenCategory ? 'rotate-180' : ''}`} + /> + + <div + className={`category-mega-box-wrapper ${ + isOpenCategory ? 'show' : '' + }`} + > <Category /> </div> </button> @@ -220,7 +234,7 @@ const NavbarDesktop = () => { </div> </nav> </DesktopView> - ) -} + ); +}; -export default NavbarDesktop +export default NavbarDesktop; diff --git a/src/core/components/elements/Navbar/NavbarMobile.jsx b/src/core/components/elements/Navbar/NavbarMobile.jsx index 704e91b6..92bd5627 100644 --- a/src/core/components/elements/Navbar/NavbarMobile.jsx +++ b/src/core/components/elements/Navbar/NavbarMobile.jsx @@ -1,51 +1,60 @@ -import Image from 'next/image' -import MobileView from '../../views/MobileView' -import Link from '../Link/Link' -import { Bars3Icon, HeartIcon, ShoppingCartIcon } from '@heroicons/react/24/outline' -import useSidebar from '@/core/hooks/useSidebar' -import dynamic from 'next/dynamic' -import IndoteknikLogo from '@/images/logo.png' -import { useEffect, useState } from 'react' -import { getCart, getCountCart } from '@/core/utils/cart' -import TopBanner from './TopBanner' +import Image from 'next/image'; +import MobileView from '../../views/MobileView'; +import Link from '../Link/Link'; +import { + Bars3Icon, + HeartIcon, + ShoppingCartIcon, +} from '@heroicons/react/24/outline'; +import useSidebar from '@/core/hooks/useSidebar'; +import dynamic from 'next/dynamic'; +import IndoteknikLogo from '@/images/logo.png'; +import { useEffect, useState } from 'react'; +import { getCart, getCountCart } from '@/core/utils/cart'; +// import TopBanner from './TopBanner'; -const Search = dynamic(() => import('./Search')) +const Search = dynamic(() => import('./Search')); const NavbarMobile = () => { - const { Sidebar, open } = useSidebar() + const { Sidebar, open } = useSidebar(); - const [cartCount, setCartCount] = useState(0) + const [cartCount, setCartCount] = useState(0); useEffect(() => { const handleCartChange = () => { const cart = async () => { - const listCart = await getCountCart() - setCartCount(listCart) - } - cart() - } - handleCartChange() + const listCart = await getCountCart(); + setCartCount(listCart); + }; + cart(); + }; + handleCartChange(); - window.addEventListener('localStorageChange', handleCartChange) + window.addEventListener('localStorageChange', handleCartChange); return () => { - window.removeEventListener('localStorageChange', handleCartChange) - } - }, []) + window.removeEventListener('localStorageChange', handleCartChange); + }; + }, []); return ( <MobileView> - <TopBanner /> + {/* <TopBanner /> */} <nav className='px-4 py-2 pb-3 sticky top-0 z-50 bg-white shadow'> <div className='flex justify-between items-center mb-2'> <Link href='/'> - <Image src={IndoteknikLogo} alt='Indoteknik Logo' width={120} height={40} /> + <Image + src={IndoteknikLogo} + alt='Indoteknik Logo' + width={120} + height={40} + /> </Link> <div className='flex gap-x-3'> - <Link href='/my/wishlist'> + <Link href='/my/wishlist' aria-label='Wishlist'> <HeartIcon className='w-6 text-gray_r-12' /> </Link> - <Link href='/shop/cart' className='relative'> + <Link href='/shop/cart' className='relative' aria-label='Cart'> <ShoppingCartIcon className='w-6 text-gray_r-12' /> {cartCount > 0 && ( <span className='absolute -top-2 -right-2 badge-solid-red rounded-full w-5 h-5 flex items-center justify-center'> @@ -62,7 +71,7 @@ const NavbarMobile = () => { </nav> {Sidebar} </MobileView> - ) -} + ); +}; -export default NavbarMobile +export default NavbarMobile; diff --git a/src/core/components/elements/Skeleton/TopBannerSkeleton.jsx b/src/core/components/elements/Skeleton/TopBannerSkeleton.jsx index f7d2e748..8d1a51d2 100644 --- a/src/core/components/elements/Skeleton/TopBannerSkeleton.jsx +++ b/src/core/components/elements/Skeleton/TopBannerSkeleton.jsx @@ -1,19 +1,17 @@ -import useDevice from '@/core/hooks/useDevice' -import classNames from 'classnames' -import Skeleton from 'react-loading-skeleton' +import useDevice from '@/core/hooks/useDevice'; +import classNames from 'classnames'; +import { Skeleton } from '@chakra-ui/react'; const TopBannerSkeleton = () => { - const { isDesktop, isMobile } = useDevice() + const { isDesktop, isMobile } = useDevice(); const deviceClassName = { - 'h-10': isDesktop, - 'h-2.5': isMobile - } - const combinedClassName = classNames(deviceClassName) + '!h-[36px]': isDesktop, + 'h-2.5': isMobile, + }; + const combinedClassName = classNames(deviceClassName); - return ( - <Skeleton className={combinedClassName} count={1} containerClassName='w-full h-full block' /> - ) -} + return <Skeleton className={combinedClassName} />; +}; -export { TopBannerSkeleton } +export { TopBannerSkeleton }; diff --git a/src/core/components/layouts/AppLayout.jsx b/src/core/components/layouts/AppLayout.jsx index d74d61e3..ebbc1ad5 100644 --- a/src/core/components/layouts/AppLayout.jsx +++ b/src/core/components/layouts/AppLayout.jsx @@ -1,6 +1,12 @@ -import AppBar from '../elements/Appbar/Appbar' -import BasicFooter from '../elements/Footer/BasicFooter' -import AnimationLayout from './AnimationLayout' +import dynamic from 'next/dynamic'; +import AnimationLayout from './AnimationLayout'; + +const AppBar = dynamic(() => import('../elements/Appbar/Appbar'), { + ssr: false, +}); +const BasicFooter = dynamic(() => import('../elements/Footer/BasicFooter'), { + ssr: false, +}); const AppLayout = ({ children, title, withFooter = true }) => { return ( @@ -11,7 +17,7 @@ const AppLayout = ({ children, title, withFooter = true }) => { </AnimationLayout> {withFooter && <BasicFooter />} </> - ) -} + ); +}; -export default AppLayout +export default AppLayout; diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx index 9441dbd7..2962a08b 100644 --- a/src/core/components/layouts/BasicLayout.jsx +++ b/src/core/components/layouts/BasicLayout.jsx @@ -1,55 +1,61 @@ -import dynamic from 'next/dynamic' -import BasicFooter from '../elements/Footer/BasicFooter' -import Image from 'next/image' -import whatsappUrl from '@/core/utils/whatsappUrl' -import { useEffect, useState } from 'react' -import axios from 'axios' -import odooApi from '@/core/api/odooApi' -import { useRouter } from 'next/router' -import productApi from '@/lib/product/api/productApi' -import { getAuth, setAuth } from '@/core/utils/auth' -import { createSlug, getIdFromSlug } from '@/core/utils/slug' -import { useSession } from 'next-auth/react' -import { setCookie } from 'cookies-next' -import { useProductContext } from '@/contexts/ProductContext' +import dynamic from 'next/dynamic'; +import Image from 'next/image'; +import { useEffect, useState } from 'react'; +import axios from 'axios'; -const Navbar = dynamic(() => import('../elements/Navbar/Navbar')) -const AnimationLayout = dynamic(() => import('./AnimationLayout')) +import whatsappUrl from '@/core/utils/whatsappUrl'; +import odooApi from '@/core/api/odooApi'; +import { useRouter } from 'next/router'; +import { useProductContext } from '@/contexts/ProductContext'; + +const Navbar = dynamic(() => import('../elements/Navbar/Navbar'), { + ssr: false, +}); +const AnimationLayout = dynamic(() => import('./AnimationLayout'), { + ssr: false, +}); +const BasicFooter = dynamic(() => import('../elements/Footer/BasicFooter'), { + ssr: false, +}); const BasicLayout = ({ children }) => { - const [templateWA, setTemplateWA] = useState(null) - const [payloadWA, setPayloadWa] = useState(null) - const [urlPath, setUrlPath] = useState(null) + const [templateWA, setTemplateWA] = useState(null); + const [payloadWA, setPayloadWa] = useState(null); + const [urlPath, setUrlPath] = useState(null); - const router = useRouter() + const router = useRouter(); - const { product } = useProductContext() + const { product } = useProductContext(); useEffect(() => { - if (router.pathname === '/shop/product/[slug]' || router.pathname === '/shop/product/variant/[slug]') { + if ( + router.pathname === '/shop/product/[slug]' || + router.pathname === '/shop/product/variant/[slug]' + ) { setPayloadWa({ name: product?.name, manufacture: product?.manufacture.name, - url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath - }) - setTemplateWA('product') + url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, + }); + setTemplateWA('product'); - setUrlPath(router.asPath) + setUrlPath(router.asPath); } - }, [product, router]) - + }, [product, router]); useEffect(() => { const getIP = async () => { - const ip = await odooApi('GET', '/api/ip-address') + const ip = await odooApi('GET', '/api/ip-address'); const data = { page_title: document.title, url: window.location.href, - ip: ip - } - axios.get(`/api/user-activity?page_title=${data.page_title}&url=${data.url}&ip=${data.ip}`) - } - getIP() - }, []) + ip: ip, + }; + axios.get( + `/api/user-activity?page_title=${data.page_title}&url=${data.url}&ip=${data.ip}` + ); + }; + getIP(); + }, []); return ( <> <Navbar /> @@ -82,7 +88,7 @@ const BasicLayout = ({ children }) => { </AnimationLayout> <BasicFooter /> </> - ) -} + ); +}; -export default BasicLayout +export default BasicLayout; diff --git a/src/fonts/Inter/inter.css b/src/fonts/Inter/inter.css index de6ce273..3a1de02a 100644 --- a/src/fonts/Inter/inter.css +++ b/src/fonts/Inter/inter.css @@ -1,57 +1,6 @@ @font-face { font-family: 'Inter'; font-style: normal; - font-weight: 100; - font-display: swap; - src: url('Inter-Thin.woff2?v=3.19') format('woff2'), - url('Inter-Thin.woff?v=3.19') format('woff'); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 100; - font-display: swap; - src: url('Inter-ThinItalic.woff2?v=3.19') format('woff2'), - url('Inter-ThinItalic.woff?v=3.19') format('woff'); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 200; - font-display: swap; - src: url('Inter-ExtraLight.woff2?v=3.19') format('woff2'), - url('Inter-ExtraLight.woff?v=3.19') format('woff'); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 200; - font-display: swap; - src: url('Inter-ExtraLightItalic.woff2?v=3.19') format('woff2'), - url('Inter-ExtraLightItalic.woff?v=3.19') format('woff'); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url('Inter-Light.woff2?v=3.19') format('woff2'), - url('Inter-Light.woff?v=3.19') format('woff'); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 300; - font-display: swap; - src: url('Inter-LightItalic.woff2?v=3.19') format('woff2'), - url('Inter-LightItalic.woff?v=3.19') format('woff'); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; font-weight: 400; font-display: swap; src: url('Inter-Regular.woff2?v=3.19') format('woff2'), @@ -117,40 +66,6 @@ url('Inter-BoldItalic.woff?v=3.19') format('woff'); } -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url('Inter-ExtraBold.woff2?v=3.19') format('woff2'), - url('Inter-ExtraBold.woff?v=3.19') format('woff'); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 800; - font-display: swap; - src: url('Inter-ExtraBoldItalic.woff2?v=3.19') format('woff2'), - url('Inter-ExtraBoldItalic.woff?v=3.19') format('woff'); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url('Inter-Black.woff2?v=3.19') format('woff2'), - url('Inter-Black.woff?v=3.19') format('woff'); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 900; - font-display: swap; - src: url('Inter-BlackItalic.woff2?v=3.19') format('woff2'), - url('Inter-BlackItalic.woff?v=3.19') format('woff'); -} - /* ------------------------------------------------------- Variable font. Usage: diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index e2b961f1..ed4365a8 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -253,12 +253,14 @@ const ProductSearch = ({ router.push(`${prefixUrl}?${params}`); }; + const isNotReadyStockPage = router.asPath !== '/shop/search?orderBy=stock'; + return ( <> <MobileView> {productSearch.isLoading && <ProductSearchSkeleton />} <div className='p-4 pt-0'> - {isBrand && isBrand.logo && ( + {isNotReadyStockPage && isBrand && isBrand.logo && ( <div className='mb-3'> <h1 className='mb-2 font-semibold text-h-sm'> Brand Pencarian {q} @@ -403,7 +405,7 @@ const ProductSearch = ({ </div> )} - {isBrand && isBrand.logo && ( + {isNotReadyStockPage && isBrand && isBrand.logo && ( <div className='mb-3'> <h1 className='text-2xl mb-2 font-semibold'> Brand Pencarian {q} diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 3fe1d3cf..9067fd03 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -1,58 +1,82 @@ -import '@/fonts/Inter/inter.css' -import '@/styles/globals.css' -import 'react-loading-skeleton/dist/skeleton.css' +import '@/fonts/Inter/inter.css'; +import '@/styles/globals.css'; +// import 'react-loading-skeleton/dist/skeleton.css'; -import NextProgress from 'next-progress' -import { useRouter, Router } from 'next/router' -import { AnimatePresence, motion } from 'framer-motion' -import { Toaster } from 'react-hot-toast' -import { QueryClient, QueryClientProvider } from 'react-query' -import useDevice from '@/core/hooks/useDevice' -import { useEffect, useState } from 'react' -import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner' -import { SessionProvider } from 'next-auth/react' -import { ProductProvider } from '@/contexts/ProductContext' -import { ProductCartProvider } from '@/contexts/ProductCartContext' -import { ChakraProvider } from '@chakra-ui/react' -import theme from '../../chakra.theme' +import { useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { useRouter, Router } from 'next/router'; +import { AnimatePresence, motion } from 'framer-motion'; +import { QueryClient, QueryClientProvider } from 'react-query'; -const queryClient = new QueryClient() +import useDevice from '@/core/hooks/useDevice'; +import theme from '../../chakra.theme'; + +const NextProgress = dynamic(() => import('next-progress'), { ssr: false }); +const ChakraProvider = dynamic( + () => import('@chakra-ui/react').then((mod) => mod.ChakraProvider), + { ssr: false } +); +const ProductProvider = dynamic( + () => import('@/contexts/ProductContext').then((mod) => mod.ProductProvider), + { ssr: false } +); +const ProductCartProvider = dynamic( + () => + import('@/contexts/ProductCartContext').then( + (mod) => mod.ProductCartProvider + ), + { ssr: false } +); +const SessionProvider = dynamic( + () => import('next-auth/react').then((mod) => mod.SessionProvider), + { ssr: false } +); +const LogoSpinner = dynamic( + () => import('@/core/components/elements/Spinner/LogoSpinner'), + { ssr: false } +); +const Toaster = dynamic( + () => import('react-hot-toast').then((mod) => mod.Toaster), + { ssr: false } +); + +const queryClient = new QueryClient(); function MyApp({ Component, pageProps: { session, ...pageProps } }) { - const router = useRouter() - const { isMobile } = useDevice() + const router = useRouter(); + const { isMobile } = useDevice(); - const [animateLoader, setAnimateLoader] = useState(false) + const [animateLoader, setAnimateLoader] = useState(false); useEffect(() => { - const handleRouteChangeStart = () => setAnimateLoader(true) - const handleRouteChangeComplete = () => setAnimateLoader(false) + const handleRouteChangeStart = () => setAnimateLoader(true); + const handleRouteChangeComplete = () => setAnimateLoader(false); - Router.events.on('routeChangeStart', handleRouteChangeStart) - Router.events.on('routeChangeComplete', handleRouteChangeComplete) - Router.events.on('routeChangeError', handleRouteChangeComplete) + Router.events.on('routeChangeStart', handleRouteChangeStart); + Router.events.on('routeChangeComplete', handleRouteChangeComplete); + Router.events.on('routeChangeError', handleRouteChangeComplete); return () => { - Router.events.off('routeChangeStart', handleRouteChangeStart) - Router.events.off('routeChangeComplete', handleRouteChangeComplete) - Router.events.off('routeChangeError', handleRouteChangeComplete) - } - }, []) + Router.events.off('routeChangeStart', handleRouteChangeStart); + Router.events.off('routeChangeComplete', handleRouteChangeComplete); + Router.events.off('routeChangeError', handleRouteChangeComplete); + }; + }, []); - const [toasterStyle, setToasterStyle] = useState({}) + const [toasterStyle, setToasterStyle] = useState({}); useEffect(() => { - let elems = document.querySelectorAll('nav') - let totalNavHeight = 0 + let elems = document.querySelectorAll('nav'); + let totalNavHeight = 0; elems.forEach(function (elem) { - totalNavHeight += elem.offsetHeight - }) + totalNavHeight += elem.offsetHeight; + }); setToasterStyle({ - marginTop: isMobile ? totalNavHeight - 8 : totalNavHeight - }) - }, [isMobile]) + marginTop: isMobile ? totalNavHeight - 8 : totalNavHeight, + }); + }, [isMobile]); return ( <SessionProvider session={session}> @@ -63,7 +87,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { animate={{ opacity: 1 }} exit={{ opacity: 0.4 }} transition={{ - duration: 0.1 + duration: 0.1, }} className='fixed w-screen h-screen z-[500] bg-white flex justify-center items-center' > @@ -76,7 +100,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { containerStyle={toasterStyle} toastOptions={{ duration: 3000, - className: 'border border-gray_r-8' + className: 'border border-gray_r-8', }} /> <NextProgress color='#F01C21' options={{ showSpinner: false }} /> @@ -90,7 +114,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { </ProductProvider> </QueryClientProvider> </SessionProvider> - ) + ); } -export default MyApp +export default MyApp; diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx index 3762c63b..fc9f2ee0 100644 --- a/src/pages/_document.jsx +++ b/src/pages/_document.jsx @@ -1,16 +1,24 @@ -import { Html, Head, Main, NextScript } from 'next/document' -import Script from 'next/script' +import { Html, Head, Main, NextScript } from 'next/document'; +import Script from 'next/script'; export default function MyDocument() { - const env = process.env.NODE_ENV + const env = process.env.NODE_ENV; return ( <Html> <Head> + <link rel='preconnect' href='https://connect.facebook.net' /> + <link rel='preconnect' href='https://googleads.g.doubleclick.net' /> + <link rel='preconnect' href={process.env.NEXT_PUBLIC_ODOO_API_HOST} /> + <link rel='prefetch' href='/images/logo-indoteknik-gear.png' /> + <link rel='icon' href='/favicon.ico' /> <link rel='manifest' href='/manifest.json' /> <link rel='apple-touch-icon' href='/icon.jpg'></link> - <link rel='apple-touch-startup-image' href='/images/splash/launch.png' /> + <link + rel='apple-touch-startup-image' + href='/images/splash/launch.png' + /> <meta name='mobile-web-app-capable' content='yes' /> <meta name='apple-mobile-web-app-capable' content='yes' /> @@ -18,9 +26,11 @@ export default function MyDocument() { <meta name='apple-mobile-web-app-title' content='Indoteknik.com' /> <meta name='theme-color' content='#fff' /> - <link rel='prefetch' href='/images/logo-indoteknik-gear.png' /> + <meta + name='facebook-domain-verification' + content='328wmjs7hcnz74rwsqzxvq50rmbtm2' + /> - <meta name='facebook-domain-verification' content='328wmjs7hcnz74rwsqzxvq50rmbtm2' /> <Script async strategy='beforeInteractive' @@ -28,6 +38,7 @@ export default function MyDocument() { /> <Script + async id='google-analytics-ua' strategy='beforeInteractive' dangerouslySetInnerHTML={{ @@ -36,7 +47,7 @@ export default function MyDocument() { function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-10501937-1'); - ` + `, }} /> @@ -47,6 +58,7 @@ export default function MyDocument() { /> <Script + async id='google-analytics-ga' strategy='beforeInteractive' dangerouslySetInnerHTML={{ @@ -55,11 +67,12 @@ export default function MyDocument() { function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-G1W8MNZ11P'); - ` + `, }} /> <Script + async id='google-tag-manager' strategy='afterInteractive' dangerouslySetInnerHTML={{ @@ -68,7 +81,7 @@ export default function MyDocument() { f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-PHRB7RP'); - ` + `, }} /> @@ -79,6 +92,7 @@ export default function MyDocument() { /> <Script + async id='google-ads' strategy='afterInteractive' dangerouslySetInnerHTML={{ @@ -87,7 +101,7 @@ export default function MyDocument() { function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); - gtag('config', 'AW-954540379');` + gtag('config', 'AW-954540379');`, }} /> @@ -119,5 +133,5 @@ export default function MyDocument() { <NextScript /> </body> </Html> - ) + ); } diff --git a/src/pages/shop/cart.jsx b/src/pages/shop/cart.jsx index 34cae86a..7475b23d 100644 --- a/src/pages/shop/cart.jsx +++ b/src/pages/shop/cart.jsx @@ -8,7 +8,7 @@ import dynamic from 'next/dynamic'; import Link from 'next/link'; const AppLayout = dynamic(() => import('@/core/components/layouts/AppLayout')); -const CartDetail = dynamic(() => import('~/modules/cart/components/Detail')); +const CartDetail = dynamic(() => import('~/pages/shop/cart')); export default function Cart() { return ( diff --git a/src/pages/shop/product/[slug].jsx b/src/pages/shop/product/[slug].jsx index 667373b4..73e8987c 100644 --- a/src/pages/shop/product/[slug].jsx +++ b/src/pages/shop/product/[slug].jsx @@ -1,114 +1,6 @@ -import Seo from '@/core/components/Seo'; -import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'; -import { getIdFromSlug } from '@/core/utils/slug'; -import productApi from '@/lib/product/api/productApi'; -import PageNotFound from '@/pages/404'; -import dynamic from 'next/dynamic'; -import { useRouter } from 'next/router'; -import cookie from 'cookie'; -import axios from 'axios'; -import { useProductContext } from '@/contexts/ProductContext'; -import { useEffect } from 'react'; -import { updateItemCart } from '@/core/utils/cart'; +import ProductDetailPage, { + getServerSideProps, +} from '~/pages/shop/product/[slug]'; -const BasicLayout = dynamic(() => - import('@/core/components/layouts/BasicLayout') -); -const Product = dynamic(() => - import('@/lib/product/components/Product/Product') -); - -export async function getServerSideProps(context) { - const { slug } = context.query; - const cookies = context.req.headers.cookie; - const cookieObj = cookies ? cookie.parse(cookies) : {}; - const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {}; - const tier = auth.pricelist ? auth.pricelist : false; - - let response = await axios( - `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/product-detail?id=` + - getIdFromSlug(slug) + - '&auth=' + - tier - ); - let product = response.data; - // let productSolr = await productApi({ id: getIdFromSlug(slug), headers: { Token: authToken } }) - // let productSolr = null - if (product?.length == 1) { - product = product[0]; - } else { - product = null; - } - - return { - props: { product }, - }; -} - -export default function ProductDetail({ product }) { - const router = useRouter(); - const { setProduct } = useProductContext(); - - useEffect(() => { - if (product) { - setProduct(product); - } - }, [product, setProduct]); - - useEffect(() => { - const { action, variantId, qty } = router.query; - const addToCart = async () => { - const data = { - productId: variantId, - quantity: qty, - selected: true, - programLineId: null, - source: action, - }; - - await updateItemCart(data); - const redirectURL = - action === 'buy' ? '/shop/checkout?source=buy' : '/shop/cart'; - router.push(redirectURL); - }; - - if (action && variantId && qty) { - addToCart(); - } - }, [router]); - - if (!product) return <PageNotFound />; - - return ( - <BasicLayout> - <Seo - title={product?.name || '' + ' - Indoteknik.com' || ''} - description='Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.' - openGraph={{ - url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, - images: [ - { - url: product?.image, - width: 800, - height: 800, - alt: product?.name, - }, - ], - type: 'product', - }} - additionalMetaTags={[ - { - name: 'keywords', - content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`, - }, - ]} - /> - {!product && ( - <div className='container mx-auto flex justify-center pt-10'> - <LogoSpinner width={36} height={36} /> - </div> - )} - {product && <Product product={product} />} - </BasicLayout> - ); -} +export { getServerSideProps }; +export default ProductDetailPage; diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js index 41d24b53..7e887253 100644 --- a/src/utils/solrMapping.js +++ b/src/utils/solrMapping.js @@ -1,17 +1,18 @@ export const productMappingSolr = (products, pricelist) => { return products.map((product) => { - let price = product.price_tier1_v2_f || 0 - let priceDiscount = 0 - let discountPercentage = 0 + let price = product.price_tier1_v2_f || 0; + let priceDiscount = price; + let discountPercentage = 0; if (pricelist && product?.[`price_${pricelist}_f`] < price) { - price = product?.[`price_${pricelist}_f`] || 0 + price = product?.[`price_${pricelist}_f`] || 0; + priceDiscount = price; } - if (product?.flashsale_id_i > 0 ) { - price = product?.flashsale_base_price_f || 0 - priceDiscount = product?.flashsale_price_f || 0 - discountPercentage = product?.flashsale_discount_f || 0 + if (product?.flashsale_id_i > 0) { + price = product?.flashsale_base_price_f || 0; + priceDiscount = product?.flashsale_price_f || 0; + discountPercentage = product?.flashsale_discount_f || 0; } let productMapped = { @@ -29,48 +30,48 @@ export const productMappingSolr = (products, pricelist) => { categories: [], flashSale: { id: product?.flashsale_id_i, - remainingTime: flashsaleTime(product?.flashsale_end_date_s)?.remainingTime, + remainingTime: flashsaleTime(product?.flashsale_end_date_s) + ?.remainingTime, name: product?.product?.flashsale_name_s, - tag: product?.flashsale_tag_s || 'FLASH SALE' + tag: product?.flashsale_tag_s || 'FLASH SALE', }, - qtySold : product?.qty_sold_f || 0 - } + qtySold: product?.qty_sold_f || 0, + }; if (product.manufacture_id_i && product.manufacture_name_s) { productMapped.manufacture = { id: product.manufacture_id_i || '', name: product.manufacture_name_s || '', imagePromotion1: product.image_promotion_1_s || '', - imagePromotion2: product.image_promotion_2_s || '' - } + imagePromotion2: product.image_promotion_2_s || '', + }; } productMapped.categories = [ { id: product.category_id_i || '', - name: product.category_name_s || '' - } - ] - return productMapped - }) -} + name: product.category_name_s || '', + }, + ]; + return productMapped; + }); +}; export const variantsMappingSolr = (parent, products, pricelist) => { return products.map((product) => { - let price = product.price_tier1_v2_f || 0 - let priceDiscount = 0 - let discountPercentage = 0 + let price = product.price_tier1_v2_f || 0; + let priceDiscount = price; + let discountPercentage = 0; - if (pricelist) { - if (product?.[`price_${pricelist}_f`] < price) { - price = product?.[`price_${pricelist}_f`] || 0 - } + if (pricelist && product?.[`price_${pricelist}_f`] < price) { + price = product?.[`price_${pricelist}_f`] || 0; + priceDiscount = price; } if (product?.flashsale_id_i > 0 && product?.flashsale_price_f < price) { - price = product?.flashsale_base_price_f || 0 - priceDiscount = product?.flashsale_price_f || 0 - discountPercentage = product?.flashsale_discount_f || 0 + price = product?.flashsale_base_price_f || 0; + priceDiscount = product?.flashsale_price_f || 0; + discountPercentage = product?.flashsale_discount_f || 0; } let productMapped = { @@ -87,30 +88,33 @@ export const variantsMappingSolr = (parent, products, pricelist) => { weight: product.weight_f || 0, manufacture: {}, parent: {}, - qtySold : product?.qty_sold_f || 0 - } + qtySold: product?.qty_sold_f || 0, + }; if (product.manufacture_id_i && product.manufacture_name_s) { productMapped.manufacture = { id: product.manufacture_id_i || '', - name: product.manufacture_name_s || '' - } + name: product.manufacture_name_s || '', + }; } productMapped.parent = { id: parent.product_id_i || '', image: parent.image_s || '', - name: parent.name_s || '' - } - return productMapped - }) -} + name: parent.name_s || '', + }; + return productMapped; + }); +}; const flashsaleTime = (endDate) => { - const flashsaleEndDate = new Date(endDate) - const currentTime = new Date() + const flashsaleEndDate = new Date(endDate); + const currentTime = new Date(); - const timeDifferenceInMillis = flashsaleEndDate - currentTime - const timeDifferenceInSeconds = timeDifferenceInMillis / 1000 + const timeDifferenceInMillis = flashsaleEndDate - currentTime; + const timeDifferenceInSeconds = timeDifferenceInMillis / 1000; - return { remainingTime: timeDifferenceInSeconds, isFlashSale: flashsaleEndDate > currentTime } -} + return { + remainingTime: timeDifferenceInSeconds, + isFlashSale: flashsaleEndDate > currentTime, + }; +}; |
