mirror of
https://github.com/songquanpeng/one-api.git
synced 2026-04-28 20:34:26 +08:00
feat: add new theme berry (#860)
* feat: add theme berry * docs: add development notes * fix: fix blank page * chore: update implementation * fix: fix package.json * chore: update ui copy --------- Co-authored-by: JustSong <songquanpeng@foxmail.com>
This commit is contained in:
68
web/berry/src/views/Authentication/Auth/ForgetPassword.js
Normal file
68
web/berry/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;
|
||||
94
web/berry/src/views/Authentication/Auth/GitHubOAuth.js
Normal file
94
web/berry/src/views/Authentication/Auth/GitHubOAuth.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { showError } from 'utils/common';
|
||||
import useLogin from 'hooks/useLogin';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Grid, Stack, Typography, useMediaQuery, CircularProgress } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import AuthWrapper from '../AuthWrapper';
|
||||
import AuthCardWrapper from '../AuthCardWrapper';
|
||||
import Logo from 'ui-component/Logo';
|
||||
|
||||
// assets
|
||||
|
||||
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||
|
||||
const GitHubOAuth = () => {
|
||||
const theme = useTheme();
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const [prompt, setPrompt] = useState('处理中...');
|
||||
const { githubLogin } = useLogin();
|
||||
|
||||
let navigate = useNavigate();
|
||||
|
||||
const sendCode = async (code, state, count) => {
|
||||
const { success, message } = await githubLogin(code, state);
|
||||
if (!success) {
|
||||
if (message) {
|
||||
showError(message);
|
||||
}
|
||||
if (count === 0) {
|
||||
setPrompt(`操作失败,重定向至登录界面中...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
navigate('/login');
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
await sendCode(code, state, count);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let code = searchParams.get('code');
|
||||
let state = searchParams.get('state');
|
||||
sendCode(code, state, 0).then();
|
||||
}, []);
|
||||
|
||||
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'}>
|
||||
GitHub 登录
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} container direction="column" justifyContent="center" alignItems="center" style={{ height: '200px' }}>
|
||||
<CircularProgress />
|
||||
<Typography variant="h3" paddingTop={'20px'}>
|
||||
{prompt}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthCardWrapper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default GitHubOAuth;
|
||||
66
web/berry/src/views/Authentication/Auth/Login.js
Normal file
66
web/berry/src/views/Authentication/Auth/Login.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 AuthLogin from '../AuthForms/AuthLogin';
|
||||
import Logo from 'ui-component/Logo';
|
||||
|
||||
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||
|
||||
const Login = () => {
|
||||
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}>
|
||||
<AuthLogin />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item container direction="column" alignItems="center" xs={12}>
|
||||
<Typography component={Link} to="/register" variant="subtitle1" sx={{ textDecoration: 'none' }}>
|
||||
注册
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthCardWrapper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
71
web/berry/src/views/Authentication/Auth/Register.js
Normal file
71
web/berry/src/views/Authentication/Auth/Register.js
Normal file
@@ -0,0 +1,71 @@
|
||||
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 Logo from 'ui-component/Logo';
|
||||
import AuthRegister from '../AuthForms/AuthRegister';
|
||||
|
||||
// assets
|
||||
|
||||
// ===============================|| AUTH3 - REGISTER ||=============================== //
|
||||
|
||||
const Register = () => {
|
||||
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}>
|
||||
<AuthRegister />
|
||||
</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 item xs={12} sx={{ m: 3, mt: 1 }}>
|
||||
<AuthFooter />
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Register;
|
||||
66
web/berry/src/views/Authentication/Auth/ResetPassword.js
Normal file
66
web/berry/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;
|
||||
32
web/berry/src/views/Authentication/AuthCardWrapper.js
Normal file
32
web/berry/src/views/Authentication/AuthCardWrapper.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// material-ui
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import MainCard from 'ui-component/cards/MainCard';
|
||||
|
||||
// ==============================|| AUTHENTICATION CARD WRAPPER ||============================== //
|
||||
|
||||
const AuthCardWrapper = ({ children, ...other }) => (
|
||||
<MainCard
|
||||
sx={{
|
||||
maxWidth: { xs: 400, lg: 475 },
|
||||
margin: { xs: 2.5, md: 3 },
|
||||
'& > *': {
|
||||
flexGrow: 1,
|
||||
flexBasis: '50%'
|
||||
}
|
||||
}}
|
||||
content={false}
|
||||
{...other}
|
||||
>
|
||||
<Box sx={{ p: { xs: 2, sm: 3, xl: 5 } }}>{children}</Box>
|
||||
</MainCard>
|
||||
);
|
||||
|
||||
AuthCardWrapper.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export default AuthCardWrapper;
|
||||
268
web/berry/src/views/Authentication/AuthForms/AuthLogin.js
Normal file
268
web/berry/src/views/Authentication/AuthForms/AuthLogin.js
Normal file
@@ -0,0 +1,268 @@
|
||||
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,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
useMediaQuery
|
||||
} from '@mui/material';
|
||||
|
||||
// third party
|
||||
import * as Yup from 'yup';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
// project imports
|
||||
import useLogin from 'hooks/useLogin';
|
||||
import AnimateButton from 'ui-component/extended/AnimateButton';
|
||||
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
|
||||
|
||||
// assets
|
||||
import Visibility from '@mui/icons-material/Visibility';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
|
||||
import Github from 'assets/images/icons/github.svg';
|
||||
import Wechat from 'assets/images/icons/wechat.svg';
|
||||
import { onGitHubOAuthClicked } from 'utils/common';
|
||||
|
||||
// ============================|| FIREBASE - LOGIN ||============================ //
|
||||
|
||||
const LoginForm = ({ ...others }) => {
|
||||
const theme = useTheme();
|
||||
const { login, wechatLogin } = useLogin();
|
||||
const [openWechat, setOpenWechat] = useState(false);
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const customization = useSelector((state) => state.customization);
|
||||
const siteInfo = useSelector((state) => state.siteInfo);
|
||||
// const [checked, setChecked] = useState(true);
|
||||
|
||||
let tripartiteLogin = false;
|
||||
if (siteInfo.github_oauth || siteInfo.wechat_login) {
|
||||
tripartiteLogin = true;
|
||||
}
|
||||
|
||||
const handleWechatOpen = () => {
|
||||
setOpenWechat(true);
|
||||
};
|
||||
|
||||
const handleWechatClose = () => {
|
||||
setOpenWechat(false);
|
||||
};
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const handleClickShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const handleMouseDownPassword = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{tripartiteLogin && (
|
||||
<Grid container direction="column" justifyContent="center" spacing={2}>
|
||||
{siteInfo.github_oauth && (
|
||||
<Grid item xs={12}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disableElevation
|
||||
fullWidth
|
||||
onClick={() => onGitHubOAuthClicked(siteInfo.github_client_id)}
|
||||
size="large"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
color: 'grey.700',
|
||||
backgroundColor: theme.palette.grey[50],
|
||||
borderColor: theme.palette.grey[100]
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
||||
<img src={Github} alt="github" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
||||
</Box>
|
||||
使用 Github 登录
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Grid>
|
||||
)}
|
||||
{siteInfo.wechat_login && (
|
||||
<Grid item xs={12}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disableElevation
|
||||
fullWidth
|
||||
onClick={handleWechatOpen}
|
||||
size="large"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
color: 'grey.700',
|
||||
backgroundColor: theme.palette.grey[50],
|
||||
borderColor: theme.palette.grey[100]
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
||||
<img src={Wechat} alt="Wechat" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
||||
</Box>
|
||||
使用 Wechat 登录
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
<WechatModal open={openWechat} handleClose={handleWechatClose} wechatLogin={wechatLogin} qrCode={siteInfo.wechat_qrcode} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex'
|
||||
}}
|
||||
>
|
||||
<Divider sx={{ flexGrow: 1 }} orientation="horizontal" />
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
cursor: 'unset',
|
||||
m: 2,
|
||||
py: 0.5,
|
||||
px: 7,
|
||||
borderColor: `${theme.palette.grey[100]} !important`,
|
||||
color: `${theme.palette.grey[900]}!important`,
|
||||
fontWeight: 500,
|
||||
borderRadius: `${customization.borderRadius}px`
|
||||
}}
|
||||
disableRipple
|
||||
disabled
|
||||
>
|
||||
OR
|
||||
</Button>
|
||||
|
||||
<Divider sx={{ flexGrow: 1 }} orientation="horizontal" />
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
username: '',
|
||||
password: '',
|
||||
submit: null
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
username: Yup.string().max(255).required('Username is required'),
|
||||
password: Yup.string().max(255).required('Password is required')
|
||||
})}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
const { success, message } = await login(values.username, values.password);
|
||||
if (success) {
|
||||
setStatus({ success: true });
|
||||
} else {
|
||||
setStatus({ success: false });
|
||||
if (message) {
|
||||
setErrors({ submit: message });
|
||||
}
|
||||
}
|
||||
setSubmitting(false);
|
||||
}}
|
||||
>
|
||||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||
<form noValidate onSubmit={handleSubmit} {...others}>
|
||||
<FormControl fullWidth error={Boolean(touched.username && errors.username)} sx={{ ...theme.typography.customInput }}>
|
||||
<InputLabel htmlFor="outlined-adornment-username-login">用户名</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-username-login"
|
||||
type="text"
|
||||
value={values.username}
|
||||
name="username"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
label="用户名"
|
||||
inputProps={{ autoComplete: 'username' }}
|
||||
/>
|
||||
{touched.username && errors.username && (
|
||||
<FormHelperText error id="standard-weight-helper-text-username-login">
|
||||
{errors.username}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth error={Boolean(touched.password && errors.password)} sx={{ ...theme.typography.customInput }}>
|
||||
<InputLabel htmlFor="outlined-adornment-password-login">密码</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-password-login"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={values.password}
|
||||
name="password"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end"
|
||||
size="large"
|
||||
>
|
||||
{showPassword ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
label="Password"
|
||||
/>
|
||||
{touched.password && errors.password && (
|
||||
<FormHelperText error id="standard-weight-helper-text-password-login">
|
||||
{errors.password}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
|
||||
{/* <FormControlLabel
|
||||
control={
|
||||
<Checkbox checked={checked} onChange={(event) => setChecked(event.target.checked)} name="checked" color="primary" />
|
||||
}
|
||||
label="记住我"
|
||||
/> */}
|
||||
<Typography
|
||||
component={Link}
|
||||
to="/reset"
|
||||
variant="subtitle1"
|
||||
color="primary"
|
||||
sx={{ textDecoration: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
忘记密码?
|
||||
</Typography>
|
||||
</Stack>
|
||||
{errors.submit && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<AnimateButton>
|
||||
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
|
||||
登录
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Box>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
310
web/berry/src/views/Authentication/AuthForms/AuthRegister.js
Normal file
310
web/berry/src/views/Authentication/AuthForms/AuthRegister.js
Normal file
@@ -0,0 +1,310 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import useRegister from 'hooks/useRegister';
|
||||
import Turnstile from 'react-turnstile';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
// import { useSelector } from 'react-redux';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
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';
|
||||
import { strengthColor, strengthIndicator } from 'utils/password-strength';
|
||||
|
||||
// assets
|
||||
import Visibility from '@mui/icons-material/Visibility';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
import { showError, showInfo } from 'utils/common';
|
||||
|
||||
// ===========================|| FIREBASE - REGISTER ||=========================== //
|
||||
|
||||
const RegisterForm = ({ ...others }) => {
|
||||
const theme = useTheme();
|
||||
const { register, sendVerificationCode } = useRegister();
|
||||
const siteInfo = useSelector((state) => state.siteInfo);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [showEmailVerification, setShowEmailVerification] = useState(false);
|
||||
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
|
||||
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
|
||||
const [turnstileToken, setTurnstileToken] = useState('');
|
||||
|
||||
const [strength, setStrength] = useState(0);
|
||||
const [level, setLevel] = useState();
|
||||
|
||||
const handleClickShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const handleMouseDownPassword = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const changePassword = (value) => {
|
||||
const temp = strengthIndicator(value);
|
||||
setStrength(temp);
|
||||
setLevel(strengthColor(temp));
|
||||
};
|
||||
|
||||
const handleSendCode = async (email) => {
|
||||
if (email === '') {
|
||||
showError('请输入邮箱');
|
||||
return;
|
||||
}
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showError('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { success, message } = await sendVerificationCode(email, turnstileToken);
|
||||
if (!success) {
|
||||
showError(message);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let affCode = searchParams.get('aff');
|
||||
if (affCode) {
|
||||
localStorage.setItem('aff', affCode);
|
||||
}
|
||||
|
||||
setShowEmailVerification(siteInfo.email_verification);
|
||||
if (siteInfo.turnstile_check) {
|
||||
setTurnstileEnabled(true);
|
||||
setTurnstileSiteKey(siteInfo.turnstile_site_key);
|
||||
}
|
||||
}, [siteInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Formik
|
||||
initialValues={{
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
email: showEmailVerification ? '' : undefined,
|
||||
verification_code: showEmailVerification ? '' : undefined,
|
||||
submit: null
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
username: Yup.string().max(255).required('用户名是必填项'),
|
||||
password: Yup.string().max(255).required('密码是必填项'),
|
||||
confirmPassword: Yup.string()
|
||||
.required('确认密码是必填项')
|
||||
.oneOf([Yup.ref('password'), null], '两次输入的密码不一致'),
|
||||
email: showEmailVerification ? Yup.string().email('必须是有效的Email地址').max(255).required('Email是必填项') : Yup.mixed(),
|
||||
verification_code: showEmailVerification ? Yup.string().max(255).required('验证码是必填项') : Yup.mixed()
|
||||
})}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { success, message } = await register(values, turnstileToken);
|
||||
if (success) {
|
||||
setStatus({ success: true });
|
||||
} else {
|
||||
setStatus({ success: false });
|
||||
if (message) {
|
||||
setErrors({ submit: message });
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||
<form noValidate onSubmit={handleSubmit} {...others}>
|
||||
<FormControl fullWidth error={Boolean(touched.username && errors.username)} sx={{ ...theme.typography.customInput }}>
|
||||
<InputLabel htmlFor="outlined-adornment-username-register">用户名</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-username-register"
|
||||
type="text"
|
||||
value={values.username}
|
||||
name="username"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{ autoComplete: 'username' }}
|
||||
/>
|
||||
{touched.username && errors.username && (
|
||||
<FormHelperText error id="standard-weight-helper-text--register">
|
||||
{errors.username}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth error={Boolean(touched.password && errors.password)} sx={{ ...theme.typography.customInput }}>
|
||||
<InputLabel htmlFor="outlined-adornment-password-register">密码</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-password-register"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={values.password}
|
||||
name="password"
|
||||
label="Password"
|
||||
onBlur={handleBlur}
|
||||
onChange={(e) => {
|
||||
handleChange(e);
|
||||
changePassword(e.target.value);
|
||||
}}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end"
|
||||
size="large"
|
||||
color={'primary'}
|
||||
>
|
||||
{showPassword ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.password && errors.password && (
|
||||
<FormHelperText error id="standard-weight-helper-text-password-register">
|
||||
{errors.password}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(touched.confirmPassword && errors.confirmPassword)}
|
||||
sx={{ ...theme.typography.customInput }}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-confirm-password-register">确认密码</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-confirm-password-register"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={values.confirmPassword}
|
||||
name="confirmPassword"
|
||||
label="Confirm Password"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.confirmPassword && errors.confirmPassword && (
|
||||
<FormHelperText error id="standard-weight-helper-text-confirm-password-register">
|
||||
{errors.confirmPassword}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{strength !== 0 && (
|
||||
<FormControl fullWidth>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item>
|
||||
<Box style={{ backgroundColor: level?.color }} sx={{ width: 85, height: 8, borderRadius: '7px' }} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="subtitle1" fontSize="0.75rem">
|
||||
{level?.label}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
{showEmailVerification && (
|
||||
<>
|
||||
<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}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<Button variant="contained" color="primary" onClick={() => handleSendCode(values.email)}>
|
||||
发送验证码
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
}
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.email && errors.email && (
|
||||
<FormHelperText error id="standard-weight-helper-text--register">
|
||||
{errors.email}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(touched.verification_code && errors.verification_code)}
|
||||
sx={{ ...theme.typography.customInput }}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-verification_code-register">验证码</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-verification_code-register"
|
||||
type="text"
|
||||
value={values.verification_code}
|
||||
name="verification_code"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.verification_code && errors.verification_code && (
|
||||
<FormHelperText error id="standard-weight-helper-text--register">
|
||||
{errors.verification_code}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</>
|
||||
)}
|
||||
|
||||
{errors.submit && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||
</Box>
|
||||
)}
|
||||
{turnstileEnabled ? (
|
||||
<Turnstile
|
||||
sitekey={turnstileSiteKey}
|
||||
onVerify={(token) => {
|
||||
setTurnstileToken(token);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<AnimateButton>
|
||||
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
|
||||
Sign up
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Box>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterForm;
|
||||
@@ -0,0 +1,174 @@
|
||||
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, showSuccess } 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;
|
||||
@@ -0,0 +1,75 @@
|
||||
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";
|
||||
import { API } from "utils/api";
|
||||
|
||||
// ===========================|| 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;
|
||||
70
web/berry/src/views/Authentication/AuthForms/WechatModal.js
Normal file
70
web/berry/src/views/Authentication/AuthForms/WechatModal.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// WechatModal.js
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Dialog, DialogTitle, DialogContent, TextField, Button, Typography, Grid } from '@mui/material';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import { showError } from 'utils/common';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
code: Yup.string().required('验证码不能为空')
|
||||
});
|
||||
|
||||
const WechatModal = ({ open, handleClose, wechatLogin, qrCode }) => {
|
||||
const handleSubmit = (values) => {
|
||||
const { success, message } = wechatLogin(values.code);
|
||||
if (success) {
|
||||
handleClose();
|
||||
} else {
|
||||
showError(message || '未知错误');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitle>微信验证码登录</DialogTitle>
|
||||
<DialogContent>
|
||||
<Grid container direction="column" alignItems="center">
|
||||
<img src={qrCode} alt="二维码" style={{ maxWidth: '300px', maxHeight: '300px', width: 'auto', height: 'auto' }} />
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
style={{ marginTop: '10px', textAlign: 'center', wordWrap: 'break-word', maxWidth: '300px' }}
|
||||
>
|
||||
请使用微信扫描二维码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
||||
</Typography>
|
||||
<Formik initialValues={{ code: '' }} validationSchema={validationSchema} onSubmit={handleSubmit}>
|
||||
{({ errors, touched }) => (
|
||||
<Form style={{ width: '100%' }}>
|
||||
<Grid item xs={12}>
|
||||
<Field
|
||||
as={TextField}
|
||||
name="code"
|
||||
label="验证码"
|
||||
error={touched.code && Boolean(errors.code)}
|
||||
helperText={touched.code && errors.code}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button type="submit" fullWidth>
|
||||
提交
|
||||
</Button>
|
||||
</Grid>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default WechatModal;
|
||||
|
||||
WechatModal.propTypes = {
|
||||
open: PropTypes.bool,
|
||||
handleClose: PropTypes.func,
|
||||
wechatLogin: PropTypes.func,
|
||||
qrCode: PropTypes.string
|
||||
};
|
||||
28
web/berry/src/views/Authentication/AuthWrapper.js
Normal file
28
web/berry/src/views/Authentication/AuthWrapper.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// material-ui
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useEffect, useContext } from 'react';
|
||||
import { UserContext } from 'contexts/UserContext';
|
||||
|
||||
// ==============================|| AUTHENTICATION 1 WRAPPER ||============================== //
|
||||
|
||||
const AuthStyle = styled('div')(({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.light
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line
|
||||
const AuthWrapper = ({ children }) => {
|
||||
const account = useSelector((state) => state.account);
|
||||
const { isUserLoaded } = useContext(UserContext);
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (isUserLoaded && account.user) {
|
||||
navigate('/panel');
|
||||
}
|
||||
}, [account, navigate, isUserLoaded]);
|
||||
|
||||
return <AuthStyle> {children} </AuthStyle>;
|
||||
};
|
||||
|
||||
export default AuthWrapper;
|
||||
Reference in New Issue
Block a user