🚧 补全忘记密码web页面

This commit is contained in:
Martial BE 2023-12-22 10:48:52 +08:00
parent 3ac0b256e3
commit fe72f85554
No known key found for this signature in database
GPG Key ID: D06C32DF0EDB9084
7 changed files with 358 additions and 5 deletions

View File

@ -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 />

View 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;

View File

@ -10,8 +10,6 @@ import AuthCardWrapper from '../AuthCardWrapper';
import AuthLogin from '../AuthForms/AuthLogin';
import Logo from 'ui-component/Logo';
// assets
// ================================|| AUTH3 - LOGIN ||================================ //
const Login = () => {

View 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;

View File

@ -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>

View 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;

View 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;