validate otp

This commit is contained in:
Muhammad Fajar
2022-11-11 15:38:07 +07:00
parent 3e30ce6e90
commit 91e44fcb41
14 changed files with 157 additions and 529 deletions

View File

@@ -6,7 +6,7 @@ import { setSession, getSession, getUser } from '../utils/token';
// @types
import { ActionMap, AuthState, AuthUser, JWTContextType } from '../@types/auth';
// ----------------------------------------------------------------------
import { Navigate, useLocation } from 'react-router-dom';
// import { Navigate, useLocation } from 'react-router-dom';
enum Types {
Initial = 'INITIALIZE',

View File

@@ -2,8 +2,6 @@ import { useState, ReactNode } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
// hooks
import useAuth from '../hooks/useAuth';
// pages
import Login from '../pages/auth/Login';
// components
import LoadingScreen from '../components/LoadingScreen';
@@ -26,7 +24,7 @@ export default function AuthGuard({ children }: AuthGuardProps) {
if (pathname !== requestedLocation) {
setRequestedLocation(pathname);
}
return <Navigate to="/auth/login" replace={true}/>;
return <Navigate to="/auth/login" replace={true} />;
}
if (requestedLocation && pathname !== requestedLocation) {

View File

@@ -11,7 +11,8 @@ import Image from '../../components/Image';
import { LoginEmailForm, LoginPhoneForm } from '../../sections/auth/login';
import Logo from '../../components/Logo';
// react
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
// ----------------------------------------------------------------------
@@ -36,13 +37,20 @@ const ContentStyle = styled(Card)(({ theme }) => ({
export default function Login() {
const { method } = useAuth();
const location = useLocation();
const [formPhone, setFormPhone] = useState(false);
// const { setForm } = location.state;
const handlerChange = (event: any, setForm: boolean) => {
event.preventDefault();
setFormPhone(setForm);
};
useEffect(() => {
console.log('setForm');
// setFormPhone(setForm ? setForm : true);
}, []);
return (
<Page title="Login">
<RootStyle>

View File

@@ -0,0 +1,134 @@
import { capitalCase } from 'change-case';
// @mui
import { styled } from '@mui/material/styles';
import {
Box,
Button,
Card,
Divider,
Grid,
Link,
Stack,
IconButton,
Typography,
} from '@mui/material';
// hooks
import useAuth from '../../hooks/useAuth';
// components
import Page from '../../components/Page';
import Image from '../../components/Image';
import Iconify from '../../components/Iconify';
// sections
import { VerifyCodeForm } from '../../sections/auth/verify-code';
// react
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
// ----------------------------------------------------------------------
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 OtpValidation() {
const { method } = useAuth();
const navigate = useNavigate();
const formPhone = false;
const handlerChange = (event: any, setForm: boolean) => {
event.preventDefault();
// setFormPhone(setForm);
navigate('/auth/login', { state: { setForm: setForm } });
};
const otpRequestHandler = (event: any) => {
event.preventDefault();
alert('otp sudah dikirim ulang!');
};
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 }}>
<Stack direction="column" justifyContent="flex-start" sx={{ mb: 5 }}>
<Stack direction="row" alignItems="center">
<IconButton sx={{ marginRight: '10px' }}>
<Iconify icon="heroicons-outline:arrow-narrow-left" sx={{ color: '#424242' }} />
</IconButton>
<Typography variant="h5">Verifikasi OTP</Typography>
</Stack>
{/* <Box sx={{ flexGrow: 1 }}> */}
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Masukkan kode OTP anda disini
</Typography>
{/* </Box> */}
</Stack>
<VerifyCodeForm />
<Stack alignItems="center" sx={{ marginTop: 5 }}>
<Typography variant="caption" sx={{ color: 'text.secondary' }} gutterBottom>
Tidak Mendapatkan Kode?
</Typography>
<Link
variant="subtitle2"
href=""
align="center"
underline="hover"
onClick={(event) => otpRequestHandler(event)}
>
Kirim Ulang Kode OTP
</Link>
</Stack>
<Divider sx={{ marginTop: 5 }}>Atau</Divider>
<Stack sx={{ marginTop: 5 }}>
{formPhone === false ? (
<Link
href=""
align="center"
underline="hover"
onClick={(event) => handlerChange(event, true)}
>
Masuk menggunakan nomor handphone
</Link>
) : (
<Link
href=""
align="center"
underline="hover"
onClick={(event) => handlerChange(event, false)}
>
Masuk menggunakan email
</Link>
)}
</Stack>
</Grid>
</Grid>
</ContentStyle>
</RootStyle>
</Page>
);
}

View File

@@ -1,148 +0,0 @@
import { capitalCase } from 'change-case';
import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
import { Box, Card, Link, Container, Typography, Tooltip } from '@mui/material';
// hooks
import useAuth from '../../hooks/useAuth';
import useResponsive from '../../hooks/useResponsive';
// routes
import { PATH_AUTH } from '../../routes/paths';
// components
import Page from '../../components/Page';
import Logo from '../../components/Logo';
import Image from '../../components/Image';
// sections
import { RegisterForm } from '../../sections/auth/register';
// ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({
[theme.breakpoints.up('md')]: {
display: 'flex',
},
}));
const HeaderStyle = styled('header')(({ theme }) => ({
top: 0,
zIndex: 9,
lineHeight: 0,
width: '100%',
display: 'flex',
alignItems: 'center',
position: 'absolute',
padding: theme.spacing(3),
justifyContent: 'space-between',
[theme.breakpoints.up('md')]: {
alignItems: 'flex-start',
padding: theme.spacing(7, 5, 0, 7),
},
}));
const SectionStyle = styled(Card)(({ theme }) => ({
width: '100%',
maxWidth: 464,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: theme.spacing(2, 0, 2, 2),
}));
const ContentStyle = styled('div')(({ theme }) => ({
maxWidth: 480,
margin: 'auto',
display: 'flex',
minHeight: '100vh',
flexDirection: 'column',
justifyContent: 'center',
padding: theme.spacing(12, 0),
}));
// ----------------------------------------------------------------------
export default function Register() {
const { method } = useAuth();
const smUp = useResponsive('up', 'sm');
const mdUp = useResponsive('up', 'md');
return (
<Page title="Register">
<RootStyle>
<HeaderStyle>
<Logo />
{smUp && (
<Typography variant="body2" sx={{ mt: { md: -2 } }}>
Already have an account? {''}
<Link variant="subtitle2" component={RouterLink} to={PATH_AUTH.login}>
Login
</Link>
</Typography>
)}
</HeaderStyle>
{mdUp && (
<SectionStyle>
<Typography variant="h3" sx={{ px: 5, mt: 10, mb: 5 }}>
Manage the job more effectively with Minimal
</Typography>
<Image
visibleByDefault
disabledEffect
alt="register"
src="https://minimal-assets-api.vercel.app/assets/illustrations/illustration_register.png"
/>
</SectionStyle>
)}
<Container>
<ContentStyle>
<Box sx={{ mb: 5, display: 'flex', alignItems: 'center' }}>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h4" gutterBottom>
Get started absolutely free.
</Typography>
<Typography sx={{ color: 'text.secondary' }}>
Free forever. No credit card needed.
</Typography>
</Box>
<Tooltip title={capitalCase(method)}>
<>
<Image
disabledEffect
src={`https://minimal-assets-api.vercel.app/assets/icons/auth/ic_${method}.png`}
sx={{ width: 32, height: 32 }}
/>
</>
</Tooltip>
</Box>
<RegisterForm />
<Typography variant="body2" align="center" sx={{ color: 'text.secondary', mt: 3 }}>
By registering, I agree to Minimal&nbsp;
<Link underline="always" color="text.primary" href="#">
Terms of Service
</Link>
{''}and{''}
<Link underline="always" color="text.primary" href="#">
Privacy Policy
</Link>
.
</Typography>
{!smUp && (
<Typography variant="body2" sx={{ mt: 3, textAlign: 'center' }}>
Already have an account?{' '}
<Link variant="subtitle2" to={PATH_AUTH.login} component={RouterLink}>
Login
</Link>
</Typography>
)}
</ContentStyle>
</Container>
</RootStyle>
</Page>
);
}

View File

@@ -1,98 +0,0 @@
import { useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
import { Box, Button, Container, Typography } from '@mui/material';
// layouts
import LogoOnlyLayout from '../../layouts/LogoOnlyLayout';
// routes
import { PATH_AUTH } from '../../routes/paths';
// components
import Page from '../../components/Page';
// sections
import { ResetPasswordForm } from '../../sections/auth/reset-password';
// assets
import { SentIcon } from '../../assets';
// ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({
display: 'flex',
minHeight: '100%',
alignItems: 'center',
justifyContent: 'center',
padding: theme.spacing(12, 0),
}));
// ----------------------------------------------------------------------
export default function ResetPassword() {
const [email, setEmail] = useState('');
const [sent, setSent] = useState(false);
return (
<Page title="Reset Password" sx={{ height: 1 }}>
<RootStyle>
<LogoOnlyLayout />
<Container>
<Box sx={{ maxWidth: 480, mx: 'auto' }}>
{!sent ? (
<>
<Typography variant="h3" paragraph>
Forgot your password?
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 5 }}>
Please enter the email address associated with your account and We will email you
a link to reset your password.
</Typography>
<ResetPasswordForm
onSent={() => setSent(true)}
onGetEmail={(value) => setEmail(value)}
/>
<Button
fullWidth
size="large"
component={RouterLink}
to={PATH_AUTH.login}
sx={{ mt: 1 }}
>
Back
</Button>
</>
) : (
<Box sx={{ textAlign: 'center' }}>
<SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} />
<Typography variant="h3" gutterBottom>
Request sent successfully
</Typography>
<Typography>
We have sent a confirmation email to &nbsp;
<strong>{email}</strong>
<br />
Please check your email.
</Typography>
<Button
size="large"
variant="contained"
component={RouterLink}
to={PATH_AUTH.login}
sx={{ mt: 5 }}
>
Back
</Button>
</Box>
)}
</Box>
</Container>
</RootStyle>
</Page>
);
}

View File

@@ -1,67 +0,0 @@
import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
import { Box, Button, Link, Container, Typography } from '@mui/material';
// layouts
import LogoOnlyLayout from '../../layouts/LogoOnlyLayout';
// routes
import { PATH_AUTH } from '../../routes/paths';
// components
import Page from '../../components/Page';
import Iconify from '../../components/Iconify';
// sections
import { VerifyCodeForm } from '../../sections/auth/verify-code';
// ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({
display: 'flex',
height: '100%',
alignItems: 'center',
padding: theme.spacing(12, 0),
}));
// ----------------------------------------------------------------------
export default function VerifyCode() {
return (
<Page title="Verify" sx={{ height: 1 }}>
<RootStyle>
<LogoOnlyLayout />
<Container>
<Box sx={{ maxWidth: 480, mx: 'auto' }}>
<Button
size="small"
component={RouterLink}
to={PATH_AUTH.login}
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }}
>
Back
</Button>
<Typography variant="h3" paragraph>
Please check your email!
</Typography>
<Typography sx={{ color: 'text.secondary' }}>
We have emailed a 6-digit confirmation code to acb@domain, please enter the code in
below box to verify your email.
</Typography>
<Box sx={{ mt: 5, mb: 3 }}>
<VerifyCodeForm />
</Box>
<Typography variant="body2" align="center">
Dont have a code? &nbsp;
<Link variant="subtitle2" underline="none" onClick={() => {}}>
Resend code
</Link>
</Typography>
</Box>
</Container>
</RootStyle>
</Page>
);
}

View File

@@ -6,7 +6,6 @@ import LogoOnlyLayout from '../layouts/LogoOnlyLayout';
// components
import LoadingScreen from '../components/LoadingScreen';
import GuestGuard from '../guards/GuestGuard';
import { RegisterForm } from '../sections/auth/register';
import { AuthProvider } from '../contexts/LaravelAuthContext';
import AuthGuard from '../guards/AuthGuard';
@@ -39,11 +38,11 @@ export default function Router() {
),
},
{
path: 'register',
path: 'otp-validation',
element: (
<AuthProvider>
<GuestGuard>
<RegisterForm />
<OtpValidation />
</GuestGuard>
</AuthProvider>
),
@@ -113,6 +112,7 @@ export default function Router() {
}
const Login = Loadable(lazy(() => import('../pages/auth/Login')));
const OtpValidation = Loadable(lazy(() => import('../pages/auth/OtpValidation')));
// Dashboard
const Dashboard = Loadable(lazy(() => import('../pages/Dashboard')));

View File

@@ -44,8 +44,7 @@ export default function LoginPhoneForm() {
const onSubmit = async (data: FormValuesProps) => {
try {
await axios.post('/otp-request', { phone_or_email: 0 + data.phone });
// console.log(response);
// navigate('/dashboard');
navigate('/auth/otp-validation', { state: { phone_or_email: 0 + data.phone } });
} catch (error: any) {
reset();
setError('afterSubmit', { ...error, message: error.response.data.message });

View File

@@ -1,110 +0,0 @@
import * as Yup from 'yup';
import { useState } from 'react';
// form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
// @mui
import { Stack, IconButton, InputAdornment, Alert } from '@mui/material';
import { LoadingButton } from '@mui/lab';
// hooks
import useAuth from '../../../hooks/useAuth';
import useIsMountedRef from '../../../hooks/useIsMountedRef';
// components
import Iconify from '../../../components/Iconify';
import { FormProvider, RHFTextField } from '../../../components/hook-form';
// ----------------------------------------------------------------------
type FormValuesProps = {
email: string;
password: string;
firstName: string;
lastName: string;
afterSubmit?: string;
};
export default function RegisterForm() {
const { register } = useAuth();
const isMountedRef = useIsMountedRef();
const [showPassword, setShowPassword] = useState(false);
const RegisterSchema = Yup.object().shape({
firstName: Yup.string().required('First name required'),
lastName: Yup.string().required('Last name required'),
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
password: Yup.string().required('Password is required'),
});
const defaultValues = {
firstName: '',
lastName: '',
email: '',
password: '',
};
const methods = useForm<FormValuesProps>({
resolver: yupResolver(RegisterSchema),
defaultValues,
});
const {
reset,
setError,
handleSubmit,
formState: { errors, isSubmitting },
} = methods;
const onSubmit = async (data: FormValuesProps) => {
try {
await register(data.email, data.password, data.firstName, data.lastName);
} catch (error) {
console.error(error);
reset();
if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.message });
}
}
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<RHFTextField name="firstName" label="First name" />
<RHFTextField name="lastName" label="Last name" />
</Stack>
<RHFTextField name="email" label="Email address" />
<RHFTextField
name="password"
label="Password"
type={showPassword ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton edge="end" onClick={() => setShowPassword(!showPassword)}>
<Iconify icon={showPassword ? 'eva:eye-fill' : 'eva:eye-off-fill'} />
</IconButton>
</InputAdornment>
),
}}
/>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
>
Register
</LoadingButton>
</Stack>
</FormProvider>
);
}

View File

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

View File

@@ -1,70 +0,0 @@
import * as Yup from 'yup';
// form
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
// @mui
import { Stack } from '@mui/material';
import { LoadingButton } from '@mui/lab';
// hooks
import useIsMountedRef from '../../../hooks/useIsMountedRef';
// components
import { FormProvider, RHFTextField } from '../../../components/hook-form';
// ----------------------------------------------------------------------
type FormValuesProps = {
email: string;
};
type Props = {
onSent: VoidFunction;
onGetEmail: (value: string) => void;
};
export default function ResetPasswordForm({ onSent, onGetEmail }: Props) {
const isMountedRef = useIsMountedRef();
const ResetPasswordSchema = Yup.object().shape({
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
});
const methods = useForm<FormValuesProps>({
resolver: yupResolver(ResetPasswordSchema),
defaultValues: { email: 'demo@minimals.cc' },
});
const {
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = async (data: FormValuesProps) => {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
if (isMountedRef.current) {
onSent();
onGetEmail(data.email);
}
} catch (error) {
console.error(error);
}
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
<RHFTextField name="email" label="Email address" />
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
>
Reset Password
</LoadingButton>
</Stack>
</FormProvider>
);
}

View File

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

View File

@@ -1,13 +1,12 @@
import * as Yup from 'yup';
import { useSnackbar } from 'notistack';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { useEffect } from 'react';
// form
import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
// @mui
import { OutlinedInput, Stack } from '@mui/material';
import { LoadingButton } from '@mui/lab';
// routes
// import { PATH_DASHBOARD } from '../../../routes/paths';
@@ -18,24 +17,22 @@ type FormValuesProps = {
code2: string;
code3: string;
code4: string;
code5: string;
code6: string;
};
type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6';
type ValueNames = 'code1' | 'code2' | 'code3' | 'code4';
export default function VerifyCodeForm() {
const navigate = useNavigate();
const location = useLocation();
const { enqueueSnackbar } = useSnackbar();
const { phone_or_email } = location.state;
const VerifyCodeSchema = Yup.object().shape({
code1: Yup.string().required('Code is required'),
code2: Yup.string().required('Code is required'),
code3: Yup.string().required('Code is required'),
code4: Yup.string().required('Code is required'),
code5: Yup.string().required('Code is required'),
code6: Yup.string().required('Code is required'),
});
const defaultValues = {
@@ -43,8 +40,6 @@ export default function VerifyCodeForm() {
code2: '',
code3: '',
code4: '',
code5: '',
code6: '',
};
const {
@@ -62,6 +57,7 @@ export default function VerifyCodeForm() {
const values = watch();
useEffect(() => {
console.log('phone number : ' + phone_or_email);
document.addEventListener('paste', handlePasteClipboard);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -73,7 +69,7 @@ export default function VerifyCodeForm() {
enqueueSnackbar('Verify success!');
navigate('/dashboard', { replace: true });
// navigate('/dashboard', { replace: true });
} catch (error) {
console.error(error);
}
@@ -114,7 +110,7 @@ export default function VerifyCodeForm() {
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onChange={handleSubmit(onSubmit)}>
<Stack direction="row" spacing={2} justifyContent="center">
{Object.keys(values).map((name, index) => (
<Controller
@@ -144,18 +140,6 @@ export default function VerifyCodeForm() {
/>
))}
</Stack>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
disabled={!isValid}
sx={{ mt: 3 }}
>
Verify
</LoadingButton>
</form>
);
}