From 4d81bee0d062ff29d3cb50cd4bef4b9aa7bb42fa Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Wed, 3 Jan 2024 10:54:41 +0800 Subject: [PATCH] Optimize Sign page code --- src/pages/Sign.tsx | 941 -------------------------------------- src/pages/sign/Forget.tsx | 271 +++++++++++ src/pages/sign/SignIn.tsx | 212 +++++++++ src/pages/sign/SignUp.tsx | 237 ++++++++++ src/pages/sign/Verify.tsx | 223 +++++++++ src/pages/sign/index.tsx | 74 +++ src/router/index.tsx | 2 +- 7 files changed, 1018 insertions(+), 942 deletions(-) delete mode 100644 src/pages/Sign.tsx create mode 100644 src/pages/sign/Forget.tsx create mode 100644 src/pages/sign/SignIn.tsx create mode 100644 src/pages/sign/SignUp.tsx create mode 100644 src/pages/sign/Verify.tsx create mode 100644 src/pages/sign/index.tsx diff --git a/src/pages/Sign.tsx b/src/pages/Sign.tsx deleted file mode 100644 index 44a226e..0000000 --- a/src/pages/Sign.tsx +++ /dev/null @@ -1,941 +0,0 @@ -import React, { useState } from 'react' -import { useNavigate } from 'react-router' -import Icon from '@ant-design/icons' -import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile' -import '@/assets/css/pages/sign.scss' -import { - COLOR_BACKGROUND, - DATABASE_DUPLICATE_KEY, - H_CAPTCHA_SITE_KEY, - PERMISSION_ACCOUNT_NEED_INIT, - PERMISSION_FORGET_SUCCESS, - PERMISSION_LOGIN_SUCCESS, - PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR, - PERMISSION_NO_VERIFICATION_REQUIRED, - PERMISSION_REGISTER_SUCCESS, - PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED, - PERMISSION_RETRIEVE_SUCCESS, - PERMISSION_USER_DISABLE, - PERMISSION_USER_NOT_FOUND, - PERMISSION_USERNAME_NOT_FOUND, - SYSTEM_INVALID_CAPTCHA_CODE -} from '@/constants/common.constants.ts' -import { getLoginStatus, getUserInfo, requestUserInfo, setToken } from '@/util/auth' -import { AppContext } from '@/App' -import { utcToLocalTime } from '@/util/datetime' -import { useUpdatedEffect } from '@/util/hooks' -import { - r_auth_forget, - r_auth_login, - r_auth_register, - r_auth_resend, - r_auth_retrieve, - r_auth_verify -} from '@/services/auth' -import FitFullscreen from '@/components/common/FitFullscreen' -import FitCenter from '@/components/common/FitCenter' -import FlexBox from '@/components/common/FlexBox' -import { getRedirectUrl } from '@/util/route.tsx' -import { r_api_avatar_random_base64 } from '@/services/api/avatar.tsx' - -const SignUp: React.FC = () => { - const location = useLocation() - const navigate = useNavigate() - const turnstileRef = useRef() - const [isSigningUp, setIsSigningUp] = useState(false) - const [isFinish, setIsFinish] = useState(false) - const [isSending, setIsSending] = useState(false) - const [captchaCode, setCaptchaCode] = useState('') - - useUpdatedEffect(() => { - if (location.pathname !== '/register') { - return - } - if (getLoginStatus()) { - navigate(`/login${location.search}`, { - replace: true - }) - } - setTimeout(() => { - turnstileRef.current?.execute() - }, 200) - }, [location.pathname]) - - const handleOnFinish = (registerParam: RegisterParam) => { - if (isSigningUp) { - return - } - setIsSigningUp(true) - - if (!captchaCode) { - void message.warning('请先通过验证') - setIsSigningUp(false) - return - } - - void r_auth_register({ - username: registerParam.username, - email: registerParam.email, - password: registerParam.password, - captchaCode - }) - .then((res) => { - const response = res.data - switch (response.code) { - case PERMISSION_REGISTER_SUCCESS: - setToken(response.data?.token ?? '') - void message.success('恭喜,您快要完成注册了') - setIsFinish(true) - break - case DATABASE_DUPLICATE_KEY: - void message.error('用户名或邮箱已被注册,请重试') - setIsSigningUp(false) - break - case SYSTEM_INVALID_CAPTCHA_CODE: - void message.error('验证码有误,请刷新重试') - break - default: - void message.error('服务器出错了,请稍后重试') - setIsSigningUp(false) - } - }) - .catch(() => { - setIsSigningUp(false) - }) - } - - const handleOnResend = () => { - if (isSending) { - return - } - setIsSending(true) - void message.loading({ content: '发送中', key: 'sending', duration: 0 }) - void r_auth_resend() - .then((res) => { - const response = res.data - if (response.success) { - void message.success('已发送验证邮件,请查收') - } else { - void message.error('出错了,请稍后重试') - } - }) - .finally(() => { - message.destroy('sending') - setIsSending(false) - }) - } - - return ( -
- - -
-
创建账号
-
Create account
-
- - {!isFinish ? ( - <> - - } - placeholder={'用户名'} - maxLength={39} - showCount={true} - disabled={isSigningUp} - /> - - - } - placeholder={'邮箱'} - disabled={isSigningUp} - /> - - - } - placeholder={'密码'} - disabled={isSigningUp} - /> - - ({ - validator(_, value) { - if (!value || getFieldValue('password') === value) { - return Promise.resolve() - } - return Promise.reject( - new Error('两次密码输入必须一致') - ) - } - }) - ]} - > - } - placeholder={'确认密码'} - disabled={isSigningUp} - /> - - - - - - - 注    册 - - - - ) : ( -
- 我们发送了一封包含验证账号链接的邮件到您的邮箱里,如未收到,可能被归为垃圾邮件,请仔细检查。 - 重新发送 -
- )} - - -
-
-
-
- ) -} - -const Verify: React.FC = () => { - const { refreshRouter } = useContext(AppContext) - const navigate = useNavigate() - const [searchParams] = useSearchParams() - const [hasCode, setHasCode] = useState(true) - const [needVerify, setNeedVerify] = useState(true) - const [isValid, setIsValid] = useState(true) - const [isSending, setIsSending] = useState(false) - const [isGettingAvatar, setIsGettingAvatar] = useState(false) - const [avatar, setAvatar] = useState('') - const [isVerifying, setIsVerifying] = useState(false) - - useUpdatedEffect(() => { - if (location.pathname !== '/verify') { - return - } - - if (!getLoginStatus()) { - navigate(getRedirectUrl('/login', `${location.pathname}${location.search}`), { - replace: true - }) - return - } - - const code = searchParams.get('code') - - if (!code) { - setHasCode(false) - return - } - void r_auth_verify({ code }) - .then((res) => { - const response = res.data - switch (response.code) { - case PERMISSION_ACCOUNT_NEED_INIT: - void getUserInfo().then((user) => { - setAvatar(user.userInfo.avatar) - }) - break - case PERMISSION_NO_VERIFICATION_REQUIRED: - void message.success('无需验证') - setNeedVerify(false) - break - default: - setIsValid(false) - } - }) - .catch(() => { - setIsValid(false) - }) - }, [location.pathname]) - - const handleOnResend = () => { - if (isSending) { - return - } - setIsSending(true) - void message.loading({ content: '发送中', key: 'sending', duration: 0 }) - void r_auth_resend() - .then((res) => { - const response = res.data - if (response.success) { - void message.success('已发送验证邮件,请查收') - } else { - void message.error('出错了,请稍后重试') - } - }) - .finally(() => { - message.destroy('sending') - setIsSending(false) - }) - } - - const handleOnChangeAvatar = () => { - if (isGettingAvatar) { - return - } - setIsGettingAvatar(true) - void r_api_avatar_random_base64() - .then((res) => { - const response = res.data - if (response.success) { - response.data?.base64 && setAvatar(response.data.base64) - } - }) - .finally(() => { - setIsGettingAvatar(false) - }) - } - - const handleOnFinish = (verifyParam: VerifyParam) => { - if (isVerifying) { - return - } - setIsVerifying(true) - - void r_auth_verify({ - code: searchParams.get('code') ?? '', - avatar, - nickname: verifyParam.nickname - }).then((res) => { - const response = res.data - if (response.success) { - void message.success('恭喜你,完成了') - setTimeout(() => { - void requestUserInfo().then(() => { - refreshRouter() - if (searchParams.has('redirect')) { - navigate(searchParams.get('redirect') ?? '/') - } else { - navigate('/') - } - }) - }, 1500) - } else { - void message.error('出错了,请稍后重试') - setIsVerifying(false) - } - }) - } - - return ( - <> -
- - -
-
验证账号
-
Verify account
-
- - - - - - -
-
-
- - ) -} - -const Forget: React.FC = () => { - const navigate = useNavigate() - const [searchParams] = useSearchParams() - const turnstileRef = useRef() - const retrieveTurnstileRef = useRef() - const [isSending, setIsSending] = useState(false) - const [isSent, setIsSent] = useState(false) - const [isChanging, setIsChanging] = useState(false) - const [isChanged, setIsChanged] = useState(false) - const [captchaCode, setCaptchaCode] = useState('') - const [retrieveCpatchaCode, setRetrieveCpatchaCode] = useState('') - - useUpdatedEffect(() => { - if (location.pathname !== '/forget') { - return - } - - setTimeout(() => { - if (!searchParams.get('code')) { - turnstileRef.current?.execute() - } else { - retrieveTurnstileRef.current?.execute() - } - }, 200) - }, [location.pathname]) - - const handleOnSend = (forgetParam: ForgetParam) => { - if (isSending) { - return - } - setIsSending(true) - - if (!captchaCode) { - void message.warning('请先通过验证') - setIsSending(false) - return - } - - void r_auth_forget({ email: forgetParam.email, captchaCode }).then((res) => { - const response = res.data - switch (response.code) { - case PERMISSION_FORGET_SUCCESS: - void message.success('已发送验证邮件,请查收') - setIsSent(true) - setIsSending(false) - break - case PERMISSION_USER_NOT_FOUND: - void message.error('用户不存在') - setIsSending(false) - break - case SYSTEM_INVALID_CAPTCHA_CODE: - void message.error('验证码有误,请刷新重试') - break - default: - void message.error('出错了,请稍后重试') - setIsSending(false) - } - }) - } - - const handleOnRetry = () => { - setIsSent(false) - - setTimeout(() => { - turnstileRef.current?.execute() - }, 200) - } - - const handleOnChange = (retrieveParam: RetrieveParam) => { - if (isChanging) { - return - } - setIsChanging(true) - - void r_auth_retrieve({ - code: searchParams.get('code') ?? '', - password: retrieveParam.password, - captchaCode: retrieveCpatchaCode - }).then((res) => { - const response = res.data - - switch (response.code) { - case PERMISSION_RETRIEVE_SUCCESS: - void message.success('密码已更新') - setIsChanged(true) - setIsChanging(false) - break - case PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED: - void message.error('请重新获取邮件') - setIsChanging(false) - break - case SYSTEM_INVALID_CAPTCHA_CODE: - void message.error('验证码有误,请刷新重试') - break - default: - void message.error('出错了,请稍后重试') - setIsChanging(false) - } - }) - } - - return ( -
- - -
-
找回密码
-
Retrieve password
-
-
- {!searchParams.get('code') ? ( - !isSent ? ( - <> - - - } - placeholder={'邮箱'} - disabled={isSending} - /> - - - - - - - 确    定 - - - - - ) : ( -
- 我们向您发送了一封包含找回密码链接的邮件,如未收到,可能被归为垃圾邮件,请仔细检查。 - 重新发送 -
- ) - ) : !isChanged ? ( - <> - - - 新  密  码 - } - placeholder={'密码'} - disabled={isChanging} - /> - - ({ - validator(_, value) { - if ( - !value || - getFieldValue('password') === value - ) { - return Promise.resolve() - } - return Promise.reject( - new Error('两次密码输入必须一致') - ) - } - }) - ]} - > - - - - - - - - 更    改 - - - - - ) : ( -
恭喜你,密码已更新,请重新登录。
- )} - - -
-
-
-
- ) -} - -const SignIn: React.FC = () => { - const { refreshRouter } = useContext(AppContext) - const navigate = useNavigate() - const [searchParams] = useSearchParams() - const turnstileRef = useRef() - const [isSigningIn, setIsSigningIn] = useState(false) - const [captchaCode, setCaptchaCode] = useState('') - - useUpdatedEffect(() => { - if (location.pathname !== '/login') { - return - } - setTimeout(() => { - turnstileRef.current?.execute() - }, 200) - }, [location.pathname]) - - const handleOnFinish = (loginParam: LoginParam) => { - if (isSigningIn) { - return - } - setIsSigningIn(true) - - if (!captchaCode) { - void message.warning('请先通过验证') - setIsSigningIn(false) - return - } - - void r_auth_login({ - account: loginParam.account, - password: loginParam.password, - captchaCode - }) - .then((res) => { - const response = res.data - const { code, data } = response - switch (code) { - case PERMISSION_LOGIN_SUCCESS: - setToken(data?.token ?? '') - void message.success('登录成功') - setTimeout(() => { - void getUserInfo().then((user) => { - refreshRouter() - if (searchParams.has('redirect')) { - navigate(searchParams.get('redirect') ?? '/') - } else { - navigate('/') - } - - notification.success({ - message: '欢迎回来', - description: ( - <> - - 你好 {user.userInfo.nickname} - -
- - 最近登录: - {user.lastLoginTime - ? `${utcToLocalTime(user.lastLoginTime)}【${ - user.lastLoginIp - }】` - : '无'} - - - ), - placement: 'topRight' - }) - }) - }, 1500) - break - case PERMISSION_USERNAME_NOT_FOUND: - case PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR: - void message.error( - <> - 账号密码错误,请重试 - - ) - setIsSigningIn(false) - break - case PERMISSION_USER_DISABLE: - void message.error( - <> - 该用户已被禁用 - - ) - setIsSigningIn(false) - break - case SYSTEM_INVALID_CAPTCHA_CODE: - void message.error('验证码有误,请刷新重试') - break - default: - void message.error( - <> - 服务器出错了 - - ) - setIsSigningIn(false) - } - }) - .catch(() => { - setIsSigningIn(false) - }) - } - - return ( -
- - -
-
欢迎回来
-
Welcome back
-
- - - } - placeholder={'邮箱/用户名'} - disabled={isSigningIn} - /> - - - } - placeholder={'密码'} - disabled={isSigningIn} - /> - - - - - - 记住密码 - { - navigate(`/forget${location.search}`, { replace: true }) - }} - > - 忘记密码? - - - - - 登    录 - - - - -
-
-
- ) -} - -const Sign: React.FC = () => { - const lastPage = useRef('none') - const currentPage = useRef('none') - const match = useMatches().reduce((_, second) => second) - const [isSwitch, setIsSwitch] = useState(false) - - const leftPage = ['register', 'verify', 'forget'] - - useUpdatedEffect(() => { - lastPage.current = currentPage.current - currentPage.current = match.id - - setIsSwitch(leftPage.includes(currentPage.current)) - }, [match.id]) - - const leftComponent = () => { - switch (leftPage.includes(currentPage.current) ? currentPage.current : lastPage.current) { - case 'forget': - return - case 'verify': - return - default: - return - } - } - - const rightComponent = () => { - switch (leftPage.includes(currentPage.current) ? lastPage.current : currentPage.current) { - default: - return - } - } - - return ( - <> - - - -
{leftComponent()}
-
- {rightComponent()} -
- -
-
-
-
-
-
-
-
- - - - - - ) -} - -export default Sign diff --git a/src/pages/sign/Forget.tsx b/src/pages/sign/Forget.tsx new file mode 100644 index 0000000..a307f2b --- /dev/null +++ b/src/pages/sign/Forget.tsx @@ -0,0 +1,271 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile' +import { + H_CAPTCHA_SITE_KEY, + PERMISSION_FORGET_SUCCESS, + PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED, + PERMISSION_RETRIEVE_SUCCESS, + PERMISSION_USER_NOT_FOUND, + SYSTEM_INVALID_CAPTCHA_CODE +} from '@/constants/common.constants' +import { useUpdatedEffect } from '@/util/hooks' +import { r_auth_forget, r_auth_retrieve } from '@/services/auth' +import FitCenter from '@/components/common/FitCenter' +import FlexBox from '@/components/common/FlexBox' + +const Forget: React.FC = () => { + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const turnstileRef = useRef() + const retrieveTurnstileRef = useRef() + const [isSending, setIsSending] = useState(false) + const [isSent, setIsSent] = useState(false) + const [isChanging, setIsChanging] = useState(false) + const [isChanged, setIsChanged] = useState(false) + const [captchaCode, setCaptchaCode] = useState('') + const [retrieveCaptchaCode, setRetrieveCaptchaCode] = useState('') + + useUpdatedEffect(() => { + if (!isSending) { + setCaptchaCode('') + turnstileRef.current?.reset() + turnstileRef.current?.execute() + } + }, [isSending]) + + useUpdatedEffect(() => { + if (!isChanging) { + setRetrieveCaptchaCode('') + retrieveTurnstileRef.current?.reset() + retrieveTurnstileRef.current?.execute() + } + }, [isChanging]) + + useUpdatedEffect(() => { + if (location.pathname !== '/forget') { + return + } + + setTimeout(() => { + if (!searchParams.get('code')) { + turnstileRef.current?.execute() + } else { + retrieveTurnstileRef.current?.execute() + } + }, 500) + }, [location.pathname]) + + const handleOnSend = (forgetParam: ForgetParam) => { + if (isSending) { + return + } + setIsSending(true) + + if (!captchaCode) { + void message.warning('请先通过验证') + setIsSending(false) + return + } + + void r_auth_forget({ email: forgetParam.email, captchaCode }) + .then((res) => { + const response = res.data + switch (response.code) { + case PERMISSION_FORGET_SUCCESS: + void message.success('已发送验证邮件,请查收') + setIsSent(true) + break + case PERMISSION_USER_NOT_FOUND: + void message.error('用户不存在') + break + case SYSTEM_INVALID_CAPTCHA_CODE: + void message.error('验证码有误,请重试') + break + default: + void message.error('出错了,请稍后重试') + } + }) + .finally(() => { + setIsSending(false) + }) + } + + const handleOnRetry = () => { + setIsSent(false) + + setTimeout(() => { + turnstileRef.current?.execute() + }, 500) + } + + const handleOnChange = (retrieveParam: RetrieveParam) => { + if (isChanging) { + return + } + setIsChanging(true) + + void r_auth_retrieve({ + code: searchParams.get('code') ?? '', + password: retrieveParam.password, + captchaCode: retrieveCaptchaCode + }) + .then((res) => { + const response = res.data + + switch (response.code) { + case PERMISSION_RETRIEVE_SUCCESS: + void message.success('密码已更新') + setIsChanged(true) + break + case PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED: + void message.error('请重新获取邮件') + break + case SYSTEM_INVALID_CAPTCHA_CODE: + void message.error('验证码有误,请重试') + break + default: + void message.error('出错了,请稍后重试') + } + }) + .finally(() => { + setIsChanging(false) + }) + } + + return ( +
+ + +
+
找回密码
+
Retrieve password
+
+
+ {!searchParams.get('code') ? ( + !isSent ? ( + <> + + + } + placeholder={'邮箱'} + disabled={isSending} + /> + + + + + + + 确    定 + + + + + ) : ( +
+ 我们向您发送了一封包含找回密码链接的邮件,如未收到,可能被归为垃圾邮件,请仔细检查。 + 重新发送 +
+ ) + ) : !isChanged ? ( + <> + + + 新  密  码 + } + placeholder={'密码'} + disabled={isChanging} + /> + + ({ + validator(_, value) { + if ( + !value || + getFieldValue('password') === value + ) { + return Promise.resolve() + } + return Promise.reject( + new Error('两次密码输入必须一致') + ) + } + }) + ]} + > + + + + + + + + 更    改 + + + + + ) : ( +
恭喜你,密码已更新,请重新登录。
+ )} + + +
+
+
+
+ ) +} + +export default Forget diff --git a/src/pages/sign/SignIn.tsx b/src/pages/sign/SignIn.tsx new file mode 100644 index 0000000..ae1b0aa --- /dev/null +++ b/src/pages/sign/SignIn.tsx @@ -0,0 +1,212 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile' +import { + H_CAPTCHA_SITE_KEY, + PERMISSION_LOGIN_SUCCESS, + PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR, + PERMISSION_USER_DISABLE, + PERMISSION_USERNAME_NOT_FOUND, + SYSTEM_INVALID_CAPTCHA_CODE +} from '@/constants/common.constants' +import { useUpdatedEffect } from '@/util/hooks' +import { getUserInfo, setToken } from '@/util/auth' +import { utcToLocalTime } from '@/util/datetime' +import { r_auth_login } from '@/services/auth' +import { AppContext } from '@/App' +import FitCenter from '@/components/common/FitCenter' +import FlexBox from '@/components/common/FlexBox' + +const SignIn: React.FC = () => { + const { refreshRouter } = useContext(AppContext) + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const turnstileRef = useRef() + const [isSigningIn, setIsSigningIn] = useState(false) + const [captchaCode, setCaptchaCode] = useState('') + + useUpdatedEffect(() => { + if (!isSigningIn) { + setCaptchaCode('') + turnstileRef.current?.reset() + turnstileRef.current?.execute() + } + }, [isSigningIn]) + + useUpdatedEffect(() => { + if (location.pathname !== '/login') { + return + } + setTimeout(() => { + turnstileRef.current?.execute() + }, 500) + }, [turnstileRef]) + + const handleOnFinish = (loginParam: LoginParam) => { + if (isSigningIn) { + return + } + setIsSigningIn(true) + + if (!captchaCode) { + void message.warning('请先通过验证') + setIsSigningIn(false) + return + } + + void r_auth_login({ + account: loginParam.account, + password: loginParam.password, + captchaCode + }) + .then((res) => { + const response = res.data + const { code, data } = response + switch (code) { + case PERMISSION_LOGIN_SUCCESS: + setToken(data?.token ?? '') + void message.success('登录成功') + setTimeout(() => { + void getUserInfo().then((user) => { + refreshRouter() + if (searchParams.has('redirect')) { + navigate(searchParams.get('redirect') ?? '/') + } else { + navigate('/') + } + + notification.success({ + message: '欢迎回来', + description: ( + <> + + 你好 {user.userInfo.nickname} + +
+ + 最近登录: + {user.lastLoginTime + ? `${utcToLocalTime(user.lastLoginTime)}【${ + user.lastLoginIp + }】` + : '无'} + + + ), + placement: 'topRight' + }) + }) + }, 1500) + break + case PERMISSION_USERNAME_NOT_FOUND: + case PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR: + void message.error( + <> + 账号密码错误,请重试 + + ) + setIsSigningIn(false) + break + case PERMISSION_USER_DISABLE: + void message.error( + <> + 该用户已被禁用 + + ) + setIsSigningIn(false) + break + case SYSTEM_INVALID_CAPTCHA_CODE: + void message.error('验证码有误,请重试') + setIsSigningIn(false) + break + default: + void message.error( + <> + 服务器出错了 + + ) + setIsSigningIn(false) + } + }) + .catch(() => { + setIsSigningIn(false) + }) + } + + return ( +
+ + +
+
欢迎回来
+
Welcome back
+
+ + + } + placeholder={'邮箱/用户名'} + disabled={isSigningIn} + /> + + + } + placeholder={'密码'} + disabled={isSigningIn} + /> + + + + + + 记住密码 + { + navigate(`/forget${location.search}`, { replace: true }) + }} + > + 忘记密码? + + + + + 登    录 + + + + +
+
+
+ ) +} + +export default SignIn diff --git a/src/pages/sign/SignUp.tsx b/src/pages/sign/SignUp.tsx new file mode 100644 index 0000000..536ae28 --- /dev/null +++ b/src/pages/sign/SignUp.tsx @@ -0,0 +1,237 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile' +import { + DATABASE_DUPLICATE_KEY, + H_CAPTCHA_SITE_KEY, + PERMISSION_REGISTER_SUCCESS, + SYSTEM_INVALID_CAPTCHA_CODE +} from '@/constants/common.constants' +import { useUpdatedEffect } from '@/util/hooks' +import { getLoginStatus, setToken } from '@/util/auth' +import { r_auth_register, r_auth_resend } from '@/services/auth' +import FitCenter from '@/components/common/FitCenter' +import FlexBox from '@/components/common/FlexBox' + +const SignUp: React.FC = () => { + const location = useLocation() + const navigate = useNavigate() + const turnstileRef = useRef() + const [isSigningUp, setIsSigningUp] = useState(false) + const [isFinish, setIsFinish] = useState(false) + const [isSending, setIsSending] = useState(false) + const [captchaCode, setCaptchaCode] = useState('') + + useUpdatedEffect(() => { + if (!isSigningUp) { + setCaptchaCode('') + turnstileRef.current?.reset() + turnstileRef.current?.execute() + } + }, [isSigningUp]) + + useUpdatedEffect(() => { + if (location.pathname !== '/register') { + return + } + if (getLoginStatus()) { + navigate(`/login${location.search}`, { + replace: true + }) + } + setTimeout(() => { + turnstileRef.current?.execute() + }, 500) + }, [location.pathname]) + + const handleOnFinish = (registerParam: RegisterParam) => { + if (isSigningUp) { + return + } + setIsSigningUp(true) + + if (!captchaCode) { + void message.warning('请先通过验证') + setIsSigningUp(false) + return + } + + void r_auth_register({ + username: registerParam.username, + email: registerParam.email, + password: registerParam.password, + captchaCode + }) + .then((res) => { + const response = res.data + switch (response.code) { + case PERMISSION_REGISTER_SUCCESS: + setToken(response.data?.token ?? '') + void message.success('恭喜,您快要完成注册了') + setIsFinish(true) + break + case DATABASE_DUPLICATE_KEY: + void message.error('用户名或邮箱已被注册,请重试') + setIsSigningUp(false) + break + case SYSTEM_INVALID_CAPTCHA_CODE: + void message.error('验证码有误,请重试') + setIsSigningUp(false) + break + default: + void message.error('服务器出错了,请稍后重试') + setIsSigningUp(false) + } + }) + .catch(() => { + setIsSigningUp(false) + }) + } + + const handleOnResend = () => { + if (isSending) { + return + } + setIsSending(true) + void message.loading({ content: '发送中', key: 'sending', duration: 0 }) + void r_auth_resend() + .then((res) => { + const response = res.data + if (response.success) { + void message.success('已发送验证邮件,请查收') + } else { + void message.error('出错了,请稍后重试') + } + }) + .finally(() => { + message.destroy('sending') + setIsSending(false) + }) + } + + return ( +
+ + +
+
创建账号
+
Create account
+
+ + {!isFinish ? ( + <> + + } + placeholder={'用户名'} + maxLength={39} + showCount={true} + disabled={isSigningUp} + /> + + + } + placeholder={'邮箱'} + disabled={isSigningUp} + /> + + + } + placeholder={'密码'} + disabled={isSigningUp} + /> + + ({ + validator(_, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve() + } + return Promise.reject( + new Error('两次密码输入必须一致') + ) + } + }) + ]} + > + } + placeholder={'确认密码'} + disabled={isSigningUp} + /> + + + + + + + 注    册 + + + + ) : ( +
+ 我们发送了一封包含验证账号链接的邮件到您的邮箱里,如未收到,可能被归为垃圾邮件,请仔细检查。 + 重新发送 +
+ )} + + +
+
+
+
+ ) +} + +export default SignUp diff --git a/src/pages/sign/Verify.tsx b/src/pages/sign/Verify.tsx new file mode 100644 index 0000000..17c0618 --- /dev/null +++ b/src/pages/sign/Verify.tsx @@ -0,0 +1,223 @@ +import React from 'react' +import { + COLOR_BACKGROUND, + PERMISSION_ACCOUNT_NEED_INIT, + PERMISSION_NO_VERIFICATION_REQUIRED +} from '@/constants/common.constants' +import { useUpdatedEffect } from '@/util/hooks' +import { getLoginStatus, getUserInfo, requestUserInfo } from '@/util/auth' +import { getRedirectUrl } from '@/util/route' +import { r_auth_resend, r_auth_verify } from '@/services/auth' +import { r_api_avatar_random_base64 } from '@/services/api/avatar' +import { AppContext } from '@/App' +import FitCenter from '@/components/common/FitCenter' +import FlexBox from '@/components/common/FlexBox' + +const Verify: React.FC = () => { + const { refreshRouter } = useContext(AppContext) + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const [hasCode, setHasCode] = useState(true) + const [needVerify, setNeedVerify] = useState(true) + const [isValid, setIsValid] = useState(true) + const [isSending, setIsSending] = useState(false) + const [isGettingAvatar, setIsGettingAvatar] = useState(false) + const [avatar, setAvatar] = useState('') + const [isVerifying, setIsVerifying] = useState(false) + + useUpdatedEffect(() => { + if (location.pathname !== '/verify') { + return + } + + if (!getLoginStatus()) { + navigate(getRedirectUrl('/login', `${location.pathname}${location.search}`), { + replace: true + }) + return + } + + const code = searchParams.get('code') + + if (!code) { + setHasCode(false) + return + } + void r_auth_verify({ code }) + .then((res) => { + const response = res.data + switch (response.code) { + case PERMISSION_ACCOUNT_NEED_INIT: + void getUserInfo().then((user) => { + setAvatar(user.userInfo.avatar) + }) + break + case PERMISSION_NO_VERIFICATION_REQUIRED: + void message.success('无需验证') + setNeedVerify(false) + break + default: + setIsValid(false) + } + }) + .catch(() => { + setIsValid(false) + }) + }, [location.pathname]) + + const handleOnResend = () => { + if (isSending) { + return + } + setIsSending(true) + void message.loading({ content: '发送中', key: 'sending', duration: 0 }) + void r_auth_resend() + .then((res) => { + const response = res.data + if (response.success) { + void message.success('已发送验证邮件,请查收') + } else { + void message.error('出错了,请稍后重试') + } + }) + .finally(() => { + message.destroy('sending') + setIsSending(false) + }) + } + + const handleOnChangeAvatar = () => { + if (isGettingAvatar) { + return + } + setIsGettingAvatar(true) + void r_api_avatar_random_base64() + .then((res) => { + const response = res.data + if (response.success) { + response.data?.base64 && setAvatar(response.data.base64) + } + }) + .finally(() => { + setIsGettingAvatar(false) + }) + } + + const handleOnFinish = (verifyParam: VerifyParam) => { + if (isVerifying) { + return + } + setIsVerifying(true) + + void r_auth_verify({ + code: searchParams.get('code') ?? '', + avatar, + nickname: verifyParam.nickname + }).then((res) => { + const response = res.data + if (response.success) { + void message.success('恭喜你,完成了') + setTimeout(() => { + void requestUserInfo().then(() => { + refreshRouter() + if (searchParams.has('redirect')) { + navigate(searchParams.get('redirect') ?? '/') + } else { + navigate('/') + } + }) + }, 1500) + } else { + void message.error('出错了,请稍后重试') + setIsVerifying(false) + } + }) + } + + return ( + <> +
+ + +
+
验证账号
+
Verify account
+
+ + + + + + +
+
+
+ + ) +} + +export default Verify diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx new file mode 100644 index 0000000..476acc8 --- /dev/null +++ b/src/pages/sign/index.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import '@/assets/css/pages/sign.scss' +import { useUpdatedEffect } from '@/util/hooks' +import FitFullscreen from '@/components/common/FitFullscreen' +import FitCenter from '@/components/common/FitCenter' +import FlexBox from '@/components/common/FlexBox' +import SignUp from '@/pages/sign/SignUp' +import Verify from '@/pages/sign/Verify' +import Forget from '@/pages/sign/Forget' +import SignIn from '@/pages/sign/SignIn' + +const Sign: React.FC = () => { + const lastPage = useRef('none') + const currentPage = useRef('none') + const match = useMatches().reduce((_, second) => second) + const [isSwitch, setIsSwitch] = useState(false) + + const leftPage = ['register', 'verify', 'forget'] + + useUpdatedEffect(() => { + lastPage.current = currentPage.current + currentPage.current = match.id + + setIsSwitch(leftPage.includes(currentPage.current)) + }, [match.id]) + + const leftComponent = () => { + switch (leftPage.includes(currentPage.current) ? currentPage.current : lastPage.current) { + case 'forget': + return + case 'verify': + return + default: + return + } + } + + const rightComponent = () => { + switch (leftPage.includes(currentPage.current) ? lastPage.current : currentPage.current) { + default: + return + } + } + + return ( + <> + + + +
{leftComponent()}
+
+ {rightComponent()} +
+ +
+
+
+
+
+
+
+
+ + + + + + ) +} + +export default Sign diff --git a/src/router/index.tsx b/src/router/index.tsx index 84716ac..c5aac51 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -5,7 +5,7 @@ import user from '@/router/user' import tools from '@/router/tools' import { getAuthRoute, mapJsonToRoute, setTitle } from '@/util/route' -const lazySignPage = React.lazy(() => import('@/pages/Sign')) +const lazySignPage = React.lazy(() => import('@/pages/sign')) const root: RouteJsonObject[] = [ {