summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json1
-rw-r--r--src-migrate/common/components/elements/Modal.tsx18
-rw-r--r--src-migrate/common/components/elements/ReCaptcha.tsx17
-rw-r--r--src-migrate/common/stores/useRegisterStore.ts5
-rw-r--r--src-migrate/common/types/auth.ts31
-rw-r--r--src-migrate/modules/account-activation/components/FormEmail.tsx7
-rw-r--r--src-migrate/modules/account-activation/components/FormOTP.tsx23
-rw-r--r--src-migrate/modules/account-activation/components/FormToken.tsx83
-rw-r--r--src-migrate/modules/account-activation/index.tsx12
-rw-r--r--src-migrate/modules/register/components/Form.tsx53
-rw-r--r--src-migrate/modules/register/components/FormCaptcha.tsx14
-rw-r--r--src-migrate/modules/register/components/Register.tsx39
-rw-r--r--src-migrate/modules/register/components/TermCondition.tsx26
-rw-r--r--src-migrate/modules/register/index.tsx46
-rw-r--r--src-migrate/pages/register.tsx9
-rw-r--r--src-migrate/services/auth.ts35
16 files changed, 337 insertions, 82 deletions
diff --git a/package.json b/package.json
index 127de3df..589eabc4 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"@types/node": "^20.8.7",
"@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14",
+ "@types/react-google-recaptcha": "^2.1.7",
"autoprefixer": "^10.4.14",
"eslint": "8.26.0",
"eslint-config-next": "13.0.0",
diff --git a/src-migrate/common/components/elements/Modal.tsx b/src-migrate/common/components/elements/Modal.tsx
index ad1fe51b..91421251 100644
--- a/src-migrate/common/components/elements/Modal.tsx
+++ b/src-migrate/common/components/elements/Modal.tsx
@@ -11,7 +11,8 @@ type Props = {
active: boolean
title?: string
close?: () => void,
- className?: string
+ className?: string,
+ mode?: "mobile" | "desktop"
}
const Modal = ({
@@ -19,11 +20,14 @@ const Modal = ({
active = false,
title,
close,
- className
+ className,
+ mode
}: Props) => {
const { width } = useWindowSize()
const [rendered, setRendered] = useState<boolean>(false)
+ mode = mode || width >= 768 ? "desktop" : "mobile"
+
useEffect(() => {
setRendered(true)
}, [])
@@ -31,16 +35,16 @@ const Modal = ({
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 md:w-1/4 lg:w-1/3 border rounded-xl": width >= 768,
- "left-0 w-full border-t bottom-0 rounded-t-xl": width < 768
+ "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: width >= 768 ? '45%' : '-100%', opacity: 0 },
- animate: { bottom: width >= 768 ? '50%' : 0, opacity: 1 },
- exit: { bottom: width >= 768 ? '55%' : '-100%', opacity: 0 },
+ 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 }
}
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/stores/useRegisterStore.ts b/src-migrate/common/stores/useRegisterStore.ts
index fcd2cd8b..725bbfda 100644
--- a/src-migrate/common/stores/useRegisterStore.ts
+++ b/src-migrate/common/stores/useRegisterStore.ts
@@ -6,10 +6,12 @@ type State = {
isValid: boolean;
isCheckedTNC: boolean;
isOpenTNC: boolean;
+ isValidCaptcha: boolean;
};
type Action = {
updateForm: (name: string, value: string) => void;
+ updateValidCaptcha: (value: boolean) => void;
toggleCheckTNC: () => void;
openTNC: () => void;
closeTNC: () => void;
@@ -21,10 +23,12 @@ export const useRegisterStore = create<State & Action>((set) => ({
name: '',
email: '',
password: '',
+ phone: '',
},
isValid: false,
isCheckedTNC: false,
isOpenTNC: false,
+ isValidCaptcha: false,
updateForm: (name, value) =>
set((state) => {
const updatedForm = { ...state.form, [name]: value };
@@ -49,4 +53,5 @@ export const useRegisterStore = create<State & Action>((set) => ({
toggleCheckTNC: () => set((state) => ({ isCheckedTNC: !state.isCheckedTNC })),
openTNC: () => set(() => ({ isOpenTNC: true })),
closeTNC: () => set(() => ({ isOpenTNC: false })),
+ updateValidCaptcha: (value) => set(() => ({ isValidCaptcha: value })),
}));
diff --git a/src-migrate/common/types/auth.ts b/src-migrate/common/types/auth.ts
index 63fac6e0..3d9ffee4 100644
--- a/src-migrate/common/types/auth.ts
+++ b/src-migrate/common/types/auth.ts
@@ -22,11 +22,32 @@ export type RegisterProps = {
email: string;
password: string;
company: string;
+ phone: string;
+};
+
+export type RegisterResApiProps = {
+ register: boolean;
+ reason: 'EMAIL_USED' | 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 RegisterApiProps = OdooApiProps & {
- result: {
- register: boolean;
- reason?: 'EMAIL_USED';
- };
+export type ActivationOtpResApiProps = ActivationResProps & {
+ reason: 'INVALID_OTP' | null;
};
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..f16ab93f
--- /dev/null
+++ b/src-migrate/modules/account-activation/components/FormEmail.tsx
@@ -0,0 +1,7 @@
+const FormEmail = () => {
+ return (
+ <div>FormEmail</div>
+ )
+}
+
+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..24e9e7f6
--- /dev/null
+++ b/src-migrate/modules/account-activation/components/FormOTP.tsx
@@ -0,0 +1,23 @@
+import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
+import Modal from '~/common/components/elements/Modal'
+
+const FormOTP = () => {
+ return (
+ <Modal active={false} className="w-10/12 md:w-fit px-10" 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>
+ <PinInputField autoFocus />
+ <PinInputField />
+ <PinInputField />
+ <PinInputField />
+ </PinInput>
+ </HStack>
+ <div className="mt-4">Kode OTP dikirimkan ke email anda</div>
+ </div>
+ </Modal>
+ )
+}
+
+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..041fbae8
--- /dev/null
+++ b/src-migrate/modules/account-activation/components/FormToken.tsx
@@ -0,0 +1,83 @@
+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"
+
+type StatusProps = "success" | "error" | "loading";
+
+const FormToken = () => {
+ const { query } = useRouter()
+ const [status, setStatus] = useState<StatusProps>("loading")
+ const [active, setActive] = useState<boolean>(false)
+
+ const mutation = useMutation({
+ mutationFn: (data: ActivationTokenProps) => activationUserToken(data),
+ onSuccess: (data) => {
+ if (data.activation === true) {
+ setStatus("success")
+ } else {
+ setStatus("error")
+ }
+ }
+ })
+
+ useEffect(() => {
+ if (query?.activation === 'token' && typeof query?.token === 'string') {
+ mutation.mutate({ token: query.token })
+ setActive(true)
+ }
+ //eslint-disable-next-line
+ }, [query.activation, query.token])
+
+ return (
+ <Modal active={active} mode="desktop">
+ <div className="pb-6 flex flex-col items-center gap-y-6">
+ {status === 'loading' && (
+ <>
+ <Spinner size='lg' borderWidth='3px' />
+ <div className="text-h-sm">Mohon tunggu sedang memverifikasi akun</div>
+ </>
+ )}
+
+ {status !== 'loading' && (
+ <Alert
+ status={status}
+ 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 {status === 'success' ? 'berhasil' : 'gagal'}
+ </AlertTitle>
+ <AlertDescription maxWidth="sm">
+ {status === 'success' && (
+ <>
+ Akun anda berhasil diaktifkan, selamat berbelanja di Indoteknik.
+ <Link href='/' className="block mt-8 text-success-700 underline">Kembali ke halaman utama</Link>
+ </>
+ )}
+
+ {status === 'error' && 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..edcc6652
--- /dev/null
+++ b/src-migrate/modules/account-activation/index.tsx
@@ -0,0 +1,12 @@
+import { useRouter } from "next/router"
+import FormToken from "./components/FormToken"
+
+const AccountActivation = () => {
+ return (
+ <>
+ <FormToken />
+ </>
+ )
+}
+
+export default AccountActivation \ No newline at end of file
diff --git a/src-migrate/modules/register/components/Form.tsx b/src-migrate/modules/register/components/Form.tsx
index ac194b46..fc1567ab 100644
--- a/src-migrate/modules/register/components/Form.tsx
+++ b/src-migrate/modules/register/components/Form.tsx
@@ -3,9 +3,17 @@ 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";
const Form = () => {
- const { form, isValid, isCheckedTNC, updateForm, toggleCheckTNC, openTNC } = useRegisterStore()
+ const {
+ form,
+ isValid,
+ isCheckedTNC,
+ isValidCaptcha,
+ updateForm,
+ } = useRegisterStore()
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
@@ -20,8 +28,10 @@ const Form = () => {
e.preventDefault()
const response = await mutation.mutateAsync(form)
- console.log(response);
+ if (response?.register === true) {
+
+ }
}
return (
@@ -58,6 +68,20 @@ const Form = () => {
</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}
+ />
+ </div>
+
+ <div>
<label htmlFor='email'>Alamat Email</label>
<input
@@ -68,6 +92,7 @@ const Form = () => {
placeholder='Masukan alamat email anda'
value={form.email}
onChange={handleInputChange}
+ autoComplete="username"
/>
</div>
@@ -81,32 +106,18 @@ const Form = () => {
placeholder='••••••••••••'
value={form.password}
onChange={handleInputChange}
+ autoComplete="current-password"
/>
</div>
- <div className="mt-4">
- <input
- type="checkbox"
- name="tnc"
- id="tnc"
- checked={isCheckedTNC}
- onClick={toggleCheckTNC}
- />
- <label htmlFor="tnc" className="ml-2 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>
+ <FormCaptcha />
+
+ <TermCondition />
<button
type="submit"
className="btn-yellow w-full mt-2"
- disabled={!isValid || !isCheckedTNC || mutation.isLoading}
+ disabled={!isValid || !isCheckedTNC || mutation.isLoading || !isValidCaptcha}
>
{mutation.isLoading ? 'Loading...' : 'Daftar'}
</button>
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/Register.tsx b/src-migrate/modules/register/components/Register.tsx
deleted file mode 100644
index e3e29b9f..00000000
--- a/src-migrate/modules/register/components/Register.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import PageContent from "~/modules/page-content"
-import Form from "./Form"
-import Link from "next/link"
-import Modal from "~/common/components/elements/Modal"
-import TermCondition from "./TermCondition"
-
-const Register = () => {
- return (
- <div className="container">
- <div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 pt-16">
- <section>
- <h1 className="text-2xl font-semibold">
- Daftar Akun Indoteknik
- </h1>
- <h2 className="text-gray_r-11 mt-1 mb-4">
- Buat akun sekarang lebih mudah dan terverifikasi
- </h2>
-
- <Form />
-
- <div className='text-gray_r-11 mt-10'>
- Sudah punya akun Indoteknik?{' '}
- <Link href='/login' className='inline font-medium text-danger-500'>
- Masuk
- </Link>
- </div>
- </section>
-
- <section className="my-10 md:my-0">
- <PageContent path="/register" />
- </section>
- </div>
-
- <TermCondition />
- </div>
- )
-}
-
-export default Register \ No newline at end of file
diff --git a/src-migrate/modules/register/components/TermCondition.tsx b/src-migrate/modules/register/components/TermCondition.tsx
index 304ffd69..aaba6604 100644
--- a/src-migrate/modules/register/components/TermCondition.tsx
+++ b/src-migrate/modules/register/components/TermCondition.tsx
@@ -1,15 +1,33 @@
+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 } = useRegisterStore()
+ const { isOpenTNC, closeTNC, isCheckedTNC, toggleCheckTNC, openTNC } = useRegisterStore()
return (
- <Modal active={isOpenTNC} close={closeTNC} >
- <PageContent path='/register#tnd' />
- </Modal>
+ <>
+ <div className="mt-4 flex items-center gap-x-2">
+ <Checkbox id='tnc' name='tnc' checked={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>
+ </>
)
}
diff --git a/src-migrate/modules/register/index.tsx b/src-migrate/modules/register/index.tsx
index ba5efa3a..6325ee09 100644
--- a/src-migrate/modules/register/index.tsx
+++ b/src-migrate/modules/register/index.tsx
@@ -1,3 +1,47 @@
-import Register from "./components/Register";
+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>
+ </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/register.tsx b/src-migrate/pages/register.tsx
index 4ba72cbc..bd5c37f4 100644
--- a/src-migrate/pages/register.tsx
+++ b/src-migrate/pages/register.tsx
@@ -1,12 +1,17 @@
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 (
- <BasicLayout>
+ <Layout>
<Register />
- </BasicLayout>
+ </Layout>
)
}
diff --git a/src-migrate/services/auth.ts b/src-migrate/services/auth.ts
index f2fd7761..feaabec8 100644
--- a/src-migrate/services/auth.ts
+++ b/src-migrate/services/auth.ts
@@ -1,10 +1,39 @@
import odooApi from '~/common/libs/odooApi';
-import { RegisterApiProps, RegisterProps } from '~/common/types/auth';
+import {
+ RegisterResApiProps,
+ RegisterProps,
+ ActivationTokenProps,
+ ActivationTokenResApiProps,
+ ActivationOtpProps,
+ ActivationOtpResApiProps,
+} from '~/common/types/auth';
+
+const BASE_PATH = '/api/v1/user';
export const registerUser = async (
data: RegisterProps
-): Promise<RegisterApiProps> => {
- const response = await odooApi('POST', '/api/v1/user/register', data);
+): 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;
};