From 1f5695cf6af6fc12e0d570ede3f9a02298dbf181 Mon Sep 17 00:00:00 2001 From: Muhammad Fajar Date: Tue, 29 Nov 2022 13:56:10 +0700 Subject: [PATCH] fix login & verify code --- .DS_Store | Bin 8196 -> 8196 bytes frontend/.DS_Store | Bin 6148 -> 6148 bytes .../src/contexts/LaravelAuthContext.tsx | 5 +- .../dashboard/header/AccountPopover.tsx | 16 ++- .../client-portal/src/pages/auth/Login.tsx | 130 ++++++++++++------ .../src/pages/auth/VerifyCode.tsx | 107 -------------- frontend/client-portal/src/routes/index.tsx | 11 -- .../sections/auth/login/LoginEmailForm.tsx | 32 ++--- .../sections/auth/login/LoginPhoneForm.tsx | 34 +++-- .../{verify-code => login}/VerifyCodeForm.tsx | 91 +++++++----- .../src/sections/auth/login/index.ts | 3 +- .../src/sections/auth/verify-code/index.ts | 1 - 12 files changed, 198 insertions(+), 232 deletions(-) delete mode 100755 frontend/client-portal/src/pages/auth/VerifyCode.tsx rename frontend/client-portal/src/sections/auth/{verify-code => login}/VerifyCodeForm.tsx (62%) delete mode 100755 frontend/client-portal/src/sections/auth/verify-code/index.ts diff --git a/.DS_Store b/.DS_Store index 73c922283fe8c8f6ef8505daaa222f1328d57ee4..f17439c0da796e2aeb4b6e6d0eed095464a62807 100644 GIT binary patch delta 33 ocmZp1XmQwZPFTdu$UsNI*u=b6N1@u%!dOSa)WUf4BVi>T0I97BS^xk5 delta 33 mcmZp1XmQwZPFTd;++0V&#Mq=(N1@u%$NB!ku~2NHo+2aL#(>?7ix)66F|tkOVUiPZ4+=0SEiNfyU|?Wj@Mg$l z$YV$WO56}JGcwRoFg7u-)lsOnv@q6DFtsqA{DaA_J~O$jASow5iGhJ}N>V{iW^svu z!97MMW)@a9b`DN1ZXRAfegQ##A%0B)qu~2NHo+2a5#(>?7j4YGwSZ;`zo15z>m>8SX>L^rO8X17tW|Os8 s{U)2SU)-#~@qu|WI|n}pQ150zj_=Hq`9&N#7=VD0fq`Xngvc6Z07VTI?EnA( diff --git a/frontend/client-portal/src/contexts/LaravelAuthContext.tsx b/frontend/client-portal/src/contexts/LaravelAuthContext.tsx index eed11130..8c887190 100755 --- a/frontend/client-portal/src/contexts/LaravelAuthContext.tsx +++ b/frontend/client-portal/src/contexts/LaravelAuthContext.tsx @@ -78,12 +78,9 @@ function AuthProvider({ children }: AuthProviderProps) { useEffect(() => { (async () => { - console.log('initialize', state); - try { const accessToken = getSession(); - console.log(''); if (accessToken) { setSession(accessToken); @@ -144,6 +141,8 @@ function AuthProvider({ children }: AuthProviderProps) { user, }, }); + + return response.data; }) .catch((error) => { if (error.response.status !== 404) throw error.response; diff --git a/frontend/client-portal/src/layouts/dashboard/header/AccountPopover.tsx b/frontend/client-portal/src/layouts/dashboard/header/AccountPopover.tsx index fe68232e..be9d5cbe 100755 --- a/frontend/client-portal/src/layouts/dashboard/header/AccountPopover.tsx +++ b/frontend/client-portal/src/layouts/dashboard/header/AccountPopover.tsx @@ -5,8 +5,9 @@ import { Box, Divider, Typography, Stack, MenuItem, Avatar } from '@mui/material // components import MenuPopover from '../../../components/MenuPopover'; import { IconButtonAnimate } from '../../../components/animate'; -import { useNavigate } from "react-router-dom"; +import { useNavigate } from 'react-router-dom'; import useAuth from '../../../hooks/useAuth'; +import useLocalStorage from '../../../hooks/useLocalStorage'; // ---------------------------------------------------------------------- @@ -32,6 +33,10 @@ export default function AccountPopover() { const navigate = useNavigate(); const { logout } = useAuth(); + const [emailOrPhone, setEmailOrPhone] = useLocalStorage('emailOrPhone', ''); + const [emailOrPhoneForm, setEmailOrPhoneForm] = useLocalStorage('emailOrPhoneForm', false); + const [loginOrVerifyCode, setLoginOrVerifyCode] = useLocalStorage('loginOrVerifyCode', false); + const handleOpen = (event: React.MouseEvent) => { setOpen(event.currentTarget); }; @@ -41,9 +46,12 @@ export default function AccountPopover() { }; const handleLogout = () => { + setEmailOrPhone(''); + setEmailOrPhoneForm(false); + setLoginOrVerifyCode(false); logout(); navigate('/auth/login'); - } + }; return ( <> @@ -105,7 +113,9 @@ export default function AccountPopover() { - Logout + + Logout + ); diff --git a/frontend/client-portal/src/pages/auth/Login.tsx b/frontend/client-portal/src/pages/auth/Login.tsx index 5bb691b2..b0e37c57 100755 --- a/frontend/client-portal/src/pages/auth/Login.tsx +++ b/frontend/client-portal/src/pages/auth/Login.tsx @@ -1,17 +1,16 @@ -// @mui +/* ---------------------------------- @mui ---------------------------------- */ import { styled } from '@mui/material/styles'; -import { Box, Card, Divider, Grid, Link, Stack, Typography } from '@mui/material'; -// components +import { Box, Card, Divider, Grid, Link, Stack, Typography, IconButton } from '@mui/material'; +/* ------------------------------- components ------------------------------- */ import Page from '../../components/Page'; -import Image from '../../components/Image'; -// sections -import { LoginEmailForm, LoginPhoneForm } from '../../sections/auth/login'; import Logo from '../../components/Logo'; -// react -import { useState, useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; +import Iconify from '../../components/Iconify'; +/* ---------------------------------- hooks --------------------------------- */ +import useLocalStorage from '../../hooks/useLocalStorage'; +/* -------------------------------- sections -------------------------------- */ +import { LoginEmailForm, LoginPhoneForm, VerifyCodeForm } from '../../sections/auth/login'; -// ---------------------------------------------------------------------- +/* --------------------------------- styled --------------------------------- */ const RootStyle = styled('div')(({ theme }) => ({ [theme.breakpoints.up('md')]: { @@ -33,20 +32,9 @@ const ContentStyle = styled(Card)(({ theme }) => ({ // ---------------------------------------------------------------------- export default function Login() { - const { state } = useLocation(); - const [formPhone, setFormPhone] = useState(false); - - const handlerChange = (event: any, setForm: boolean) => { - event.preventDefault(); - setFormPhone(setForm); - }; - - useEffect(() => { - if (state !== null) { - setFormPhone(state.formPhone); - } - console.log(state); - }, [state]); + const [emailOrPhone, setEmailOrPhone] = useLocalStorage('emailOrPhone', ''); + const [emailOrPhoneForm, setEmailOrPhoneForm] = useLocalStorage('emailOrPhoneForm', false); + const [loginOrVerifyCode, setLoginOrVerifyCode] = useLocalStorage('loginOrVerifyCode', false); return ( @@ -66,32 +54,87 @@ export default function Login() { - - - - - Sign in to LinkSehat - - - Enter your details below. - - - + {loginOrVerifyCode && emailOrPhone ? ( + <> + + + { + localStorage.removeItem('emailOrPhone'); + setLoginOrVerifyCode(false); + }} + > + + + + Verifikasi OTP + + + + + Masukkan kode OTP anda disini + + + - {formPhone ? ( - + + + + Tidak mendapatkan kode? + Kirim Ulang Kode OTP + + ) : ( - + <> + + + + + Sign in to LinkSehat + + + Enter your details below. + + + + + {emailOrPhoneForm ? ( + + ) : ( + + )} + )} Atau - {formPhone ? ( + {emailOrPhoneForm ? ( handlerChange(event, false)} + onClick={() => { + setEmailOrPhone(''); + setLoginOrVerifyCode(false); + setEmailOrPhoneForm(false); + }} + sx={{ cursor: 'pointer' }} > Masuk menggunakan email @@ -99,7 +142,12 @@ export default function Login() { handlerChange(event, true)} + onClick={() => { + setEmailOrPhone(''); + setLoginOrVerifyCode(false); + setEmailOrPhoneForm(true); + }} + sx={{ cursor: 'pointer' }} > Masuk menggunakan nomor handphone diff --git a/frontend/client-portal/src/pages/auth/VerifyCode.tsx b/frontend/client-portal/src/pages/auth/VerifyCode.tsx deleted file mode 100755 index 4aaf2cbd..00000000 --- a/frontend/client-portal/src/pages/auth/VerifyCode.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useLocation, useNavigate } from 'react-router-dom'; -// @mui -import { styled } from '@mui/material/styles'; -import { Box, Card, Divider, IconButton, Grid, Link, Stack, Typography } from '@mui/material'; -// components -import Page from '../../components/Page'; -import Image from '../../components/Image'; -import Iconify from '../../components/Iconify'; -// sections -import { VerifyCodeForm } from '../../sections/auth/verify-code'; -import { useEffect } from 'react'; - -// ---------------------------------------------------------------------- - -const RootStyle = styled('div')(({ theme }) => ({ - [theme.breakpoints.up('md')]: { - display: 'flex', - }, - minHeight: '100vh', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', -})); - -const ContentStyle = styled(Card)(({ theme }) => ({ - [theme.breakpoints.up('md')]: { - maxHeight: '600px', - maxWidth: '1000px', - }, -})); -// ---------------------------------------------------------------------- - -export default function VerifyCode() { - const { state } = useLocation(); - const navigate = useNavigate(); - - useEffect(() => { - if (state === null) { - navigate('/auth/login'); - } - }, [state, navigate]); - - return ( - - - - - - login - - - - - - navigate('/auth/login', { state: { formPhone: state.formPhone } }) - } - > - - - - Verifikasi OTP - - - - - Masukkan kode OTP anda disini - - - - - - - Tidak mendapatkan kode? - Kirim Ulang Kode OTP - - Atau - - - {state.formPhone ? ( - navigate('/auth/login', { state: { formPhone: false } })} - > - Masuk menggunakan email - - ) : ( - navigate('/auth/login', { state: { formPhone: true } })} - > - Masuk menggunakan nomor handphone - - )} - - - - - - - ); -} diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index 98ba671e..089595a9 100755 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -37,16 +37,6 @@ export default function Router() { ), }, - { - path: 'verify-code', - element: ( - - - - - - ), - }, ], }, { @@ -132,7 +122,6 @@ export default function Router() { // Auth const Login = Loadable(lazy(() => import('../pages/auth/Login'))); -const VerifyCode = Loadable(lazy(() => import('../pages/auth/VerifyCode'))); // Dashboard const Dashboard = Loadable(lazy(() => import('../pages/Dashboard/Dashboard'))); diff --git a/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx index 70ae2af1..1a3ba1b3 100755 --- a/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx +++ b/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx @@ -1,34 +1,33 @@ +/* ----------------------------------- yup ---------------------------------- */ import * as Yup from 'yup'; -import { useNavigate } from 'react-router-dom'; -// form +/* ---------------------------------- form ---------------------------------- */ import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -// @mui +/* ---------------------------------- @mui ---------------------------------- */ import { Stack, Alert } from '@mui/material'; import { LoadingButton } from '@mui/lab'; -// hooks +/* ---------------------------------- hooks --------------------------------- */ import useAuth from '../../../hooks/useAuth'; import useIsMountedRef from '../../../hooks/useIsMountedRef'; -// components +/* ------------------------------- components ------------------------------- */ import { FormProvider, RHFTextField } from '../../../components/hook-form'; -// ---------------------------------------------------------------------- +/* ---------------------------------- types --------------------------------- */ + +type LoginFormProps = { + setEmailOrPhone: Function; + setLoginOrVerifyCode: Function; +}; type FormValuesProps = { email: string; afterSubmit?: string; }; -interface Props { - formPhone: boolean; -} +/* -------------------------------------------------------------------------- */ -// ---------------------------------------------------------------------- - -export default function LoginForm({ formPhone }: Props) { +export default function LoginForm({ setEmailOrPhone, setLoginOrVerifyCode }: LoginFormProps) { const { login } = useAuth(); - const navigate = useNavigate(); - const isMountedRef = useIsMountedRef(); const LoginSchema = Yup.object().shape({ @@ -54,8 +53,9 @@ export default function LoginForm({ formPhone }: Props) { const onSubmit = async (data: FormValuesProps) => { try { await login(data.email); - - navigate('/auth/verify-code', { state: { phoneOrEmail: data.email, formPhone } }); + setEmailOrPhone(data.email); + setLoginOrVerifyCode(true); + reset(); } catch (error: any) { reset(); diff --git a/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx index 1ab770b2..2309a6cd 100755 --- a/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx +++ b/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx @@ -1,29 +1,34 @@ +/* ----------------------------------- yup ---------------------------------- */ import * as Yup from 'yup'; -import { useNavigate } from 'react-router-dom'; -// form +/* ---------------------------------- form ---------------------------------- */ import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -// @mui +/* ---------------------------------- @mui ---------------------------------- */ import { Stack, Alert, InputAdornment } from '@mui/material'; import { LoadingButton } from '@mui/lab'; -// components +/* ------------------------------- components ------------------------------- */ import { FormProvider, RHFTextField } from '../../../components/hook-form'; +/* ---------------------------------- hooks --------------------------------- */ import useAuth from '../../../hooks/useAuth'; +import useIsMountedRef from '../../../hooks/useIsMountedRef'; -// ---------------------------------------------------------------------- +/* ---------------------------------- types --------------------------------- */ + +type LoginFormProps = { + setEmailOrPhone: Function; + setLoginOrVerifyCode: Function; +}; type FormValuesProps = { phone: string; afterSubmit?: string; }; -interface Props { - formPhone: boolean; -} +/* -------------------------------------------------------------------------- */ -export default function LoginPhoneForm({ formPhone }: Props) { +export default function LoginPhoneForm({ setEmailOrPhone, setLoginOrVerifyCode }: LoginFormProps) { const { login } = useAuth(); - const navigate = useNavigate(); + const isMountedRef = useIsMountedRef(); const LoginSchema = Yup.object().shape({ phone: Yup.string().required('Phone is required'), @@ -48,10 +53,15 @@ export default function LoginPhoneForm({ formPhone }: Props) { const onSubmit = async (data: FormValuesProps) => { try { await login(0 + data.phone); - navigate('/auth/verify-code', { state: { phoneOrEmail: 0 + data.phone, formPhone } }); + setEmailOrPhone(0 + data.phone); + setLoginOrVerifyCode(true); + reset(); } catch (error: any) { reset(); - setError('afterSubmit', { ...error, message: error.response.data.message }); + + if (isMountedRef.current) { + setError('afterSubmit', { ...error, message: error.data.message }); + } } }; diff --git a/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx b/frontend/client-portal/src/sections/auth/login/VerifyCodeForm.tsx similarity index 62% rename from frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx rename to frontend/client-portal/src/sections/auth/login/VerifyCodeForm.tsx index 4e136299..47d99760 100755 --- a/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx +++ b/frontend/client-portal/src/sections/auth/login/VerifyCodeForm.tsx @@ -1,17 +1,25 @@ +/* ---------------------------------- @mui ---------------------------------- */ +import { OutlinedInput, Stack } from '@mui/material'; +/* ----------------------------------- yup ---------------------------------- */ import * as Yup from 'yup'; +/* -------------------------------- snackbar -------------------------------- */ import { useSnackbar } from 'notistack'; -import { useNavigate, useLocation } from 'react-router-dom'; +/* ---------------------------------- react --------------------------------- */ +import { useNavigate } from 'react-router-dom'; import { useEffect } from 'react'; -// form +/* ---------------------------------- form ---------------------------------- */ import { useForm, Controller } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -// @mui -import { OutlinedInput, Stack } from '@mui/material'; +/* ---------------------------------- hooks --------------------------------- */ import useAuth from '../../../hooks/useAuth'; -// routes -// import { PATH_DASHBOARD } from '../../../routes/paths'; -// ---------------------------------------------------------------------- +/* ---------------------------------- types --------------------------------- */ + +type VerifyCodeFormProps = { + emailOrPhone: string; + setEmailOrPhoneForm: Function; + setLoginOrVerifyCode: Function; +}; type FormValuesProps = { code1: string; @@ -20,18 +28,25 @@ type FormValuesProps = { code4: string; }; -type Props = { - phoneOrEmail: string; -}; - type ValueNames = 'code1' | 'code2' | 'code3' | 'code4'; -export default function VerifyCodeForm({ phoneOrEmail }: Props) { +type responseProps = { + status: string; + statusCode: number; + data: []; + message: string; +}; + +/* -------------------------------------------------------------------------- */ + +export default function VerifyCodeForm({ + emailOrPhone, + setEmailOrPhoneForm, + setLoginOrVerifyCode, +}: VerifyCodeFormProps) { const navigate = useNavigate(); - const location = useLocation(); const { validateOtp } = useAuth(); const { enqueueSnackbar } = useSnackbar(); - const { phone_or_email } = location.state; const VerifyCodeSchema = Yup.object().shape({ code1: Yup.string().required('Code is required'), @@ -47,13 +62,7 @@ export default function VerifyCodeForm({ phoneOrEmail }: Props) { code4: '', }; - const { - watch, - control, - setValue, - handleSubmit, - formState: { isSubmitting, isValid }, - } = useForm({ + const { watch, control, setValue, handleSubmit } = useForm({ mode: 'onBlur', resolver: yupResolver(VerifyCodeSchema), defaultValues, @@ -62,15 +71,35 @@ export default function VerifyCodeForm({ phoneOrEmail }: Props) { const values = watch(); useEffect(() => { - console.log('phone number : ' + phone_or_email); + const handlePasteClipboard = (event: ClipboardEvent) => { + let data: string | string[] = event?.clipboardData?.getData('Text') || ''; + + data = data.split(''); + + [].forEach.call(document.querySelectorAll('#field-code'), (node: any, index) => { + node.value = data[index]; + const fieldIndex = `code${index + 1}`; + setValue(fieldIndex as ValueNames, data[index]); + }); + }; + document.addEventListener('paste', handlePasteClipboard); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [setValue]); const onSubmit = async (data: FormValuesProps) => { try { await new Promise((resolve) => setTimeout(resolve, 1000)); - await validateOtp(phoneOrEmail, Object.values(data).join('')); + // @ts-ignore + const response: responseProps = await validateOtp(emailOrPhone, Object.values(data).join('')); + + if (response.data.length === 0) { + return enqueueSnackbar(response.message, { + variant: 'error', + autoHideDuration: 4000, + preventDuplicate: true, + }); + } + navigate('/dashboard'); enqueueSnackbar('Verify success!', { variant: 'success' }); } catch (error) { @@ -78,18 +107,6 @@ export default function VerifyCodeForm({ phoneOrEmail }: Props) { } }; - const handlePasteClipboard = (event: ClipboardEvent) => { - let data: string | string[] = event?.clipboardData?.getData('Text') || ''; - - data = data.split(''); - - [].forEach.call(document.querySelectorAll('#field-code'), (node: any, index) => { - node.value = data[index]; - const fieldIndex = `code${index + 1}`; - setValue(fieldIndex as ValueNames, data[index]); - }); - }; - const handleChangeWithNextField = ( event: React.ChangeEvent, handleChange: (event: React.ChangeEvent) => void diff --git a/frontend/client-portal/src/sections/auth/login/index.ts b/frontend/client-portal/src/sections/auth/login/index.ts index 9722682f..f15f3839 100755 --- a/frontend/client-portal/src/sections/auth/login/index.ts +++ b/frontend/client-portal/src/sections/auth/login/index.ts @@ -1,2 +1,3 @@ export { default as LoginEmailForm } from './LoginEmailForm'; -export { default as LoginPhoneForm } from './LoginPhoneForm'; \ No newline at end of file +export { default as LoginPhoneForm } from './LoginPhoneForm'; +export { default as VerifyCodeForm } from './VerifyCodeForm'; \ No newline at end of file diff --git a/frontend/client-portal/src/sections/auth/verify-code/index.ts b/frontend/client-portal/src/sections/auth/verify-code/index.ts deleted file mode 100755 index 6844e2d9..00000000 --- a/frontend/client-portal/src/sections/auth/verify-code/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as VerifyCodeForm } from './VerifyCodeForm';