diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2023-11-21 06:10:12 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2023-11-21 06:10:12 +0000 |
| commit | 0fc062268c71d53f8777c358b31e2a540d60d404 (patch) | |
| tree | 0183a00dcfb14583c7dfa80da082c21630afe375 /src-migrate | |
| parent | 6ebe202147269100cd63ef125e877e8f693a27a1 (diff) | |
| parent | 6a6ce21e5a552b0dc6cd541710a87fd0a0fd9d20 (diff) | |
Merged in refactor/all (pull request #116)
Refactor/all
Diffstat (limited to 'src-migrate')
78 files changed, 2141 insertions, 0 deletions
diff --git a/src-migrate/common/components/elements/Modal.tsx b/src-migrate/common/components/elements/Modal.tsx new file mode 100644 index 00000000..8e488a3a --- /dev/null +++ b/src-migrate/common/components/elements/Modal.tsx @@ -0,0 +1,90 @@ +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 { useWindowSize } from "usehooks-ts"; +import clsxm from "~/common/libs/clsxm"; + + +type Props = { + children: React.ReactNode + active: boolean + title?: string + close?: () => void, + className?: string, + mode?: "mobile" | "desktop" +} + +const Modal = ({ + children, + active = false, + title, + close, + className, + mode +}: Props) => { + const router = useRouter() + const { width } = useWindowSize() + const [rendered, setRendered] = useState<boolean>(false) + + mode = mode || width >= 768 ? "desktop" : "mobile" + + useEffect(() => { + setRendered(true) + }, []) + + const modalClassNames = clsxm( + "fixed bg-white max-h-[80vh] overflow-auto p-4 pt-0 z-[60] border-gray_r-6", + { + "left-1/2 -translate-x-1/2 translate-y-1/2 bottom-1/2 w-11/12 md:w-1/4 lg:w-1/3 border rounded-xl": mode === 'desktop', + "left-0 w-full border-t bottom-0 rounded-t-xl": mode === 'mobile' + }, + className + ) + + const variant = { + initial: { bottom: mode === 'desktop' ? '45%' : '-100%', opacity: 0 }, + animate: { bottom: mode === 'desktop' ? '50%' : 0, opacity: 1 }, + exit: { bottom: mode === 'desktop' ? '55%' : '-100%', opacity: 0 }, + transition: { ease: 'linear', duration: 0.25 } + } + + return rendered && ReactDOM.createPortal( + <AnimatePresence key={router.asPath}> + {active && ( + <motion.div + className="overlay" + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + onClick={close} + /> + )} + + {active && ( + <motion.div + {...variant} + className={modalClassNames} + > + <div className='flex justify-between py-5 sticky top-0 bg-white'> + <div className='font-semibold text-h-sm md:text-title-sm'> + {title} + </div> + {close && ( + <button type='button' onClick={close}> + <XMarkIcon className='w-5 stroke-2' /> + </button> + )} + </div> + + {children} + </motion.div> + )} + + </AnimatePresence>, + document.querySelector('body')! + ) +} + +export default Modal
\ No newline at end of file diff --git a/src-migrate/common/components/elements/ReCaptcha.tsx b/src-migrate/common/components/elements/ReCaptcha.tsx new file mode 100644 index 00000000..1bc31d90 --- /dev/null +++ b/src-migrate/common/components/elements/ReCaptcha.tsx @@ -0,0 +1,17 @@ +import ReCAPTCHA, { ReCAPTCHAProps } from "react-google-recaptcha" + +const GOOGLE_RECAPTCHA_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE || '' + +type Props = Omit<ReCAPTCHAProps, 'sitekey'> & { + sitekey?: string; +} + +const ReCaptcha = (props: Props) => { + 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/components/skeleton/PageContentSkeleton.tsx b/src-migrate/common/components/skeleton/PageContentSkeleton.tsx new file mode 100644 index 00000000..bf85cff1 --- /dev/null +++ b/src-migrate/common/components/skeleton/PageContentSkeleton.tsx @@ -0,0 +1,19 @@ +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/constants/menu.ts b/src-migrate/common/constants/menu.ts new file mode 100644 index 00000000..853da507 --- /dev/null +++ b/src-migrate/common/constants/menu.ts @@ -0,0 +1,20 @@ +import { SecondaryNavItemProps } from '../types/nav' + +export const SECONDARY_MENU_ITEMS: SecondaryNavItemProps[] = [ + { + label: 'Semua Brand', + href: '/shop/brands' + }, + { + label: 'Ready Stock', + href: '/shop/search?orderBy=stock' + }, + { + label: 'Blog Indoteknik', + href: 'https://blog.indoteknik.com/' + }, + { + label: 'Indoteknik TV', + href: '/video' + } +] diff --git a/src-migrate/common/libs/auth.ts b/src-migrate/common/libs/auth.ts new file mode 100644 index 00000000..fb4e836a --- /dev/null +++ b/src-migrate/common/libs/auth.ts @@ -0,0 +1,26 @@ +import { deleteCookie, getCookie, setCookie } from 'cookies-next'; +import { AuthProps } from '../types/auth'; + +const COOKIE_KEY = 'auth'; + +export const getAuth = (): AuthProps | boolean => { + const auth = getCookie(COOKIE_KEY); + + if (typeof auth === 'string') { + return JSON.parse(auth); + } + + return false; +}; + +export const setAuth = (user: AuthProps): boolean => { + setCookie(COOKIE_KEY, JSON.stringify(user)); + + return true; +}; + +export const deleteAuth = (): boolean => { + deleteCookie(COOKIE_KEY); + + return true; +}; diff --git a/src-migrate/common/libs/clsxm.ts b/src-migrate/common/libs/clsxm.ts new file mode 100644 index 00000000..0fc10317 --- /dev/null +++ b/src-migrate/common/libs/clsxm.ts @@ -0,0 +1,6 @@ +import clsx, { ClassValue } from 'clsx'; +import { twMerge } from 'tw-merge'; + +export default function clsxm(...classes: ClassValue[]) { + return twMerge(clsx(...classes)); +} diff --git a/src-migrate/common/libs/odooApi.ts b/src-migrate/common/libs/odooApi.ts new file mode 100644 index 00000000..2dbc18d3 --- /dev/null +++ b/src-migrate/common/libs/odooApi.ts @@ -0,0 +1,81 @@ +import axios, { AxiosRequestConfig, Method } from 'axios'; +import { getCookie, setCookie } from 'cookies-next'; +import { getAuth } from './auth'; +import { AuthApiProps, AuthProps } from '../types/auth'; + +const ODOO_HOST = process.env.NEXT_PUBLIC_ODOO_API_HOST as string; + +const renewToken = async () => { + let token = await axios.get(`${ODOO_HOST}/api/token`); + setCookie('token', token.data.result); + + return token.data.result; +}; + +const getToken = async () => { + let token = getCookie('token'); + if (token == undefined) token = await renewToken(); + + return token; +}; + +const maxConnectionAttempt = 15; +let connectionAttempt = 0; + +const odooApi = async ( + method: Method, + url: string, + data = {}, + headers = {} +): Promise<any> => { + connectionAttempt++; + + try { + let token = await getToken(); + const auth = getAuth(); + + let axiosParameter: AxiosRequestConfig = { + method, + url: process.env.NEXT_PUBLIC_ODOO_API_HOST + url, + headers: { Authorization: token, ...headers }, + }; + + if (typeof auth === 'object' && 'token' in auth) { + axiosParameter.headers = { + ...axiosParameter.headers, + Token: auth.token, + }; + } + + if (method.toUpperCase() === 'POST') { + axiosParameter.headers = { + ...axiosParameter.headers, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + + if (Object.keys(data).length > 0) { + axiosParameter.data = new URLSearchParams( + Object.entries(data) + ).toString(); + } + + let res = await axios(axiosParameter); + const authResponse: AuthApiProps = res.data; + + if ( + authResponse.status.code == 401 && + connectionAttempt < maxConnectionAttempt + ) { + await renewToken(); + return odooApi(method, url, data, headers); + } + + return authResponse.result || null; + } catch (error) { + console.log(error); + return null; + } +}; + +export default odooApi; diff --git a/src-migrate/common/stores/useRegisterStore.ts b/src-migrate/common/stores/useRegisterStore.ts new file mode 100644 index 00000000..90ce8a2b --- /dev/null +++ b/src-migrate/common/stores/useRegisterStore.ts @@ -0,0 +1,60 @@ +import { create } from 'zustand'; +import { RegisterProps } from '../types/auth'; +import { registerSchema } from '../validations/auth'; +import { ZodError } from 'zod'; + +type State = { + form: RegisterProps; + errors: { + [key in keyof RegisterProps]?: string; + }; + isCheckedTNC: boolean; + isOpenTNC: boolean; + isValidCaptcha: boolean; +}; + +type Action = { + updateForm: (name: string, value: string) => void; + updateValidCaptcha: (value: boolean) => void; + toggleCheckTNC: () => void; + openTNC: () => void; + closeTNC: () => void; + validate: () => void; +}; + +export const useRegisterStore = create<State & Action>((set, get) => ({ + form: { + company: '', + name: '', + email: '', + password: '', + phone: '', + }, + updateForm: (name, value) => + set((state) => ({ form: { ...state.form, [name]: value } })), + + errors: {}, + validate: () => { + try { + registerSchema.parse(get().form); + set({ errors: {} }); + } catch (error) { + if (error instanceof ZodError) { + const errors: State['errors'] = {}; + error.errors.forEach( + (e) => (errors[e.path[0] as keyof RegisterProps] = e.message) + ); + set({ errors }); + } + } + }, + isCheckedTNC: false, + toggleCheckTNC: () => set((state) => ({ isCheckedTNC: !state.isCheckedTNC })), + + isOpenTNC: false, + openTNC: () => set(() => ({ isOpenTNC: true })), + closeTNC: () => set(() => ({ isOpenTNC: false })), + + isValidCaptcha: false, + updateValidCaptcha: (value) => set(() => ({ isValidCaptcha: value })), +})); diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Black.woff b/src-migrate/common/styles/fonts/Inter/Inter-Black.woff Binary files differnew file mode 100644 index 00000000..a18593a0 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Black.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Black.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Black.woff2 Binary files differnew file mode 100644 index 00000000..68f64c9e --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Black.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff Binary files differnew file mode 100644 index 00000000..b6b01943 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff2 Binary files differnew file mode 100644 index 00000000..1c9c7ca8 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-BlackItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff b/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff Binary files differnew file mode 100644 index 00000000..eaf3d4bf --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff2 Binary files differnew file mode 100644 index 00000000..2846f29c --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Bold.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff Binary files differnew file mode 100644 index 00000000..32750761 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff2 Binary files differnew file mode 100644 index 00000000..0b1fe8e1 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-BoldItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff Binary files differnew file mode 100644 index 00000000..c2c17ede --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff2 Binary files differnew file mode 100644 index 00000000..c24c2bdc --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBold.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff Binary files differnew file mode 100644 index 00000000..c42f7052 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 Binary files differnew file mode 100644 index 00000000..4a81dc79 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraBoldItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff Binary files differnew file mode 100644 index 00000000..d0de5f39 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff2 Binary files differnew file mode 100644 index 00000000..f2ea706f --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLight.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff Binary files differnew file mode 100644 index 00000000..81f1a28e --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 Binary files differnew file mode 100644 index 00000000..9af717ba --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ExtraLightItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff b/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff Binary files differnew file mode 100644 index 00000000..a806b382 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff2 Binary files differnew file mode 100644 index 00000000..a619fc54 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Italic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Light.woff b/src-migrate/common/styles/fonts/Inter/Inter-Light.woff Binary files differnew file mode 100644 index 00000000..c496464d --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Light.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Light.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Light.woff2 Binary files differnew file mode 100644 index 00000000..bc4be665 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Light.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff Binary files differnew file mode 100644 index 00000000..f84a9de3 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff2 Binary files differnew file mode 100644 index 00000000..842b2dfc --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-LightItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff b/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff Binary files differnew file mode 100644 index 00000000..d546843f --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff2 Binary files differnew file mode 100644 index 00000000..f92498a2 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Medium.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff Binary files differnew file mode 100644 index 00000000..459a6568 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff2 Binary files differnew file mode 100644 index 00000000..0e3019f4 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-MediumItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff b/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff Binary files differnew file mode 100644 index 00000000..62d3a618 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff2 Binary files differnew file mode 100644 index 00000000..6c2b6893 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Regular.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff b/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff Binary files differnew file mode 100644 index 00000000..a815f43a --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff2 Binary files differnew file mode 100644 index 00000000..611e90c9 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-SemiBold.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff Binary files differnew file mode 100644 index 00000000..909e43a9 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 Binary files differnew file mode 100644 index 00000000..545685bd --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-SemiBoldItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff b/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff Binary files differnew file mode 100644 index 00000000..62bc58cd --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff2 Binary files differnew file mode 100644 index 00000000..abbc3a5c --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-Thin.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff b/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff Binary files differnew file mode 100644 index 00000000..700a7f06 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff diff --git a/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff2 Binary files differnew file mode 100644 index 00000000..ab0b2002 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-ThinItalic.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-italic.var.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-italic.var.woff2 Binary files differnew file mode 100644 index 00000000..b826d5af --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-italic.var.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter-roman.var.woff2 b/src-migrate/common/styles/fonts/Inter/Inter-roman.var.woff2 Binary files differnew file mode 100644 index 00000000..6a256a06 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter-roman.var.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/Inter.var.woff2 b/src-migrate/common/styles/fonts/Inter/Inter.var.woff2 Binary files differnew file mode 100644 index 00000000..365eedc5 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/Inter.var.woff2 diff --git a/src-migrate/common/styles/fonts/Inter/inter.css b/src-migrate/common/styles/fonts/Inter/inter.css new file mode 100644 index 00000000..de6ce273 --- /dev/null +++ b/src-migrate/common/styles/fonts/Inter/inter.css @@ -0,0 +1,199 @@ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url('Inter-Thin.woff2?v=3.19') format('woff2'), + url('Inter-Thin.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url('Inter-ThinItalic.woff2?v=3.19') format('woff2'), + url('Inter-ThinItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url('Inter-ExtraLight.woff2?v=3.19') format('woff2'), + url('Inter-ExtraLight.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url('Inter-ExtraLightItalic.woff2?v=3.19') format('woff2'), + url('Inter-ExtraLightItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url('Inter-Light.woff2?v=3.19') format('woff2'), + url('Inter-Light.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url('Inter-LightItalic.woff2?v=3.19') format('woff2'), + url('Inter-LightItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('Inter-Regular.woff2?v=3.19') format('woff2'), + url('Inter-Regular.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('Inter-Italic.woff2?v=3.19') format('woff2'), + url('Inter-Italic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('Inter-Medium.woff2?v=3.19') format('woff2'), + url('Inter-Medium.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url('Inter-MediumItalic.woff2?v=3.19') format('woff2'), + url('Inter-MediumItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('Inter-SemiBold.woff2?v=3.19') format('woff2'), + url('Inter-SemiBold.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url('Inter-SemiBoldItalic.woff2?v=3.19') format('woff2'), + url('Inter-SemiBoldItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('Inter-Bold.woff2?v=3.19') format('woff2'), + url('Inter-Bold.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url('Inter-BoldItalic.woff2?v=3.19') format('woff2'), + url('Inter-BoldItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url('Inter-ExtraBold.woff2?v=3.19') format('woff2'), + url('Inter-ExtraBold.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url('Inter-ExtraBoldItalic.woff2?v=3.19') format('woff2'), + url('Inter-ExtraBoldItalic.woff?v=3.19') format('woff'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url('Inter-Black.woff2?v=3.19') format('woff2'), + url('Inter-Black.woff?v=3.19') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url('Inter-BlackItalic.woff2?v=3.19') format('woff2'), + url('Inter-BlackItalic.woff?v=3.19') format('woff'); +} + +/* ------------------------------------------------------- +Variable font. +Usage: + + html { font-family: 'Inter', sans-serif; } + @supports (font-variation-settings: normal) { + html { font-family: 'Inter var', sans-serif; } + } +*/ +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: normal; + font-named-instance: 'Regular'; + src: url('Inter-roman.var.woff2?v=3.19') format('woff2'); +} +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: italic; + font-named-instance: 'Italic'; + src: url('Inter-italic.var.woff2?v=3.19') format('woff2'); +} + +/* -------------------------------------------------------------------------- +[EXPERIMENTAL] Multi-axis, single variable font. + +Slant axis is not yet widely supported (as of February 2019) and thus this +multi-axis single variable font is opt-in rather than the default. + +When using this, you will probably need to set font-variation-settings +explicitly, e.g. + + * { font-variation-settings: "slnt" 0deg } + .italic { font-variation-settings: "slnt" 10deg } + +*/ +@font-face { + font-family: 'Inter var experimental'; + font-weight: 100 900; + font-display: swap; + font-style: oblique 0deg 10deg; + src: url('Inter.var.woff2?v=3.19') format('woff2'); +} diff --git a/src-migrate/common/styles/globals.css b/src-migrate/common/styles/globals.css new file mode 100644 index 00000000..ea20b247 --- /dev/null +++ b/src-migrate/common/styles/globals.css @@ -0,0 +1,674 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +* { + -webkit-tap-highlight-color: transparent; +} + +html, +body { + @apply w-screen + text-body-2 + text-gray_r-12 + bg-gray_r-1 + overflow-x-clip; +} + +#__next main { + @apply min-h-screen; +} + +button { + @apply block; +} + +@layer base { + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + + input[type='number'] { + -moz-appearance: textfield; + } +} + +@layer components { + .badge-red, + .badge-solid-red, + .badge-gray, + .badge-yellow, + .badge-blue, + .badge-green, + .badge-solid-green { + @apply text-[11px] + leading-none + font-medium + px-1 + py-1 + rounded + w-fit; + } + + .badge-red { + @apply bg-danger-100 + text-danger-600; + } + + .badge-solid-red { + @apply bg-danger-500 + text-white; + } + + .badge-gray { + @apply bg-gray_r-5 + text-gray_r-10; + } + + .badge-yellow { + @apply bg-warning-500 + text-warning-900; + } + + .badge-blue { + @apply bg-blue-200 + text-blue-600; + } + + .badge-green { + @apply bg-success-100 + text-success-600; + } + + .badge-solid-green { + @apply bg-success-500 + text-white; + } + + .form-label { + @apply font-medium + block; + } + + .form-input { + @apply p-3 + rounded + border + text-gray_r-12 + border-gray_r-7 + !bg-white + bg-transparent + w-full + leading-none + focus:outline-none + focus:border-warning-500 + disabled:bg-gray_r-5; + } + + .form-input[aria-invalid] { + @apply border-danger-500 + focus:border-danger-500; + } + + .form-input[type='file'] { + @apply py-2; + } + + .btn-yellow, + .btn-light, + .btn-red, + .btn-solid-red { + @apply block + w-fit + py-3 + px-6 + rounded + border + text-center + font-medium + ease-linear + duration-150; + } + + .btn-yellow { + @apply bg-warning-500 + border-warning-500 + hover:bg-warning-500/80 + disabled:text-gray_r-10 + disabled:bg-warning-200 + disabled:border-warning-200; + } + + .btn-red { + @apply bg-danger-100 + border-danger-300 + text-danger-500 + disabled:text-danger-400 + disabled:bg-danger-200; + } + + .btn-solid-red { + @apply bg-danger-500 + border-danger-500 + text-gray_r-1 + hover:bg-danger-500/80 + disabled:text-gray_r-1 + disabled:bg-danger-200 + disabled:border-danger-200; + } + + .btn-light { + @apply bg-gray_r-3 + border-gray_r-6 + disabled:text-gray_r-10 + disabled:bg-gray_r-6; + } + + .product-card { + @apply w-full + h-full + border + border-gray_r-3 + shadow + bg-white + rounded + relative + flex + flex-col; + } + + .product-card__image { + @apply w-full + h-[160px] + object-contain + object-center + border-b + border-gray_r-6; + } + + .product-card__content { + @apply p-2 + pb-3 + flex-1; + } + + .product-card__title { + @apply text-caption-1 + text-gray_r-12 + leading-5; + } + + .product-card__brand { + @apply text-caption-1 + mb-1 + block; + } + + .product__description { + @apply text-gray_r-12/90; + } + + .product__description br { + @apply block my-1; + } + + .product__description b { + @apply font-semibold; + } +} + +@layer utilities { + .wrap-line-ellipsis-1, + .wrap-line-ellipsis-2, + .wrap-line-ellipsis-3 { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .wrap-line-ellipsis-1 { + -webkit-line-clamp: 1; + } + + .wrap-line-ellipsis-2 { + -webkit-line-clamp: 2; + } + + .wrap-line-ellipsis-3 { + -webkit-line-clamp: 3; + } +} + +.menu-wrapper { + @apply fixed + top-0 + left-0 + bg-white + w-[80%] + h-full + z-[60] + overflow-y-auto + translate-x-[-100%] + ease-linear + duration-150; +} + +.menu-wrapper.active { + @apply translate-x-0; +} + +.overlay { + @apply fixed + top-0 + left-0 + w-full + h-full + z-[55] + bg-gray_r-12/40; +} + +.sticky-header { + @apply px-4 + py-3 + bg-gray_r-1/90 + backdrop-blur-lg + sticky + top-0 + border-b + border-gray_r-7 + z-50; +} + +.content-container { + @apply max-w-full + overflow-x-hidden; +} + +#indoteknik_toast { + @apply fixed + bottom-4 + translate-y-[200%] + left-[50%] + translate-x-[-50%] + z-[100] + flex + items-center + p-4 + mb-4 + w-[90%] + text-gray-500 + bg-white + border + border-gray-300 + rounded-lg + shadow + ease-linear + duration-300; +} + +#indoteknik_toast.active { + @apply translate-y-0; +} + +.category-menu { + @apply hidden; +} + +.swiper-slide { + @apply !h-auto; +} + +.lazy-load-image-background { + @apply !block + w-full; +} + +.swiper-pagination-bullet-active { + @apply !bg-danger-500; +} + +.pagination { + @apply flex + justify-center + gap-x-1; +} + +.pagination-item { + @apply p-1 + flex + justify-center + items-center + w-10 + rounded + ease-linear + duration-150 + border + border-gray_r-6 + bg-gray_r-3 + hover:bg-gray_r-5 + text-gray_r-12; +} + +.pagination-item--active { + @apply border-warning-500 + bg-warning-500 + hover:bg-warning-500; +} + +.pagination-dots { + @apply p-1 + flex + justify-center + items-end + w-10 + rounded + ease-linear + bg-gray_r-3 + text-caption-2; +} + +.idt-transition { + @apply transition-all + ease-out + duration-200; +} + +.form-select__placeholder { + @apply !text-gray_r-9; +} + +.form-select__control { + @apply !shadow-none + !border-gray_r-7; +} + +.form-select__control--menu-is-open { + @apply !border-warning-500; +} + +.table-specification { + @apply max-h-[500px] overflow-y-auto border border-gray_r-6; +} + +.table-specification > table { + @apply table-auto + border-collapse + w-full; +} + +.table-specification > table > thead > tr > th:last-child { + @apply w-3/12; +} + +.table-specification > table > thead { + @apply sticky top-0 border-b; +} + +.table-specification > table > thead > tr { + @apply bg-gray_r-1/80 backdrop-blur-lg; +} + +.table-specification th { + @apply font-semibold; +} + +.table-specification th, +.table-specification td { + @apply p-4 text-center; +} + +.table-specification > table > tbody > tr { + @apply odd:bg-gray_r-3 even:bg-gray_r-1; +} + +.table-cart, +.table-checkout { + @apply w-full + table-auto + border-collapse; +} + +.table-cart tr, +.table-checkout tr { + @apply border-y + border-gray_r-6 + first:border-t-0; +} + +.table-cart th, +.table-cart td, +.table-checkout th, +.table-checkout td { + @apply py-4 + px-3 + text-center + text-gray_r-12/90; +} + +.table-cart th, +.table-cart td { + @apply first:w-12; +} + +.table-cart th, +.table-checkout th { + @apply font-medium; +} + +.table-data { + @apply w-full + table-auto + border-collapse; +} + +.table-data thead tr { + @apply bg-gray_r-3; +} + +.table-data thead th { + @apply font-medium whitespace-nowrap; +} + +.table-data thead th, +.table-data tbody td { + @apply px-3 + py-4 + text-center; +} + +.table-data tbody td { + @apply text-gray_r-12/90; +} + +.table-data tbody tr { + @apply border-b + border-gray_r-6; +} + +.navbar-user-dropdown-button { + @apply flex-1 + flex + gap-x-2 + p-4 + items-center + bg-danger-500 + font-medium + !text-gray_r-1 + rounded-none + rounded-t-xl; +} + +.navbar-user-dropdown-button span { + @apply line-clamp-1; +} + +.navbar-user-dropdown-wrapper a, +.navbar-user-dropdown-wrapper button { + @apply text-gray_r-12/80 hover:bg-gray_r-5 font-medium py-2 px-4 w-full text-left; +} + +.navbar-user-dropdown { + @apply bg-white + border + border-gray_r-6 + py-2 + w-full + shadow; +} + +.category-mega-box-wrapper, +.navbar-user-dropdown-wrapper { + @apply absolute + opacity-0 + left-0 + top-[125%] + flex + w-full + z-10 + transition-all + ease-in + duration-200 + pointer-events-none + text-left; +} + +.category-mega-box-wrapper.show, +.navbar-user-dropdown-button:hover ~ .navbar-user-dropdown-wrapper, +.navbar-user-dropdown-wrapper:hover { + @apply top-[100%] + opacity-100 + pointer-events-auto; +} + +.category-mega-box { + @apply relative + py-2 + border + border-t-0 + bg-white + border-gray_r-6 + h-full + w-full; +} + +.category-mega-box > div { + @apply text-gray_r-12/80; +} + +.category-mega-box > div:hover .category-mega-box__parent { + @apply bg-gray_r-5; +} + +.category-mega-box > div:hover .category-mega-box__child-wrapper { + @apply opacity-100 + top-0 + pointer-events-auto; +} + +.category-mega-box .category-mega-box__parent { + @apply py-2.5 + px-4 + text-gray_r-12/80 + font-normal; +} + +.category-mega-box__child-wrapper { + @apply absolute + left-[100%] + top-12 + w-[40vw] + bg-gray_r-1/90 + backdrop-blur-md + border + border-gray_r-6 + p-6 + opacity-0 + h-full + transition-all + ease-in + duration-200 + pointer-events-none + z-50; +} + +.category-mega-box__child-one { + @apply text-gray_r-12/80 + hover:text-danger-500 + transition-colors + ease-linear + duration-100 + font-semibold; +} + +.category-mega-box__child-two { + @apply text-gray_r-12/80 + hover:text-danger-500 + transition-colors + ease-linear + duration-100 + font-normal; +} + +@keyframes page-loader { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.page-loader { + animation-name: page-loader; + animation-duration: 1000ms; + animation-delay: 50ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; +} + +@keyframes shake { + 0% { + transform: translateX(0); + } + 10%, + 90% { + transform: translateX(-10px); + } + 20%, + 80% { + transform: translateX(10px); + } + 30%, + 50%, + 70% { + transform: translateX(-10px); + } + 40%, + 60% { + transform: translateX(10px); + } + 100% { + transform: translateX(0); + } +} + +.blink-color-flash-sale { + @apply text-body-1 md:text-title-sm; + transform: rotateY(180deg) rotateZ(120deg); + animation-name: blink-color-flash-sale; + animation-duration: 300ms; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +@keyframes blink-color-flash-sale { + from { + @apply text-danger-500; + } + to { + @apply text-warning-500; + } +} diff --git a/src-migrate/common/types/auth.ts b/src-migrate/common/types/auth.ts new file mode 100644 index 00000000..65fd06c7 --- /dev/null +++ b/src-migrate/common/types/auth.ts @@ -0,0 +1,58 @@ +import { registerSchema } from '../validations/auth'; +import { OdooApiProps } from './odoo'; +import { z } from 'zod'; + +export type AuthProps = { + id: number; + parent_id: number; + parent_name: string; + partner_id: number; + name: string; + email: string; + phone: string; + mobile: string; + external: boolean; + company: boolean; + pricelist: string | null; + token: string; +}; + +export type AuthApiProps = OdooApiProps & { result: AuthProps }; + +export type RegisterProps = z.infer<typeof registerSchema>; + +export type RegisterResApiProps = { + register: boolean; + reason: 'EMAIL_USED' | 'NOT_ACTIVE' | null; +}; + +type ActivationResProps = { + activation: boolean; + user: AuthProps | null; +}; + +export type ActivationTokenProps = { + token: string; +}; + +export type ActivationTokenResApiProps = ActivationResProps & { + reason: 'INVALID_TOKEN' | null; +}; + +export type ActivationOtpProps = { + email: string; + otp: string; +}; + +export type ActivationOtpResApiProps = ActivationResProps & { + reason: 'INVALID_OTP' | null; +}; + +export type ActivationReqProps = { + email: string; +}; + +export type ActivationReqResApiProps = { + activation_request: boolean; + reason: 'NOT_FOUND' | 'ACTIVE' | null; +}; diff --git a/src-migrate/common/types/nav.ts b/src-migrate/common/types/nav.ts new file mode 100644 index 00000000..ba97b1bf --- /dev/null +++ b/src-migrate/common/types/nav.ts @@ -0,0 +1,4 @@ +export type SecondaryNavItemProps = { + label: string + href: string +} diff --git a/src-migrate/common/types/odoo.ts b/src-migrate/common/types/odoo.ts new file mode 100644 index 00000000..b34bc667 --- /dev/null +++ b/src-migrate/common/types/odoo.ts @@ -0,0 +1,6 @@ +export type OdooApiProps = { + status: { + code: number; + description: string; + }; +}; diff --git a/src-migrate/common/types/pageContent.ts b/src-migrate/common/types/pageContent.ts new file mode 100644 index 00000000..4361deb7 --- /dev/null +++ b/src-migrate/common/types/pageContent.ts @@ -0,0 +1,5 @@ +export type PageContentProps = { + id: number; + url_path: string; + content: string; +} | null; diff --git a/src-migrate/common/validations/auth.ts b/src-migrate/common/validations/auth.ts new file mode 100644 index 00000000..78fc5e71 --- /dev/null +++ b/src-migrate/common/validations/auth.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +export const registerSchema = z.object({ + name: z.string().min(1, { message: 'Nama harus diisi' }), + email: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + password: z.string().min(6, { message: 'Password minimal 6 karakter' }), + company: z.string().optional(), + phone: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), +}); diff --git a/src-migrate/images/LOGO-INDOTEKNIK-GEAR-GREY.png b/src-migrate/images/LOGO-INDOTEKNIK-GEAR-GREY.png Binary files differnew file mode 100644 index 00000000..2ab07ff5 --- /dev/null +++ b/src-migrate/images/LOGO-INDOTEKNIK-GEAR-GREY.png diff --git a/src-migrate/images/LOGO-INDOTEKNIK-GEAR.png b/src-migrate/images/LOGO-INDOTEKNIK-GEAR.png Binary files differnew file mode 100644 index 00000000..89f749ec --- /dev/null +++ b/src-migrate/images/LOGO-INDOTEKNIK-GEAR.png diff --git a/src-migrate/images/logo.png b/src-migrate/images/logo.png Binary files differnew file mode 100644 index 00000000..dccc0cd6 --- /dev/null +++ b/src-migrate/images/logo.png diff --git a/src-migrate/modules/account-activation/components/FormEmail.tsx b/src-migrate/modules/account-activation/components/FormEmail.tsx new file mode 100644 index 00000000..ec300ba4 --- /dev/null +++ b/src-migrate/modules/account-activation/components/FormEmail.tsx @@ -0,0 +1,97 @@ +import { Alert, AlertIcon } from "@chakra-ui/react" +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 { activationReq } from "~/services/auth" + +const FormEmail = () => { + const router = useRouter() + const { query } = router + const [active, setActive] = useState<boolean>(false) + const [isBtnDisabled, setIsBtnDisabled] = useState<boolean>(true) + const [email, setEmail] = useState<string>('') + + const { form } = useRegisterStore() + + useEffect(() => { + if (form) setEmail(form?.email) + }, [form]) + + useEffect(() => { + setIsBtnDisabled(email === '') + }, [email]) + + useEffect(() => { + setActive(query?.activation === 'email') + }, [query.activation]) + + const mutation = useMutation({ + mutationFn: (data: ActivationReqProps) => activationReq(data) + }) + + const handleSubmit = (e: ChangeEvent<HTMLFormElement>) => { + e.preventDefault() + mutation.data = undefined + mutation.mutate({ email }) + } + + useEffect(() => { + if (mutation.data?.activation_request === true) { + const urlParams = new URLSearchParams({ + activation: 'otp', + email, + redirect: (router.query?.redirect || '/') as string + }) + router.push(`${router.route}?${urlParams}`) + } + }, [mutation.data?.activation_request, router, email]) + + return ( + <Modal active={active} mode="desktop" close={() => router.push(router.route)} title="Aktivasi Akun"> + <form onSubmit={handleSubmit} className="py-3 flex flex-col items-center gap-y-4"> + { + mutation.data !== undefined && + !mutation.data?.activation_request && + ( + <Alert status="warning"> + <AlertIcon /> + {mutation.data?.reason === 'ACTIVE' && 'Akun sudah aktif.'} + {mutation.data?.reason === 'NOT_FOUND' && 'Akun tidak ditemukan.'} + + {mutation.data?.reason === 'ACTIVE' && ( + <Link href="/login" className="ml-1 text-yellow-700 underline">Klik untuk masuk akun anda</Link> + )} + + {mutation.data?.reason === 'NOT_FOUND' && ( + <Link href="/register" className="ml-1 text-yellow-700 underline">Klik untuk daftar akun baru</Link> + )} + </Alert> + ) + } + + <input + type="text" + className="form-input w-full" + placeholder="Masukan alamat email anda" + autoComplete="email" + value={email} + onChange={(e) => setEmail(e.target.value)} + autoFocus + /> + <button + type="submit" + className="btn-yellow w-full" + disabled={isBtnDisabled || mutation.isLoading} + > + {mutation.isLoading ? 'Loading...' : 'Kirim Aktivasi'} + </button> + </form> + </Modal> + ) +} + +export default FormEmail
\ No newline at end of file diff --git a/src-migrate/modules/account-activation/components/FormOTP.tsx b/src-migrate/modules/account-activation/components/FormOTP.tsx new file mode 100644 index 00000000..6815a088 --- /dev/null +++ b/src-migrate/modules/account-activation/components/FormOTP.tsx @@ -0,0 +1,125 @@ +import { Button, HStack, PinInput, PinInputField, Spinner } from "@chakra-ui/react" +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 { activationReq, activationUserOTP } from "~/services/auth" + +const FormOTP = () => { + const router = useRouter() + const { query } = router + const [active, setActive] = useState<boolean>(false) + const [email, setEmail] = useState<string>('') + const [otp, setOtp] = useState<string>('') + const [count, { startCountdown, resetCountdown }] = useCountdown({ + countStart: 60 * 1, + intervalMs: 1000, + }) + + useEffect(() => { + setEmail((query?.email || '') as string) + }, [query?.email]) + + useEffect(() => { + setActive(query?.activation === 'otp') + }, [query.activation]) + + useEffect(() => { + !!active && startCountdown() + }, [active, startCountdown]) + + const mutationActivationReq = useMutation({ + mutationFn: (data: ActivationReqProps) => activationReq(data), + onSuccess: () => { + resetCountdown() + startCountdown() + } + }) + + const mutationActivation = useMutation({ + mutationFn: (data: ActivationOtpProps) => activationUserOTP(data) + }) + + useEffect(() => { + if (otp.length === 4 && !mutationActivation.data?.activation) { + mutationActivation.mutate({ email, otp }) + } + //eslint-disable-next-line + }, [otp]) + + useEffect(() => { + if (mutationActivation.data?.user) { + setAuth(mutationActivation.data.user) + router.push((query?.redirect || '/') as string) + } + }, [mutationActivation.data, router, query.redirect]) + + return ( + <Modal active={active} className="w-10/12 md:w-fit px-10" close={() => setActive(false)} mode="desktop"> + <div className="pb-8 flex flex-col items-center"> + <div className="text-title-sm font-medium mb-4">Masukan Kode OTP</div> + <HStack> + <PinInput size='lg' otp onChange={(val) => setOtp(val)}> + <PinInputField autoFocus /> + <PinInputField /> + <PinInputField /> + <PinInputField /> + </PinInput> + </HStack> + <div className="mt-4 text-center"> + {mutationActivation.isLoading && <Spinner size='sm' />} + + {!mutationActivation.isLoading && + mutationActivation.data?.reason === 'INVALID_OTP' && + ( + <span className="text-red-700">Mohon maaf kode OTP yand anda masukan salah</span> + ) + } + </div> + + {!mutationActivation.data?.activation && ( + <div className="mt-4 text-center text-gray-700"> + Kode verifikasi telah dikirimkan ke alamat email <span className="font-medium">{email}</span>. Silakan periksa kotak masuk atau folder spam. + </div> + )} + + <div className="mt-4 flex flex-col items-center"> + {!mutationActivation.data?.activation && ( + <> + {count > 0 && timeFormat(count)} + + {!mutationActivation.data?.activation && count == 0 && ( + <> + <div className="mb-3">Belum menerima kode apapun?</div> + + <Button + onClick={() => mutationActivationReq.mutate({ email })} + disabled={mutationActivationReq.isLoading} + > + {mutationActivationReq.isLoading ? <Spinner size='sm' /> : 'Kirim Ulang'} + </Button> + </> + )} + </> + )} + + </div> + </div> + </Modal> + ) +} + +const timeFormat = (time: number): string => { + const minute = Math.floor(time / 60); + const second = time % 60; + + const minuteFormat = minute < 10 ? `0${minute}` : `${minute}` + const secondFormat = second < 10 ? `0${second}` : `${second}` + + return `${minuteFormat}:${secondFormat}` +} + +export default FormOTP
\ No newline at end of file diff --git a/src-migrate/modules/account-activation/components/FormToken.tsx b/src-migrate/modules/account-activation/components/FormToken.tsx new file mode 100644 index 00000000..b68b244f --- /dev/null +++ b/src-migrate/modules/account-activation/components/FormToken.tsx @@ -0,0 +1,75 @@ +import { Alert, AlertDescription, AlertIcon, AlertTitle, Spinner } from "@chakra-ui/react" +import { useRouter } from "next/router" +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 { activationUserToken } from "~/services/auth" +import { setAuth } from "~/common/libs/auth" + +const FormToken = () => { + const router = useRouter() + const { query } = router + const [active, setActive] = useState<boolean>(false) + + const mutation = useMutation({ + mutationFn: (data: ActivationTokenProps) => activationUserToken(data), + }) + + useEffect(() => { + if (query?.activation === 'token' && typeof query?.token === 'string') { + mutation.mutate({ token: query.token }) + setActive(true) + } + //eslint-disable-next-line + }, [query.activation, query.token]) + + useEffect(() => { + if (mutation.data?.user) { + setAuth(mutation.data.user) + router.push((query?.redirect || '/') as string) + } + }, [mutation.data, router, query.redirect]) + + return ( + <Modal active={active} mode="desktop"> + <div className="pb-6 flex flex-col items-center gap-y-6"> + {mutation.isLoading && ( + <> + <Spinner size='lg' borderWidth='3px' /> + <div className="text-h-sm">Mohon tunggu sedang memverifikasi akun</div> + </> + )} + + {!mutation.isLoading && !mutation.data?.activation && ( + <Alert + status={mutation.data?.activation ? 'success' : 'error'} + flexDirection="column" + alignItems="center" + justifyContent="center" + textAlign="center" + height="200px" + bg="transparent" + > + <AlertIcon boxSize="40px" mr={0} /> + <AlertTitle className="mt-4 mb-1 text-h-sm"> + Aktivasi akun gagal + </AlertTitle> + <AlertDescription maxWidth="sm"> + {mutation.data?.reason === 'INVALID_TOKEN' && ( + <> + Token sudah kadaluwarsa, silahkan coba kembali. + <Link href='/register?activation=email' className="block mt-8 text-red-700 underline">Aktivasi Akun</Link> + </> + )} + </AlertDescription> + </Alert> + )} + </div> + </Modal> + ) +} + +export default FormToken
\ No newline at end of file diff --git a/src-migrate/modules/account-activation/index.tsx b/src-migrate/modules/account-activation/index.tsx new file mode 100644 index 00000000..97c96953 --- /dev/null +++ b/src-migrate/modules/account-activation/index.tsx @@ -0,0 +1,16 @@ +import { useRouter } from "next/router" +import FormToken from "./components/FormToken" +import FormEmail from "./components/FormEmail" +import FormOTP from "./components/FormOTP" + +const AccountActivation = () => { + return ( + <> + <FormEmail /> + <FormToken /> + <FormOTP /> + </> + ) +} + +export default AccountActivation
\ No newline at end of file diff --git a/src-migrate/modules/header/components/HeaderDesktop.tsx b/src-migrate/modules/header/components/HeaderDesktop.tsx new file mode 100644 index 00000000..3860bded --- /dev/null +++ b/src-migrate/modules/header/components/HeaderDesktop.tsx @@ -0,0 +1,82 @@ +import Logo from "~/images/logo.png"; +import { DocumentCheckIcon, HeartIcon } from "@heroicons/react/24/outline"; + +import Image from 'next/image' +import Link from 'next/link' + +// Components +import SearchBar from "./SearchBar"; + +// Constants +import { SECONDARY_MENU_ITEMS } from "~/common/constants/menu"; + +const LOGO_WIDTH = 210; +const LOGO_HEIGHT = LOGO_WIDTH / 3; + +const HeaderDesktop = () => { + return ( + <header> + <nav className="pt-6 sticky top-0 z-50 bg-white border-b-2 border-danger-500"> + <div className="container flex items-center gap-x-6"> + <Link href='/'> + <Image src={Logo} alt="Logo Indoteknik.com" width={LOGO_WIDTH} height={LOGO_HEIGHT} /> + </Link> + + <SearchBar /> + + <div className="flex gap-x-4 items-center"> + <Link + target='_blank' + rel='noreferrer' + href='/my/transactions' + className='flex items-center gap-x-2 !text-gray-900' + > + <DocumentCheckIcon className='w-7' /> + Daftar<br />Quotation + </Link> + + <Link + target='_blank' + rel='noreferrer' + href='/my/wishlist' + className='flex items-center gap-x-2 !text-gray-900' + > + <HeartIcon className='w-7' /> + Wishlist + </Link> + + <a + href={''} + target='_blank' + rel='noreferrer' + className='flex items-center gap-x-1 !text-gray_r-12/80' + > + <Image src='/images/socials/Whatsapp-2.png' alt='Whatsapp' width={48} height={48} /> + <div> + <div className='font-semibold'>Whatsapp</div> + 0812 8080 622 (Chat) + </div> + </a> + </div> + </div> + + <div className="container mt-6 flex"> + <button type="button" className="w-3/12 p-4 font-semibold border border-gray_r-6 rounded-t-xl flex items-center relative"> + Kategori + </button> + + <nav className="w-6/12 flex px-1 divide-x divide-gray-200"> + {SECONDARY_MENU_ITEMS.map((item, index) => ( + <Link key={index} href={item.href} target="_blank" rel="noreferrer" className="font-medium text-center p-4 flex-1 !text-gray-800 hover:bg-gray-100 transition-all"> + {item.label} + </Link> + ))} + </nav> + + </div> + </nav> + </header> + ) +} + +export default HeaderDesktop
\ No newline at end of file diff --git a/src-migrate/modules/header/components/HeaderMobile.tsx b/src-migrate/modules/header/components/HeaderMobile.tsx new file mode 100644 index 00000000..626f30d7 --- /dev/null +++ b/src-migrate/modules/header/components/HeaderMobile.tsx @@ -0,0 +1,7 @@ +const HeaderMobile = () => { + return ( + <div>HeaderMobile</div> + ) +} + +export default HeaderMobile
\ No newline at end of file diff --git a/src-migrate/modules/header/components/SearchBar.tsx b/src-migrate/modules/header/components/SearchBar.tsx new file mode 100644 index 00000000..ec17abe8 --- /dev/null +++ b/src-migrate/modules/header/components/SearchBar.tsx @@ -0,0 +1,24 @@ + +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline' + +const SearchBar = () => { + return ( + <form className="flex-1 flex items-center"> + <input + type="text" + className="form-input p-3 rounded-r-none border-r-0 border-gray-300 focus:border-gray-300" + placeholder="Ketik nama / part number / merk" + /> + + <button + type="submit" + className="rounded-r border border-l-0 border-gray-300 px-2 py-2.5" + > + <MagnifyingGlassIcon className='w-6' /> + </button> + + </form> + ) +} + +export default SearchBar
\ No newline at end of file diff --git a/src-migrate/modules/header/index.tsx b/src-migrate/modules/header/index.tsx new file mode 100644 index 00000000..5c0e2933 --- /dev/null +++ b/src-migrate/modules/header/index.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { useWindowSize } from "usehooks-ts" + +import HeaderDesktop from './components/HeaderDesktop' +import HeaderMobile from './components/HeaderMobile' + +const Header = () => { + const { width } = useWindowSize() + + return width > 768 ? <HeaderDesktop /> : <HeaderMobile /> +} + +export default Header
\ No newline at end of file diff --git a/src-migrate/modules/home/components/Home.tsx b/src-migrate/modules/home/components/Home.tsx new file mode 100644 index 00000000..5d3bf104 --- /dev/null +++ b/src-migrate/modules/home/components/Home.tsx @@ -0,0 +1,15 @@ +import VerticalBanner from "./VerticalBanner" + +const Home = () => { + return ( + <> + <div className="container grid"> + <div className="col-span-2"> + <VerticalBanner /> + </div> + </div> + </> + ) +} + +export default Home
\ No newline at end of file diff --git a/src-migrate/modules/home/components/VerticalBanner.tsx b/src-migrate/modules/home/components/VerticalBanner.tsx new file mode 100644 index 00000000..57328037 --- /dev/null +++ b/src-migrate/modules/home/components/VerticalBanner.tsx @@ -0,0 +1,7 @@ +const VerticalBanner = () => { + return ( + <div>VerticalBanner</div> + ) +} + +export default VerticalBanner
\ No newline at end of file diff --git a/src-migrate/modules/home/index.tsx b/src-migrate/modules/home/index.tsx new file mode 100644 index 00000000..6993d9cf --- /dev/null +++ b/src-migrate/modules/home/index.tsx @@ -0,0 +1,3 @@ +import Home from "./components/Home"; + +export default Home
\ No newline at end of file diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx new file mode 100644 index 00000000..cbd58633 --- /dev/null +++ b/src-migrate/modules/page-content/index.tsx @@ -0,0 +1,22 @@ +import { useQuery } from "react-query" +import PageContentSkeleton from "~/common/components/skeleton/PageContentSkeleton" +import { PageContentProps } from "~/common/types/pageContent" +import { getPageContent } from "~/services/pageContent" + +type Props = { + path: string +} + +const PageContent = ({ path }: Props) => { + const { data, isLoading } = useQuery<PageContentProps>(`page-content:${path}`, async () => await getPageContent({ path })) + + if (isLoading) { + return <PageContentSkeleton /> + } + + return ( + <div dangerouslySetInnerHTML={{ __html: data?.content || '' }}></div> + ) +} + +export default PageContent
\ No newline at end of file diff --git a/src-migrate/modules/register/components/Form.tsx b/src-migrate/modules/register/components/Form.tsx new file mode 100644 index 00000000..dc9107b2 --- /dev/null +++ b/src-migrate/modules/register/components/Form.tsx @@ -0,0 +1,179 @@ +import { ChangeEvent, useMemo } from "react"; +import { useMutation } from "react-query"; +import { useRegisterStore } from "~/common/stores/useRegisterStore"; +import { RegisterProps } from "~/common/types/auth"; +import { registerUser } from "~/services/auth"; +import TermCondition from "./TermCondition"; +import FormCaptcha from "./FormCaptcha"; +import { useRouter } from "next/router"; +import { UseToastOptions, useToast } from "@chakra-ui/react"; +import Link from "next/link"; + +const Form = () => { + const { + form, + isCheckedTNC, + isValidCaptcha, + errors, + updateForm, + validate, + } = useRegisterStore() + + const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]) + + const router = useRouter() + const toast = useToast() + + const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { + const { name, value } = event.target; + updateForm(name, value) + validate() + } + + const mutation = useMutation({ + mutationFn: (data: RegisterProps) => registerUser(data) + }) + + const handleSubmit = async (e: ChangeEvent<HTMLFormElement>) => { + e.preventDefault() + + const response = await mutation.mutateAsync(form) + + if (response?.register === true) { + const urlParams = new URLSearchParams({ + activation: 'otp', + email: form.email, + redirect: (router.query?.next || '/') as string + }) + router.push(`${router.route}?${urlParams}`) + } + + const toastProps: UseToastOptions = { + duration: 5000, + isClosable: true + } + + switch (response?.reason) { + case 'EMAIL_USED': + toast({ + ...toastProps, + title: 'Email sudah digunakan', + status: 'warning' + }) + break; + case 'NOT_ACTIVE': + const activationUrl = `${router.route}?activation=email` + toast({ + ...toastProps, + title: 'Akun belum aktif', + description: <>Akun sudah terdaftar namun belum aktif. <Link href={activationUrl} className="underline">Klik untuk aktivasi akun</Link></>, + status: 'warning' + }) + break + } + } + + return ( + <form className="mt-6 grid grid-cols-1 gap-y-4" onSubmit={handleSubmit}> + <div> + <label htmlFor="company"> + Nama Perusahaan <span className='text-gray_r-11'>(opsional)</span> + </label> + + <input + type="text" + name="company" + id="company" + className="form-input mt-3" + placeholder="cth: INDOTEKNIK DOTCOM GEMILANG" + autoCapitalize="true" + value={form.company} + onChange={handleInputChange} + /> + </div> + + <div> + <label htmlFor='name'>Nama Lengkap</label> + + <input + type='text' + id='name' + name='name' + className='form-input mt-3' + placeholder='Masukan nama lengkap anda' + value={form.name} + onChange={handleInputChange} + aria-invalid={!!errors.name} + /> + + {!!errors.name && <span className="form-msg-danger">{errors.name}</span>} + </div> + + <div> + <label htmlFor='phone'>No Handphone</label> + + <input + type='tel' + id='phone' + name='phone' + className='form-input mt-3' + placeholder='08xxxxxxxx' + value={form.phone} + onChange={handleInputChange} + aria-invalid={!!errors.phone} + /> + + {!!errors.phone && <span className="form-msg-danger">{errors.phone}</span>} + </div> + + <div> + <label htmlFor='email'>Alamat Email</label> + + <input + type='text' + id='email' + name='email' + className='form-input mt-3' + placeholder='Masukan alamat email anda' + value={form.email} + onChange={handleInputChange} + autoComplete="username" + aria-invalid={!!errors.email} + /> + + {!!errors.email && <span className="form-msg-danger">{errors.email}</span>} + </div> + + <div> + <label htmlFor='password'>Kata Sandi</label> + <input + type='password' + name='password' + id='password' + className='form-input mt-3' + placeholder='••••••••••••' + value={form.password} + onChange={handleInputChange} + autoComplete="current-password" + aria-invalid={!!errors.password} + /> + + {!!errors.password && <span className="form-msg-danger">{errors.password}</span>} + </div> + + <FormCaptcha /> + + <TermCondition /> + + <button + type="submit" + className="btn-yellow w-full mt-2" + disabled={!isFormValid || !isCheckedTNC || mutation.isLoading || !isValidCaptcha} + > + {mutation.isLoading ? 'Loading...' : 'Daftar'} + </button> + </form> + ) +} + +export default Form
\ No newline at end of file diff --git a/src-migrate/modules/register/components/FormCaptcha.tsx b/src-migrate/modules/register/components/FormCaptcha.tsx new file mode 100644 index 00000000..967be017 --- /dev/null +++ b/src-migrate/modules/register/components/FormCaptcha.tsx @@ -0,0 +1,14 @@ +import ReCaptcha from '~/common/components/elements/ReCaptcha' +import { useRegisterStore } from '~/common/stores/useRegisterStore' + +const FormCaptcha = () => { + const { updateValidCaptcha } = useRegisterStore() + + const handleCaptchaChange = (value: string | null) => { + updateValidCaptcha(value !== null) + } + + return <ReCaptcha onChange={handleCaptchaChange} /> +} + +export default FormCaptcha
\ No newline at end of file diff --git a/src-migrate/modules/register/components/TermCondition.tsx b/src-migrate/modules/register/components/TermCondition.tsx new file mode 100644 index 00000000..6b95ba19 --- /dev/null +++ b/src-migrate/modules/register/components/TermCondition.tsx @@ -0,0 +1,34 @@ +import { Checkbox } from '@chakra-ui/react' +import React from 'react' +import Modal from '~/common/components/elements/Modal' +import { useRegisterStore } from '~/common/stores/useRegisterStore' +import PageContent from '~/modules/page-content' + +const TermCondition = () => { + const { isOpenTNC, closeTNC, isCheckedTNC, toggleCheckTNC, openTNC } = useRegisterStore() + + return ( + <> + <div className="mt-4 flex items-center gap-x-2"> + <Checkbox id='tnc' name='tnc' isChecked={isCheckedTNC} onChange={toggleCheckTNC} /> + <div> + <label htmlFor="tnc" className="cursor-pointer">Dengan ini saya menyetujui</label> + {' '} + <span + className="font-medium text-danger-500 cursor-pointer" + onClick={openTNC} + > + syarat dan ketentuan + </span> + <label htmlFor="tnc" className="ml-2 cursor-pointer">yang berlaku</label> + </div> + </div> + + <Modal active={isOpenTNC} close={closeTNC} > + <PageContent path='/register#tnd' /> + </Modal> + </> + ) +} + +export default TermCondition
\ No newline at end of file diff --git a/src-migrate/modules/register/index.tsx b/src-migrate/modules/register/index.tsx new file mode 100644 index 00000000..00931284 --- /dev/null +++ b/src-migrate/modules/register/index.tsx @@ -0,0 +1,54 @@ +import PageContent from "~/modules/page-content" +import Form from "./components/Form" +import Link from "next/link" +import Image from "next/image" +import IndoteknikLogo from "~/images/logo.png" +import AccountActivation from "../account-activation" + +const LOGO_WIDTH = 150; +const LOGO_HEIGHT = LOGO_WIDTH / 3; + +const Register = () => { + return ( + <div className="container"> + <div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 pt-10 px-2 md:pt-16"> + <section> + <Link href='/' className="block md:hidden"> + <Image src={IndoteknikLogo} alt='Logo Indoteknik' width={LOGO_WIDTH} height={LOGO_HEIGHT} className="mx-auto mb-4 w-auto h-auto" priority /> + </Link> + + <h1 className="text-2xl font-semibold text-center md:text-left"> + Daftar Akun Indoteknik + </h1> + <h2 className="text-gray_r-11 mt-1 mb-10 text-center md:text-left"> + Buat akun sekarang lebih mudah dan terverifikasi + </h2> + + <Form /> + + <div className='text-gray_r-11 mt-4 text-center md:text-left'> + Sudah punya akun Indoteknik?{' '} + <Link href='/login' className='inline font-medium text-danger-500'> + Masuk + </Link> + </div> + + <div className='text-gray_r-11 mt-4 text-center md:text-left'> + Akun anda belum aktif?{' '} + <Link href='/register?activation=email' className='inline font-medium text-danger-500'> + Aktivasi + </Link> + </div> + </section> + + <section className="my-10 md:my-0"> + <PageContent path="/register" /> + </section> + </div> + + <AccountActivation /> + </div> + ) +} + +export default Register
\ No newline at end of file diff --git a/src-migrate/pages/_app.tsx b/src-migrate/pages/_app.tsx new file mode 100644 index 00000000..2dc82559 --- /dev/null +++ b/src-migrate/pages/_app.tsx @@ -0,0 +1,7 @@ +import '~/common/styles/fonts/Inter/inter.css' +import '~/common/styles/globals.css' +import type { AppProps } from "next/app" + +export default function MyApp({ Component, pageProps }: AppProps) { + return <Component {...pageProps} /> +}
\ No newline at end of file diff --git a/src-migrate/pages/register.tsx b/src-migrate/pages/register.tsx new file mode 100644 index 00000000..bd5c37f4 --- /dev/null +++ b/src-migrate/pages/register.tsx @@ -0,0 +1,18 @@ + +import BasicLayout from "@/core/components/layouts/BasicLayout" +import { useWindowSize } from "usehooks-ts" +import Register from "~/modules/register" + +const RegisterPage = () => { + const { width } = useWindowSize() + + const Layout = width > 768 ? BasicLayout : "div"; + + return ( + <Layout> + <Register /> + </Layout> + ) +} + +export default RegisterPage
\ No newline at end of file diff --git a/src-migrate/services/auth.ts b/src-migrate/services/auth.ts new file mode 100644 index 00000000..a5d02754 --- /dev/null +++ b/src-migrate/services/auth.ts @@ -0,0 +1,53 @@ +import odooApi from '~/common/libs/odooApi'; +import { + RegisterResApiProps, + RegisterProps, + ActivationTokenProps, + ActivationTokenResApiProps, + ActivationOtpProps, + ActivationOtpResApiProps, + ActivationReqProps, + ActivationReqResApiProps, +} from '~/common/types/auth'; + +const BASE_PATH = '/api/v1/user'; + +export const registerUser = async ( + data: RegisterProps +): Promise<RegisterResApiProps> => { + const response = await odooApi('POST', `${BASE_PATH}/register`, data); + + return response; +}; + +export const activationUserToken = async ( + params: ActivationTokenProps +): Promise<ActivationTokenResApiProps> => { + const response = await odooApi( + 'POST', + `${BASE_PATH}/activation-token`, + params + ); + + return response; +}; + +export const activationUserOTP = async ( + params: ActivationOtpProps +): Promise<ActivationOtpResApiProps> => { + const response = await odooApi('POST', `${BASE_PATH}/activation-otp`, params); + + return response; +}; + +export const activationReq = async ( + params: ActivationReqProps +): Promise<ActivationReqResApiProps> => { + const response = await odooApi( + 'POST', + `${BASE_PATH}/activation-request`, + params + ); + + return response; +}; diff --git a/src-migrate/services/banner.ts b/src-migrate/services/banner.ts new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src-migrate/services/banner.ts diff --git a/src-migrate/services/pageContent.ts b/src-migrate/services/pageContent.ts new file mode 100644 index 00000000..24f2c2f0 --- /dev/null +++ b/src-migrate/services/pageContent.ts @@ -0,0 +1,14 @@ +import odooApi from '~/common/libs/odooApi'; + +export const getPageContent = async ({ path }: { path: string }) => { + const params = new URLSearchParams({ + url_path: path, + }); + + const pageContent = await odooApi( + 'GET', + `/api/v1/page-content?${params.toString()}` + ); + + return pageContent; +}; |
