|
|
|
@@ -1,283 +1,317 @@ |
|
|
|
import React, {useState} from 'react'; |
|
|
|
import {useNavigate} from 'react-router-dom'; |
|
|
|
import React, { useState } from 'react'; |
|
|
|
import { useNavigate } from 'react-router-dom'; |
|
|
|
import { useDispatch } from 'react-redux'; |
|
|
|
|
|
|
|
// material-ui |
|
|
|
import { |
|
|
|
Button, |
|
|
|
//Checkbox, |
|
|
|
//Divider, |
|
|
|
//FormControlLabel, |
|
|
|
FormHelperText, |
|
|
|
Grid, |
|
|
|
//Link, |
|
|
|
IconButton, |
|
|
|
InputAdornment, |
|
|
|
InputLabel, |
|
|
|
Stack, TextField, |
|
|
|
//Typography |
|
|
|
Button, |
|
|
|
FormHelperText, |
|
|
|
Grid, |
|
|
|
InputLabel, |
|
|
|
Stack, |
|
|
|
TextField, |
|
|
|
IconButton, |
|
|
|
InputAdornment, |
|
|
|
Dialog, |
|
|
|
DialogTitle, |
|
|
|
DialogContent, |
|
|
|
DialogActions, |
|
|
|
Typography, |
|
|
|
Box |
|
|
|
} from '@mui/material'; |
|
|
|
|
|
|
|
// third party |
|
|
|
import * as Yup from 'yup'; |
|
|
|
import { Formik } from 'formik'; |
|
|
|
|
|
|
|
// project import |
|
|
|
//import FirebaseSocial from './FirebaseSocial'; |
|
|
|
// project imports |
|
|
|
import AnimateButton from 'components/@extended/AnimateButton'; |
|
|
|
import { handleLogin } from 'auth/index'; |
|
|
|
import axios from 'axios'; |
|
|
|
import { apiPath } from '../../../auth/utils'; |
|
|
|
import { LOGIN_PATH } from '../../../utils/ApiPathConst'; |
|
|
|
import { LIONER_LOGIN_THEME } from '../../../themes/themeConst'; |
|
|
|
import { ThemeProvider } from '@emotion/react'; |
|
|
|
|
|
|
|
// assets |
|
|
|
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; |
|
|
|
//import axios from "axios"; |
|
|
|
import {useDispatch} from "react-redux"; |
|
|
|
import {handleLogin} from "auth/index"; |
|
|
|
//import useJwt from "../../../auth/jwt/useJwt"; |
|
|
|
import axios from "axios"; |
|
|
|
import {apiPath} from "../../../auth/utils"; |
|
|
|
import {LOGIN_PATH} from "../../../utils/ApiPathConst"; |
|
|
|
import {LIONER_LOGIN_THEME} from "../../../themes/themeConst"; |
|
|
|
import {ThemeProvider} from "@emotion/react"; |
|
|
|
//import {AbilityContext} from "utils/context/Can" |
|
|
|
// ============================|| FIREBASE - LOGIN ||============================ // |
|
|
|
import { QRCodeSVG } from 'qrcode.react'; |
|
|
|
|
|
|
|
// ============================|| AUTH LOGIN WITH 2FA ||============================ // |
|
|
|
|
|
|
|
const AuthLogin = () => { |
|
|
|
//const ability = useContext(AbilityContext) |
|
|
|
const dispatch = useDispatch() |
|
|
|
const navigate = useNavigate() |
|
|
|
//const [checked, setChecked] = useState(false); |
|
|
|
const dispatch = useDispatch(); |
|
|
|
const navigate = useNavigate(); |
|
|
|
|
|
|
|
const [showPassword, setShowPassword] = useState(false); |
|
|
|
const [userName, setUserName] = useState(''); |
|
|
|
const [userPassword, setUserPassword] = useState(''); |
|
|
|
const [errors, setErrors] = useState({}); |
|
|
|
|
|
|
|
// 2FA States |
|
|
|
const [twoFAModalOpen, setTwoFAModalOpen] = useState(false); |
|
|
|
const [twoFAMode, setTwoFAMode] = useState('login'); // 'login' or 'setup' |
|
|
|
const [qrUrl, setQrUrl] = useState(''); |
|
|
|
const [twoFACode, setTwoFACode] = useState(''); |
|
|
|
const [twoFAError, setTwoFAError] = useState(''); |
|
|
|
const [partialToken, setPartialToken] = useState(''); // If backend uses partial session |
|
|
|
|
|
|
|
const handleClickShowPassword = () => { |
|
|
|
setShowPassword(!showPassword); |
|
|
|
}; |
|
|
|
|
|
|
|
const [userName, setUserName] = useState(""); |
|
|
|
const [userPassword, setUserPassword] = useState(""); |
|
|
|
|
|
|
|
const handleMouseDownPassword = (event) => { |
|
|
|
event.preventDefault(); |
|
|
|
}; |
|
|
|
|
|
|
|
const onUserNameChange = (e) => setUserName(e.target.value); |
|
|
|
const onPasswordChange = (e) => setUserPassword(e.target.value); |
|
|
|
|
|
|
|
const tryLogin = () => { |
|
|
|
const formErrors = {}; |
|
|
|
|
|
|
|
if (!userName) formErrors.loginError = 'Username is required.'; |
|
|
|
if (!userPassword) formErrors.passwordError = 'Password is required.'; |
|
|
|
|
|
|
|
setErrors(formErrors); |
|
|
|
|
|
|
|
if (Object.keys(formErrors).length === 0) { |
|
|
|
axios |
|
|
|
.post(`${apiPath}${LOGIN_PATH}`, { |
|
|
|
username: userName, |
|
|
|
password: userPassword |
|
|
|
}) |
|
|
|
.then(async (response) => { |
|
|
|
const data = response.data; |
|
|
|
|
|
|
|
// Case 1: 2FA is required |
|
|
|
if (data.requires2FA) { |
|
|
|
setPartialToken(data.partialToken || ''); // optional, depending on backend |
|
|
|
setTwoFAMode('login'); |
|
|
|
setTwoFAModalOpen(true); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Case 2: Login successful (no 2FA or already verified) |
|
|
|
if (data.accessToken) { |
|
|
|
const userData = { |
|
|
|
id: data.id, |
|
|
|
fullName: data.name, |
|
|
|
email: data.email, |
|
|
|
role: data.role, |
|
|
|
abilities: data.abilities, |
|
|
|
subDivisionId: data.subDivisionId, |
|
|
|
lotusNotesUser: data.lotusNotesUser |
|
|
|
}; |
|
|
|
|
|
|
|
await dispatch(handleLogin({ ...userData, accessToken: data.accessToken, refreshToken: data.refreshToken })); |
|
|
|
|
|
|
|
const lastPath = localStorage.getItem('lastVisitedPath'); |
|
|
|
navigate(lastPath || '/client'); |
|
|
|
window.location.reload(); |
|
|
|
localStorage.removeItem('lastVisitedPath'); |
|
|
|
|
|
|
|
// Optional: Prompt to setup 2FA if not enabled |
|
|
|
if (!data.twoFactorEnabled) { |
|
|
|
setTimeout(() => { |
|
|
|
if (window.confirm('For extra security, would you like to enable 2FA with Microsoft Authenticator?')) { |
|
|
|
start2FASetup(); |
|
|
|
} |
|
|
|
}, 1000); |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch((error) => { |
|
|
|
const formErrors = {}; |
|
|
|
formErrors.passwordError = error.response?.data?.message || 'Invalid credentials'; |
|
|
|
setErrors(formErrors); |
|
|
|
}); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const start2FASetup = () => { |
|
|
|
axios |
|
|
|
.post(`${apiPath}/2fa/setup`) // Requires user to be authenticated |
|
|
|
.then((res) => { |
|
|
|
setQrUrl(res.data.otpauthUrl); |
|
|
|
setTwoFAMode('setup'); |
|
|
|
setTwoFAModalOpen(true); |
|
|
|
setTwoFAError(''); |
|
|
|
}) |
|
|
|
.catch((err) => { |
|
|
|
alert('Failed to start 2FA setup: ' + (err.response?.data?.message || err.message)); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
const handle2FASubmit = () => { |
|
|
|
if (twoFACode.length !== 6 || !/^\d+$/.test(twoFACode)) { |
|
|
|
setTwoFAError('Please enter a valid 6-digit code'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const endpoint = twoFAMode === 'setup' ? '/2fa/verify-setup' : '/2fa/verify-login'; |
|
|
|
|
|
|
|
// Use the actual username from login form, NOT hard-coded '2fi' |
|
|
|
const payload = { |
|
|
|
code: twoFACode, |
|
|
|
username: userName // ← This is your state from <TextField onChange={onUserNameChange} /> |
|
|
|
}; |
|
|
|
|
|
|
|
axios |
|
|
|
.post(`${apiPath}${endpoint}`, payload) |
|
|
|
.then(async (res) => { |
|
|
|
if (twoFAMode === 'setup') { |
|
|
|
alert('2FA enabled!'); |
|
|
|
setTwoFAModalOpen(false); |
|
|
|
} else { |
|
|
|
// Full login - use the exact same structure as normal login |
|
|
|
await dispatch(handleLogin(res.data)); // res.data should be JwtResponse |
|
|
|
setTwoFAModalOpen(false); |
|
|
|
const lastPath = localStorage.getItem('lastVisitedPath'); |
|
|
|
navigate(lastPath || '/client'); |
|
|
|
window.location.reload(); |
|
|
|
} |
|
|
|
setTwoFACode(''); |
|
|
|
}) |
|
|
|
.catch((err) => { |
|
|
|
setTwoFAError(err.response?.data?.message || 'Invalid or expired code'); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
const formErrors = {}; |
|
|
|
|
|
|
|
if (userName.length===0) { |
|
|
|
formErrors.loginError = 'Username are required.'; |
|
|
|
} |
|
|
|
|
|
|
|
if (userPassword.length===0){ |
|
|
|
formErrors.passwordError = 'Password are required.'; |
|
|
|
} |
|
|
|
|
|
|
|
setErrors(formErrors); |
|
|
|
|
|
|
|
if (Object.keys(formErrors).length === 0) { |
|
|
|
axios.post(`${apiPath}${LOGIN_PATH}`, |
|
|
|
{username: userName, password: userPassword}, |
|
|
|
) |
|
|
|
.then(async (response) => { |
|
|
|
const userData = { |
|
|
|
id: response.data.id, |
|
|
|
fullName: response.data.name, |
|
|
|
email: response.data.email, |
|
|
|
role: response.data.role, |
|
|
|
abilities: response.data.abilities, |
|
|
|
subDivisionId: response.data.subDivisionId, |
|
|
|
lotusNotesUser: response.data.lotusNotesUser |
|
|
|
//avatar: require('src/assets/images/users/avatar-3.png').default, |
|
|
|
} |
|
|
|
const data = { |
|
|
|
...userData, |
|
|
|
accessToken: response.data.accessToken, |
|
|
|
refreshToken: response.data.refreshToken |
|
|
|
} |
|
|
|
await dispatch(handleLogin(data)) |
|
|
|
//const abilities = response.data.abilities |
|
|
|
//ability.update(abilities) |
|
|
|
const lastPath = localStorage.getItem('lastVisitedPath'); |
|
|
|
|
|
|
|
await navigate(lastPath === null ? '/client' : lastPath); |
|
|
|
// await navigate(lastPath === null ? '/lionerDashboard' : lastPath); |
|
|
|
await window.location.reload(); |
|
|
|
await localStorage.removeItem('lastVisitedPath'); |
|
|
|
|
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
const formErrors = {}; |
|
|
|
formErrors.loginError = " "; |
|
|
|
formErrors.passwordError = error.response.data.message; |
|
|
|
setErrors(formErrors); |
|
|
|
|
|
|
|
return false; |
|
|
|
}); |
|
|
|
|
|
|
|
/*useJwt |
|
|
|
.login({username: userName, password: userPassword}) |
|
|
|
.then((response) => { |
|
|
|
const userData = { |
|
|
|
id: response.data.id, |
|
|
|
fullName: response.data.name, |
|
|
|
email: response.data.email, |
|
|
|
role: response.data.role, |
|
|
|
abilities: response.data.abilities, |
|
|
|
//avatar: require('src/assets/images/users/avatar-3.png').default, |
|
|
|
} |
|
|
|
const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken} |
|
|
|
dispatch(handleLogin(data)) |
|
|
|
//const abilities = response.data.abilities |
|
|
|
//ability.update(abilities) |
|
|
|
navigate('/lionerDashboard'); |
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
console.log(error); |
|
|
|
return Promise.reject(error); |
|
|
|
});*/ |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const onUserNameChange = (event) => { |
|
|
|
setUserName(event.target.value); |
|
|
|
} |
|
|
|
|
|
|
|
const onPasswordChange = (event) => { |
|
|
|
setUserPassword(event.target.value); |
|
|
|
} |
|
|
|
return ( |
|
|
|
<> |
|
|
|
<Formik |
|
|
|
initialValues={{ |
|
|
|
userName: 'test', |
|
|
|
email: 'info@codedthemes.com', |
|
|
|
password: '123456', |
|
|
|
userName: '', |
|
|
|
password: '', |
|
|
|
submit: null |
|
|
|
}} |
|
|
|
validationSchema={Yup.object().shape({ |
|
|
|
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'), |
|
|
|
password: Yup.string().max(255).required('Password is required') |
|
|
|
userName: Yup.string().required('Username is required'), |
|
|
|
password: Yup.string().required('Password is required') |
|
|
|
})} |
|
|
|
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => { |
|
|
|
try { |
|
|
|
setStatus({ success: false }); |
|
|
|
setSubmitting(false); |
|
|
|
} catch (err) { |
|
|
|
setStatus({ success: false }); |
|
|
|
setErrors({ submit: err.message }); |
|
|
|
setSubmitting(false); |
|
|
|
} |
|
|
|
}} |
|
|
|
> |
|
|
|
{({ handleBlur, handleSubmit, isSubmitting /*touched*/ }) => ( |
|
|
|
<form onSubmit={handleSubmit}> |
|
|
|
<ThemeProvider theme={LIONER_LOGIN_THEME}> |
|
|
|
{({ handleBlur }) => ( |
|
|
|
<form> |
|
|
|
<ThemeProvider theme={LIONER_LOGIN_THEME}> |
|
|
|
<Grid container spacing={3}> |
|
|
|
<Grid item xs={12}> |
|
|
|
<Stack spacing={1}> |
|
|
|
<InputLabel htmlFor="username">User Name</InputLabel> |
|
|
|
<TextField |
|
|
|
id="username" |
|
|
|
name="username" |
|
|
|
onBlur={handleBlur} |
|
|
|
onChange={onUserNameChange} |
|
|
|
placeholder="Enter user name" |
|
|
|
fullWidth |
|
|
|
variant="outlined" |
|
|
|
error={!!errors.loginError} |
|
|
|
helperText={errors.loginError} |
|
|
|
/> |
|
|
|
{/*{touched.email && errors.email && (*/} |
|
|
|
{/* <FormHelperText error id="standard-weight-helper-text-email-login">*/} |
|
|
|
{/* {errors.email}*/} |
|
|
|
{/* </FormHelperText>*/} |
|
|
|
{/*)}*/} |
|
|
|
</Stack> |
|
|
|
</Grid> |
|
|
|
<Grid item xs={12}> |
|
|
|
<Stack spacing={1}> |
|
|
|
<InputLabel htmlFor="-password-login">Password</InputLabel> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
variant="outlined" |
|
|
|
error={!!errors.passwordError} |
|
|
|
helperText={errors.passwordError} |
|
|
|
id="-password-login" |
|
|
|
type={showPassword ? 'text' : 'password'} |
|
|
|
name="password" |
|
|
|
onBlur={handleBlur} |
|
|
|
onChange={onPasswordChange} |
|
|
|
InputProps={{ |
|
|
|
<Grid item xs={12}> |
|
|
|
<Stack spacing={1}> |
|
|
|
<InputLabel htmlFor="username">User Name</InputLabel> |
|
|
|
<TextField |
|
|
|
id="username" |
|
|
|
name="username" |
|
|
|
value={userName} |
|
|
|
onBlur={handleBlur} |
|
|
|
onChange={onUserNameChange} |
|
|
|
placeholder="Enter user name" |
|
|
|
fullWidth |
|
|
|
variant="outlined" |
|
|
|
error={!!errors.loginError} |
|
|
|
helperText={errors.loginError} |
|
|
|
/> |
|
|
|
</Stack> |
|
|
|
</Grid> |
|
|
|
|
|
|
|
<Grid item xs={12}> |
|
|
|
<Stack spacing={1}> |
|
|
|
<InputLabel htmlFor="password-login">Password</InputLabel> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
variant="outlined" |
|
|
|
error={!!errors.passwordError} |
|
|
|
helperText={errors.passwordError} |
|
|
|
id="password-login" |
|
|
|
type={showPassword ? 'text' : 'password'} |
|
|
|
value={userPassword} |
|
|
|
name="password" |
|
|
|
onBlur={handleBlur} |
|
|
|
onChange={onPasswordChange} |
|
|
|
InputProps={{ |
|
|
|
endAdornment: ( |
|
|
|
<InputAdornment position="end"> |
|
|
|
<IconButton |
|
|
|
aria-label="toggle password visibility" |
|
|
|
onClick={handleClickShowPassword} |
|
|
|
onMouseDown={handleMouseDownPassword} |
|
|
|
edge="end" |
|
|
|
size="large" |
|
|
|
> |
|
|
|
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />} |
|
|
|
</IconButton> |
|
|
|
</InputAdornment> |
|
|
|
), |
|
|
|
}} |
|
|
|
placeholder="Enter password" |
|
|
|
/> |
|
|
|
{/*{touched.password && errors.password && (*/} |
|
|
|
{/* <FormHelperText error id="standard-weight-helper-text-password-login">*/} |
|
|
|
{/* {errors.password}*/} |
|
|
|
{/* </FormHelperText>*/} |
|
|
|
{/*)}*/} |
|
|
|
</Stack> |
|
|
|
</Grid> |
|
|
|
<InputAdornment position="end"> |
|
|
|
<IconButton |
|
|
|
aria-label="toggle password visibility" |
|
|
|
onClick={handleClickShowPassword} |
|
|
|
onMouseDown={handleMouseDownPassword} |
|
|
|
edge="end" |
|
|
|
size="large" |
|
|
|
> |
|
|
|
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />} |
|
|
|
</IconButton> |
|
|
|
</InputAdornment> |
|
|
|
) |
|
|
|
}} |
|
|
|
placeholder="Enter password" |
|
|
|
/> |
|
|
|
</Stack> |
|
|
|
</Grid> |
|
|
|
|
|
|
|
<Grid item xs={12} sx={{ mt: -1 }}> |
|
|
|
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}> |
|
|
|
{/*<FormControlLabel*/} |
|
|
|
{/* control={*/} |
|
|
|
{/* <Checkbox*/} |
|
|
|
{/* checked={checked}*/} |
|
|
|
{/* onChange={(event) => setChecked(event.target.checked)}*/} |
|
|
|
{/* name="checked"*/} |
|
|
|
{/* color="primary"*/} |
|
|
|
{/* size="small"*/} |
|
|
|
{/* />*/} |
|
|
|
{/* }*/} |
|
|
|
{/* label={<Typography variant="h6">Keep me sign in</Typography>}*/} |
|
|
|
{/*/>*/} |
|
|
|
{/*<Link variant="h6" component={RouterLink} to="" color="text.primary">*/} |
|
|
|
{/* Forgot Password?*/} |
|
|
|
{/*</Link>*/} |
|
|
|
</Stack> |
|
|
|
</Grid> |
|
|
|
{errors.submit && ( |
|
|
|
<Grid item xs={12}> |
|
|
|
<FormHelperText error>{errors.submit}</FormHelperText> |
|
|
|
<AnimateButton> |
|
|
|
<Button |
|
|
|
onClick={tryLogin} |
|
|
|
sx={{ height: '50px' }} |
|
|
|
fullWidth |
|
|
|
size="large" |
|
|
|
variant="contained" |
|
|
|
color="primary" |
|
|
|
> |
|
|
|
Login |
|
|
|
</Button> |
|
|
|
</AnimateButton> |
|
|
|
</Grid> |
|
|
|
)} |
|
|
|
<Grid item xs={12}> |
|
|
|
<AnimateButton> |
|
|
|
<Button disableElevation onClick={tryLogin} |
|
|
|
sx={{height: '50px'}} |
|
|
|
disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary"> |
|
|
|
Login |
|
|
|
</Button> |
|
|
|
</AnimateButton> |
|
|
|
</Grid> |
|
|
|
{/*<Grid item xs={12}>*/} |
|
|
|
{/* <Divider>*/} |
|
|
|
{/* <Typography variant="caption"> Login with</Typography>*/} |
|
|
|
{/* </Divider>*/} |
|
|
|
{/*</Grid>*/} |
|
|
|
{/*<Grid item xs={12}>*/} |
|
|
|
{/* <FirebaseSocial />*/} |
|
|
|
{/*</Grid>*/} |
|
|
|
</Grid> |
|
|
|
</ThemeProvider> |
|
|
|
</ThemeProvider> |
|
|
|
</form> |
|
|
|
)} |
|
|
|
</Formik> |
|
|
|
|
|
|
|
{/* 2FA Modal */} |
|
|
|
<Dialog open={twoFAModalOpen} onClose={() => setTwoFAModalOpen(false)} maxWidth="sm" fullWidth> |
|
|
|
<DialogTitle> |
|
|
|
{twoFAMode === 'setup' ? 'Set Up Two-Factor Authentication' : 'Two-Factor Authentication Required'} |
|
|
|
</DialogTitle> |
|
|
|
<DialogContent> |
|
|
|
{twoFAMode === 'setup' && qrUrl && ( |
|
|
|
<Box sx={{ textAlign: 'center', my: 3 }}> |
|
|
|
<Typography variant="body1" gutterBottom> |
|
|
|
Scan this QR code with <strong>Microsoft Authenticator</strong>: |
|
|
|
</Typography> |
|
|
|
<QRCodeSVG value={qrUrl} size={220} level="M" /> |
|
|
|
<Typography variant="caption" display="block" sx={{ mt: 2 }}> |
|
|
|
After scanning, enter a code from the app to verify. |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
)} |
|
|
|
|
|
|
|
{twoFAMode === 'login' && ( |
|
|
|
<Typography variant="body1" sx={{ mb: 2 }}> |
|
|
|
Open Microsoft Authenticator and enter the 6-digit code for your account. |
|
|
|
</Typography> |
|
|
|
)} |
|
|
|
|
|
|
|
<TextField |
|
|
|
autoFocus |
|
|
|
fullWidth |
|
|
|
label="6-Digit Code" |
|
|
|
value={twoFACode} |
|
|
|
onChange={(e) => setTwoFACode(e.target.value.replace(/\D/g, '').slice(0, 6))} |
|
|
|
error={!!twoFAError} |
|
|
|
helperText={twoFAError} |
|
|
|
inputProps={{ maxLength: 6 }} |
|
|
|
sx={{ mt: 2 }} |
|
|
|
/> |
|
|
|
</DialogContent> |
|
|
|
<DialogActions> |
|
|
|
<Button onClick={() => setTwoFAModalOpen(false)}>Cancel</Button> |
|
|
|
<Button onClick={handle2FASubmit} variant="contained" disabled={twoFACode.length !== 6}> |
|
|
|
Verify |
|
|
|
</Button> |
|
|
|
</DialogActions> |
|
|
|
</Dialog> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
export default AuthLogin; |
|
|
|
export default AuthLogin; |