diff --git a/shared/index.tsx b/shared/index.tsx index dc109eed..13402cd8 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -141,6 +141,8 @@ export { loginWithToken, registerWithEmail, modifyUserPassword, + forgetPassword, + resetPassword, createTemporaryUser, claimTemporaryUser, searchUserWithUniqueName, diff --git a/shared/model/user.ts b/shared/model/user.ts index ef1b7d38..db07ee89 100644 --- a/shared/model/user.ts +++ b/shared/model/user.ts @@ -82,6 +82,9 @@ export async function registerWithEmail( return data; } +/** + * 修改密码 + */ export async function modifyUserPassword( oldPassword: string, newPassword: string @@ -92,6 +95,32 @@ export async function modifyUserPassword( }); } +/** + * 忘记密码 + * @param email 邮箱 + */ +export async function forgetPassword(email: string) { + await request.post('/api/user/forgetPassword', { + email, + }); +} + +/** + * 忘记密码 + * @param email 邮箱 + */ +export async function resetPassword( + email: string, + password: string, + otp: string +) { + await request.post('/api/user/resetPassword', { + email, + password, + otp, + }); +} + /** * 创建访客账号 * @param nickname 访客昵称 diff --git a/web/src/routes/Entry/ForgetPasswordView.tsx b/web/src/routes/Entry/ForgetPasswordView.tsx new file mode 100644 index 00000000..f5515ca8 --- /dev/null +++ b/web/src/routes/Entry/ForgetPasswordView.tsx @@ -0,0 +1,128 @@ +import { Icon } from '@/components/Icon'; +import { + forgetPassword, + resetPassword, + showToasts, + t, + useAsyncRequest, +} from 'tailchat-shared'; +import React, { useState } from 'react'; +import { Spinner } from '../../components/Spinner'; +import { string } from 'yup'; +import { useNavToView } from './utils'; + +/** + * 登录视图 + */ +export const ForgetPasswordView: React.FC = React.memo(() => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [otp, setOtp] = useState(''); + const [sendedEmail, setSendedEmail] = useState(false); + + const navToView = useNavToView(); + + const [{ loading: sendEmailLoading }, handleSendEmail] = + useAsyncRequest(async () => { + await forgetPassword(email); + setSendedEmail(true); + showToasts(`已发送邮件到 ${email}`, 'success'); + }, [email]); + + const [{ loading }, handleResetPassword] = useAsyncRequest(async () => { + await string() + .email(t('邮箱格式不正确')) + .required(t('邮箱不能为空')) + .validate(email); + + await string() + .min(6, t('密码不能低于6位')) + .required(t('密码不能为空')) + .validate(password); + + await string().length(6, t('OTP为6位数字')).validate(otp); + + await resetPassword(email, password, otp); + + showToasts(t('密码重置成功,现在回到登录页'), 'success'); + navToView('/entry/login'); + }, [email, password, otp, navToView]); + + return ( +
+
{t('忘记密码')}
+ +
+
+
{t('邮箱')}
+ setEmail(e.target.value)} + /> +
+ + {!sendedEmail && ( + + )} + + {sendedEmail && ( + <> +
+
{t('OTP')}
+ setOtp(e.target.value)} + /> +
+ +
+
{t('新密码')}
+ setPassword(e.target.value)} + /> +
+ + + + )} + + +
+
+ ); +}); +ForgetPasswordView.displayName = 'ForgetPasswordView'; diff --git a/web/src/routes/Entry/LoginView.tsx b/web/src/routes/Entry/LoginView.tsx index 43c89d35..76414313 100644 --- a/web/src/routes/Entry/LoginView.tsx +++ b/web/src/routes/Entry/LoginView.tsx @@ -63,7 +63,7 @@ export const LoginView: React.FC = React.memo(() => { return (
-
{t('登录 Tail Chat')}
+
{t('登录 Tailchat')}
@@ -89,7 +89,17 @@ export const LoginView: React.FC = React.memo(() => { />
- {error &&

{error.message}

} + {error && ( +
+

{error.message}

+
navToView('/entry/forget')} + > + {t('忘记密码?')} +
+
+ )}