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-migrate | |
| parent | 065396828266e2de42cb0182c81ea2d7a5b00e2b (diff) | |
| parent | 91086d8b1af2e1c0ca9db38d037f6331c9e6131a (diff) | |
Merged in Feature/perf/product-detail (pull request #127)
Feature/perf/product detail
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/common/components/skeleton/PageContentSkeleton.tsx | 19 | ||||
| -rw-r--r-- | src-migrate/common/libs/parse | 0 | ||||
| -rw-r--r-- | src-migrate/common/types/productVariant.ts | 13 | ||||
| -rw-r--r-- | src-migrate/components/seo.tsx (renamed from src-migrate/common/components/elements/Seo.tsx) | 6 | ||||
| -rw-r--r-- | src-migrate/components/ui/image.tsx | 33 | ||||
| -rw-r--r-- | src-migrate/components/ui/modal.tsx (renamed from src-migrate/common/components/elements/Modal.tsx) | 19 | ||||
| -rw-r--r-- | src-migrate/components/ui/re-captcha.tsx (renamed from src-migrate/common/components/elements/ReCaptcha.tsx) | 6 | ||||
| -rw-r--r-- | src-migrate/constants/menu.ts (renamed from src-migrate/common/constants/menu.ts) | 14 | ||||
| -rw-r--r-- | src-migrate/libs/auth.ts (renamed from src-migrate/common/libs/auth.ts) | 2 | ||||
| -rw-r--r-- | src-migrate/libs/clsxm.ts (renamed from src-migrate/common/libs/clsxm.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/libs/formatCurrency.ts (renamed from src-migrate/common/libs/formatCurrency.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/libs/formatNumber.ts | 8 | ||||
| -rw-r--r-- | src-migrate/libs/odooApi.ts (renamed from src-migrate/common/libs/odooApi.ts) | 2 | ||||
| -rw-r--r-- | src-migrate/libs/slug.ts | 34 | ||||
| -rw-r--r-- | src-migrate/libs/toTitleCase.ts | 5 | ||||
| -rw-r--r-- | src-migrate/libs/whatsappUrl.ts | 48 | ||||
| -rw-r--r-- | src-migrate/modules/account-activation/components/FormEmail.tsx | 6 | ||||
| -rw-r--r-- | src-migrate/modules/account-activation/components/FormOTP.tsx | 6 | ||||
| -rw-r--r-- | src-migrate/modules/account-activation/components/FormToken.tsx | 6 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/Detail.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/Item.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/ItemAction.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/ItemPromo.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/ItemSelect.tsx | 7 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/Summary.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/cart/stores/useCartStore.ts | 2 | ||||
| -rw-r--r-- | src-migrate/modules/cart/styles/detail.module.css | 3 | ||||
| -rw-r--r-- | src-migrate/modules/cart/styles/item-promo.module.css | 2 | ||||
| -rw-r--r-- | src-migrate/modules/header/components/HeaderDesktop.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/modules/page-content/index.tsx | 19 | ||||
| -rw-r--r-- | src-migrate/modules/popup-information/index.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/product-card/components/ProductCard.tsx | 106 | ||||
| -rw-r--r-- | src-migrate/modules/product-card/index.tsx | 3 | ||||
| -rw-r--r-- | src-migrate/modules/product-card/styles/product-card.module.css | 54 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/AddToCart.tsx | 79 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/AddToWishlist.tsx | 61 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/Breadcrumb.tsx | 41 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/Image.tsx | 99 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/Information.tsx | 56 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/PriceAction.tsx | 76 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductDetail.tsx | 178 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/SimilarBottom.tsx | 21 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/SimilarSide.tsx | 34 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/VariantList.tsx | 117 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/index.ts | 3 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/stores/useProductDetail.ts | 31 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/image.module.css | 35 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/information.module.css | 19 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/price-action.module.css | 24 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/product-detail.module.css | 15 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/side-similar.module.css | 3 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/variant-list.module.css | 35 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/AddToCart.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/Card.tsx | 8 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/CardCountdown.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/CategoryTab.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/Item.tsx | 6 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/Modal.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/ModalContent.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/components/Section.tsx | 11 | ||||
| -rw-r--r-- | src-migrate/modules/product-promo/stores/useModalStore.ts | 2 | ||||
| -rw-r--r-- | src-migrate/modules/product-similar/hooks/useProductSimilar.tsx | 15 | ||||
| -rw-r--r-- | src-migrate/modules/product-slider/components/ProductSlider.tsx | 42 | ||||
| -rw-r--r-- | src-migrate/modules/product-slider/index.ts | 3 | ||||
| -rw-r--r-- | src-migrate/modules/register/components/Form.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/register/components/FormCaptcha.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/register/components/TermCondition.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/register/stores/useRegisterStore.ts (renamed from src-migrate/common/stores/useRegisterStore.ts) | 4 | ||||
| -rw-r--r-- | src-migrate/pages/_app.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/pages/api/product-variant/[id].tsx | 2 | ||||
| -rw-r--r-- | src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx | 2 | ||||
| -rw-r--r-- | src-migrate/pages/api/product-variant/[id]/promotion/highlight.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/pages/api/promotion-program/[id].tsx | 3 | ||||
| -rw-r--r-- | src-migrate/pages/register.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/pages/shop/cart/cart.module.css | 31 | ||||
| -rw-r--r-- | src-migrate/pages/shop/cart/index.tsx | 93 | ||||
| -rw-r--r-- | src-migrate/pages/shop/product/[slug].tsx | 76 | ||||
| -rw-r--r-- | src-migrate/services/auth.ts | 4 | ||||
| -rw-r--r-- | src-migrate/services/cart.ts | 2 | ||||
| -rw-r--r-- | src-migrate/services/checkout.ts | 2 | ||||
| -rw-r--r-- | src-migrate/services/pageContent.ts | 2 | ||||
| -rw-r--r-- | src-migrate/services/product.ts | 66 | ||||
| -rw-r--r-- | src-migrate/services/productVariant.ts (renamed from src-migrate/services/variant.ts) | 11 | ||||
| -rw-r--r-- | src-migrate/services/promotionProgram.ts | 2 | ||||
| -rw-r--r-- | src-migrate/services/wishlist.ts | 23 | ||||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Black.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Black.woff) | bin | 138764 -> 138764 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Black.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Black.woff2) | bin | 102868 -> 102868 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-BlackItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff) | bin | 146824 -> 146824 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-BlackItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff2) | bin | 108752 -> 108752 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Bold.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Bold.woff) | bin | 143208 -> 143208 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Bold.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Bold.woff2) | bin | 106140 -> 106140 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-BoldItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff) | bin | 151052 -> 151052 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-BoldItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff2) | bin | 111808 -> 111808 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraBold.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff) | bin | 142920 -> 142920 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraBold.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff2) | bin | 106108 -> 106108 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraBoldItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff) | bin | 150628 -> 150628 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2) | bin | 111708 -> 111708 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraLight.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff) | bin | 140724 -> 140724 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraLight.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff2) | bin | 104232 -> 104232 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraLightItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff) | bin | 149996 -> 149996 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff2) | bin | 111392 -> 111392 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Italic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Italic.woff) | bin | 144372 -> 144372 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Italic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Italic.woff2) | bin | 106876 -> 106876 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Light.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Light.woff) | bin | 140632 -> 140632 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Light.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Light.woff2) | bin | 104332 -> 104332 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-LightItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff) | bin | 150092 -> 150092 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-LightItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff2) | bin | 111332 -> 111332 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Medium.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Medium.woff) | bin | 142552 -> 142552 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Medium.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Medium.woff2) | bin | 105924 -> 105924 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-MediumItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff) | bin | 150988 -> 150988 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-MediumItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff2) | bin | 112184 -> 112184 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Regular.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Regular.woff) | bin | 133844 -> 133844 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Regular.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Regular.woff2) | bin | 98868 -> 98868 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-SemiBold.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff) | bin | 142932 -> 142932 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-SemiBold.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff2) | bin | 105804 -> 105804 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-SemiBoldItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff) | bin | 151180 -> 151180 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff2) | bin | 112048 -> 112048 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Thin.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-Thin.woff) | bin | 135920 -> 135920 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-Thin.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-Thin.woff2) | bin | 99632 -> 99632 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ThinItalic.woff (renamed from src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff) | bin | 145480 -> 145480 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-ThinItalic.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff2) | bin | 106496 -> 106496 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-italic.var.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-italic.var.woff2) | bin | 245036 -> 245036 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter-roman.var.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter-roman.var.woff2) | bin | 227180 -> 227180 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/Inter.var.woff2 (renamed from src-migrate/common/styles/fonts/Inter/Inter.var.woff2) | bin | 324864 -> 324864 bytes | |||
| -rw-r--r-- | src-migrate/styles/fonts/Inter/inter.css (renamed from src-migrate/common/styles/fonts/Inter/inter.css) | 0 | ||||
| -rw-r--r-- | src-migrate/styles/globals.css (renamed from src-migrate/common/styles/globals.css) | 0 | ||||
| -rw-r--r-- | src-migrate/types/auth.ts (renamed from src-migrate/common/types/auth.ts) | 12 | ||||
| -rw-r--r-- | src-migrate/types/cart.ts (renamed from src-migrate/common/types/cart.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/types/category.ts | 4 | ||||
| -rw-r--r-- | src-migrate/types/checkout.ts (renamed from src-migrate/common/types/checkout.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/types/nav.ts (renamed from src-migrate/common/types/nav.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/types/odoo.ts (renamed from src-migrate/common/types/odoo.ts) | 5 | ||||
| -rw-r--r-- | src-migrate/types/pageContent.ts (renamed from src-migrate/common/types/pageContent.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/types/product.ts | 36 | ||||
| -rw-r--r-- | src-migrate/types/productVariant.ts | 33 | ||||
| -rw-r--r-- | src-migrate/types/promotion.ts (renamed from src-migrate/common/types/promotion.ts) | 15 | ||||
| -rw-r--r-- | src-migrate/types/promotionProgram.ts (renamed from src-migrate/common/types/promotionProgram.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/types/solr.ts (renamed from src-migrate/common/types/solr.ts) | 0 | ||||
| -rw-r--r-- | src-migrate/validations/auth.ts (renamed from src-migrate/common/validations/auth.ts) | 0 |
139 files changed, 1781 insertions, 142 deletions
diff --git a/src-migrate/common/components/skeleton/PageContentSkeleton.tsx b/src-migrate/common/components/skeleton/PageContentSkeleton.tsx deleted file mode 100644 index bf85cff1..00000000 --- a/src-migrate/common/components/skeleton/PageContentSkeleton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -const PageContentSkeleton = () => { - return ( - <div className="animate-pulse grid gap-y-4"> - <div className="w-full h-10 bg-gray-300 rounded" /> - <div className="h-2" /> - <div className="w-full h-4 bg-gray-300 rounded" /> - <div className="w-full h-4 bg-gray-300 rounded" /> - <div className="w-full h-4 bg-gray-300 rounded" /> - <div className="w-8/12 h-4 bg-gray-300 rounded" /> - <div className="h-2" /> - <div className="w-full h-4 bg-gray-300 rounded" /> - <div className="w-full h-4 bg-gray-300 rounded" /> - <div className="w-full h-4 bg-gray-300 rounded" /> - <div className="w-1/2 h-4 bg-gray-300 rounded" /> - </div> - ) -} - -export default PageContentSkeleton
\ No newline at end of file diff --git a/src-migrate/common/libs/parse b/src-migrate/common/libs/parse deleted file mode 100644 index e69de29b..00000000 --- a/src-migrate/common/libs/parse +++ /dev/null diff --git a/src-migrate/common/types/productVariant.ts b/src-migrate/common/types/productVariant.ts deleted file mode 100644 index c4aa9534..00000000 --- a/src-migrate/common/types/productVariant.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface IProductVariant { - id: number; - parent_id: number; - display_name: string; - image: string; - name: string; - default_code: string; - price: { - price: number; - discount_percentage: number; - price_discount: number; - }; -} diff --git a/src-migrate/common/components/elements/Seo.tsx b/src-migrate/components/seo.tsx index 2245663a..1e78ed4d 100644 --- a/src-migrate/common/components/elements/Seo.tsx +++ b/src-migrate/components/seo.tsx @@ -3,7 +3,7 @@ import React from 'react' import { NextSeo } from "next-seo" import { MetaTag, NextSeoProps } from 'next-seo/lib/types'; -const Seo = (props: NextSeoProps) => { +export const Seo = (props: NextSeoProps) => { const router = useRouter() const additionalMetaTags: MetaTag[] = [ @@ -29,6 +29,4 @@ const Seo = (props: NextSeoProps) => { additionalMetaTags={additionalMetaTags} /> ) -} - -export default Seo
\ No newline at end of file +}
\ No newline at end of file diff --git a/src-migrate/components/ui/image.tsx b/src-migrate/components/ui/image.tsx new file mode 100644 index 00000000..c1dde170 --- /dev/null +++ b/src-migrate/components/ui/image.tsx @@ -0,0 +1,33 @@ +import NextImage, { ImageProps as NextImageProps } from 'next/image'; +import { useState } from 'react'; + +import clsxm from '~/libs/clsxm'; + +type ImageProps = { + rounded?: string; +} & NextImageProps; + +const Image = (props: ImageProps) => { + const { alt, src, className, rounded, ...rest } = props; + const [isLoading, setLoading] = useState(true); + + return ( + <NextImage + className={clsxm( + 'duration-500 ease-in-out', + isLoading + ? 'scale-[1.02] blur-xl grayscale' + : 'scale-100 blur-0 grayscale-0', + rounded, + className + )} + src={src} + alt={alt} + loading='lazy' + quality={100} + onLoadingComplete={() => setLoading(false)} + {...rest} + /> + ); +}; +export default Image;
\ No newline at end of file diff --git a/src-migrate/common/components/elements/Modal.tsx b/src-migrate/components/ui/modal.tsx index c9c621e0..34e1d1c3 100644 --- a/src-migrate/common/components/elements/Modal.tsx +++ b/src-migrate/components/ui/modal.tsx @@ -1,13 +1,12 @@ -import { XMarkIcon } from "@heroicons/react/24/outline"; -import { AnimatePresence, motion } from "framer-motion" -import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import ReactDOM from "react-dom"; +import { useRouter } from "next/router"; +import { AnimatePresence, motion } from "framer-motion" import { useWindowSize } from "usehooks-ts"; -import clsxm from "~/common/libs/clsxm"; - +import { XMarkIcon } from "@heroicons/react/24/outline"; +import clsxm from "~/libs/clsxm"; -type Props = { +export interface ModalProps { children: React.ReactNode active: boolean title?: string @@ -16,14 +15,14 @@ type Props = { mode?: "mobile" | "desktop" } -const Modal = ({ +export const Modal = ({ children, active = false, title, close, className, mode -}: Props) => { +}: ModalProps) => { const router = useRouter() const { width } = useWindowSize() const [rendered, setRendered] = useState<boolean>(false) @@ -85,6 +84,4 @@ const Modal = ({ </AnimatePresence>, document.querySelector('body')! ) -} - -export default Modal
\ No newline at end of file +}
\ No newline at end of file diff --git a/src-migrate/common/components/elements/ReCaptcha.tsx b/src-migrate/components/ui/re-captcha.tsx index 1bc31d90..e31aa1e3 100644 --- a/src-migrate/common/components/elements/ReCaptcha.tsx +++ b/src-migrate/components/ui/re-captcha.tsx @@ -2,16 +2,14 @@ import ReCAPTCHA, { ReCAPTCHAProps } from "react-google-recaptcha" const GOOGLE_RECAPTCHA_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE || '' -type Props = Omit<ReCAPTCHAProps, 'sitekey'> & { +export interface ReCaptchaProps extends Omit<ReCAPTCHAProps, 'sitekey'> { sitekey?: string; } -const ReCaptcha = (props: Props) => { +export const ReCaptcha = (props: ReCaptchaProps) => { const { sitekey, ...rest } = props return ( <ReCAPTCHA sitekey={sitekey || GOOGLE_RECAPTCHA_KEY} {...rest} /> ) } - -export default ReCaptcha
\ No newline at end of file diff --git a/src-migrate/common/constants/menu.ts b/src-migrate/constants/menu.ts index 853da507..d1adebca 100644 --- a/src-migrate/common/constants/menu.ts +++ b/src-migrate/constants/menu.ts @@ -1,20 +1,20 @@ -import { SecondaryNavItemProps } from '../types/nav' +import { SecondaryNavItemProps } from '~/types/nav'; export const SECONDARY_MENU_ITEMS: SecondaryNavItemProps[] = [ { label: 'Semua Brand', - href: '/shop/brands' + href: '/shop/brands', }, { label: 'Ready Stock', - href: '/shop/search?orderBy=stock' + href: '/shop/search?orderBy=stock', }, { label: 'Blog Indoteknik', - href: 'https://blog.indoteknik.com/' + href: 'https://blog.indoteknik.com/', }, { label: 'Indoteknik TV', - href: '/video' - } -] + href: '/video', + }, +]; diff --git a/src-migrate/common/libs/auth.ts b/src-migrate/libs/auth.ts index fb4e836a..86ce26e1 100644 --- a/src-migrate/common/libs/auth.ts +++ b/src-migrate/libs/auth.ts @@ -1,5 +1,5 @@ import { deleteCookie, getCookie, setCookie } from 'cookies-next'; -import { AuthProps } from '../types/auth'; +import { AuthProps } from '~/types/auth'; const COOKIE_KEY = 'auth'; diff --git a/src-migrate/common/libs/clsxm.ts b/src-migrate/libs/clsxm.ts index 0fc10317..0fc10317 100644 --- a/src-migrate/common/libs/clsxm.ts +++ b/src-migrate/libs/clsxm.ts diff --git a/src-migrate/common/libs/formatCurrency.ts b/src-migrate/libs/formatCurrency.ts index 41db4a6f..41db4a6f 100644 --- a/src-migrate/common/libs/formatCurrency.ts +++ b/src-migrate/libs/formatCurrency.ts diff --git a/src-migrate/libs/formatNumber.ts b/src-migrate/libs/formatNumber.ts new file mode 100644 index 00000000..da243418 --- /dev/null +++ b/src-migrate/libs/formatNumber.ts @@ -0,0 +1,8 @@ +export const formatToShortText = (number: number) => { + if (number > 1000) { + return `${Math.floor(number / 1000)}rb+`; + } else if (number > 100) { + return `${Math.floor(number / 100) * 100}+`; + } + return number.toString(); +}; diff --git a/src-migrate/common/libs/odooApi.ts b/src-migrate/libs/odooApi.ts index 2dbc18d3..9482542b 100644 --- a/src-migrate/common/libs/odooApi.ts +++ b/src-migrate/libs/odooApi.ts @@ -1,7 +1,7 @@ import axios, { AxiosRequestConfig, Method } from 'axios'; import { getCookie, setCookie } from 'cookies-next'; import { getAuth } from './auth'; -import { AuthApiProps, AuthProps } from '../types/auth'; +import { AuthApiProps } from '~/types/auth'; const ODOO_HOST = process.env.NEXT_PUBLIC_ODOO_API_HOST as string; diff --git a/src-migrate/libs/slug.ts b/src-migrate/libs/slug.ts new file mode 100644 index 00000000..5ab3b3dd --- /dev/null +++ b/src-migrate/libs/slug.ts @@ -0,0 +1,34 @@ +import { toTitleCase } from './toTitleCase'; + +export const createSlug = ( + prefix: string, + name: string, + id: string, + withHost = false +) => { + const cleanName = name + .trim() + .replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-') + .toLowerCase(); + + let slug = `${cleanName}-${id}`; + const splitSlug = slug.split('-'); + const filterSlug = splitSlug.filter((x) => x !== ''); + + slug = `${prefix}${filterSlug.join('-')}`; + + if (withHost) slug = process.env.NEXT_PUBLIC_SELF_HOST + slug; + + return slug; +}; + +export const getIdFromSlug = (slug: string) => { + let id = slug.split('-'); + return id[id.length - 1]; +}; + +export const getNameFromSlug = (slug: string) => { + let name = slug.split('-'); + name.pop(); + return toTitleCase(name.join(' ')); +}; diff --git a/src-migrate/libs/toTitleCase.ts b/src-migrate/libs/toTitleCase.ts new file mode 100644 index 00000000..dad66813 --- /dev/null +++ b/src-migrate/libs/toTitleCase.ts @@ -0,0 +1,5 @@ +export const toTitleCase = (val: string) => { + return val.replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); +}; diff --git a/src-migrate/libs/whatsappUrl.ts b/src-migrate/libs/whatsappUrl.ts new file mode 100644 index 00000000..66879585 --- /dev/null +++ b/src-migrate/libs/whatsappUrl.ts @@ -0,0 +1,48 @@ +import { getAuth } from './auth'; + +const TEMPLATES = { + default: 'Bisa tolong bantu kebutuhan saya?', + product: + 'Saya mencari barang berikut:\n\n{{url}}\n\n```Brand: {{manufacture}}\nName: {{productName}}```', +}; + +interface WhatsappUrlProps { + template: keyof typeof TEMPLATES; + payload: any; + greeting?: boolean; + needLogin?: boolean; + fallbackUrl?: string; +} + +export const whatsappUrl = ({ + template, + payload, + greeting = true, + needLogin = true, + fallbackUrl, +}: WhatsappUrlProps) => { + const auth = getAuth(); + + let greetingText = ''; + + if (needLogin && !auth) { + return fallbackUrl + ? `/login?next=${encodeURIComponent(fallbackUrl)}` + : '/login'; + } + + let result = TEMPLATES[template].replace( + /{{(.*?)}}/g, + (match, key) => payload[key] || '' + ); + + if (greeting && typeof auth === 'object') { + greetingText = `Halo Indoteknik.com, Saya ${auth.name} `; + if (auth.parentName) greetingText += `dari ${auth.parentName}`; + greetingText += '.\n\n'; + + result = greetingText + result; + } + + return `https://wa.me/628128080622?text=${encodeURIComponent(result)}`; +}; diff --git a/src-migrate/modules/account-activation/components/FormEmail.tsx b/src-migrate/modules/account-activation/components/FormEmail.tsx index ec300ba4..f7925481 100644 --- a/src-migrate/modules/account-activation/components/FormEmail.tsx +++ b/src-migrate/modules/account-activation/components/FormEmail.tsx @@ -3,9 +3,9 @@ import Link from "next/link" import { useRouter } from "next/router" import { ChangeEvent, useEffect, useState } from "react" import { useMutation } from "react-query" -import Modal from "~/common/components/elements/Modal" -import { useRegisterStore } from "~/common/stores/useRegisterStore" -import { ActivationReqProps } from "~/common/types/auth" +import { Modal } from "~/components/ui/modal" +import { useRegisterStore } from "~/modules/register/stores/useRegisterStore" +import { ActivationReqProps } from "~/types/auth" import { activationReq } from "~/services/auth" const FormEmail = () => { diff --git a/src-migrate/modules/account-activation/components/FormOTP.tsx b/src-migrate/modules/account-activation/components/FormOTP.tsx index 6815a088..cf4da2db 100644 --- a/src-migrate/modules/account-activation/components/FormOTP.tsx +++ b/src-migrate/modules/account-activation/components/FormOTP.tsx @@ -3,9 +3,9 @@ import { useRouter } from "next/router" import { useEffect, useState } from "react" import { useMutation } from "react-query" import { useCountdown } from "usehooks-ts" -import Modal from '~/common/components/elements/Modal' -import { setAuth } from "~/common/libs/auth" -import { ActivationOtpProps, ActivationReqProps } from "~/common/types/auth" +import { Modal } from "~/components/ui/modal" +import { setAuth } from "~/libs/auth" +import { ActivationOtpProps, ActivationReqProps } from "~/types/auth" import { activationReq, activationUserOTP } from "~/services/auth" const FormOTP = () => { diff --git a/src-migrate/modules/account-activation/components/FormToken.tsx b/src-migrate/modules/account-activation/components/FormToken.tsx index b68b244f..2835ec0e 100644 --- a/src-migrate/modules/account-activation/components/FormToken.tsx +++ b/src-migrate/modules/account-activation/components/FormToken.tsx @@ -4,10 +4,10 @@ import { useEffect, useState } from "react" import Link from "next/link" import { useMutation } from "react-query" -import Modal from "~/common/components/elements/Modal" -import { ActivationTokenProps } from "~/common/types/auth" +import { Modal } from "~/components/ui/modal" +import { ActivationTokenProps } from "~/types/auth" import { activationUserToken } from "~/services/auth" -import { setAuth } from "~/common/libs/auth" +import { setAuth } from "~/libs/auth" const FormToken = () => { const router = useRouter() diff --git a/src-migrate/modules/cart/components/Detail.tsx b/src-migrate/modules/cart/components/Detail.tsx index 99fe4c91..b1532729 100644 --- a/src-migrate/modules/cart/components/Detail.tsx +++ b/src-migrate/modules/cart/components/Detail.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useMemo } from 'react' import Link from 'next/link' import { Button, Tooltip } from '@chakra-ui/react' -import { getAuth } from '~/common/libs/auth' +import { getAuth } from '~/libs/auth' import { useCartStore } from '../stores/useCartStore' import CartItem from './Item' diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx index baf48bb6..08823d19 100644 --- a/src-migrate/modules/cart/components/Item.tsx +++ b/src-migrate/modules/cart/components/Item.tsx @@ -7,8 +7,8 @@ import { InfoIcon } from 'lucide-react' import { PROMO_CATEGORY } from '~/constants/promotion' -import formatCurrency from '~/common/libs/formatCurrency' -import { CartItem as CartItemProps } from '~/common/types/cart' +import formatCurrency from '~/libs/formatCurrency' +import { CartItem as CartItemProps } from '~/types/cart' import CartItemPromo from './ItemPromo' import CartItemAction from './ItemAction' diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx index 3e264aef..859c758c 100644 --- a/src-migrate/modules/cart/components/ItemAction.tsx +++ b/src-migrate/modules/cart/components/ItemAction.tsx @@ -5,8 +5,8 @@ import React, { useEffect, useState } from 'react' import { Spinner, Tooltip } from '@chakra-ui/react' import { MinusIcon, PlusIcon, Trash2Icon } from 'lucide-react' -import { CartItem } from '~/common/types/cart' -import { getAuth } from '~/common/libs/auth' +import { CartItem } from '~/types/cart' +import { getAuth } from '~/libs/auth' import { deleteUserCart, upsertUserCart } from '~/services/cart' import { useDebounce } from 'usehooks-ts' diff --git a/src-migrate/modules/cart/components/ItemPromo.tsx b/src-migrate/modules/cart/components/ItemPromo.tsx index bb286e8b..bc507578 100644 --- a/src-migrate/modules/cart/components/ItemPromo.tsx +++ b/src-migrate/modules/cart/components/ItemPromo.tsx @@ -3,7 +3,7 @@ import style from '../styles/item-promo.module.css' import Image from 'next/image' import React from 'react' -import { CartProduct } from '~/common/types/cart' +import { CartProduct } from '~/types/cart' type Props = { product: CartProduct diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 96e7c713..1d8886a2 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -1,8 +1,8 @@ import { Checkbox, Spinner } from '@chakra-ui/react' import React, { useState } from 'react' -import { getAuth } from '~/common/libs/auth' -import { CartItem } from '~/common/types/cart' +import { getAuth } from '~/libs/auth' +import { CartItem } from '~/types/cart' import { upsertUserCart } from '~/services/cart' import { useCartStore } from '../stores/useCartStore' @@ -27,10 +27,11 @@ const CartItemSelect = ({ item }: Props) => { } return ( - <div className='w-5 my-auto'> + <div className='w-6 my-auto'> {isLoad && ( <Spinner className='my-auto' size='sm' /> )} + {!isLoad && ( <Checkbox borderColor='gray.600' diff --git a/src-migrate/modules/cart/components/Summary.tsx b/src-migrate/modules/cart/components/Summary.tsx index a835bca9..2e55c8df 100644 --- a/src-migrate/modules/cart/components/Summary.tsx +++ b/src-migrate/modules/cart/components/Summary.tsx @@ -1,8 +1,8 @@ import style from '../styles/summary.module.css' import React from 'react' -import formatCurrency from '~/common/libs/formatCurrency' -import clsxm from '~/common/libs/clsxm' +import formatCurrency from '~/libs/formatCurrency' +import clsxm from '~/libs/clsxm' import { Skeleton } from '@chakra-ui/react' import _ from 'lodash' diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts index 0643b8e6..3d9a0aed 100644 --- a/src-migrate/modules/cart/stores/useCartStore.ts +++ b/src-migrate/modules/cart/stores/useCartStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { CartProps } from '~/common/types/cart'; +import { CartProps } from '~/types/cart'; import { getUserCart } from '~/services/cart'; type State = { diff --git a/src-migrate/modules/cart/styles/detail.module.css b/src-migrate/modules/cart/styles/detail.module.css deleted file mode 100644 index 42d492bb..00000000 --- a/src-migrate/modules/cart/styles/detail.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.wrapper { - @apply flex flex-wrap; -} diff --git a/src-migrate/modules/cart/styles/item-promo.module.css b/src-migrate/modules/cart/styles/item-promo.module.css index 5bc192c0..15bf8146 100644 --- a/src-migrate/modules/cart/styles/item-promo.module.css +++ b/src-migrate/modules/cart/styles/item-promo.module.css @@ -1,5 +1,5 @@ .wrapper { - @apply md:ml-12 ml-8 mt-4 flex; + @apply md:ml-16 ml-8 mt-4 flex; } .imageWrapper { diff --git a/src-migrate/modules/header/components/HeaderDesktop.tsx b/src-migrate/modules/header/components/HeaderDesktop.tsx index 3860bded..8f5a8efa 100644 --- a/src-migrate/modules/header/components/HeaderDesktop.tsx +++ b/src-migrate/modules/header/components/HeaderDesktop.tsx @@ -8,7 +8,7 @@ import Link from 'next/link' import SearchBar from "./SearchBar"; // Constants -import { SECONDARY_MENU_ITEMS } from "~/common/constants/menu"; +import { SECONDARY_MENU_ITEMS } from "~/constants/menu"; const LOGO_WIDTH = 210; const LOGO_HEIGHT = LOGO_WIDTH / 3; diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx index 608079f8..547b1957 100644 --- a/src-migrate/modules/page-content/index.tsx +++ b/src-migrate/modules/page-content/index.tsx @@ -1,7 +1,6 @@ import { useMemo } from "react" import { useQuery } from "react-query" -import PageContentSkeleton from "~/common/components/skeleton/PageContentSkeleton" -import { PageContentProps } from "~/common/types/pageContent" +import { PageContentProps } from "~/types/pageContent" import { getPageContent } from "~/services/pageContent" type Props = { @@ -26,4 +25,20 @@ const PageContent = ({ path }: Props) => { ) } +const PageContentSkeleton = () => ( + <div className="animate-pulse grid gap-y-4"> + <div className="w-full h-10 bg-gray-300 rounded" /> + <div className="h-2" /> + <div className="w-full h-4 bg-gray-300 rounded" /> + <div className="w-full h-4 bg-gray-300 rounded" /> + <div className="w-full h-4 bg-gray-300 rounded" /> + <div className="w-8/12 h-4 bg-gray-300 rounded" /> + <div className="h-2" /> + <div className="w-full h-4 bg-gray-300 rounded" /> + <div className="w-full h-4 bg-gray-300 rounded" /> + <div className="w-full h-4 bg-gray-300 rounded" /> + <div className="w-1/2 h-4 bg-gray-300 rounded" /> + </div> +) + export default PageContent
\ No newline at end of file diff --git a/src-migrate/modules/popup-information/index.tsx b/src-migrate/modules/popup-information/index.tsx index cd1fd5f2..3d537236 100644 --- a/src-migrate/modules/popup-information/index.tsx +++ b/src-migrate/modules/popup-information/index.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; -import Modal from '~/common/components/elements/Modal'; -import { getAuth } from '~/common/libs/auth'; +import { Modal } from "~/components/ui/modal" +import { getAuth } from '~/libs/auth'; import PageContent from '../page-content'; import Link from 'next/link'; diff --git a/src-migrate/modules/product-card/components/ProductCard.tsx b/src-migrate/modules/product-card/components/ProductCard.tsx new file mode 100644 index 00000000..0a97b344 --- /dev/null +++ b/src-migrate/modules/product-card/components/ProductCard.tsx @@ -0,0 +1,106 @@ +import style from '../styles/product-card.module.css' + +import Link from 'next/link' +import React from 'react' +import Image from '~/components/ui/image' +import clsxm from '~/libs/clsxm' +import formatCurrency from '~/libs/formatCurrency' +import { formatToShortText } from '~/libs/formatNumber' +import { createSlug } from '~/libs/slug' +import { IProduct } from '~/types/product' + +type Props = { + product: IProduct + layout?: 'vertical' | 'horizontal' +} + +const ProductCard = ({ product, layout = 'vertical' }: Props) => { + const URL = { + product: createSlug('/shop/product/', product.name, product.id.toString()), + manufacture: createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString()), + } + + return ( + <div className={clsxm(style['wrapper'], { + [style['wrapper-v']]: layout === 'vertical', + [style['wrapper-h']]: layout === 'horizontal', + })} + > + <div className={clsxm('relative', { + [style['image-v']]: layout === 'vertical', + [style['image-h']]: layout === 'horizontal', + })}> + <Link href={URL.product}> + <Image + src={product.image || '/images/noimage.jpeg'} + alt={product.name} + width={128} + height={128} + className='object-contain object-center h-full w-full' + /> + {product.variant_total > 1 && ( + <div className={style['variant-badge']}>{product.variant_total} Varian</div> + )} + </Link> + </div> + + <div className={clsxm({ + [style['content-v']]: layout === 'vertical', + [style['content-h']]: layout === 'horizontal', + })}> + <Link + href={URL.manufacture} + className={style['brand']} + > + {product.manufacture.name} + </Link> + + <div className='h-0.5' /> + + <Link + href={URL.product} + className={clsxm(style['name'], { + [style['name-v']]: layout === 'vertical', + [style['name-h']]: layout === 'horizontal', + })} + > + {product.name} + </Link> + <div className='h-1.5' /> + + <div className={style['price']}> + Rp {formatCurrency(product.lowest_price.price)} + </div> + + <div className='h-1.5' /> + + <div className={style['price-inc']}> + Inc PPN: + Rp {formatCurrency(Math.round(product.lowest_price.price * 1.11))} + </div> + + <div className='h-1' /> + + <div className='flex items-center gap-x-2.5'> + {product.stock_total > 0 && ( + <div className={style['ready-stock']}> + Ready Stock + </div> + )} + {product.qty_sold > 0 && ( + <div className={style['sold']}> + {formatToShortText(product.qty_sold)} Terjual + </div> + )} + </div> + + </div> + </div> + ) +} + +const classPrefix = ({ layout }: Props) => { + +} + +export default ProductCard
\ No newline at end of file diff --git a/src-migrate/modules/product-card/index.tsx b/src-migrate/modules/product-card/index.tsx new file mode 100644 index 00000000..c87167bc --- /dev/null +++ b/src-migrate/modules/product-card/index.tsx @@ -0,0 +1,3 @@ +import ProductCard from "./components/ProductCard"; + +export default ProductCard
\ No newline at end of file diff --git a/src-migrate/modules/product-card/styles/product-card.module.css b/src-migrate/modules/product-card/styles/product-card.module.css new file mode 100644 index 00000000..653bf2ca --- /dev/null +++ b/src-migrate/modules/product-card/styles/product-card.module.css @@ -0,0 +1,54 @@ +.wrapper { + @apply w-full flex; +} +.wrapper-v { + @apply flex-col border border-gray-300 rounded-md h-[350px]; +} +.wrapper-h { + @apply flex-row gap-x-2 pt-4; +} + +.image-v { + @apply w-full h-48 px-4 border-b border-gray-300; +} +.image-h { + @apply w-4/12 h-24 px-1; +} + +.content-v { + @apply w-full p-2; +} +.content-h { + @apply w-8/12; +} + +.brand { + @apply text-danger-500 font-medium block; +} + +.name { + @apply text-gray-700 font-medium line-clamp-3; +} +.name-v { + @apply min-h-[64px]; +} +.name-h { + @apply min-h-[32px]; +} + +.price { + @apply text-danger-500 font-medium; +} + +.ready-stock { + @apply bg-danger-500 text-white text-[11px] px-2 py-1 rounded-md whitespace-nowrap; +} + +.price-inc, +.sold { + @apply text-gray-600 text-[11px]; +} + +.variant-badge { + @apply bg-gray-500/20 backdrop-blur-md absolute rounded-md bottom-2 left-2 px-2 py-1 text-caption-2; +} diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx new file mode 100644 index 00000000..4accab17 --- /dev/null +++ b/src-migrate/modules/product-detail/components/AddToCart.tsx @@ -0,0 +1,79 @@ +import React from 'react' +import { Button, useToast } from '@chakra-ui/react' +import { getAuth } from '~/libs/auth' +import { useRouter } from 'next/router' +import Link from 'next/link' +import { upsertUserCart } from '~/services/cart' + +type Props = { + variantId: number | null, + quantity?: number; + source?: 'buy' | 'add_to_cart'; +} + +const AddToCart = ({ + variantId, + quantity = 1, + source = 'add_to_cart' +}: Props) => { + const auth = getAuth() + const router = useRouter() + const toast = useToast({ + position: 'top', + isClosable: true + }) + + const handleClick = async () => { + if (typeof auth !== 'object') { + const currentUrl = encodeURIComponent(router.asPath) + toast({ + title: 'Masuk Akun', + description: <> + Masuk akun untuk dapat menambahkan barang ke keranjang belanja. {' '} + <Link className='underline' href={`/login?next=${currentUrl}`}>Klik disini</Link> + </>, + status: 'error', + duration: 4000, + }) + return; + } + + if ( + !variantId || + isNaN(quantity) || + typeof auth !== 'object' + ) return; + + toast.promise( + upsertUserCart(auth.id, 'product', variantId, quantity, true, source), + { + loading: { title: 'Menambahkan ke keranjang', description: 'Mohon tunggu...' }, + success: { title: 'Menambahkan ke keranjang', description: 'Berhasil menambahkan ke keranjang belanja' }, + error: { title: 'Menambahkan ke keranjang', description: 'Gagal menambahkan ke keranjang belanja' }, + } + ) + + if (source === 'buy') { + router.push('/shop/checkout?source=buy') + } + } + + const btnConfig = { + 'add_to_cart': { + colorScheme: 'yellow', + text: 'Keranjang' + }, + 'buy': { + colorScheme: 'red', + text: 'Beli' + } + } + + return ( + <Button onClick={handleClick} colorScheme={btnConfig[source].colorScheme} className='w-full'> + {btnConfig[source].text} + </Button> + ) +} + +export default AddToCart
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/AddToWishlist.tsx b/src-migrate/modules/product-detail/components/AddToWishlist.tsx new file mode 100644 index 00000000..697b2d5c --- /dev/null +++ b/src-migrate/modules/product-detail/components/AddToWishlist.tsx @@ -0,0 +1,61 @@ +import { Button, useToast } from '@chakra-ui/react' +import { HeartIcon } from 'lucide-react' +import React from 'react' +import { useQuery } from 'react-query' +import { getAuth } from '~/libs/auth' +import clsxm from '~/libs/clsxm' +import { getUserWishlist, upsertUserWishlist } from '~/services/wishlist' + +type Props = { + productId: number +} + +const AddToWishlist = ({ productId }: Props) => { + const auth = getAuth() + const toast = useToast({ + position: 'top', + isClosable: true + }) + + const searchParams = { product_id: productId.toString() } + const query = useQuery({ + queryKey: ['wishlist', searchParams, auth], + queryFn: () => { + if (typeof auth !== 'object') return null; + return getUserWishlist(auth.id, searchParams) + }, + refetchOnWindowFocus: false + }) + + const isAdded = query.data?.product_total ? query.data.product_total > 0 : false; + + const toggleWishlist = async () => { + if (typeof auth !== 'object') return; + await upsertUserWishlist(auth.id, productId) + await query.refetch() + } + + const handleClick = async () => { + toast.promise(toggleWishlist(), { + loading: { title: 'Update Wishlist', description: 'Mohon tunggu...' }, + success: { title: 'Update Wishlist', description: 'Berhasil update wishlist' }, + error: { title: 'Update Wishlist', description: 'Gagal update wishlist' }, + }) + } + + return ( + <Button + variant='link' + colorScheme='gray' + onClick={handleClick} + leftIcon={<HeartIcon size={18} className={clsxm('transition-colors', { + 'text-danger-500 fill-danger-500': isAdded, + 'fill-transparent': !isAdded + })} />} + > + Wishlist + </Button> + ) +} + +export default AddToWishlist
\ No newline at end of file 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..f41859a9 --- /dev/null +++ b/src-migrate/modules/product-detail/components/Breadcrumb.tsx @@ -0,0 +1,41 @@ +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), + refetchOnWindowFocus: false + }) + + const breadcrumbs = query.data || [] + + return ( + <div className='line-clamp-2 md:line-clamp-1 leading-7 text-caption-1'> + <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 new file mode 100644 index 00000000..2ab3ff59 --- /dev/null +++ b/src-migrate/modules/product-detail/components/Image.tsx @@ -0,0 +1,99 @@ +import style from '../styles/image.module.css'; + +import React, { useEffect, useState } from 'react' +import { InfoIcon } from 'lucide-react' +import { Tooltip } from '@chakra-ui/react' + +import { IProductDetail } from '~/types/product' +import ImageUI from '~/components/ui/image' +import moment from 'moment'; + +type Props = { + product: IProductDetail +} + +const Image = ({ product }: Props) => { + const flashSale = product.flash_sale + + const [count, setCount] = useState(flashSale?.remaining_time || 0); + + useEffect(() => { + let interval: NodeJS.Timeout; + + if (flashSale?.remaining_time && flashSale.remaining_time > 0) { + setCount(flashSale.remaining_time); + + interval = setInterval(() => { + setCount((prevCount) => prevCount - 1); + }, 1000); + } + + return () => { + clearInterval(interval); + }; + }, [flashSale?.remaining_time]); + + const duration = moment.duration(count, 'seconds') + + return ( + <div className={style['wrapper']}> + <ImageUI + src={product.image || '/images/noimage.jpeg'} + alt={product.name} + width={256} + height={256} + className={style['image']} + loading='eager' + priority + /> + + <div className={style['absolute-info']}> + <Tooltip + placement='bottom-end' + label='Gambar atau foto berperan sebagai ilustrasi produk. Kadang tidak sesuai dengan kondisi terbaru dengan berbagai perubahan dan perbaikan. Hubungi admin kami untuk informasi yang lebih baik perihal gambar.' + > + <div className="text-gray-600"> + <InfoIcon size={20} /> + </div> + </Tooltip> + </div> + + {flashSale.remaining_time > 0 && ( + <div className='absolute bottom-0 w-full h-14'> + <div className="relative w-full h-full"> + <ImageUI + src='/images/GAMBAR-BG-FLASH-SALE.jpg' + alt='Flash Sale Indoteknik' + width={200} + height={100} + className={style['flashsale-bg']} + /> + + <div className={style['flashsale']}> + <div className='flex items-center gap-x-3'> + <div className={style['disc-badge']}>{Math.floor(product.lowest_price.discount_percentage)}%</div> + <div className={style['flashsale-text']}> + <ImageUI + src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg' + alt='Icon Flash Sale' + width={20} + height={20} + /> + {product.flash_sale.tag} + </div> + </div> + <div className={style['countdown']}> + <span>{duration.hours().toString().padStart(2, '0')}</span> + <span>{duration.minutes().toString().padStart(2, '0')}</span> + <span>{duration.seconds().toString().padStart(2, '0')}</span> + </div> + </div> + + </div> + </div> + )} + </div> + ) +} + +export default Image
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx new file mode 100644 index 00000000..52eb6b88 --- /dev/null +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -0,0 +1,56 @@ +import style from '../styles/information.module.css' + +import React from 'react' +import dynamic from 'next/dynamic' +import Link from 'next/link' +import { useQuery } from 'react-query' + +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)) + +type Props = { + product: IProductDetail +} + +const Information = ({ product }: Props) => { + const querySLA = useQuery<IProductVariantSLA>({ + queryKey: ['variant-sla', product.variants[0].id], + queryFn: () => getVariantSLA(product.variants[0].id), + enabled: product.variant_total === 1 + }) + + const sla = querySLA?.data + + return ( + <div className={style['wrapper']}> + <div className={style['row']}> + <div className={style['label']}>SKU Number</div> + <div className={style['value']}>SKU-{product.id}</div> + </div> + <div className={style['row']}> + <div className={style['label']}>Manufacture</div> + <div className={style['value']}> + {!!product.manufacture.name ? ( + <Link + href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())} + className='text-danger-500 hover:underline' + > + {product.manufacture.name} + </Link> + ) : '-'} + </div> + </div> + <div className={style['row']}> + <div className={style['label']}>Terjual</div> + <div className={style['value']}>{product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}</div> + </div> + </div> + ) +} + +export default Information
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx new file mode 100644 index 00000000..f25847a5 --- /dev/null +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -0,0 +1,76 @@ +import style from '../styles/price-action.module.css' + +import React, { useEffect } from 'react' +import formatCurrency from '~/libs/formatCurrency' +import { IProductDetail } from '~/types/product' +import { useProductDetail } from '../stores/useProductDetail' +import AddToCart from './AddToCart' +import Link from 'next/link' + +type Props = { + product: IProductDetail +} + +const PriceAction = ({ product }: Props) => { + const { + activePrice, + setActive, + activeVariantId, + quantityInput, + setQuantityInput, + askAdminUrl + } = useProductDetail() + + useEffect(() => { + setActive(product.variants[0]) + }, [product, setActive]); + + return ( + <div className='block md:sticky top-[150px] bg-white py-0 md:py-6 z-10' id='price-section'> + {!!activePrice && activePrice.price > 0 && ( + <> + <div className='flex items-end gap-x-2'> + {activePrice.discount_percentage > 0 && ( + <> + <div className={style['disc-badge']}> + {Math.floor(activePrice.discount_percentage)}% + </div> + <div className={style['disc-price']}> + Rp {formatCurrency(activePrice.price || 0)} + </div> + </> + )} + <div className={style['main-price']}> + Rp {formatCurrency(activePrice.price_discount || 0)} + </div> + </div> + <div className='h-1' /> + <div className={style['secondary-text']}> + Termasuk PPN: {' '} + Rp {formatCurrency(Math.round(activePrice.price_discount * 1.11))} + </div> + </> + )} + + {!!activePrice && activePrice.price === 0 && ( + <span> + Hubungi kami untuk dapatkan harga terbaik,{' '} + <Link href={askAdminUrl} target='_blank' className={style['contact-us']}> + klik disini + </Link> + </span> + )} + + <div className='h-4' /> + + <div className={style['action-wrapper']}> + <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> + </div> + ) +} + +export default PriceAction
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx new file mode 100644 index 00000000..80f43aea --- /dev/null +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -0,0 +1,178 @@ +import style from '../styles/product-detail.module.css' + +import React, { useEffect } from 'react' +import Link from 'next/link' +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 PriceAction from './PriceAction' +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() + const { setAskAdminUrl, askAdminUrl, activeVariantId } = useProductDetail() + + useEffect(() => { + const createdAskUrl = whatsappUrl({ + template: 'product', + payload: { + manufacture: product.manufacture.name, + productName: product.name, + url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath + }, + fallbackUrl: router.asPath + }) + + setAskAdminUrl(createdAskUrl) + }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]) + + return ( + <> + <div className='md:flex md:flex-wrap'> + <div className="w-full mb-4 md:mb-0 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 md:pt-6'> + <div className='md:flex md:flex-wrap'> + <div className="md:w-4/12"> + <ProductImage product={product} /> + </div> + + <div className='md:w-8/12 px-4 md:pl-6'> + <div className='h-6 md:h-0' /> + + <h1 className={style['title']}> + {product.name} + </h1> + + <div className='h-6 md:h-8' /> + + <Information product={product} /> + + <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 productId={product.id} /> + + <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> + + </div> + </div> + + <div className='h-full'> + {isMobile && ( + <div className='px-4 pt-6'> + <PriceAction product={product} /> + </div> + )} + + <div className='h-4 md:h-10' /> + {!!activeVariantId && ( + <ProductPromoSection productId={activeVariantId} /> + )} + + <div className={style['section-card']}> + <h2 className={style['heading']}> + Variant ({product.variant_total}) + </h2> + <div className='h-4' /> + <VariantList variants={product.variants} /> + </div> + + <div className='h-0 md:h-6' /> + + <div className={style['section-card']}> + <h2 className={style['heading']}> + Informasi Produk + </h2> + <div className='h-4' /> + <div + className={style['description']} + dangerouslySetInnerHTML={{ __html: !product.description || product.description == '<p><br></p>' ? 'Belum ada deskripsi' : product.description }} + /> + </div> + </div> + </div> + + {isDesktop && ( + <div className="md:w-3/12"> + <PriceAction product={product} /> + + <div className='h-6' /> + + <div className={style['heading']}> + Produk Serupa + </div> + + <div className='h-4' /> + + <SimilarSide product={product} /> + </div> + )} + + <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> + <div className={style['heading']}> + Kamu Mungkin Juga Suka + </div> + + <div className='h-6' /> + + <SimilarBottom product={product} /> + </div> + + <div className='h-6 md:h-0' /> + </div> + </> + ) +} + +export default ProductDetail
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/SimilarBottom.tsx b/src-migrate/modules/product-detail/components/SimilarBottom.tsx new file mode 100644 index 00000000..9a12a6ef --- /dev/null +++ b/src-migrate/modules/product-detail/components/SimilarBottom.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import useProductSimilar from '~/modules/product-similar/hooks/useProductSimilar' +import ProductSlider from '~/modules/product-slider' +import { IProductDetail } from '~/types/product' + +type Props = { + product: IProductDetail +} + +const SimilarBottom = ({ product }: Props) => { + const productSimilar = useProductSimilar({ + name: product.name, + except: { productId: product.id } + }) + + const products = productSimilar.data?.products || [] + + return <ProductSlider products={products} productLayout='vertical' />; +} + +export default SimilarBottom
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/SimilarSide.tsx b/src-migrate/modules/product-detail/components/SimilarSide.tsx new file mode 100644 index 00000000..646a1c51 --- /dev/null +++ b/src-migrate/modules/product-detail/components/SimilarSide.tsx @@ -0,0 +1,34 @@ +import style from '../styles/side-similar.module.css' + +import React from 'react' + +import ProductCard from '~/modules/product-card' +import useProductSimilar from '~/modules/product-similar/hooks/useProductSimilar' +import { IProductDetail } from '~/types/product' + +type Props = { + product: IProductDetail +} + +const SimilarSide = ({ product }: Props) => { + const productSimilar = useProductSimilar({ + name: product.name, + except: { productId: product.id, manufactureId: product.manufacture.id }, + }) + + const products = productSimilar.data?.products || [] + + return ( + <div className={style['wrapper']}> + {products.map((product) => ( + <ProductCard + key={product.id} + product={product} + layout='horizontal' + /> + ))} + </div> + ) +} + +export default SimilarSide
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/VariantList.tsx b/src-migrate/modules/product-detail/components/VariantList.tsx new file mode 100644 index 00000000..3d5b9b74 --- /dev/null +++ b/src-migrate/modules/product-detail/components/VariantList.tsx @@ -0,0 +1,117 @@ +import style from '../styles/variant-list.module.css' + +import React from 'react' +import { Button, Skeleton } from '@chakra-ui/react' + +import formatCurrency from '~/libs/formatCurrency' +import clsxm from '~/libs/clsxm' +import { IProductVariantDetail, IProductVariantSLA } from '~/types/productVariant' +import { useProductDetail } from '../stores/useProductDetail' +import { LazyLoadComponent } from 'react-lazy-load-image-component'; +import { getVariantSLA } from '~/services/productVariant' +import { useQuery } from 'react-query' +import useDevice from '@/core/hooks/useDevice' + +type Props = { + variants: IProductVariantDetail[] +} + +const VariantList = ({ variants }: Props) => { + return ( + <div className='overflow-auto'> + <div className={style['wrapper']}> + <div className={style['header']}> + <div className="w-2/12">Part Number</div> + <div className="w-2/12">Variant</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-3/12">Harga</div> + <div className='w-1/12 sticky right-0 bg-gray-200'></div> + </div> + {variants.map((variant) => ( + <LazyLoadComponent key={variant.id}> + <Row variant={variant} /> + </LazyLoadComponent> + ))} + </div> + </div> + ) +} + +const Row = ({ variant }: { variant: IProductVariantDetail }) => { + const { isMobile } = useDevice() + + const { activeVariantId, setActive } = useProductDetail() + const querySLA = useQuery<IProductVariantSLA>({ + queryKey: ['variant-sla', variant.id], + queryFn: () => getVariantSLA(variant.id), + refetchOnWindowFocus: false, + }) + + const sla = querySLA?.data + + const handleSelect = (variant: IProductVariantDetail) => { + const priceSectionEl = document.getElementById('price-section') + if (isMobile && priceSectionEl) { + window.scrollTo({ + top: priceSectionEl.offsetTop - 120, + behavior: 'smooth' + }) + } + setActive(variant) + } + + return ( + <div className={style['row']}> + <div className='w-2/12'>{variant.code}</div> + <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 && ( + <div className={clsxm('text-center rounded-md', { + [style['stock-empty']]: sla.qty == 0, + [style['stock-ready']]: sla.qty > 0, + })} + > + {sla.qty > 0 && sla.qty} + {sla.qty == 0 && '-'} + </div> + )} + </Skeleton> + </div> + <div className='w-2/12'> + <Skeleton isLoaded={querySLA.isSuccess} h='21px' w={16}> + {sla?.sla_date} + </Skeleton> + </div> + <div className='w-1/12'> + {variant.weight > 0 ? `${variant.weight} Kg` : '-'} + </div> + <div className='w-3/12'> + {variant.price.discount_percentage > 0 && ( + <div className='flex items-center gap-x-1'> + <div className={style['disc-badge']}>{Math.floor(variant.price.discount_percentage)}%</div> + <div className={style['disc-price']}>Rp {formatCurrency(variant.price.price)}</div> + </div> + )} + {variant.price.price_discount > 0 && `Rp ${formatCurrency(variant.price.price_discount)}`} + {variant.price.price_discount === 0 && '-'} + </div> + <div className='w-1/12 sticky right-0 bg-white md:bg-transparent'> + <Button + onClick={() => handleSelect(variant)} + size='sm' + w='100%' + className={clsxm(style['select-btn'], { + [style['select-btn--active']]: variant.id === activeVariantId + })} + > + Pilih + </Button> + </div> + </div> + ) +} + +export default VariantList
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/index.ts b/src-migrate/modules/product-detail/index.ts new file mode 100644 index 00000000..246bc06a --- /dev/null +++ b/src-migrate/modules/product-detail/index.ts @@ -0,0 +1,3 @@ +import ProductDetail from './components/ProductDetail'; + +export default ProductDetail; diff --git a/src-migrate/modules/product-detail/stores/useProductDetail.ts b/src-migrate/modules/product-detail/stores/useProductDetail.ts new file mode 100644 index 00000000..794f0346 --- /dev/null +++ b/src-migrate/modules/product-detail/stores/useProductDetail.ts @@ -0,0 +1,31 @@ +import { create } from 'zustand'; +import { IProductVariantDetail } from '~/types/productVariant'; + +type State = { + activeVariantId: number | null; + activePrice: IProductVariantDetail['price'] | null; + quantityInput: string; + askAdminUrl: string; +}; + +type Action = { + setActive: (variant: IProductVariantDetail) => void; + setQuantityInput: (value: string) => void; + setAskAdminUrl: (url: string) => void; +}; + +export const useProductDetail = create<State & Action>((set, get) => ({ + activeVariantId: null, + activePrice: null, + quantityInput: '1', + askAdminUrl: '', + setActive: (variant) => { + set({ activeVariantId: variant.id, activePrice: variant.price }); + }, + setQuantityInput: (value: string) => { + set({ quantityInput: value }); + }, + setAskAdminUrl: (url: string) => { + set({ askAdminUrl: url }); + }, +})); diff --git a/src-migrate/modules/product-detail/styles/image.module.css b/src-migrate/modules/product-detail/styles/image.module.css new file mode 100644 index 00000000..e472fe8d --- /dev/null +++ b/src-migrate/modules/product-detail/styles/image.module.css @@ -0,0 +1,35 @@ +.wrapper { + @apply h-[250px] md:h-[340px] flex items-center justify-center border border-gray-200 rounded-lg p-4 relative; +} + +.image { + @apply object-contain object-center h-full w-full; +} + +.absolute-info { + @apply absolute hidden md:block top-4 right-4; +} + +.disc-badge { + @apply bg-warning-500 py-1 px-3 w-fit font-semibold rounded-full; +} + +.countdown { + @apply flex gap-x-1; +} + +.countdown span { + @apply py-0.5 w-8 bg-warning-500 rounded-md text-center; +} + +.flashsale-text { + @apply flex items-center gap-x-2 text-white font-medium text-caption-1; +} + +.flashsale-bg { + @apply absolute top-0 w-full h-full object-cover object-center z-10; +} + +.flashsale { + @apply absolute top-0 w-full h-full z-20 flex items-center justify-between px-3; +} diff --git a/src-migrate/modules/product-detail/styles/information.module.css b/src-migrate/modules/product-detail/styles/information.module.css new file mode 100644 index 00000000..c9b29020 --- /dev/null +++ b/src-migrate/modules/product-detail/styles/information.module.css @@ -0,0 +1,19 @@ +.wrapper { + @apply grid grid-cols-1; +} + +.row { + @apply flex p-3 rounded; +} + +.row:nth-child(odd) { + @apply bg-gray-100; +} + +.label { + @apply w-1/2 md:w-1/3 font-medium text-gray-500; +} + +.value { + @apply w-1/2 md:w-3/4 text-gray-950; +} diff --git a/src-migrate/modules/product-detail/styles/price-action.module.css b/src-migrate/modules/product-detail/styles/price-action.module.css new file mode 100644 index 00000000..651de958 --- /dev/null +++ b/src-migrate/modules/product-detail/styles/price-action.module.css @@ -0,0 +1,24 @@ +.secondary-text { + @apply font-medium text-gray-500; +} +.main-price { + @apply font-medium text-danger-500 text-title-md; +} +.action-wrapper { + @apply flex gap-x-2.5; +} +.quantity-input { + @apply px-2 rounded text-center border border-gray-300 w-14 h-10 focus:outline-none; +} + +.contact-us { + @apply text-danger-500 font-medium underline; +} + +.disc-badge { + @apply bg-danger-500 px-2 py-1.5 rounded text-white text-caption-2; +} + +.disc-price { + @apply line-through text-gray-600 text-caption-2; +} diff --git a/src-migrate/modules/product-detail/styles/product-detail.module.css b/src-migrate/modules/product-detail/styles/product-detail.module.css new file mode 100644 index 00000000..c668167c --- /dev/null +++ b/src-migrate/modules/product-detail/styles/product-detail.module.css @@ -0,0 +1,15 @@ +.title { + @apply font-medium text-h-lg leading-8 md:text-title-md md:leading-10; +} + +.section-card { + @apply p-4 md:p-6 md:bg-gray-50 rounded-xl; +} + +.heading { + @apply text-h-md md:text-h-lg font-medium; +} + +.description { + @apply leading-relaxed text-gray-700; +} diff --git a/src-migrate/modules/product-detail/styles/side-similar.module.css b/src-migrate/modules/product-detail/styles/side-similar.module.css new file mode 100644 index 00000000..08692efa --- /dev/null +++ b/src-migrate/modules/product-detail/styles/side-similar.module.css @@ -0,0 +1,3 @@ +.wrapper { + @apply max-h-[500px] overflow-auto grid grid-cols-1 gap-y-4 divide-y divide-gray-300 border border-gray-300 rounded-lg; +} diff --git a/src-migrate/modules/product-detail/styles/variant-list.module.css b/src-migrate/modules/product-detail/styles/variant-list.module.css new file mode 100644 index 00000000..6d46df84 --- /dev/null +++ b/src-migrate/modules/product-detail/styles/variant-list.module.css @@ -0,0 +1,35 @@ +.wrapper { + @apply grid grid-cols-1 w-[200%] md:w-full; +} + +.header { + @apply flex py-2.5 pl-4 font-medium bg-gray-200 rounded-md; +} + +.row { + @apply flex items-center py-2.5 pl-4 text-gray-800; +} + +.select-btn { + @apply !bg-gray-200 hover:!bg-danger-500 hover:!text-white; +} + +.select-btn--active { + @apply !text-white !bg-danger-500 hover:!text-white; +} + +.stock-empty { + @apply bg-red-50 border border-red-500 text-red-800; +} + +.stock-ready { + @apply bg-green-50 border border-green-500 text-green-800; +} + +.disc-badge { + @apply bg-danger-500 p-1 rounded text-white text-caption-2; +} + +.disc-price { + @apply text-caption-2 line-through text-gray-600; +} diff --git a/src-migrate/modules/product-promo/components/AddToCart.tsx b/src-migrate/modules/product-promo/components/AddToCart.tsx index 58bb2ad7..3bac3c66 100644 --- a/src-migrate/modules/product-promo/components/AddToCart.tsx +++ b/src-migrate/modules/product-promo/components/AddToCart.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react' import { CheckIcon, PlusIcon } from 'lucide-react' -import { IPromotion } from '~/common/types/promotion' +import { IPromotion } from '~/types/promotion' import { upsertUserCart } from '~/services/cart' -import { getAuth } from '~/common/libs/auth' +import { getAuth } from '~/libs/auth' import { Button, Spinner, useToast } from '@chakra-ui/react' import Link from 'next/link' import { useRouter } from 'next/router' diff --git a/src-migrate/modules/product-promo/components/Card.tsx b/src-migrate/modules/product-promo/components/Card.tsx index e894c143..59110098 100644 --- a/src-migrate/modules/product-promo/components/Card.tsx +++ b/src-migrate/modules/product-promo/components/Card.tsx @@ -6,11 +6,11 @@ import { Skeleton, Tooltip } from '@chakra-ui/react' import { motion } from "framer-motion" import { PROMO_CATEGORY } from "~/constants/promotion" -import { getVariantById } from "~/services/variant" +import { getVariantById } from "~/services/productVariant" -import { IProductVariantPromo, IPromotion } from '~/common/types/promotion' -import formatCurrency from '~/common/libs/formatCurrency' -import clsxm from '~/common/libs/clsxm' +import { IProductVariantPromo, IPromotion } from '~/types/promotion' +import formatCurrency from '~/libs/formatCurrency' +import clsxm from '~/libs/clsxm' import ProductPromoItem from './Item' import ProductPromoAddToCart from "./AddToCart" diff --git a/src-migrate/modules/product-promo/components/CardCountdown.tsx b/src-migrate/modules/product-promo/components/CardCountdown.tsx index e398a390..b61ad115 100644 --- a/src-migrate/modules/product-promo/components/CardCountdown.tsx +++ b/src-migrate/modules/product-promo/components/CardCountdown.tsx @@ -6,8 +6,8 @@ import { ClockIcon } from 'lucide-react' import { Skeleton } from '@chakra-ui/react' import moment from 'moment' -import clsxm from '~/common/libs/clsxm' -import { IPromotion } from '~/common/types/promotion' +import clsxm from '~/libs/clsxm' +import { IPromotion } from '~/types/promotion' import { getPromotionProgram } from '~/services/promotionProgram' type Props = { diff --git a/src-migrate/modules/product-promo/components/CategoryTab.tsx b/src-migrate/modules/product-promo/components/CategoryTab.tsx index edc4aa92..c8e698c2 100644 --- a/src-migrate/modules/product-promo/components/CategoryTab.tsx +++ b/src-migrate/modules/product-promo/components/CategoryTab.tsx @@ -1,8 +1,8 @@ import React from 'react' import style from '../styles/category-tab.module.css' import { useModalStore } from '../stores/useModalStore' -import clsxm from '~/common/libs/clsxm' -import { ICategoryPromo } from '~/common/types/promotion' +import clsxm from '~/libs/clsxm' +import { ICategoryPromo } from '~/types/promotion' const TABS: ICategoryPromo[] = [ { value: 'bundling', label: 'Bundling' }, diff --git a/src-migrate/modules/product-promo/components/Item.tsx b/src-migrate/modules/product-promo/components/Item.tsx index 15ca4878..8012c17e 100644 --- a/src-migrate/modules/product-promo/components/Item.tsx +++ b/src-migrate/modules/product-promo/components/Item.tsx @@ -1,9 +1,9 @@ import style from '../styles/item.module.css' import React from 'react' -import Image from 'next/image' +import Image from '~/components/ui/image' -import { IProductVariantPromo } from '~/common/types/promotion' +import { IProductVariantPromo } from '~/types/promotion' type Props = { variant: IProductVariantPromo, @@ -17,7 +17,7 @@ const ProductPromoItem = ({ return ( <div className={style.item}> <div className={style.image}> - <Image src={variant.image} alt={variant.display_name} width={120} height={120} quality={100} /> + <Image src={variant.image || '/images/noimage.jpeg'} alt={variant.display_name} width={120} height={120} quality={100} /> <div className={style.quantity}> {variant.qty} pcs {isFree ? '(free)' : ''} </div> diff --git a/src-migrate/modules/product-promo/components/Modal.tsx b/src-migrate/modules/product-promo/components/Modal.tsx index 598b7bbe..0de672c2 100644 --- a/src-migrate/modules/product-promo/components/Modal.tsx +++ b/src-migrate/modules/product-promo/components/Modal.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Modal from '~/common/components/elements/Modal' +import { Modal } from "~/components/ui/modal" import { useModalStore } from '../stores/useModalStore' import ProductPromoCategoryTab from './CategoryTab' import ProductPromoModalContent from './ModalContent' diff --git a/src-migrate/modules/product-promo/components/ModalContent.tsx b/src-migrate/modules/product-promo/components/ModalContent.tsx index 90cf79e7..ab5129f8 100644 --- a/src-migrate/modules/product-promo/components/ModalContent.tsx +++ b/src-migrate/modules/product-promo/components/ModalContent.tsx @@ -1,7 +1,7 @@ import { useQuery } from "react-query" import { Skeleton } from "@chakra-ui/react" -import { getVariantPromoByCategory } from "~/services/variant" +import { getVariantPromoByCategory } from "~/services/productVariant" import { useModalStore } from "../stores/useModalStore" import ProductPromoCard from "./Card" diff --git a/src-migrate/modules/product-promo/components/Section.tsx b/src-migrate/modules/product-promo/components/Section.tsx index 47e1de29..b6753be7 100644 --- a/src-migrate/modules/product-promo/components/Section.tsx +++ b/src-migrate/modules/product-promo/components/Section.tsx @@ -5,9 +5,10 @@ import { useQuery } from 'react-query' import { Button, Skeleton } from '@chakra-ui/react' import ProductPromoCard from './Card' -import { IPromotion } from '~/common/types/promotion' +import { IPromotion } from '~/types/promotion' import ProductPromoModal from "./Modal" import { useModalStore } from "../stores/useModalStore" +import clsxm from "~/libs/clsxm" type Props = { productId: number @@ -36,7 +37,13 @@ const ProductPromoSection = ({ productId }: Props) => { </div> )} - <Skeleton isLoaded={promotionsQuery.isSuccess} className="flex gap-x-4 overflow-x-auto min-h-[340px] px-4 md:px-0"> + <Skeleton + isLoaded={promotionsQuery.isSuccess} + className={clsxm( + "flex gap-x-4 overflow-x-auto px-4 md:px-0", { + "min-h-[340px]": promotions?.data && promotions?.data.length > 0 + })} + > {promotions?.data.map((promotion) => ( <div key={promotion.id} className="min-w-[400px] max-w-[400px]"> <ProductPromoCard promotion={promotion} /> diff --git a/src-migrate/modules/product-promo/stores/useModalStore.ts b/src-migrate/modules/product-promo/stores/useModalStore.ts index bbb2b1fb..464bb598 100644 --- a/src-migrate/modules/product-promo/stores/useModalStore.ts +++ b/src-migrate/modules/product-promo/stores/useModalStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { CategoryPromo } from '~/common/types/promotion'; +import { CategoryPromo } from '~/types/promotion'; type State = { active: boolean; diff --git a/src-migrate/modules/product-similar/hooks/useProductSimilar.tsx b/src-migrate/modules/product-similar/hooks/useProductSimilar.tsx new file mode 100644 index 00000000..f2c49472 --- /dev/null +++ b/src-migrate/modules/product-similar/hooks/useProductSimilar.tsx @@ -0,0 +1,15 @@ +import { useQuery } from 'react-query' +import { GetProductSimilarProps, getProductSimilar } from '~/services/product' + +type Props = GetProductSimilarProps + +const useProductSimilar = (props: Props) => { + const similarQuery = useQuery({ + queryKey: ['product-similar', props], + queryFn: () => getProductSimilar(props), + }) + + return similarQuery +} + +export default useProductSimilar
\ No newline at end of file diff --git a/src-migrate/modules/product-slider/components/ProductSlider.tsx b/src-migrate/modules/product-slider/components/ProductSlider.tsx new file mode 100644 index 00000000..3d6e7593 --- /dev/null +++ b/src-migrate/modules/product-slider/components/ProductSlider.tsx @@ -0,0 +1,42 @@ +import 'swiper/css' + +import React from 'react' +import { Swiper, SwiperSlide } from 'swiper/react' +import { FreeMode } from 'swiper' + +import ProductCard from '~/modules/product-card' +import { IProduct } from '~/types/product' +import useDevice from '@/core/hooks/useDevice' + +type Props = { + products: IProduct[], + productLayout?: 'vertical' | 'horizontal', +} + +const ProductSlider = ({ products, productLayout }: Props) => { + const { isDesktop } = useDevice() + + return ( + <div> + <Swiper + slidesPerView={isDesktop ? 6.7 : 1.85} + spaceBetween={isDesktop ? 16 : 12} + prefix='product-slider' + modules={[FreeMode]} + freeMode={{ enabled: true, sticky: false }} + className='!pb-0.5' + > + {products.map((product) => ( + <SwiperSlide key={product.id}> + <ProductCard + product={product} + layout={productLayout} + /> + </SwiperSlide> + ))} + </Swiper> + </div > + ) +} + +export default ProductSlider
\ No newline at end of file diff --git a/src-migrate/modules/product-slider/index.ts b/src-migrate/modules/product-slider/index.ts new file mode 100644 index 00000000..1593a543 --- /dev/null +++ b/src-migrate/modules/product-slider/index.ts @@ -0,0 +1,3 @@ +import ProductSlider from './components/ProductSlider'; + +export default ProductSlider; diff --git a/src-migrate/modules/register/components/Form.tsx b/src-migrate/modules/register/components/Form.tsx index dc9107b2..b834f97a 100644 --- a/src-migrate/modules/register/components/Form.tsx +++ b/src-migrate/modules/register/components/Form.tsx @@ -1,7 +1,7 @@ import { ChangeEvent, useMemo } from "react"; import { useMutation } from "react-query"; -import { useRegisterStore } from "~/common/stores/useRegisterStore"; -import { RegisterProps } from "~/common/types/auth"; +import { useRegisterStore } from "../stores/useRegisterStore"; +import { RegisterProps } from "~/types/auth"; import { registerUser } from "~/services/auth"; import TermCondition from "./TermCondition"; import FormCaptcha from "./FormCaptcha"; diff --git a/src-migrate/modules/register/components/FormCaptcha.tsx b/src-migrate/modules/register/components/FormCaptcha.tsx index 967be017..fbea2b10 100644 --- a/src-migrate/modules/register/components/FormCaptcha.tsx +++ b/src-migrate/modules/register/components/FormCaptcha.tsx @@ -1,5 +1,5 @@ -import ReCaptcha from '~/common/components/elements/ReCaptcha' -import { useRegisterStore } from '~/common/stores/useRegisterStore' +import { ReCaptcha } from '~/components/ui/re-captcha' +import { useRegisterStore } from "../stores/useRegisterStore"; const FormCaptcha = () => { const { updateValidCaptcha } = useRegisterStore() diff --git a/src-migrate/modules/register/components/TermCondition.tsx b/src-migrate/modules/register/components/TermCondition.tsx index 6b95ba19..b7729deb 100644 --- a/src-migrate/modules/register/components/TermCondition.tsx +++ b/src-migrate/modules/register/components/TermCondition.tsx @@ -1,7 +1,7 @@ import { Checkbox } from '@chakra-ui/react' import React from 'react' -import Modal from '~/common/components/elements/Modal' -import { useRegisterStore } from '~/common/stores/useRegisterStore' +import { Modal } from '~/components/ui/modal' +import { useRegisterStore } from "../stores/useRegisterStore"; import PageContent from '~/modules/page-content' const TermCondition = () => { diff --git a/src-migrate/common/stores/useRegisterStore.ts b/src-migrate/modules/register/stores/useRegisterStore.ts index 90ce8a2b..d8abf52b 100644 --- a/src-migrate/common/stores/useRegisterStore.ts +++ b/src-migrate/modules/register/stores/useRegisterStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -import { RegisterProps } from '../types/auth'; -import { registerSchema } from '../validations/auth'; +import { RegisterProps } from '~/types/auth'; +import { registerSchema } from '~/validations/auth'; import { ZodError } from 'zod'; type State = { diff --git a/src-migrate/pages/_app.tsx b/src-migrate/pages/_app.tsx index 2dc82559..36640c04 100644 --- a/src-migrate/pages/_app.tsx +++ b/src-migrate/pages/_app.tsx @@ -1,5 +1,5 @@ -import '~/common/styles/fonts/Inter/inter.css' -import '~/common/styles/globals.css' +import '~/styles/fonts/Inter/inter.css' +import '~/styles/globals.css' import type { AppProps } from "next/app" export default function MyApp({ Component, pageProps }: AppProps) { diff --git a/src-migrate/pages/api/product-variant/[id].tsx b/src-migrate/pages/api/product-variant/[id].tsx index b3bd4096..c25c10ac 100644 --- a/src-migrate/pages/api/product-variant/[id].tsx +++ b/src-migrate/pages/api/product-variant/[id].tsx @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { SolrResponse } from "~/common/types/solr"; +import { SolrResponse } from "~/types/solr"; const SOLR_HOST = process.env.SOLR_HOST as string diff --git a/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx b/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx index 745f9944..50671afd 100644 --- a/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx +++ b/src-migrate/pages/api/product-variant/[id]/promotion/[category].tsx @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { SolrResponse } from "~/common/types/solr"; +import { SolrResponse } from "~/types/solr"; const SOLR_HOST = process.env.SOLR_HOST as string diff --git a/src-migrate/pages/api/product-variant/[id]/promotion/highlight.tsx b/src-migrate/pages/api/product-variant/[id]/promotion/highlight.tsx index 0fe8fd1b..8153f346 100644 --- a/src-migrate/pages/api/product-variant/[id]/promotion/highlight.tsx +++ b/src-migrate/pages/api/product-variant/[id]/promotion/highlight.tsx @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { SolrResponse } from "~/common/types/solr"; +import { SolrResponse } from "~/types/solr"; const SOLR_HOST = process.env.SOLR_HOST as string diff --git a/src-migrate/pages/api/promotion-program/[id].tsx b/src-migrate/pages/api/promotion-program/[id].tsx index ba716e85..c509b802 100644 --- a/src-migrate/pages/api/promotion-program/[id].tsx +++ b/src-migrate/pages/api/promotion-program/[id].tsx @@ -1,6 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { SolrResponse } from "~/common/types/solr"; -import moment from 'moment' +import { SolrResponse } from "~/types/solr"; const SOLR_HOST = process.env.SOLR_HOST as string diff --git a/src-migrate/pages/register.tsx b/src-migrate/pages/register.tsx index 1246c6f5..136eaa3b 100644 --- a/src-migrate/pages/register.tsx +++ b/src-migrate/pages/register.tsx @@ -1,7 +1,7 @@ import BasicLayout from "@/core/components/layouts/BasicLayout" import { useWindowSize } from "usehooks-ts" -import Seo from "~/common/components/elements/Seo" +import { Seo } from "~/components/seo" import Register from "~/modules/register" const RegisterPage = () => { diff --git a/src-migrate/pages/shop/cart/cart.module.css b/src-migrate/pages/shop/cart/cart.module.css new file mode 100644 index 00000000..d523a55a --- /dev/null +++ b/src-migrate/pages/shop/cart/cart.module.css @@ -0,0 +1,31 @@ +.title { + @apply text-h-lg font-semibold; +} + +.content { + @apply flex flex-wrap; +} + +.item-wrapper { + @apply w-full md:w-3/4; +} + +.item-skeleton { + @apply grid grid-cols-1 gap-y-4; +} + +.items { + @apply flex flex-col gap-y-6 border-t border-gray-300 pt-6; +} + +.summary-wrapper { + @apply w-full md:w-1/4 md:pl-6 mt-6 md:mt-0; +} + +.summary { + @apply border border-gray-300 p-4 rounded-md sticky top-[180px]; +} + +.summary-buttons { + @apply grid grid-cols-2 gap-x-3 mt-6; +} diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx new file mode 100644 index 00000000..397852f9 --- /dev/null +++ b/src-migrate/pages/shop/cart/index.tsx @@ -0,0 +1,93 @@ +import style from './cart.module.css' + +import React, { useEffect, useMemo } from 'react' +import Link from 'next/link' +import { Button, Tooltip } from '@chakra-ui/react' + +import { getAuth } from '~/libs/auth' +import { useCartStore } from '~/modules/cart/stores/useCartStore' + +import CartItem from '~/modules/cart/components/Item' +import CartSummary from '~/modules/cart/components/Summary' + +const CartPage = () => { + const auth = getAuth() + + const { loadCart, cart, summary } = useCartStore() + + useEffect(() => { + if (typeof auth === 'object' && !cart) loadCart(auth.id) + }, [auth, loadCart, cart]) + + const hasSelectedPromo = useMemo(() => { + if (!cart) return false + for (const item of cart.products) { + if (item.cart_type === 'promotion' && item.selected) return true + } + return false + }, [cart]) + + const hasSelected = useMemo(() => { + if (!cart) return false + for (const item of cart.products) { + if (item.selected) return true + } + return false + }, [cart]) + + return ( + <> + <div className={style['title']}> + Keranjang Belanja + </div> + + <div className='h-6' /> + + <div className={style['content']}> + <div className={style['item-wrapper']}> + <div className={style['item-skeleton']}> + {!cart && <CartItem.Skeleton count={5} height='120px' />} + </div> + + <div className={style['items']}> + {cart?.products.map((item) => <CartItem key={item.id} item={item} />)} + </div> + </div> + + <div className={style['summary-wrapper']}> + <div className={style['summary']}> + <CartSummary {...summary} isLoaded={!!cart} /> + + <div className={style['summary-buttons']}> + <Tooltip label={hasSelectedPromo && 'Barang promo tidak dapat dibuat quotation'}> + <Button + colorScheme='yellow' + w='full' + isDisabled={hasSelectedPromo || !hasSelected} + as={Link} + href='/shop/quotation' + > + Quotation + </Button> + </Tooltip> + + <Tooltip label={!hasSelected && 'Tidak ada item yang dipilih'}> + <Button + colorScheme='red' + w='full' + isDisabled={!hasSelected} + as={Link} + href='/shop/checkout' + > + Checkout + </Button> + </Tooltip> + </div> + </div> + </div> + </div> + </> + ) +} + +export default CartPage
\ No newline at end of file diff --git a/src-migrate/pages/shop/product/[slug].tsx b/src-migrate/pages/shop/product/[slug].tsx new file mode 100644 index 00000000..733e10d9 --- /dev/null +++ b/src-migrate/pages/shop/product/[slug].tsx @@ -0,0 +1,76 @@ +import { GetServerSideProps, NextPage } from 'next' +import React from 'react' +import dynamic from 'next/dynamic' +import cookie from 'cookie' + +import { getProductById } from '~/services/product' +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 }) + +type PageProps = { + product: IProductDetail +} + +export const getServerSideProps: GetServerSideProps<PageProps> = (async (context) => { + const { slug } = context.query + const cookieString = context.req.headers.cookie; + const cookies = cookieString ? cookie.parse(cookieString) : {}; + const auth = cookies?.auth ? JSON.parse(cookies.auth) : {}; + const tier = auth?.pricelist || '' + + const productId = getIdFromSlug(slug as string) + + const product = await getProductById(productId, tier) + + if (!product) return { notFound: true } + + return { + props: { product } + } +}) + +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: 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}`, + } + ]} + canonical={SELF_HOST + router.asPath} + /> + + <div className='md:container pt-4 md:pt-6'> + <ProductDetail product={product} /> + </div> + </BasicLayout> + ) +} + +export default ProductDetailPage
\ No newline at end of file diff --git a/src-migrate/services/auth.ts b/src-migrate/services/auth.ts index 1cc09c10..35ba290a 100644 --- a/src-migrate/services/auth.ts +++ b/src-migrate/services/auth.ts @@ -1,4 +1,4 @@ -import odooApi from '~/common/libs/odooApi'; +import odooApi from '~/libs/odooApi'; import { RegisterResApiProps, RegisterProps, @@ -8,7 +8,7 @@ import { ActivationOtpResApiProps, ActivationReqProps, ActivationReqResApiProps, -} from '~/common/types/auth'; +} from '~/types/auth'; const BASE_PATH = '/api/v1/user'; diff --git a/src-migrate/services/cart.ts b/src-migrate/services/cart.ts index b238be3d..73967073 100644 --- a/src-migrate/services/cart.ts +++ b/src-migrate/services/cart.ts @@ -1,4 +1,4 @@ -import odooApi from '~/common/libs/odooApi'; +import odooApi from '~/libs/odooApi'; export const getUserCart = async (userId: number) => { return await odooApi('GET', `/api/v1/user/${userId}/cart`); diff --git a/src-migrate/services/checkout.ts b/src-migrate/services/checkout.ts index 3dd1c8e8..3eff95a8 100644 --- a/src-migrate/services/checkout.ts +++ b/src-migrate/services/checkout.ts @@ -1,4 +1,4 @@ -import odooApi from '~/common/libs/odooApi'; +import odooApi from '~/libs/odooApi'; export const getUserCheckout = async (userId: number) => { return await odooApi('GET', `/api/v1/user/${userId}/sale_order/checkout`); diff --git a/src-migrate/services/pageContent.ts b/src-migrate/services/pageContent.ts index 16146059..516b4bed 100644 --- a/src-migrate/services/pageContent.ts +++ b/src-migrate/services/pageContent.ts @@ -1,4 +1,4 @@ -import odooApi from '~/common/libs/odooApi'; +import odooApi from '~/libs/odooApi'; export const getPageContent = async ({ path }: { path: string }) => { const params = new URLSearchParams({ url_path: path }); diff --git a/src-migrate/services/product.ts b/src-migrate/services/product.ts new file mode 100644 index 00000000..fe415d11 --- /dev/null +++ b/src-migrate/services/product.ts @@ -0,0 +1,66 @@ +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; + +export const getProductById = async ( + id: string, + tier: string +): Promise<IProductDetail | null> => { + const url = `${SELF_HOST}/api/shop/product-detail`; + const params = new URLSearchParams({ id, auth: tier }); + return await fetch(`${url}?${params.toString()}`) + .then((res) => res.json()) + .then((res) => { + if (res.length > 0) return snakeCase(res[0]) as IProductDetail; + return null; + }); +}; + +export interface GetProductSimilarProps { + name: string; + except?: { + productId?: number; + manufactureId?: number; + }; + limit?: number; +} + +export interface GetProductSimilarRes { + products: IProduct[]; + num_found: number; + num_found_exact: boolean; + start: number; +} + +export const getProductSimilar = async ({ + name, + except, + limit = 30, +}: GetProductSimilarProps): Promise<GetProductSimilarRes> => { + const query = [ + `q=${name}`, + 'page=1', + 'orderBy=popular-weekly', + 'operation=OR', + 'priceFrom=1', + ]; + + if (except?.productId) query.push(`fq=-product_id_i:${except.productId}`); + if (except?.manufactureId) + query.push(`fq=-manufacture_id_i:${except.manufactureId}`); + + const url = `${SELF_HOST}/api/shop/search?${query.join('&')}`; + + return await fetch(url) + .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/services/variant.ts b/src-migrate/services/productVariant.ts index 213187d2..9fec4d1f 100644 --- a/src-migrate/services/variant.ts +++ b/src-migrate/services/productVariant.ts @@ -1,4 +1,6 @@ -import { CategoryPromo, IPromotion } from '~/common/types/promotion'; +import odooApi from '~/libs/odooApi'; +import { IProductVariantSLA } from '~/types/productVariant'; +import { CategoryPromo, IPromotion } from '~/types/promotion'; export const getVariantById = async (variantId: number) => { const url = `/api/product-variant/${variantId}`; @@ -12,3 +14,10 @@ export const getVariantPromoByCategory = async ( const url = `/api/product-variant/${variantId}/promotion/${type}`; return await fetch(url).then((res) => res.json()); }; + +export const getVariantSLA = async ( + variantId: number +): Promise<IProductVariantSLA> => { + const url = `/api/v1/product_variant/${variantId}/stock`; + return await odooApi('GET', url); +}; diff --git a/src-migrate/services/promotionProgram.ts b/src-migrate/services/promotionProgram.ts index a5026c71..c8c46c65 100644 --- a/src-migrate/services/promotionProgram.ts +++ b/src-migrate/services/promotionProgram.ts @@ -1,4 +1,4 @@ -import { IPromotionProgram } from '~/common/types/promotionProgram'; +import { IPromotionProgram } from '~/types/promotionProgram'; export const getPromotionProgram = async ( programId: number diff --git a/src-migrate/services/wishlist.ts b/src-migrate/services/wishlist.ts new file mode 100644 index 00000000..6fb8cb2e --- /dev/null +++ b/src-migrate/services/wishlist.ts @@ -0,0 +1,23 @@ +import odooApi from '~/libs/odooApi'; + +export const getUserWishlist = async ( + userId: number, + searchParams: { + product_id?: string; + } = {} +): Promise<{ product_total: number }> => { + const url = `/api/v1/user/${userId}/wishlist`; + const searchParamsObj = new URLSearchParams(searchParams); + + return await odooApi('GET', url + '?' + searchParamsObj.toString()); +}; + +export const upsertUserWishlist = async ( + userId: number, + productId: number +): Promise<{ id: number }> => { + const url = `/api/v1/user/${userId}/wishlist/create-or-delete`; + const data = { product_id: productId }; + + return await odooApi('POST', url, data); +}; diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Black.woff b/src-migrate/styles/fonts/Inter/Inter-Black.woff Binary files differindex a18593a0..a18593a0 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Black.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Black.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Black.woff2 b/src-migrate/styles/fonts/Inter/Inter-Black.woff2 Binary files differindex 68f64c9e..68f64c9e 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Black.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Black.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff b/src-migrate/styles/fonts/Inter/Inter-BlackItalic.woff Binary files differindex b6b01943..b6b01943 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-BlackItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-BlackItalic.woff2 Binary files differindex 1c9c7ca8..1c9c7ca8 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-BlackItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff b/src-migrate/styles/fonts/Inter/Inter-Bold.woff Binary files differindex eaf3d4bf..eaf3d4bf 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Bold.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff2 b/src-migrate/styles/fonts/Inter/Inter-Bold.woff2 Binary files differindex 2846f29c..2846f29c 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Bold.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff b/src-migrate/styles/fonts/Inter/Inter-BoldItalic.woff Binary files differindex 32750761..32750761 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-BoldItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-BoldItalic.woff2 Binary files differindex 0b1fe8e1..0b1fe8e1 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-BoldItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff b/src-migrate/styles/fonts/Inter/Inter-ExtraBold.woff Binary files differindex c2c17ede..c2c17ede 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraBold.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff2 b/src-migrate/styles/fonts/Inter/Inter-ExtraBold.woff2 Binary files differindex c24c2bdc..c24c2bdc 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraBold.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff b/src-migrate/styles/fonts/Inter/Inter-ExtraBoldItalic.woff Binary files differindex c42f7052..c42f7052 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraBoldItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 Binary files differindex 4a81dc79..4a81dc79 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff b/src-migrate/styles/fonts/Inter/Inter-ExtraLight.woff Binary files differindex d0de5f39..d0de5f39 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraLight.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff2 b/src-migrate/styles/fonts/Inter/Inter-ExtraLight.woff2 Binary files differindex f2ea706f..f2ea706f 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraLight.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff b/src-migrate/styles/fonts/Inter/Inter-ExtraLightItalic.woff Binary files differindex 81f1a28e..81f1a28e 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraLightItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 Binary files differindex 9af717ba..9af717ba 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff b/src-migrate/styles/fonts/Inter/Inter-Italic.woff Binary files differindex a806b382..a806b382 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Italic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff2 b/src-migrate/styles/fonts/Inter/Inter-Italic.woff2 Binary files differindex a619fc54..a619fc54 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Italic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Light.woff b/src-migrate/styles/fonts/Inter/Inter-Light.woff Binary files differindex c496464d..c496464d 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Light.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Light.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Light.woff2 b/src-migrate/styles/fonts/Inter/Inter-Light.woff2 Binary files differindex bc4be665..bc4be665 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Light.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Light.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff b/src-migrate/styles/fonts/Inter/Inter-LightItalic.woff Binary files differindex f84a9de3..f84a9de3 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-LightItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-LightItalic.woff2 Binary files differindex 842b2dfc..842b2dfc 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-LightItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff b/src-migrate/styles/fonts/Inter/Inter-Medium.woff Binary files differindex d546843f..d546843f 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Medium.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff2 b/src-migrate/styles/fonts/Inter/Inter-Medium.woff2 Binary files differindex f92498a2..f92498a2 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Medium.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff b/src-migrate/styles/fonts/Inter/Inter-MediumItalic.woff Binary files differindex 459a6568..459a6568 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-MediumItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-MediumItalic.woff2 Binary files differindex 0e3019f4..0e3019f4 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-MediumItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff b/src-migrate/styles/fonts/Inter/Inter-Regular.woff Binary files differindex 62d3a618..62d3a618 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Regular.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff2 b/src-migrate/styles/fonts/Inter/Inter-Regular.woff2 Binary files differindex 6c2b6893..6c2b6893 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Regular.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff b/src-migrate/styles/fonts/Inter/Inter-SemiBold.woff Binary files differindex a815f43a..a815f43a 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff +++ b/src-migrate/styles/fonts/Inter/Inter-SemiBold.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff2 b/src-migrate/styles/fonts/Inter/Inter-SemiBold.woff2 Binary files differindex 611e90c9..611e90c9 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-SemiBold.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff b/src-migrate/styles/fonts/Inter/Inter-SemiBoldItalic.woff Binary files differindex 909e43a9..909e43a9 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-SemiBoldItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 Binary files differindex 545685bd..545685bd 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff b/src-migrate/styles/fonts/Inter/Inter-Thin.woff Binary files differindex 62bc58cd..62bc58cd 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff +++ b/src-migrate/styles/fonts/Inter/Inter-Thin.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff2 b/src-migrate/styles/fonts/Inter/Inter-Thin.woff2 Binary files differindex abbc3a5c..abbc3a5c 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-Thin.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff b/src-migrate/styles/fonts/Inter/Inter-ThinItalic.woff Binary files differindex 700a7f06..700a7f06 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff +++ b/src-migrate/styles/fonts/Inter/Inter-ThinItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff2 b/src-migrate/styles/fonts/Inter/Inter-ThinItalic.woff2 Binary files differindex ab0b2002..ab0b2002 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-ThinItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-italic.var.woff2 b/src-migrate/styles/fonts/Inter/Inter-italic.var.woff2 Binary files differindex b826d5af..b826d5af 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-italic.var.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-italic.var.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-roman.var.woff2 b/src-migrate/styles/fonts/Inter/Inter-roman.var.woff2 Binary files differindex 6a256a06..6a256a06 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter-roman.var.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter-roman.var.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter.var.woff2 b/src-migrate/styles/fonts/Inter/Inter.var.woff2 Binary files differindex 365eedc5..365eedc5 100644 --- a/src-migrate/common/styles/fonts/Inter/Inter.var.woff2 +++ b/src-migrate/styles/fonts/Inter/Inter.var.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/inter.css b/src-migrate/styles/fonts/Inter/inter.css index de6ce273..de6ce273 100644 --- a/src-migrate/common/styles/fonts/Inter/inter.css +++ b/src-migrate/styles/fonts/Inter/inter.css diff --git a/src-migrate/common/styles/globals.css b/src-migrate/styles/globals.css index ea20b247..ea20b247 100644 --- a/src-migrate/common/styles/globals.css +++ b/src-migrate/styles/globals.css diff --git a/src-migrate/common/types/auth.ts b/src-migrate/types/auth.ts index 65fd06c7..02e3623d 100644 --- a/src-migrate/common/types/auth.ts +++ b/src-migrate/types/auth.ts @@ -1,12 +1,12 @@ -import { registerSchema } from '../validations/auth'; -import { OdooApiProps } from './odoo'; +import { registerSchema } from '~/validations/auth'; +import { OdooApiRes } from './odoo'; import { z } from 'zod'; export type AuthProps = { id: number; - parent_id: number; - parent_name: string; - partner_id: number; + parentId: number; + parentName: string; + partnerId: number; name: string; email: string; phone: string; @@ -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/common/types/cart.ts b/src-migrate/types/cart.ts index 3aceeac4..3aceeac4 100644 --- a/src-migrate/common/types/cart.ts +++ b/src-migrate/types/cart.ts 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/common/types/checkout.ts b/src-migrate/types/checkout.ts index dc1365d8..dc1365d8 100644 --- a/src-migrate/common/types/checkout.ts +++ b/src-migrate/types/checkout.ts diff --git a/src-migrate/common/types/nav.ts b/src-migrate/types/nav.ts index ba97b1bf..ba97b1bf 100644 --- a/src-migrate/common/types/nav.ts +++ b/src-migrate/types/nav.ts diff --git a/src-migrate/common/types/odoo.ts b/src-migrate/types/odoo.ts index b34bc667..73a029e9 100644 --- a/src-migrate/common/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-migrate/common/types/pageContent.ts b/src-migrate/types/pageContent.ts index 4361deb7..4361deb7 100644 --- a/src-migrate/common/types/pageContent.ts +++ b/src-migrate/types/pageContent.ts diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts new file mode 100644 index 00000000..08de98e0 --- /dev/null +++ b/src-migrate/types/product.ts @@ -0,0 +1,36 @@ +import { IProductVariantDetail } from './productVariant'; + +export interface IProduct { + id: number; + image: string; + code: string; + display_name: string; + name: string; + weight: number; + qty_sold: number; + stock_total: number; + variant_total: number; + description: string; + categories: { + id: string; + name: string; + }[]; + flash_sale: { + id: string; + remaining_time: number; + tag: string; + }; + lowest_price: { + price: number; + price_discount: number; + discount_percentage: number; + }; + manufacture: { + id: number; + name: string; + }; +} + +export interface IProductDetail extends IProduct { + variants: IProductVariantDetail[]; +} diff --git a/src-migrate/types/productVariant.ts b/src-migrate/types/productVariant.ts new file mode 100644 index 00000000..861b216a --- /dev/null +++ b/src-migrate/types/productVariant.ts @@ -0,0 +1,33 @@ +export interface IProductVariantDetail { + id: number; + image: string; + code: string; + name: string; + weight: number; + is_flashsale: { + remaining_time: number; + is_flashsale: boolean; + }; + price: { + price: number; + price_discount: number; + discount_percentage: number; + }; + manufacture: + | { + id: string; + name: string; + } + | {}; + parent: { + id: string; + name: string; + image: string; + }; + attributes: string[]; +} + +export interface IProductVariantSLA { + qty: number; + sla_date: string; +} diff --git a/src-migrate/common/types/promotion.ts b/src-migrate/types/promotion.ts index 1f8316cf..85190aad 100644 --- a/src-migrate/common/types/promotion.ts +++ b/src-migrate/types/promotion.ts @@ -1,5 +1,3 @@ -import { IProductVariant } from './productVariant'; - export interface IPromotion { id: number; program_id: number; @@ -23,7 +21,18 @@ export interface IPromotion { }[]; } -export interface IProductVariantPromo extends IProductVariant { +export interface IProductVariantPromo { + id: number; + parent_id: number; + display_name: string; + image: string; + name: string; + default_code: string; + price: { + price: number; + discount_percentage: number; + price_discount: number; + }; qty: number; } diff --git a/src-migrate/common/types/promotionProgram.ts b/src-migrate/types/promotionProgram.ts index 205884b6..205884b6 100644 --- a/src-migrate/common/types/promotionProgram.ts +++ b/src-migrate/types/promotionProgram.ts diff --git a/src-migrate/common/types/solr.ts b/src-migrate/types/solr.ts index d231c305..d231c305 100644 --- a/src-migrate/common/types/solr.ts +++ b/src-migrate/types/solr.ts diff --git a/src-migrate/common/validations/auth.ts b/src-migrate/validations/auth.ts index 78fc5e71..78fc5e71 100644 --- a/src-migrate/common/validations/auth.ts +++ b/src-migrate/validations/auth.ts |
