Bläddra i källkod

Merge pull request #1999 from visuddhinanda/agile

forgot-password
visuddhinanda 2 år sedan
förälder
incheckning
a204341977

+ 70 - 51
dashboard/src/components/nut/users/ForgotPassword.tsx

@@ -1,65 +1,84 @@
 import { useIntl } from "react-intl";
 import { ProForm, ProFormText } from "@ant-design/pro-components";
-import { message } from "antd";
+import { Alert, AlertProps } from "antd";
 
 import { post } from "../../../request";
 import { useState } from "react";
+import { get as getUiLang } from "../../../locales";
+import { fullUrl } from "../../../utils";
 
+interface IForgotPasswordRequest {
+  email: string;
+  lang: string;
+  dashboard: string;
+}
 interface IFormData {
-	email: string;
+  email: string;
 }
 interface IForgotPasswordResponse {
-	ok: boolean;
-	message: string;
-	data: string;
+  ok: boolean;
+  message: string;
+  data: string;
 }
 const Widget = () => {
-	const intl = useIntl();
-	const [notify, setNotify] = useState(
-		"系统将向您的注册邮箱发送包含重置密码所需信息的链接。请输入您的注册邮箱。并确保该邮箱可以接受邮件。"
-	);
-
-	return (
-		<>
-			<div>{notify}</div>
-			<ProForm<IFormData>
-				onFinish={async (values: IFormData) => {
-					// TODO
-					console.log(values);
-					const user = {
-						email: values.email,
-					};
-					const signin = await post<
-						IFormData,
-						IForgotPasswordResponse
-					>("/v2/auth/forgotpassword", user);
-					if (signin.ok) {
-						console.log("token", signin.data);
-						setNotify("重置密码的邮件已经发送到您的邮箱。");
-						message.success(
-							intl.formatMessage({ id: "flashes.success" })
-						);
-					} else {
-						message.error(signin.message);
-					}
-				}}
-			>
-				<ProForm.Group>
-					<ProFormText
-						width="md"
-						name="email"
-						required
-						label={intl.formatMessage({
-							id: "forms.fields.email.label",
-						})}
-						rules={[
-							{ required: true, type: "email", max: 255, min: 6 },
-						]}
-					/>
-				</ProForm.Group>
-			</ProForm>
-		</>
-	);
+  const intl = useIntl();
+  const [notify, setNotify] = useState<string>(
+    intl.formatMessage({
+      id: "message.send.reset.email",
+    })
+  );
+  const [type, setType] = useState<AlertProps["type"]>("info");
+  return (
+    <>
+      {notify ? <Alert message={notify} type={type} showIcon /> : <></>}
+      <ProForm<IFormData>
+        onFinish={async (values: IFormData) => {
+          console.debug(values);
+          const user: IForgotPasswordRequest = {
+            email: values.email,
+            lang: getUiLang(),
+            dashboard: fullUrl(""),
+          };
+          const url = "/v2/auth/forgot-password";
+          console.info("forgot password url", url, user);
+          try {
+            const result = await post<
+              IForgotPasswordRequest,
+              IForgotPasswordResponse
+            >(url, user);
+            if (result.ok) {
+              console.debug("token", result.data);
+              setNotify(
+                intl.formatMessage({
+                  id: "message.send.reset.email.successful",
+                })
+              );
+              setType("success");
+            } else {
+              setType("error");
+              setNotify(result.message);
+            }
+          } catch (error) {
+            setType("error");
+            setNotify("服务器内部错误");
+            console.error(error);
+          }
+        }}
+      >
+        <ProForm.Group>
+          <ProFormText
+            width="md"
+            name="email"
+            required
+            label={intl.formatMessage({
+              id: "forms.fields.email.label",
+            })}
+            rules={[{ required: true, type: "email", max: 255, min: 6 }]}
+          />
+        </ProForm.Group>
+      </ProForm>
+    </>
+  );
 };
 
 export default Widget;

+ 1 - 1
dashboard/src/components/nut/users/NonSignInSharedLinks.tsx

@@ -14,7 +14,7 @@ const Widget = () => {
       </Link>
       <Divider type="vertical" />
       <Link to="/anonymous/users/forgot-password">
-        <FormattedMessage id="nut.users.forgot-password.title" />
+        <FormattedMessage id="buttons.forgot.password" />
       </Link>
     </Space>
   );

+ 183 - 0
dashboard/src/components/nut/users/ResetPassword.tsx

@@ -0,0 +1,183 @@
+import { useIntl } from "react-intl";
+import {
+  ProForm,
+  ProFormInstance,
+  ProFormText,
+} from "@ant-design/pro-components";
+import { Alert, AlertProps, message } from "antd";
+import { EyeInvisibleOutlined, EyeTwoTone } from "@ant-design/icons";
+
+import { get, post } from "../../../request";
+import { useRef, useState } from "react";
+import { Link } from "react-router-dom";
+import { RuleObject } from "antd/lib/form";
+import { StoreValue } from "antd/lib/form/interface";
+
+interface IFormData {
+  username: string;
+  token?: string | null;
+  password?: string;
+  confirmPassword?: string;
+}
+interface IResetPasswordResponse {
+  ok: boolean;
+  message: string;
+  data: { username: string };
+}
+
+interface IWidget {
+  token?: string | null;
+}
+const Widget = ({ token }: IWidget) => {
+  const intl = useIntl();
+  const [notify, setNotify] = useState<React.ReactNode>();
+  const [type, setType] = useState<AlertProps["type"]>("info");
+  const formRef = useRef<ProFormInstance>();
+  const [ok, setOk] = useState(false);
+
+  const checkPass2 = (
+    rule: RuleObject,
+    value: StoreValue,
+    callback: (error?: string) => void
+  ) => {
+    if (value && value !== formRef.current?.getFieldValue("password")) {
+      callback(
+        intl.formatMessage({
+          id: "message.confirm-password.validate.fail",
+        })
+      );
+    } else {
+      callback();
+    }
+  };
+
+  return (
+    <>
+      {notify ? (
+        <Alert
+          message={notify}
+          type={type}
+          showIcon
+          action={
+            ok ? (
+              <Link to={"/anonymous/users/sign-in"}>
+                {intl.formatMessage({
+                  id: "buttons.sign-in",
+                })}
+              </Link>
+            ) : undefined
+          }
+        />
+      ) : (
+        <></>
+      )}
+      <ProForm<IFormData>
+        formRef={formRef}
+        onFinish={async (values: IFormData) => {
+          if (!token) {
+            return;
+          }
+          console.debug(values);
+          values["token"] = token;
+          const url = "/v2/auth/reset-password";
+          console.info("reset password url", url, values);
+          const result = await post<IFormData, IResetPasswordResponse>(
+            url,
+            values
+          );
+          if (result.ok) {
+            console.log("token", result.data);
+            setType("success");
+            setNotify(
+              intl.formatMessage({
+                id: "message.password.reset.successful",
+              })
+            );
+            setOk(true);
+            message.success(intl.formatMessage({ id: "flashes.success" }));
+          } else {
+            setType("error");
+            setNotify(result.message);
+          }
+        }}
+        request={async () => {
+          const url = `/v2/auth/reset-password/${token}`;
+          console.log("url", url);
+          try {
+            const res = await get<IResetPasswordResponse>(url);
+            console.log("ResetPassword get", res);
+            if (res.ok) {
+              setType("info");
+              setNotify(intl.formatMessage({ id: "message.password.reset" }));
+              return {
+                username: res.data.username,
+              };
+            } else {
+              return {
+                username: "",
+              };
+            }
+          } catch (err) {
+            //error
+            if (err === 404) {
+              setNotify(intl.formatMessage({ id: "获取token失败" }));
+              setType("error");
+            }
+            console.error(err);
+          }
+          return {
+            username: "",
+          };
+        }}
+      >
+        <ProForm.Group>
+          <ProFormText
+            width="md"
+            name="username"
+            readonly
+            required
+            label={intl.formatMessage({
+              id: "forms.fields.username.label",
+            })}
+            rules={[{ required: true, max: 255, min: 6 }]}
+          />
+        </ProForm.Group>
+        <ProForm.Group>
+          <ProFormText.Password
+            width="md"
+            name="password"
+            fieldProps={{
+              type: "password",
+
+              iconRender: (visible) =>
+                visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />,
+            }}
+            required
+            label={intl.formatMessage({
+              id: "forms.fields.password.label",
+            })}
+            rules={[{ required: true, max: 32, min: 6 }]}
+          />
+        </ProForm.Group>
+        <ProForm.Group>
+          <ProFormText.Password
+            width="md"
+            name="password2"
+            fieldProps={{
+              type: "password",
+              iconRender: (visible) =>
+                visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />,
+            }}
+            required
+            label={intl.formatMessage({
+              id: "forms.fields.confirm-password.label",
+            })}
+            rules={[{ required: true, max: 32, min: 6, validator: checkPass2 }]}
+          />
+        </ProForm.Group>
+      </ProForm>
+    </>
+  );
+};
+
+export default Widget;

+ 2 - 0
dashboard/src/locales/en-US/buttons.ts

@@ -87,6 +87,8 @@ const items = {
   "buttons.admin": "admin",
   "buttons.open.in.tab": "Open in New Tab",
   "buttons.reset.wbw": "reset wbw",
+  "buttons.reset.password": "reset password",
+  "buttons.forgot.password": "forgot password",
 };
 
 export default items;

+ 8 - 0
dashboard/src/locales/en-US/message.ts

@@ -6,6 +6,14 @@ const items = {
   "message.channel.empty.alert": "channel is empty,only original text",
   "message.time": "{time} seconds",
   "message.result": "{count} results",
+  "message.send.reset.email":
+    "The system will send a link containing the information necessary to reset your password to your registered email address. Please enter your registered email address and ensure that the email is capable of receiving messages.",
+  "message.send.reset.email.successful":
+    "The password for reset email has been sent to your inbox.",
+  "message.password.reset.successful": "password reset successful",
+  "message.password.reset": "please set new password",
+  "message.get.token.fail": "get token fail",
+  "message.confirm-password.validate.fail": "password validate fail",
 };
 
 export default items;

+ 2 - 0
dashboard/src/locales/zh-Hans/buttons.ts

@@ -87,6 +87,8 @@ const items = {
   "buttons.admin": "后台管理",
   "buttons.open.in.tab": "在新标签页中打开",
   "buttons.reset.wbw": "重置逐词解析",
+  "buttons.reset.password": "重置密码",
+  "buttons.forgot.password": "忘记密码",
 };
 
 export default items;

+ 7 - 0
dashboard/src/locales/zh-Hans/message.ts

@@ -7,6 +7,13 @@ const items = {
   "message.channel.empty.alert": "没有版本风格被选择,仅显示原文。",
   "message.time": "用时 {time} 秒",
   "message.result": "{count} 条结果",
+  "message.send.reset.email":
+    "系统将向您的注册邮箱发送邮件。请输入您的注册邮箱。并确保该邮箱可以接受邮件。",
+  "message.send.reset.email.successful": "重置密码的邮件已经发送到您的邮箱",
+  "message.password.reset.successful": "重置密码成功",
+  "message.password.reset": "请设置新的密码",
+  "message.get.token.fail": "获取token失败",
+  "message.confirm-password.validate.fail": "两次密码不一致",
 };
 
 export default items;

+ 9 - 2
dashboard/src/pages/nut/users/forgot-password.tsx

@@ -1,11 +1,18 @@
-import { Card } from "antd";
+import { Card, Divider } from "antd";
 import ForgotPassword from "../../../components/nut/users/ForgotPassword";
 import NonSignInSharedLinks from "../../../components/nut/users/NonSignInSharedLinks";
+import { useIntl } from "react-intl";
 
 const Widget = () => {
+  const intl = useIntl();
   return (
-    <Card title="重置密码">
+    <Card
+      title={intl.formatMessage({
+        id: "buttons.forgot.password",
+      })}
+    >
       <ForgotPassword />
+      <Divider />
       <NonSignInSharedLinks />
     </Card>
   );

+ 14 - 4
dashboard/src/pages/nut/users/reset-password.tsx

@@ -1,11 +1,21 @@
-import { Card } from "antd";
-import ForgotPassword from "../../../components/nut/users/ForgotPassword";
+import { Card, Divider } from "antd";
 import NonSignInSharedLinks from "../../../components/nut/users/NonSignInSharedLinks";
+import { useParams } from "react-router-dom";
+import ResetPassword from "../../../components/nut/users/ResetPassword";
+import { useIntl } from "react-intl";
 
 const Widget = () => {
+  const { token } = useParams();
+  const intl = useIntl();
+
   return (
-    <Card title="重置密码">
-      <ForgotPassword />
+    <Card
+      title={intl.formatMessage({
+        id: "buttons.reset.password",
+      })}
+    >
+      <ResetPassword token={token} />
+      <Divider />
       <NonSignInSharedLinks />
     </Card>
   );