Skip to content

Commit 13a2a9f

Browse files
authored
feat(web): reset password (#974)
1 parent 260b335 commit 13a2a9f

File tree

9 files changed

+317
-19
lines changed

9 files changed

+317
-19
lines changed

web/public/locales/en/translation.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@
293293
"LoginTip": "Please input your phone number and verification code",
294294
"LoginWithGithub": "Login with GitHub",
295295
"ValidationCodeTip": "Please input the verification code",
296-
"ForgotPassword": "Forgot Password",
296+
"ForgotPassword": "Forgot Password?",
297297
"Register": "Register",
298298
"ToRegister": "Go to Register",
299299
"ToLogin": "Go to Login",
@@ -308,7 +308,10 @@
308308
"PasswordNotMatch": "The two passwords you entered do not match",
309309
"SmsCodeSendSuccess": "Verification code sent successfully",
310310
"ValidationCodePlaceholder": "Please input the verification code",
311-
"SignupSuccess": "Sign up successfully"
311+
"SignupSuccess": "Sign up successfully",
312+
"ResetPassword": "Reset Password",
313+
"ResetPasswordSuccess": "Reset password successfully",
314+
"NewPassword": "New Password"
312315
},
313316
"Time": "Time",
314317
"CreateTime": "Created",

web/public/locales/zh-CN/translation.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@
293293
"LoginTip": "请输入手机号和验证码",
294294
"LoginWithGithub": "GitHub 登录",
295295
"ValidationCodeTip": "请输入验证码",
296-
"ForgotPassword": "忘记密码",
296+
"ForgotPassword": "忘记密码",
297297
"Register": "注册",
298298
"ToRegister": "去注册",
299299
"ToLogin": "去登录",
@@ -308,7 +308,10 @@
308308
"PasswordNotMatch": "两次输入的密码不一致",
309309
"SmsCodeSendSuccess": "验证码发送成功",
310310
"ValidationCodePlaceholder": "请输入验证码",
311-
"SignupSuccess": "注册成功"
311+
"SignupSuccess": "注册成功",
312+
"ResetPassword": "重置密码",
313+
"ResetPasswordSuccess": "重置密码成功",
314+
"NewPassword": "新密码"
312315
},
313316
"Time": "时间",
314317
"CreateTime": "创建时间",
@@ -327,4 +330,4 @@
327330
"CreateFolder": "新建文件夹",
328331
"Or": "",
329332
"BeforeunloadTip": "确定要离开当前编辑页面吗?"
330-
}
333+
}

web/public/locales/zh/translation.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@
293293
"LoginTip": "请输入手机号和验证码",
294294
"LoginWithGithub": "GitHub 登录",
295295
"ValidationCodeTip": "请输入验证码",
296-
"ForgotPassword": "忘记密码",
296+
"ForgotPassword": "忘记密码",
297297
"Register": "注册",
298298
"ToRegister": "去注册",
299299
"ToLogin": "去登录",
@@ -308,7 +308,10 @@
308308
"PasswordNotMatch": "两次输入的密码不一致",
309309
"SmsCodeSendSuccess": "验证码发送成功",
310310
"ValidationCodePlaceholder": "请输入验证码",
311-
"SignupSuccess": "注册成功"
311+
"SignupSuccess": "注册成功",
312+
"ResetPassword": "重置密码",
313+
"ResetPasswordSuccess": "重置密码成功",
314+
"NewPassword": "新密码"
312315
},
313316
"Time": "时间",
314317
"CreateTime": "创建时间",
@@ -327,4 +330,4 @@
327330
"CreateFolder": "新建文件夹",
328331
"Or": "",
329332
"BeforeunloadTip": "确定要离开当前编辑页面吗?"
330-
}
333+
}

web/src/apis/v1/api-auto.d.ts

+16
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,22 @@ declare namespace Paths {
602602
export type Responses = any;
603603
}
604604

605+
namespace AuthControllerBindPhone {
606+
export type QueryParameters = any;
607+
608+
export type BodyParameters = any;
609+
610+
export type Responses = any;
611+
}
612+
613+
namespace AuthControllerBindUsername {
614+
export type QueryParameters = any;
615+
616+
export type BodyParameters = any;
617+
618+
export type Responses = any;
619+
}
620+
605621
namespace SubscriptionControllerCreate {
606622
export type QueryParameters = any;
607623

web/src/apis/v1/auth.ts

+26
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,29 @@ export async function AuthControllerGetProviders(
8989
params: params,
9090
});
9191
}
92+
93+
/**
94+
* bind phone
95+
* @param {string} phone
96+
* @param {string} code
97+
*/
98+
export async function AuthControllerBindPhone(
99+
params: Paths.AuthControllerBindPhone.BodyParameters | any,
100+
): Promise<Paths.AuthControllerBindPhone.Responses> {
101+
return request(`/v1/auth/bind/phone`, {
102+
method: "POST",
103+
data: params,
104+
});
105+
}
106+
107+
/**
108+
* bind username
109+
*/
110+
export async function AuthControllerBindUsername(
111+
params: Paths.AuthControllerBindUsername.BodyParameters | any,
112+
): Promise<Paths.AuthControllerBindUsername.Responses> {
113+
return request(`/v1/auth/bind/username`, {
114+
method: "POST",
115+
data: params,
116+
});
117+
}

web/src/components/DependenceList/index.module.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
vertical-align: middle;
1515
}
1616
}
17-
}
17+
}
+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { useState } from "react";
2+
import { useForm } from "react-hook-form";
3+
import { useNavigate } from "react-router-dom";
4+
import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons";
5+
import {
6+
Button,
7+
FormControl,
8+
FormLabel,
9+
Input,
10+
InputGroup,
11+
InputRightElement,
12+
} from "@chakra-ui/react";
13+
import { t } from "i18next";
14+
15+
import { useResetPasswordMutation, useSendSmsCodeMutation } from "@/pages/auth/service";
16+
import useGlobalStore from "@/pages/globalStore";
17+
18+
type FormData = {
19+
phone?: string;
20+
validationCode?: string;
21+
account: string;
22+
password: string;
23+
confirmPassword: string;
24+
};
25+
26+
export default function ResetPassword() {
27+
const resetPasswordMutation = useResetPasswordMutation();
28+
const sendSmsCodeMutation = useSendSmsCodeMutation();
29+
30+
const { showSuccess, showError } = useGlobalStore();
31+
const navigate = useNavigate();
32+
33+
const [isSendSmsCode, setIsSendSmsCode] = useState(false);
34+
const [countdown, setCountdown] = useState(60);
35+
const [isShowPassword, setIsShowPassword] = useState(false);
36+
37+
const {
38+
register,
39+
getValues,
40+
handleSubmit,
41+
formState: { errors },
42+
} = useForm<FormData>({
43+
defaultValues: {
44+
phone: "",
45+
validationCode: "",
46+
password: "",
47+
confirmPassword: "",
48+
},
49+
});
50+
51+
const onSubmit = async (data: FormData) => {
52+
if (data.password !== data.confirmPassword) {
53+
showError(t("AuthPanel.PasswordNotMatch"));
54+
return;
55+
}
56+
57+
const params = {
58+
phone: data.phone,
59+
code: data.validationCode,
60+
password: data.password,
61+
type: "ResetPassword",
62+
};
63+
64+
const res = await resetPasswordMutation.mutateAsync(params);
65+
66+
if (res?.data) {
67+
showSuccess(t("AuthPanel.ResetPasswordSuccess"));
68+
navigate("/login", { replace: true });
69+
}
70+
};
71+
72+
const handleSendSmsCode = async () => {
73+
if (isSendSmsCode) {
74+
return;
75+
}
76+
77+
const phone = getValues("phone") || "";
78+
const isValidate = /^1[2-9]\d{9}$/.test(phone);
79+
if (!isValidate) {
80+
showError(t("AuthPanel.PhoneTip"));
81+
return;
82+
}
83+
84+
switchSmsCodeStatus();
85+
86+
const res = await sendSmsCodeMutation.mutateAsync({
87+
phone,
88+
type: "ResetPassword",
89+
});
90+
91+
if (res?.data) {
92+
showSuccess(t("AuthPanel.SmsCodeSendSuccess"));
93+
}
94+
};
95+
96+
const switchSmsCodeStatus = () => {
97+
setIsSendSmsCode(true);
98+
setCountdown(60);
99+
const timer = setInterval(() => {
100+
setCountdown((countdown) => {
101+
if (countdown === 0) {
102+
clearInterval(timer);
103+
setIsSendSmsCode(false);
104+
return 0;
105+
}
106+
return countdown - 1;
107+
});
108+
}, 1000);
109+
};
110+
111+
return (
112+
<div className="bg-white absolute left-1/2 top-1/2 -translate-y-1/2 w-[560px] rounded-[10px] p-[65px]">
113+
<div className="mb-[45px]">
114+
<img src="/logo.png" alt="logo" width={40} className="mr-4" />
115+
</div>
116+
<div className="mb-[65px]">
117+
<FormControl isInvalid={!!errors?.phone} className="flex mb-6 items-center">
118+
<FormLabel className="w-20" htmlFor="phone">
119+
{t("AuthPanel.Phone")}
120+
</FormLabel>
121+
<InputGroup>
122+
<Input
123+
{...register("phone", {
124+
required: true,
125+
pattern: {
126+
value: /^1[2-9]\d{9}$/,
127+
message: t("AuthPanel.PhoneTip"),
128+
},
129+
})}
130+
type="tel"
131+
id="phone"
132+
placeholder={t("AuthPanel.PhonePlaceholder") || ""}
133+
/>
134+
<InputRightElement width="6rem">
135+
<Button
136+
className="w-20"
137+
variant={isSendSmsCode ? "thirdly_disabled" : "thirdly"}
138+
onClick={handleSendSmsCode}
139+
>
140+
{isSendSmsCode ? `${countdown}s` : t("AuthPanel.getValidationCode")}
141+
</Button>
142+
</InputRightElement>
143+
</InputGroup>
144+
</FormControl>
145+
<FormControl isInvalid={!!errors.validationCode} className="flex mb-6 items-center">
146+
<FormLabel className="w-20" htmlFor="validationCode">
147+
{t("AuthPanel.ValidationCode")}
148+
</FormLabel>
149+
<Input
150+
type="number"
151+
{...register("validationCode", {
152+
required: true,
153+
pattern: {
154+
value: /^\d{6}$/,
155+
message: t("AuthPanel.ValidationCodePlaceholder"),
156+
},
157+
})}
158+
id="validationCode"
159+
placeholder={t("AuthPanel.ValidationCodePlaceholder") || ""}
160+
/>
161+
</FormControl>
162+
<FormControl isInvalid={!!errors.password} className="flex mb-6 items-center">
163+
<FormLabel className="w-20" htmlFor="password">
164+
{t("AuthPanel.NewPassword")}
165+
</FormLabel>
166+
<InputGroup>
167+
<Input
168+
type={isShowPassword ? "text" : "password"}
169+
{...register("password", {
170+
required: true,
171+
})}
172+
id="password"
173+
placeholder={t("AuthPanel.PasswordPlaceholder") || ""}
174+
/>
175+
<InputRightElement width="2rem">
176+
{isShowPassword ? (
177+
<ViewOffIcon className="cursor-pointer" onClick={() => setIsShowPassword(false)} />
178+
) : (
179+
<ViewIcon className="cursor-pointer" onClick={() => setIsShowPassword(true)} />
180+
)}
181+
</InputRightElement>
182+
</InputGroup>
183+
</FormControl>
184+
<FormControl isInvalid={!!errors.confirmPassword} className="flex mb-6 items-center">
185+
<FormLabel className="w-20" htmlFor="confirmPassword">
186+
{t("AuthPanel.ConfirmPassword")}
187+
</FormLabel>
188+
<InputGroup>
189+
<Input
190+
type={isShowPassword ? "text" : "password"}
191+
{...register("confirmPassword", {
192+
required: true,
193+
})}
194+
id="confirmPassword"
195+
placeholder={t("AuthPanel.ConfirmPassword") || ""}
196+
/>
197+
<InputRightElement width="2rem">
198+
{isShowPassword ? (
199+
<ViewOffIcon className="cursor-pointer" onClick={() => setIsShowPassword(false)} />
200+
) : (
201+
<ViewIcon className="cursor-pointer" onClick={() => setIsShowPassword(true)} />
202+
)}
203+
</InputRightElement>
204+
</InputGroup>
205+
</FormControl>
206+
<div className="mb-6">
207+
<Button
208+
type="submit"
209+
className="w-full pt-5 pb-5"
210+
isLoading={resetPasswordMutation.isLoading}
211+
onClick={handleSubmit(onSubmit)}
212+
>
213+
{t("AuthPanel.ResetPassword")}
214+
</Button>
215+
</div>
216+
<div className="mt-2 flex justify-end">
217+
<Button size="xs" variant={"text"} onClick={() => navigate("/login", { replace: true })}>
218+
{t("AuthPanel.ToLogin")}
219+
</Button>
220+
</div>
221+
</div>
222+
</div>
223+
);
224+
}

0 commit comments

Comments
 (0)