🚧 补全忘记密码web页面
This commit is contained in:
parent
3ac0b256e3
commit
fe72f85554
@ -8,6 +8,8 @@ import MinimalLayout from 'layout/MinimalLayout';
|
||||
const AuthLogin = Loadable(lazy(() => import('views/Authentication/Auth/Login')));
|
||||
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
||||
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
||||
const ForgetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ForgetPassword')));
|
||||
const ResetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ResetPassword')));
|
||||
const Home = Loadable(lazy(() => import('views/Home')));
|
||||
const About = Loadable(lazy(() => import('views/About')));
|
||||
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
||||
@ -34,6 +36,14 @@ const OtherRoutes = {
|
||||
path: '/register',
|
||||
element: <AuthRegister />
|
||||
},
|
||||
{
|
||||
path: '/reset',
|
||||
element: <ForgetPassword />
|
||||
},
|
||||
{
|
||||
path: '/user/reset',
|
||||
element: <ResetPassword />
|
||||
},
|
||||
{
|
||||
path: '/oauth/github',
|
||||
element: <GitHubOAuth />
|
||||
|
68
web/src/views/Authentication/Auth/ForgetPassword.js
Normal file
68
web/src/views/Authentication/Auth/ForgetPassword.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Divider, Grid, Stack, Typography, useMediaQuery } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import AuthWrapper from '../AuthWrapper';
|
||||
import AuthCardWrapper from '../AuthCardWrapper';
|
||||
import ForgetPasswordForm from '../AuthForms/ForgetPasswordForm';
|
||||
import Logo from 'ui-component/Logo';
|
||||
|
||||
// assets
|
||||
|
||||
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||
|
||||
const ForgetPassword = () => {
|
||||
const theme = useTheme();
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<Grid container direction="column" justifyContent="flex-end">
|
||||
<Grid item xs={12}>
|
||||
<Grid container justifyContent="center" alignItems="center" sx={{ minHeight: 'calc(100vh - 136px)' }}>
|
||||
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||
<AuthCardWrapper>
|
||||
<Grid container spacing={2} alignItems="center" justifyContent="center">
|
||||
<Grid item sx={{ mb: 3 }}>
|
||||
<Link to="#">
|
||||
<Logo />
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container direction={matchDownSM ? 'column-reverse' : 'row'} alignItems="center" justifyContent="center">
|
||||
<Grid item>
|
||||
<Stack alignItems="center" justifyContent="center" spacing={1}>
|
||||
<Typography color={theme.palette.primary.main} gutterBottom variant={matchDownSM ? 'h3' : 'h2'}>
|
||||
密码重置
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ForgetPasswordForm />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item container direction="column" alignItems="center" xs={12}>
|
||||
<Typography component={Link} to="/login" variant="subtitle1" sx={{ textDecoration: 'none' }}>
|
||||
登录
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthCardWrapper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgetPassword;
|
@ -10,8 +10,6 @@ import AuthCardWrapper from '../AuthCardWrapper';
|
||||
import AuthLogin from '../AuthForms/AuthLogin';
|
||||
import Logo from 'ui-component/Logo';
|
||||
|
||||
// assets
|
||||
|
||||
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||
|
||||
const Login = () => {
|
||||
|
66
web/src/views/Authentication/Auth/ResetPassword.js
Normal file
66
web/src/views/Authentication/Auth/ResetPassword.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Divider, Grid, Stack, Typography, useMediaQuery } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import AuthWrapper from '../AuthWrapper';
|
||||
import AuthCardWrapper from '../AuthCardWrapper';
|
||||
import ResetPasswordForm from '../AuthForms/ResetPasswordForm';
|
||||
import Logo from 'ui-component/Logo';
|
||||
|
||||
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||
|
||||
const ResetPassword = () => {
|
||||
const theme = useTheme();
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<Grid container direction="column" justifyContent="flex-end">
|
||||
<Grid item xs={12}>
|
||||
<Grid container justifyContent="center" alignItems="center" sx={{ minHeight: 'calc(100vh - 136px)' }}>
|
||||
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||
<AuthCardWrapper>
|
||||
<Grid container spacing={2} alignItems="center" justifyContent="center">
|
||||
<Grid item sx={{ mb: 3 }}>
|
||||
<Link to="#">
|
||||
<Logo />
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container direction={matchDownSM ? 'column-reverse' : 'row'} alignItems="center" justifyContent="center">
|
||||
<Grid item>
|
||||
<Stack alignItems="center" justifyContent="center" spacing={1}>
|
||||
<Typography color={theme.palette.primary.main} gutterBottom variant={matchDownSM ? 'h3' : 'h2'}>
|
||||
密码重置确认
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ResetPasswordForm />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item container direction="column" alignItems="center" xs={12}>
|
||||
<Typography component={Link} to="/login" variant="subtitle1" sx={{ textDecoration: 'none' }}>
|
||||
登录
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthCardWrapper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
@ -1,15 +1,14 @@
|
||||
import { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
// Checkbox,
|
||||
Divider,
|
||||
FormControl,
|
||||
// FormControlLabel,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
@ -236,7 +235,13 @@ const LoginForm = ({ ...others }) => {
|
||||
}
|
||||
label="记住我"
|
||||
/> */}
|
||||
<Typography variant="subtitle1" color="primary" sx={{ textDecoration: 'none', cursor: 'pointer' }}>
|
||||
<Typography
|
||||
component={Link}
|
||||
to="/reset"
|
||||
variant="subtitle1"
|
||||
color="primary"
|
||||
sx={{ textDecoration: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
忘记密码?
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
144
web/src/views/Authentication/AuthForms/ForgetPasswordForm.js
Normal file
144
web/src/views/Authentication/AuthForms/ForgetPasswordForm.js
Normal file
@ -0,0 +1,144 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Turnstile from 'react-turnstile';
|
||||
import { API } from 'utils/api';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box, Button, FormControl, FormHelperText, InputLabel, OutlinedInput, Typography } from '@mui/material';
|
||||
|
||||
// third party
|
||||
import * as Yup from 'yup';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
// project imports
|
||||
import AnimateButton from 'ui-component/extended/AnimateButton';
|
||||
|
||||
// assets
|
||||
import { showError, showInfo } from 'utils/common';
|
||||
|
||||
// ===========================|| FIREBASE - REGISTER ||=========================== //
|
||||
|
||||
const ForgetPasswordForm = ({ ...others }) => {
|
||||
const theme = useTheme();
|
||||
const siteInfo = useSelector((state) => state.siteInfo);
|
||||
|
||||
const [sendEmail, setSendEmail] = useState(false);
|
||||
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
|
||||
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
|
||||
const [turnstileToken, setTurnstileToken] = useState('');
|
||||
const [disableButton, setDisableButton] = useState(false);
|
||||
const [countdown, setCountdown] = useState(30);
|
||||
|
||||
const submit = async (values, { setSubmitting }) => {
|
||||
setDisableButton(true);
|
||||
setSubmitting(true);
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
const res = await API.get(`/api/reset_password?email=${values.email}&turnstile=${turnstileToken}`);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('重置邮件发送成功,请检查邮箱!');
|
||||
setSendEmail(true);
|
||||
} else {
|
||||
showError(message);
|
||||
setDisableButton(false);
|
||||
setCountdown(30);
|
||||
}
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let countdownInterval = null;
|
||||
if (disableButton && countdown > 0) {
|
||||
countdownInterval = setInterval(() => {
|
||||
setCountdown(countdown - 1);
|
||||
}, 1000);
|
||||
} else if (countdown === 0) {
|
||||
setDisableButton(false);
|
||||
setCountdown(30);
|
||||
}
|
||||
return () => clearInterval(countdownInterval);
|
||||
}, [disableButton, countdown]);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteInfo.turnstile_check) {
|
||||
setTurnstileEnabled(true);
|
||||
setTurnstileSiteKey(siteInfo.turnstile_site_key);
|
||||
}
|
||||
}, [siteInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sendEmail ? (
|
||||
<Typography variant="h3" padding={'20px'}>
|
||||
重置邮件发送成功,请检查邮箱!
|
||||
</Typography>
|
||||
) : (
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: ''
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
email: Yup.string().email('必须是有效的Email地址').max(255).required('Email是必填项')
|
||||
})}
|
||||
onSubmit={submit}
|
||||
>
|
||||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||
<form noValidate onSubmit={handleSubmit} {...others}>
|
||||
<FormControl fullWidth error={Boolean(touched.email && errors.email)} sx={{ ...theme.typography.customInput }}>
|
||||
<InputLabel htmlFor="outlined-adornment-email-register">Email</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-email-register"
|
||||
type="text"
|
||||
value={values.email}
|
||||
name="email"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.email && errors.email && (
|
||||
<FormHelperText error id="standard-weight-helper-text--register">
|
||||
{errors.email}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{turnstileEnabled ? (
|
||||
<Turnstile
|
||||
sitekey={turnstileSiteKey}
|
||||
onVerify={(token) => {
|
||||
setTurnstileToken(token);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={isSubmitting || disableButton}
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
{disableButton ? `重试 (${countdown})` : '提交'}
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Box>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgetPasswordForm;
|
62
web/src/views/Authentication/AuthForms/ResetPasswordForm.js
Normal file
62
web/src/views/Authentication/AuthForms/ResetPasswordForm.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { Button, Stack, Typography, Alert } from '@mui/material';
|
||||
|
||||
// assets
|
||||
import { showError, showInfo } from 'utils/common';
|
||||
|
||||
// ===========================|| FIREBASE - REGISTER ||=========================== //
|
||||
|
||||
const ResetPasswordForm = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [inputs, setInputs] = useState({
|
||||
email: '',
|
||||
token: ''
|
||||
});
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
|
||||
const submit = async () => {
|
||||
const res = await API.post(`/api/user/reset`, inputs);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
let password = res.data.data;
|
||||
setNewPassword(password);
|
||||
navigator.clipboard.writeText(password);
|
||||
showInfo(`新密码已复制到剪贴板:${password}`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let email = searchParams.get('email');
|
||||
let token = searchParams.get('token');
|
||||
setInputs({
|
||||
token,
|
||||
email
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack spacing={3} padding={'24px'} justifyContent={'center'} alignItems={'center'}>
|
||||
{!inputs.email || !inputs.token ? (
|
||||
<Typography variant="h3" sx={{ textDecoration: 'none' }}>
|
||||
无效的链接
|
||||
</Typography>
|
||||
) : newPassword ? (
|
||||
<Alert severity="error">
|
||||
你的新密码是: <b>{newPassword}</b> <br />
|
||||
请登录后及时修改密码
|
||||
</Alert>
|
||||
) : (
|
||||
<Button fullWidth onClick={submit} size="large" type="submit" variant="contained" color="primary">
|
||||
点击重置密码
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordForm;
|
Loading…
Reference in New Issue
Block a user