From a70fd5b6d9c7a769ac1aaa22a7d037ba3be27a05 Mon Sep 17 00:00:00 2001 From: Rafi Zadanly Date: Tue, 16 Jan 2024 16:08:43 +0700 Subject: Update improve product detail performance --- package.json | 1 + src-migrate/components/ui/image.tsx | 2 +- src-migrate/libs/whatsappUrl.ts | 4 +- .../product-detail/components/AddToWishlist.tsx | 2 +- .../product-detail/components/Breadcrumb.tsx | 40 +++++++ .../modules/product-detail/components/Image.tsx | 12 +- .../product-detail/components/Information.tsx | 34 +----- .../product-detail/components/PriceAction.tsx | 10 +- .../product-detail/components/ProductDetail.tsx | 68 +++++++---- .../product-detail/components/VariantList.tsx | 10 +- src-migrate/pages/shop/product/[slug].tsx | 13 ++- src-migrate/pages/shop/product/product.module.css | 0 src-migrate/services/product.ts | 8 ++ src-migrate/types/auth.ts | 4 +- src-migrate/types/category.ts | 4 + src-migrate/types/odoo.ts | 5 +- .../components/elements/Navbar/NavbarDesktop.jsx | 124 ++++++++++++--------- .../components/elements/Navbar/NavbarMobile.jsx | 69 +++++++----- .../elements/Skeleton/TopBannerSkeleton.jsx | 24 ++-- src/fonts/Inter/inter.css | 85 -------------- src/pages/_app.jsx | 15 ++- src/pages/_document.jsx | 31 ++++-- 22 files changed, 288 insertions(+), 277 deletions(-) create mode 100644 src-migrate/modules/product-detail/components/Breadcrumb.tsx delete mode 100644 src-migrate/pages/shop/product/product.module.css create mode 100644 src-migrate/types/category.ts diff --git a/package.json b/package.json index 9b9a002a..7f855b5f 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "react-loading-skeleton": "^3.3.1", "react-query": "^3.39.3", "react-select": "^5.7.0", + "react-web-share": "^2.0.2", "snakecase-keys": "^5.5.0", "swiper": "^8.4.4", "tw-merge": "^0.0.1-alpha.3", diff --git a/src-migrate/components/ui/image.tsx b/src-migrate/components/ui/image.tsx index a91b2a9d..c7ed0b0e 100644 --- a/src-migrate/components/ui/image.tsx +++ b/src-migrate/components/ui/image.tsx @@ -26,7 +26,7 @@ const Image = (props: ImageProps) => { > { return ( + + + + + + + - @@ -117,9 +147,7 @@ const ProductDetail = ({ product }: Props) => {
- - -
+
Produk Serupa @@ -131,7 +159,7 @@ const ProductDetail = ({ product }: Props) => {
)} -
+
Kamu Mungkin Juga Suka
diff --git a/src-migrate/modules/product-detail/components/VariantList.tsx b/src-migrate/modules/product-detail/components/VariantList.tsx index 96b7486b..e8c18921 100644 --- a/src-migrate/modules/product-detail/components/VariantList.tsx +++ b/src-migrate/modules/product-detail/components/VariantList.tsx @@ -22,10 +22,10 @@ const VariantList = ({ variants }: Props) => {
Part Number
Variant
-
Stock
+
Stock
Masa Persiapan
Berat
-
Harga
+
Harga
{variants.map((variant) => ( @@ -49,8 +49,8 @@ const Row = ({ variant }: { variant: IProductVariantDetail }) => { return (
{variant.code}
-
{variant.attributes.join(', ')}
-
+
{variant.attributes.join(', ') || '-'}
+
{sla?.qty !== undefined && ( <> @@ -68,7 +68,7 @@ const Row = ({ variant }: { variant: IProductVariantDetail }) => {
{variant.weight > 0 ? `${variant.weight} Kg` : '-'}
-
+
{variant.price.price > 0 && `Rp ${formatCurrency(variant.price.price)}`} {variant.price.price === 0 && '-'}
diff --git a/src-migrate/pages/shop/product/[slug].tsx b/src-migrate/pages/shop/product/[slug].tsx index 883532ed..733e10d9 100644 --- a/src-migrate/pages/shop/product/[slug].tsx +++ b/src-migrate/pages/shop/product/[slug].tsx @@ -1,5 +1,3 @@ -import style from './product.module.css' - import { GetServerSideProps, NextPage } from 'next' import React from 'react' import dynamic from 'next/dynamic' @@ -10,6 +8,7 @@ import { getIdFromSlug } from '~/libs/slug' import { IProductDetail } from '~/types/product' import { Seo } from '~/components/seo' +import { useRouter } from 'next/router' const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout'), { ssr: false }) const ProductDetail = dynamic(() => import('~/modules/product-detail'), { ssr: false }) @@ -36,14 +35,18 @@ export const getServerSideProps: GetServerSideProps = (async (context } }) +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST + const ProductDetailPage: NextPage = ({ product }) => { + const router = useRouter(); + return ( = ({ product }) => { content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`, } ]} - // canonical='' + canonical={SELF_HOST + router.asPath} /> -
+
diff --git a/src-migrate/pages/shop/product/product.module.css b/src-migrate/pages/shop/product/product.module.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src-migrate/services/product.ts b/src-migrate/services/product.ts index c9e93396..4ef027e1 100644 --- a/src-migrate/services/product.ts +++ b/src-migrate/services/product.ts @@ -1,5 +1,7 @@ import { IProduct, IProductDetail } from '~/types/product'; import snakeCase from 'snakecase-keys'; +import odooApi from '~/libs/odooApi'; +import { ICategoryBreadcrumb } from '~/types/category'; const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; @@ -57,3 +59,9 @@ export const getProductSimilar = async ({ .then((res) => res.json()) .then((res) => snakeCase(res.response)); }; + +export const getProductCategoryBreadcrumb = async ( + id: number +): Promise => { + return await odooApi('GET', `/api/v1/product/${id}/category-breadcrumb`); +}; diff --git a/src-migrate/types/auth.ts b/src-migrate/types/auth.ts index 4f69bb96..02e3623d 100644 --- a/src-migrate/types/auth.ts +++ b/src-migrate/types/auth.ts @@ -1,5 +1,5 @@ import { registerSchema } from '~/validations/auth'; -import { OdooApiProps } from './odoo'; +import { OdooApiRes } from './odoo'; import { z } from 'zod'; export type AuthProps = { @@ -17,7 +17,7 @@ export type AuthProps = { token: string; }; -export type AuthApiProps = OdooApiProps & { result: AuthProps }; +export type AuthApiProps = OdooApiRes; export type RegisterProps = z.infer; diff --git a/src-migrate/types/category.ts b/src-migrate/types/category.ts new file mode 100644 index 00000000..1037b5f9 --- /dev/null +++ b/src-migrate/types/category.ts @@ -0,0 +1,4 @@ +export interface ICategoryBreadcrumb { + id: number; + name: string; +} diff --git a/src-migrate/types/odoo.ts b/src-migrate/types/odoo.ts index b34bc667..73a029e9 100644 --- a/src-migrate/types/odoo.ts +++ b/src-migrate/types/odoo.ts @@ -1,6 +1,7 @@ -export type OdooApiProps = { +export interface OdooApiRes { status: { code: number; description: string; }; -}; + result: T; +} 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: () => -}) + 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 ( @@ -93,7 +91,12 @@ const NavbarDesktop = () => { - ) -} + ); +}; -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 ( - + {/* */} {Sidebar} - ) -} + ); +}; -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 ( - - ) -} + return ; +}; -export { TopBannerSkeleton } +export { TopBannerSkeleton }; 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,54 +1,3 @@ -@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; @@ -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/pages/_app.jsx b/src/pages/_app.jsx index aa545863..9067fd03 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -1,6 +1,6 @@ import '@/fonts/Inter/inter.css'; import '@/styles/globals.css'; -import 'react-loading-skeleton/dist/skeleton.css'; +// import 'react-loading-skeleton/dist/skeleton.css'; import { useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; @@ -9,8 +9,6 @@ import { AnimatePresence, motion } from 'framer-motion'; import { QueryClient, QueryClientProvider } from 'react-query'; import useDevice from '@/core/hooks/useDevice'; -import { ProductProvider } from '@/contexts/ProductContext'; -import { ProductCartProvider } from '@/contexts/ProductCartContext'; import theme from '../../chakra.theme'; const NextProgress = dynamic(() => import('next-progress'), { ssr: false }); @@ -18,6 +16,17 @@ 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 } diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx index 3762c63b..8eb3f15e 100644 --- a/src/pages/_document.jsx +++ b/src/pages/_document.jsx @@ -1,16 +1,23 @@ -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 ( + + + + - + @@ -20,7 +27,11 @@ export default function MyDocument() { - + +