diff --git a/web/src/auth/ForgetPage.js b/web/src/auth/ForgetPage.js index d627d973..b49fe48d 100644 --- a/web/src/auth/ForgetPage.js +++ b/web/src/auth/ForgetPage.js @@ -13,7 +13,7 @@ // limitations under the License. import React from "react"; -import {Button, Col, Form, Input, Row, Select, Steps} from "antd"; +import {Button, Col, Form, Input, Popover, Row, Select, Steps} from "antd"; import * as AuthBackend from "./AuthBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as Util from "./Util"; @@ -385,30 +385,48 @@ class ForgetPage extends React.Component { }, ]} /> - + { - const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions); - if (errorMsg === "") { - return Promise.resolve(); - } else { - return Promise.reject(errorMsg); - } + + { + const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions); + if (errorMsg === "") { + return Promise.resolve(); + } else { + return Promise.reject(errorMsg); + } + }, }, - }, - ]} - hasFeedback - > - - + ]} + hasFeedback + > + { + this.setState({ + passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value), + }); + }} + onFocus={() => { + this.setState({ + passwordPopoverOpen: true, + passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("password") ?? ""), + }); + }} + onBlur={() => { + this.setState({ + passwordPopoverOpen: false, + }); + }} /> + + ); } else if (signupItem.name === "Confirm password") { return ( diff --git a/web/src/common/PasswordChecker.js b/web/src/common/PasswordChecker.js index b458a04a..1a95cfdd 100644 --- a/web/src/common/PasswordChecker.js +++ b/web/src/common/PasswordChecker.js @@ -13,6 +13,8 @@ // limitations under the License. import i18next from "i18next"; +import React from "react"; +import {CheckCircleTwoTone, CloseCircleTwoTone} from "@ant-design/icons"; function isValidOption_AtLeast6(password) { if (password.length < 6) { @@ -52,6 +54,33 @@ function isValidOption_NoRepeat(password) { return ""; } +const checkers = { + AtLeast6: isValidOption_AtLeast6, + AtLeast8: isValidOption_AtLeast8, + Aa123: isValidOption_Aa123, + SpecialChar: isValidOption_SpecialChar, + NoRepeat: isValidOption_NoRepeat, +}; + +function getOptionDescription(option, password) { + switch (option) { + case "AtLeast6": return i18next.t("user:The password must have at least 6 characters"); + case "AtLeast8": return i18next.t("user:The password must have at least 8 characters"); + case "Aa123": return i18next.t("user:The password must contain at least one uppercase letter, one lowercase letter and one digit"); + case "SpecialChar": return i18next.t("user:The password must contain at least one special character"); + case "NoRepeat": return i18next.t("user:The password must not contain any repeated characters"); + } +} + +export function renderPasswordPopover(options, password) { + return
+ {options.map((option, idx) => { + return
{checkers[option](password) === "" ? : + } {getOptionDescription(option, password)}
; + })} +
; +} + export function checkPasswordComplexity(password, options) { if (password.length === 0) { return i18next.t("login:Please input your password!"); @@ -61,14 +90,6 @@ export function checkPasswordComplexity(password, options) { return ""; } - const checkers = { - AtLeast6: isValidOption_AtLeast6, - AtLeast8: isValidOption_AtLeast8, - Aa123: isValidOption_Aa123, - SpecialChar: isValidOption_SpecialChar, - NoRepeat: isValidOption_NoRepeat, - }; - for (const option of options) { const checkerFunc = checkers[option]; if (checkerFunc) { diff --git a/web/src/common/modal/PasswordModal.js b/web/src/common/modal/PasswordModal.js index 0bf68ce1..36570c2e 100644 --- a/web/src/common/modal/PasswordModal.js +++ b/web/src/common/modal/PasswordModal.js @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {Button, Col, Input, Modal, Row} from "antd"; +import {Button, Col, Input, Modal, Popover, Row} from "antd"; import i18next from "i18next"; import React from "react"; import * as UserBackend from "../../backend/UserBackend"; @@ -35,6 +35,8 @@ export const PasswordModal = (props) => { const [rePasswordValid, setRePasswordValid] = React.useState(false); const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState(""); const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState(""); + const [passwordPopoverOpen, setPasswordPopoverOpen] = React.useState(false); + const [passwordPopover, setPasswordPopover] = React.useState(); React.useEffect(() => { if (organization) { @@ -130,12 +132,26 @@ export const PasswordModal = (props) => { ) : null} - {handleNewPassword(e.target.value);}} - status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined} - /> + + { + handleNewPassword(e.target.value); + setPasswordPopoverOpen(true); + setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, e.target.value)); + + }} + onFocus={() => { + setPasswordPopoverOpen(true); + setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, newPassword)); + }} + onBlur={() => { + setPasswordPopoverOpen(false); + }} + status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined} + /> + {!newPasswordValid && newPasswordErrorMessage &&
{newPasswordErrorMessage}
}