🚧 补全忘记密码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 AuthLogin = Loadable(lazy(() => import('views/Authentication/Auth/Login')));
|
||||||
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
||||||
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
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 Home = Loadable(lazy(() => import('views/Home')));
|
||||||
const About = Loadable(lazy(() => import('views/About')));
|
const About = Loadable(lazy(() => import('views/About')));
|
||||||
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
||||||
@ -34,6 +36,14 @@ const OtherRoutes = {
|
|||||||
path: '/register',
|
path: '/register',
|
||||||
element: <AuthRegister />
|
element: <AuthRegister />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/reset',
|
||||||
|
element: <ForgetPassword />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/user/reset',
|
||||||
|
element: <ResetPassword />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/oauth/github',
|
path: '/oauth/github',
|
||||||
element: <GitHubOAuth />
|
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 AuthLogin from '../AuthForms/AuthLogin';
|
||||||
import Logo from 'ui-component/Logo';
|
import Logo from 'ui-component/Logo';
|
||||||
|
|
||||||
// assets
|
|
||||||
|
|
||||||
// ================================|| AUTH3 - LOGIN ||================================ //
|
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||||
|
|
||||||
const 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 { useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
// Checkbox,
|
|
||||||
Divider,
|
Divider,
|
||||||
FormControl,
|
FormControl,
|
||||||
// FormControlLabel,
|
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -236,7 +235,13 @@ const LoginForm = ({ ...others }) => {
|
|||||||
}
|
}
|
||||||
label="记住我"
|
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>
|
</Typography>
|
||||||
</Stack>
|
</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