fix login & verify code

This commit is contained in:
Muhammad Fajar
2022-11-29 13:56:10 +07:00
parent 52b9dd49c8
commit 1f5695cf6a
12 changed files with 198 additions and 232 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
frontend/.DS_Store vendored

Binary file not shown.

View File

@@ -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;

View File

@@ -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<HTMLElement>) => {
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() {
<Divider sx={{ borderStyle: 'dashed' }} />
<MenuItem sx={{ m: 1 }} onClick={handleLogout}>Logout</MenuItem>
<MenuItem sx={{ m: 1 }} onClick={handleLogout}>
Logout
</MenuItem>
</MenuPopover>
</>
);

View File

@@ -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 (
<Page title="Login">
@@ -66,32 +54,87 @@ export default function Login() {
</video>
</Grid>
<Grid item xs={6} sx={{ padding: 3 }}>
<Stack direction="row" alignItems="center" sx={{ mb: 5 }}>
<Logo sx={{ width: 90, height: 90 }} />
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h4" gutterBottom>
Sign in to LinkSehat
</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
Enter your details below.
</Typography>
</Box>
</Stack>
{loginOrVerifyCode && emailOrPhone ? (
<>
<Stack direction="column" sx={{ mb: 5 }}>
<Stack direction="row" alignItems="center">
<IconButton
onClick={() => {
localStorage.removeItem('emailOrPhone');
setLoginOrVerifyCode(false);
}}
>
<Iconify
icon="heroicons-outline:arrow-narrow-left"
sx={{ marginRight: '10px' }}
/>
</IconButton>
<Typography variant="h4" gutterBottom>
Verifikasi OTP
</Typography>
</Stack>
<Box sx={{ flexGrow: 1 }}>
<Typography
variant="body1"
sx={{ color: 'text.secondary', textAlign: 'left' }}
>
Masukkan kode OTP anda disini
</Typography>
</Box>
</Stack>
{formPhone ? (
<LoginPhoneForm formPhone={formPhone} />
<VerifyCodeForm
setEmailOrPhoneForm={setEmailOrPhoneForm}
setLoginOrVerifyCode={setLoginOrVerifyCode}
emailOrPhone={emailOrPhone}
/>
<Stack sx={{ marginTop: 5 }} spacing={1} alignItems="center">
<Typography>Tidak mendapatkan kode?</Typography>
<Link sx={{ cursor: 'pointer' }}>Kirim Ulang Kode OTP</Link>
</Stack>
</>
) : (
<LoginEmailForm formPhone={formPhone} />
<>
<Stack direction="row" alignItems="center" sx={{ mb: 5 }}>
<Logo sx={{ width: 90, height: 90 }} />
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h4" gutterBottom>
Sign in to LinkSehat
</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
Enter your details below.
</Typography>
</Box>
</Stack>
{emailOrPhoneForm ? (
<LoginPhoneForm
setEmailOrPhone={setEmailOrPhone}
setLoginOrVerifyCode={setLoginOrVerifyCode}
/>
) : (
<LoginEmailForm
setEmailOrPhone={setEmailOrPhone}
setLoginOrVerifyCode={setLoginOrVerifyCode}
/>
)}
</>
)}
<Divider sx={{ marginTop: 5 }}>Atau</Divider>
<Stack sx={{ marginTop: 5 }}>
{formPhone ? (
{emailOrPhoneForm ? (
<Link
align="center"
underline="hover"
onClick={(event) => handlerChange(event, false)}
onClick={() => {
setEmailOrPhone('');
setLoginOrVerifyCode(false);
setEmailOrPhoneForm(false);
}}
sx={{ cursor: 'pointer' }}
>
Masuk menggunakan email
</Link>
@@ -99,7 +142,12 @@ export default function Login() {
<Link
align="center"
underline="hover"
onClick={(event) => handlerChange(event, true)}
onClick={() => {
setEmailOrPhone('');
setLoginOrVerifyCode(false);
setEmailOrPhoneForm(true);
}}
sx={{ cursor: 'pointer' }}
>
Masuk menggunakan nomor handphone
</Link>

View File

@@ -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 (
<Page title="Login">
<RootStyle>
<ContentStyle>
<Grid container>
<Grid item xs={6}>
<Image visibleByDefault disabledEffect src="/images/login-image.gif" alt="login" />
</Grid>
<Grid item xs={6} sx={{ padding: 3, textAlign: 'center' }}>
<Stack direction="column" sx={{ mb: 5 }}>
<Stack direction="row" alignItems="center">
<IconButton
onClick={() =>
navigate('/auth/login', { state: { formPhone: state.formPhone } })
}
>
<Iconify
icon="heroicons-outline:arrow-narrow-left"
sx={{ marginRight: '10px' }}
/>
</IconButton>
<Typography variant="h4" gutterBottom>
Verifikasi OTP
</Typography>
</Stack>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="body1" sx={{ color: 'text.secondary', textAlign: 'left' }}>
Masukkan kode OTP anda disini
</Typography>
</Box>
</Stack>
<VerifyCodeForm phoneOrEmail={state.phoneOrEmail} />
<Typography sx={{ marginTop: 5 }}>Tidak mendapatkan kode?</Typography>
<Link sx={{ marginTop: 1 }}>Kirim Ulang Kode OTP</Link>
<Divider sx={{ marginTop: 5 }}>Atau</Divider>
<Stack sx={{ marginTop: 5 }}>
{state.formPhone ? (
<Link
align="center"
underline="hover"
onClick={() => navigate('/auth/login', { state: { formPhone: false } })}
>
Masuk menggunakan email
</Link>
) : (
<Link
align="center"
underline="hover"
onClick={() => navigate('/auth/login', { state: { formPhone: true } })}
>
Masuk menggunakan nomor handphone
</Link>
)}
</Stack>
</Grid>
</Grid>
</ContentStyle>
</RootStyle>
</Page>
);
}

View File

@@ -37,16 +37,6 @@ export default function Router() {
</AuthProvider>
),
},
{
path: 'verify-code',
element: (
<AuthProvider>
<GuestGuard>
<VerifyCode />
</GuestGuard>
</AuthProvider>
),
},
],
},
{
@@ -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')));

View File

@@ -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();

View File

@@ -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 });
}
}
};

View File

@@ -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<HTMLInputElement>,
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void

View File

@@ -1,2 +1,3 @@
export { default as LoginEmailForm } from './LoginEmailForm';
export { default as LoginPhoneForm } from './LoginPhoneForm';
export { default as LoginPhoneForm } from './LoginPhoneForm';
export { default as VerifyCodeForm } from './VerifyCodeForm';

View File

@@ -1 +0,0 @@
export { default as VerifyCodeForm } from './VerifyCodeForm';