diff --git a/web/src/routes/OtherRoutes.js b/web/src/routes/OtherRoutes.js
index 870307a0..085c4add 100644
--- a/web/src/routes/OtherRoutes.js
+++ b/web/src/routes/OtherRoutes.js
@@ -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:
},
+ {
+ path: '/reset',
+ element:
+ },
+ {
+ path: '/user/reset',
+ element:
+ },
{
path: '/oauth/github',
element:
diff --git a/web/src/views/Authentication/Auth/ForgetPassword.js b/web/src/views/Authentication/Auth/ForgetPassword.js
new file mode 100644
index 00000000..bac570e4
--- /dev/null
+++ b/web/src/views/Authentication/Auth/ForgetPassword.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 密码重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ForgetPassword;
diff --git a/web/src/views/Authentication/Auth/Login.js b/web/src/views/Authentication/Auth/Login.js
index 524e0f70..965d8470 100644
--- a/web/src/views/Authentication/Auth/Login.js
+++ b/web/src/views/Authentication/Auth/Login.js
@@ -10,8 +10,6 @@ import AuthCardWrapper from '../AuthCardWrapper';
import AuthLogin from '../AuthForms/AuthLogin';
import Logo from 'ui-component/Logo';
-// assets
-
// ================================|| AUTH3 - LOGIN ||================================ //
const Login = () => {
diff --git a/web/src/views/Authentication/Auth/ResetPassword.js b/web/src/views/Authentication/Auth/ResetPassword.js
new file mode 100644
index 00000000..75c2566d
--- /dev/null
+++ b/web/src/views/Authentication/Auth/ResetPassword.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 密码重置确认
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ResetPassword;
diff --git a/web/src/views/Authentication/AuthForms/AuthLogin.js b/web/src/views/Authentication/AuthForms/AuthLogin.js
index 06f83dc2..cb421946 100644
--- a/web/src/views/Authentication/AuthForms/AuthLogin.js
+++ b/web/src/views/Authentication/AuthForms/AuthLogin.js
@@ -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="记住我"
/> */}
-
+
忘记密码?
diff --git a/web/src/views/Authentication/AuthForms/ForgetPasswordForm.js b/web/src/views/Authentication/AuthForms/ForgetPasswordForm.js
new file mode 100644
index 00000000..82135bf0
--- /dev/null
+++ b/web/src/views/Authentication/AuthForms/ForgetPasswordForm.js
@@ -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 ? (
+
+ 重置邮件发送成功,请检查邮箱!
+
+ ) : (
+
+ {({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
+
+ )}
+
+ )}
+ >
+ );
+};
+
+export default ForgetPasswordForm;
diff --git a/web/src/views/Authentication/AuthForms/ResetPasswordForm.js b/web/src/views/Authentication/AuthForms/ResetPasswordForm.js
new file mode 100644
index 00000000..632bbde3
--- /dev/null
+++ b/web/src/views/Authentication/AuthForms/ResetPasswordForm.js
@@ -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 (
+
+ {!inputs.email || !inputs.token ? (
+
+ 无效的链接
+
+ ) : newPassword ? (
+
+ 你的新密码是: {newPassword}
+ 请登录后及时修改密码
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default ResetPasswordForm;