import React, { useState, useRef, useEffect, useMemo } from "react";
|
import { useLocation } from 'react-router-dom';
|
import {
|
Box,
|
CircularProgress,
|
Typography,
|
Button,
|
TextField,
|
Stack,
|
Autocomplete,
|
InputAdornment,
|
IconButton,
|
} from '@mui/material';
|
import {
|
useTranslate,
|
useLogin,
|
useNotify,
|
email as validEmail,
|
} from 'react-admin';
|
import { useForm, Controller } from 'react-hook-form';
|
import ProviderChoices from "./ProviderChoices";
|
import Visibility from '@mui/icons-material/Visibility';
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
import { sendEmailCode, register } from '@/api/auth';
|
|
const Register = (props) => {
|
const translate = useTranslate();
|
const notify = useNotify();
|
const login = useLogin();
|
const location = useLocation();
|
const { systemInfo } = props;
|
|
const { control, watch, handleSubmit, setValue, setError, clearErrors } = useForm();
|
|
const email = watch('email');
|
const username = watch('username');
|
const password = watch('password');
|
const confirmPassword = watch('confirmPassword');
|
const code = watch('code');
|
|
const [loading, setLoading] = useState(false);
|
const [codeLoading, setCodeLoading] = useState(false);
|
const [showPassword, setShowPassword] = useState(true);
|
const [isCounting, setIsCounting] = useState(false);
|
const [countdown, setCountdown] = useState(60);
|
|
// send code
|
const handleSendCode = async () => {
|
if (!email) {
|
setError("email", {
|
message: translate('ra.validation.required')
|
})
|
return;
|
}
|
const emailError = validEmail()(email);
|
if (emailError) {
|
setError("email", {
|
message: translate("ra.validation.email")
|
})
|
return;
|
}
|
clearErrors("email");
|
setCodeLoading(true);
|
sendEmailCode({ email }).then(res => {
|
setCodeLoading(false);
|
const { code, msg, data } = res;
|
if (code === 200) {
|
notify(msg, { type: 'success', messageArgs: { _: msg } });
|
|
const timestamp = Math.floor(Date.now() / 1000);
|
const expirationTime = timestamp + 60;
|
localStorage.setItem('codeExpirationTime', expirationTime);
|
|
setIsCounting(true);
|
setCountdown(60);
|
} else if (code === 10005 || code === 10006) {
|
setError('email', {
|
message: msg
|
})
|
} else {
|
notify(msg, { type: 'error', messageArgs: { _: msg } });
|
}
|
}).catch((error) => {
|
setCodeLoading(false);
|
notify(error.message, { type: 'error', messageArgs: { _: error.message } });
|
console.error(error);
|
})
|
};
|
|
// countdown
|
useEffect(() => {
|
const codeExpirationTime = localStorage.getItem('codeExpirationTime');
|
if (codeExpirationTime) {
|
const currentTimestamp = Math.floor(Date.now() / 1000);
|
const remainingTime = codeExpirationTime - currentTimestamp;
|
if (remainingTime > 0) {
|
setCountdown(remainingTime);
|
setIsCounting(true);
|
}
|
}
|
|
const interval = setInterval(() => {
|
if (isCounting && countdown > 0) {
|
setCountdown(prev => prev - 1);
|
} else if (countdown <= 0) {
|
clearInterval(interval);
|
setIsCounting(false);
|
localStorage.removeItem('codeExpirationTime');
|
}
|
}, 1000);
|
|
return () => clearInterval(interval);
|
}, [countdown, isCounting]);
|
|
// register
|
const onSubmit = (params) => {
|
setLoading(true);
|
register(params).then(res => {
|
setLoading(false);
|
const { code, msg, data } = res;
|
if (code === 200) {
|
notify(msg, { type: 'success', messageArgs: { _: msg } });
|
// to login
|
login(
|
params,
|
location.state ? (location.state).nextPathname : '/'
|
).catch(({ code, msg }) => {
|
setLoading(false);
|
notify(msg, { type: 'error', messageArgs: { _: msg } });
|
});
|
} else if (code === 10002) {
|
setError("username", {
|
message: msg
|
})
|
} else {
|
notify(msg, { type: 'error', messageArgs: { _: msg } });
|
}
|
}).catch((error) => {
|
setLoading(false);
|
notify(error.message, { type: 'error', messageArgs: { _: error.message } });
|
console.error(error);
|
})
|
};
|
|
return (
|
<>
|
<Box
|
p={2}
|
display="flex"
|
flexDirection='column'
|
component="form" onSubmit={handleSubmit(onSubmit)} noValidate
|
>
|
<Stack spacing={2}>
|
<Controller
|
name="username"
|
control={control}
|
defaultValue=""
|
rules={{ required: true }}
|
render={({ field, fieldState: { error } }) => (
|
<TextField
|
{...field}
|
label={translate("page.login.username")}
|
variant="standard"
|
disabled={loading}
|
autoFocus
|
autoComplete="off"
|
error={!!error}
|
helperText={error?.message || ""}
|
/>
|
)}
|
/>
|
|
<Controller
|
name="password"
|
control={control}
|
defaultValue=""
|
rules={{
|
required: translate('ra.validation.required'),
|
pattern: {
|
value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d\.]{6,13}$/,
|
message: translate('page.settings.resetPwd.tip.pwdInputLimit'),
|
},
|
}}
|
render={({ field, fieldState: { error } }) => (
|
<TextField
|
{...field}
|
label={translate("page.login.password")}
|
type={showPassword ? 'text' : 'password'}
|
variant="standard"
|
disabled={loading}
|
autoComplete="off"
|
error={!!error}
|
helperText={error?.message || ""}
|
InputProps={{
|
endAdornment: (
|
<InputAdornment position="end">
|
<IconButton
|
aria-label="toggle password visibility"
|
onClick={() => setShowPassword((show) => !show)}
|
onMouseDown={(event) => { event.preventDefault() }}
|
edge="end"
|
>
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
</IconButton>
|
</InputAdornment>
|
),
|
}}
|
/>
|
)}
|
/>
|
|
<Controller
|
name="confirmPassword"
|
control={control}
|
defaultValue=""
|
rules={{
|
required: translate('ra.validation.required'),
|
validate: value =>
|
value === password || translate('page.settings.resetPwd.tip.pwdNotMatch'),
|
}}
|
render={({ field, fieldState: { error } }) => (
|
<TextField
|
{...field}
|
label={translate('page.login.confirmPwd')}
|
type={showPassword ? 'text' : 'password'}
|
variant="standard"
|
disabled={loading}
|
autoComplete="off"
|
error={!!error}
|
helperText={error?.message || ""}
|
InputProps={{
|
endAdornment: (
|
<InputAdornment position="end">
|
<IconButton
|
aria-label="toggle password visibility"
|
onClick={() => setShowPassword((show) => !show)}
|
onMouseDown={(event) => { event.preventDefault() }}
|
edge="end"
|
>
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
</IconButton>
|
</InputAdornment>
|
),
|
}}
|
/>
|
)}
|
/>
|
|
<Controller
|
name="email"
|
control={control}
|
defaultValue=""
|
rules={{
|
required: translate('ra.validation.required'),
|
pattern: {
|
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
message: translate("ra.validation.email"),
|
},
|
}}
|
render={({ field, fieldState: { error } }) => (
|
<TextField
|
{...field}
|
label={translate('page.login.email')}
|
variant="standard"
|
disabled={loading}
|
// autoComplete="off"
|
error={!!error}
|
helperText={error ? error.message : ""}
|
/>
|
)}
|
/>
|
|
<Box display="flex" alignItems="center" justifyContent='center' width="100%">
|
<Controller
|
name="code"
|
control={control}
|
defaultValue=""
|
rules={{
|
required: translate('ra.validation.required'),
|
}}
|
render={({ field, fieldState: { error } }) => (
|
<TextField
|
{...field}
|
label={translate('page.login.code')}
|
variant="standard"
|
disabled={loading}
|
autoComplete="off"
|
error={!!error}
|
helperText={error ? error.message : ""}
|
sx={{
|
width: '65%',
|
mr: 2,
|
}}
|
/>
|
)}
|
/>
|
|
<Button
|
variant="outlined"
|
onClick={handleSendCode}
|
disabled={codeLoading || isCounting}
|
sx={{
|
width: '35%',
|
mt: 1,
|
whiteSpace: 'nowrap',
|
}}
|
>
|
{codeLoading ? (
|
<CircularProgress size={20} color="primary" sx={{ marginRight: 1 }} />
|
) :
|
isCounting ? (
|
`${countdown}s`
|
) : (
|
translate('page.login.button.code')
|
)
|
}
|
</Button>
|
</Box>
|
|
<Box />
|
|
<Button
|
type="submit"
|
variant="contained"
|
disabled={loading || !(email && username && password && confirmPassword && code)}
|
sx={{
|
backgroundColor: "#3D4BA7"
|
}}
|
>
|
{loading && <CircularProgress size={25} thickness={2} />}
|
{translate('page.login.button.register')}
|
</Button>
|
|
</Stack>
|
<Box mt={1} mb={1} sx={{ textAlign: 'center' }}>or</Box>
|
|
<ProviderChoices type="REGISTER" />
|
</Box >
|
</>
|
)
|
}
|
|
export default Register;
|