diff options
| author | Rafi Zadanly <zadanlyr@gmail.com> | 2024-01-16 16:08:43 +0700 |
|---|---|---|
| committer | Rafi Zadanly <zadanlyr@gmail.com> | 2024-01-16 16:08:43 +0700 |
| commit | a70fd5b6d9c7a769ac1aaa22a7d037ba3be27a05 (patch) | |
| tree | 825d6b5de089bb22003bb2a517d371dc291f1962 | |
| parent | d9dafa74857959974e9d379dc1a3abfbaf2af83d (diff) | |
Update improve product detail performance
22 files changed, 288 insertions, 277 deletions
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) => { > <NextImage className={clsxm( - 'duration-700 ease-in-out', + 'duration-500 ease-in-out', isLoading ? 'scale-[1.02] blur-xl grayscale' : 'scale-100 blur-0 grayscale-0', diff --git a/src-migrate/libs/whatsappUrl.ts b/src-migrate/libs/whatsappUrl.ts index 02c36cef..66879585 100644 --- a/src-migrate/libs/whatsappUrl.ts +++ b/src-migrate/libs/whatsappUrl.ts @@ -26,7 +26,9 @@ export const whatsappUrl = ({ let greetingText = ''; if (needLogin && !auth) { - return fallbackUrl ? `/login=next?${fallbackUrl}` : '/login'; + return fallbackUrl + ? `/login?next=${encodeURIComponent(fallbackUrl)}` + : '/login'; } let result = TEMPLATES[template].replace( diff --git a/src-migrate/modules/product-detail/components/AddToWishlist.tsx b/src-migrate/modules/product-detail/components/AddToWishlist.tsx index eab3c7be..fccbdcf5 100644 --- a/src-migrate/modules/product-detail/components/AddToWishlist.tsx +++ b/src-migrate/modules/product-detail/components/AddToWishlist.tsx @@ -6,7 +6,7 @@ const AddToWishlist = () => { return ( <Button variant='link' - className='!text-gray-500 !font-medium' + colorScheme='gray' leftIcon={<HeartIcon size={18} />} > Wishlist diff --git a/src-migrate/modules/product-detail/components/Breadcrumb.tsx b/src-migrate/modules/product-detail/components/Breadcrumb.tsx new file mode 100644 index 00000000..0ee5b115 --- /dev/null +++ b/src-migrate/modules/product-detail/components/Breadcrumb.tsx @@ -0,0 +1,40 @@ +import React, { Fragment } from 'react' +import { useQuery } from 'react-query' +import { getProductCategoryBreadcrumb } from '~/services/product' +import Link from 'next/link' +import { createSlug } from '~/libs/slug' + +type Props = { + id: number, + name: string +} + +const Breadcrumb = ({ id, name }: Props) => { + const query = useQuery({ + queryKey: ['product-category-breadcrumb'], + queryFn: () => getProductCategoryBreadcrumb(id) + }) + + const breadcrumbs = query.data || [] + + return ( + <div className='line-clamp-3 md:line-clamp-1 leading-7'> + <Link href='/' className='text-danger-500'>Home</Link> + <span className='mx-2'>/</span> + {breadcrumbs.map((category, index) => ( + <Fragment key={index}> + <Link + href={createSlug('/shop/category/', category.name, category.id.toString())} + className='text-danger-500' + > + {category.name} + </Link> + <span className='mx-2'>/</span> + </Fragment> + ))} + <span>{name}</span> + </div> + ) +} + +export default Breadcrumb
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/Image.tsx b/src-migrate/modules/product-detail/components/Image.tsx index 361580ea..2c5e989b 100644 --- a/src-migrate/modules/product-detail/components/Image.tsx +++ b/src-migrate/modules/product-detail/components/Image.tsx @@ -11,14 +11,16 @@ type Props = { const Image = ({ product }: Props) => { return ( - <div className='h-[340px] border border-gray-200 rounded-lg p-2 relative'> + <div className='h-[250px] md:h-[340px] flex items-center justify-center border border-gray-200 rounded-lg p-4 relative'> <ImageUI src={product.image || '/images/noimage.jpeg'} alt={product.name} - width={512} - height={512} - className='object-contain object-center h-full' - classNames={{ wrapper: 'h-full' }} + width={256} + height={256} + className='object-contain object-center h-full w-full' + classNames={{ wrapper: 'h-full w-full' }} + loading='eager' + priority /> <div className='absolute hidden md:block top-4 right-4'> <Tooltip diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index fd0e0b3c..52eb6b88 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -9,6 +9,7 @@ import { IProductDetail } from '~/types/product' import { IProductVariantSLA } from '~/types/productVariant' import { createSlug } from '~/libs/slug' import { getVariantSLA } from '~/services/productVariant' +import { formatToShortText } from '~/libs/formatNumber' const Skeleton = dynamic(() => import('@chakra-ui/react').then((mod) => mod.Skeleton)) @@ -31,10 +32,6 @@ const Information = ({ product }: Props) => { <div className={style['label']}>SKU Number</div> <div className={style['value']}>SKU-{product.id}</div> </div> - {/* <div className={style['row']}> - <div className={style['label']}>Part Number</div> - <div className={style['value']}>{product.code || '-'}</div> - </div> */} <div className={style['row']}> <div className={style['label']}>Manufacture</div> <div className={style['value']}> @@ -48,35 +45,10 @@ const Information = ({ product }: Props) => { ) : '-'} </div> </div> - {/* <div className={style['row']}> - <div className={style['label']}>Preparation Time</div> - <div className={style['value']}> - {product.variant_total > 1 && 'Lihat Variant'} - {product.variant_total === 1 && ( - <Skeleton isLoaded={querySLA.isSuccess} w={querySLA.isSuccess ? '100%' : '40px'} h='100%'> - {sla?.sla_date} - </Skeleton> - )} - </div> - </div> <div className={style['row']}> - <div className={style['label']}>Stock</div> - <div className={style['value']}> - {product.variant_total > 1 && 'Lihat Variant'} - {product.variant_total === 1 && ( - <Skeleton isLoaded={querySLA.isSuccess} w={querySLA.isSuccess ? '100%' : '10px'} h='100%'> - {sla?.qty && sla.qty > 0 ? sla?.qty : '-'} - </Skeleton> - )} - </div> + <div className={style['label']}>Terjual</div> + <div className={style['value']}>{product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}</div> </div> - <div className={style['row']}> - <div className={style['label']}>Weight</div> - <div className={style['value']}> - {product.variant_total > 1 && 'Lihat Variant'} - {product.variant_total === 1 && (product.weight > 0 ? `${product.weight} kg` : '-')} - </div> - </div> */} </div> ) } diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index cfb596fa..dd211f6f 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -28,13 +28,6 @@ const PriceAction = ({ product }: Props) => { return ( <div className='block md:sticky top-[150px] bg-white py-0 md:py-6 z-10'> - {product.qty_sold > 0 && ( - <div className={style['secondary-text']}> - {formatToShortText(product.qty_sold)} Terjual - </div> - )} - <div className='h-2' /> - {!!activePrice && activePrice.price > 0 && ( <> <div className={style['main-price']}> @@ -60,7 +53,8 @@ const PriceAction = ({ product }: Props) => { <div className='h-4' /> <div className={style['action-wrapper']}> - <input type='number' value={quantityInput} onChange={(e) => setQuantityInput(e.target.value)} className={style['quantity-input']} /> + <label htmlFor="quantity" className='hidden'>Quantity</label> + <input type='number' id='quantity' value={quantityInput} onChange={(e) => setQuantityInput(e.target.value)} className={style['quantity-input']} /> <AddToCart variantId={activeVariantId} quantity={Number(quantityInput)} /> <AddToCart source='buy' variantId={activeVariantId} quantity={Number(quantityInput)} /> </div> diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 08ad7d51..93fa7118 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -2,28 +2,34 @@ import style from '../styles/product-detail.module.css' import React, { useEffect } from 'react' import Link from 'next/link' -import { MessageCircleIcon } from 'lucide-react' +import { useRouter } from 'next/router' + +import { MessageCircleIcon, Share2Icon } from 'lucide-react' import { Button } from '@chakra-ui/react' import { IProductDetail } from '~/types/product' +import useDevice from '@/core/hooks/useDevice' +import { whatsappUrl } from '~/libs/whatsappUrl' +import { useProductDetail } from '../stores/useProductDetail' + +import { RWebShare } from 'react-web-share' import ProductImage from './Image' import Information from './Information' import AddToWishlist from './AddToWishlist' import VariantList from './VariantList' import SimilarSide from './SimilarSide' import SimilarBottom from './SimilarBottom' -import useDevice from '@/core/hooks/useDevice' import PriceAction from './PriceAction' -import { whatsappUrl } from '~/libs/whatsappUrl' -import { useRouter } from 'next/router' -import { useProductDetail } from '../stores/useProductDetail' import ProductPromoSection from '~/modules/product-promo/components/Section' +import Breadcrumb from './Breadcrumb' type Props = { product: IProductDetail } +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST + const ProductDetail = ({ product }: Props) => { const { isDesktop, isMobile } = useDevice() const router = useRouter() @@ -46,6 +52,9 @@ const ProductDetail = ({ product }: Props) => { return ( <> <div className='md:flex md:flex-wrap'> + <div className="w-full mb-4 md:mb-6 px-4 md:px-0"> + <Breadcrumb id={product.id} name={product.name} /> + </div> <div className='md:w-9/12 md:flex md:flex-col md:pr-4'> <div className='md:flex md:flex-wrap'> <div className="md:w-4/12"> @@ -63,18 +72,39 @@ const ProductDetail = ({ product }: Props) => { <Information product={product} /> - <div className='h-4' /> + <div className='h-6' /> + + <div className="flex gap-x-5"> + <Button + as={Link} + href={askAdminUrl} + variant='link' + target='_blank' + colorScheme='gray' + leftIcon={<MessageCircleIcon size={18} />} + > + Ask Admin + </Button> + + <AddToWishlist /> + + <RWebShare + data={{ + text: 'Check out this product', + title: `${product.name} - Indoteknik.com`, + url: SELF_HOST + router.asPath + }} + > + <Button + variant='link' + colorScheme='gray' + leftIcon={<Share2Icon size={18} />} + > + Share + </Button> + </RWebShare> + </div> - <Button - as={Link} - href={askAdminUrl} - variant='link' - target='_blank' - colorScheme='red' - leftIcon={<MessageCircleIcon size={18} />} - > - Ask Admin - </Button> </div> </div> @@ -117,9 +147,7 @@ const ProductDetail = ({ product }: Props) => { <div className="md:w-3/12"> <PriceAction product={product} /> - <AddToWishlist /> - - <div className='h-8' /> + <div className='h-6' /> <div className={style['heading']}> Produk Serupa @@ -131,7 +159,7 @@ const ProductDetail = ({ product }: Props) => { </div> )} - <div className='md:w-full py-0 md:py-10 px-4 md:px-0'> + <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> <div className={style['heading']}> Kamu Mungkin Juga Suka </div> 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) => { <div className={style['header']}> <div className="w-2/12 sticky left-0 bg-gray-200">Part Number</div> <div className="w-2/12">Variant</div> - <div className="w-2/12">Stock</div> + <div className="w-1/12">Stock</div> <div className="w-2/12">Masa Persiapan</div> <div className="w-1/12">Berat</div> - <div className="w-2/12">Harga</div> + <div className="w-3/12">Harga</div> </div> {variants.map((variant) => ( <LazyLoadComponent key={variant.id}> @@ -49,8 +49,8 @@ const Row = ({ variant }: { variant: IProductVariantDetail }) => { return ( <div className={style['row']}> <div className='w-2/12 sticky left-0 bg-white md:bg-transparent'>{variant.code}</div> - <div className='w-2/12'>{variant.attributes.join(', ')}</div> - <div className='w-2/12'> + <div className='w-2/12'>{variant.attributes.join(', ') || '-'}</div> + <div className='w-1/12'> <Skeleton isLoaded={querySLA.isSuccess} h='21px' w={16}> {sla?.qty !== undefined && ( <> @@ -68,7 +68,7 @@ const Row = ({ variant }: { variant: IProductVariantDetail }) => { <div className='w-1/12'> {variant.weight > 0 ? `${variant.weight} Kg` : '-'} </div> - <div className='w-2/12'> + <div className='w-3/12'> {variant.price.price > 0 && `Rp ${formatCurrency(variant.price.price)}`} {variant.price.price === 0 && '-'} </div> 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<PageProps> = (async (context } }) +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST + const ProductDetailPage: NextPage<PageProps> = ({ product }) => { + const router = useRouter(); + 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, + url: SELF_HOST + router.asPath, images: [ { url: product?.image, @@ -60,10 +63,10 @@ const ProductDetailPage: NextPage<PageProps> = ({ product }) => { content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`, } ]} - // canonical='' + canonical={SELF_HOST + router.asPath} /> - <div className='md:container pt-10'> + <div className='md:container pt-4 md:pt-6'> <ProductDetail product={product} /> </div> </BasicLayout> 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 --- a/src-migrate/pages/shop/product/product.module.css +++ /dev/null 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<ICategoryBreadcrumb[]> => { + 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<AuthProps>; export type RegisterProps = z.infer<typeof registerSchema>; 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<T> { 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: () => <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/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/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 ( <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='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' /> @@ -20,7 +27,11 @@ export default function MyDocument() { <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' @@ -36,7 +47,7 @@ export default function MyDocument() { function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-10501937-1'); - ` + `, }} /> @@ -55,7 +66,7 @@ export default function MyDocument() { function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-G1W8MNZ11P'); - ` + `, }} /> @@ -68,7 +79,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'); - ` + `, }} /> @@ -87,7 +98,7 @@ export default function MyDocument() { function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); - gtag('config', 'AW-954540379');` + gtag('config', 'AW-954540379');`, }} /> @@ -119,5 +130,5 @@ export default function MyDocument() { <NextScript /> </body> </Html> - ) + ); } |
