From faeb66954c49b6ab5d8689f954828c89e19c4a3c Mon Sep 17 00:00:00 2001 From: SleeplessOne1917 Date: Fri, 14 Jul 2023 17:33:24 +0000 Subject: [PATCH] Add show/hide button to password fields (#1861) * Make working password inputs * Make show/hide password button use icon * Tweak look * Handle delete account form separately from change settings form * Adjust password strengthometer position * Incorporate PR feedback * Add translations --- lemmy-translations | 2 +- src/assets/symbols.svg | 7 + .../components/common/password-input.tsx | 157 ++++++++++++++++++ src/shared/components/home/login.tsx | 32 +--- src/shared/components/home/setup.tsx | 49 ++---- src/shared/components/home/signup.tsx | 112 ++----------- .../components/person/password-change.tsx | 46 ++--- src/shared/components/person/settings.tsx | 104 +++++------- 8 files changed, 264 insertions(+), 245 deletions(-) create mode 100644 src/shared/components/common/password-input.tsx diff --git a/lemmy-translations b/lemmy-translations index 3638cde3..a1a19aea 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit 3638cde3b3d59a969872d5f8e65f80faa9d3ab1c +Subproject commit a1a19aea1ad7d91195775a5ccea62ccc9076a2c7 diff --git a/src/assets/symbols.svg b/src/assets/symbols.svg index 72214eaf..6e9c6ef9 100644 --- a/src/assets/symbols.svg +++ b/src/assets/symbols.svg @@ -258,5 +258,12 @@ + + + + + + + diff --git a/src/shared/components/common/password-input.tsx b/src/shared/components/common/password-input.tsx new file mode 100644 index 00000000..47005a00 --- /dev/null +++ b/src/shared/components/common/password-input.tsx @@ -0,0 +1,157 @@ +import { Options, passwordStrength } from "check-password-strength"; +import classNames from "classnames"; +import { NoOptionI18nKeys } from "i18next"; +import { Component, FormEventHandler, linkEvent } from "inferno"; +import { NavLink } from "inferno-router"; +import { I18NextService } from "../../services"; +import { Icon } from "./icon"; + +interface PasswordInputProps { + id: string; + value?: string; + onInput: FormEventHandler; + className?: string; + showStrength?: boolean; + label?: string | null; + showForgotLink?: boolean; +} + +interface PasswordInputState { + show: boolean; +} + +const passwordStrengthOptions: Options = [ + { + id: 0, + value: "very_weak", + minDiversity: 0, + minLength: 0, + }, + { + id: 1, + value: "weak", + minDiversity: 2, + minLength: 10, + }, + { + id: 2, + value: "medium", + minDiversity: 3, + minLength: 12, + }, + { + id: 3, + value: "strong", + minDiversity: 4, + minLength: 14, + }, +]; + +function handleToggleShow(i: PasswordInput) { + i.setState(prev => ({ + ...prev, + show: !prev.show, + })); +} + +class PasswordInput extends Component { + state: PasswordInputState = { + show: false, + }; + + constructor(props: PasswordInputProps, context: any) { + super(props, context); + } + + render() { + const { + props: { + id, + value, + onInput, + className, + showStrength, + label, + showForgotLink, + }, + state: { show }, + } = this; + + return ( + <> +
+ {label && ( + + )} +
+
+ + +
+ {showStrength && value && ( +
+ {I18NextService.i18n.t( + this.passwordStrength as NoOptionI18nKeys + )} +
+ )} + {showForgotLink && ( + + {I18NextService.i18n.t("forgot_password")} + + )} +
+
+ + ); + } + + get passwordStrength(): string | undefined { + const password = this.props.value; + return password + ? passwordStrength(password, passwordStrengthOptions).value + : undefined; + } + + get passwordColorClass(): string { + const strength = this.passwordStrength; + + if (strength && ["weak", "medium"].includes(strength)) { + return "text-warning"; + } else if (strength == "strong") { + return "text-success"; + } else { + return "text-danger"; + } + } +} + +export default PasswordInput; diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index 62e57214..828cbb5f 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -1,13 +1,13 @@ import { myAuth, setIsoData } from "@utils/app"; import { isBrowser } from "@utils/browser"; import { Component, linkEvent } from "inferno"; -import { NavLink } from "inferno-router"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { I18NextService, UserService } from "../../services"; import { HttpService, RequestState } from "../../services/HttpService"; import { toast } from "../../toast"; import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; +import PasswordInput from "../common/password-input"; interface State { loginRes: RequestState; @@ -90,28 +90,14 @@ export class Login extends Component { /> -
- -
- - - {I18NextService.i18n.t("forgot_password")} - -
+
+
{this.state.showTotp && (
diff --git a/src/shared/components/home/setup.tsx b/src/shared/components/home/setup.tsx index f4bdb555..7b3d4c27 100644 --- a/src/shared/components/home/setup.tsx +++ b/src/shared/components/home/setup.tsx @@ -10,6 +10,7 @@ import { import { I18NextService, UserService } from "../../services"; import { HttpService, RequestState } from "../../services/HttpService"; import { Spinner } from "../common/icon"; +import PasswordInput from "../common/password-input"; import { SiteForm } from "./site-form"; interface State { @@ -121,41 +122,21 @@ export class Setup extends Component { />
-
- -
- -
+
+
-
- -
- -
+
+
diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx index bb1e1f11..c57d545a 100644 --- a/src/shared/components/home/signup.tsx +++ b/src/shared/components/home/signup.tsx @@ -1,8 +1,6 @@ import { myAuth, setIsoData } from "@utils/app"; import { isBrowser } from "@utils/browser"; import { validEmail } from "@utils/helpers"; -import { Options, passwordStrength } from "check-password-strength"; -import { NoOptionI18nKeys } from "i18next"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { @@ -20,33 +18,7 @@ import { toast } from "../../toast"; import { HtmlTags } from "../common/html-tags"; import { Icon, Spinner } from "../common/icon"; import { MarkdownTextArea } from "../common/markdown-textarea"; - -const passwordStrengthOptions: Options = [ - { - id: 0, - value: "very_weak", - minDiversity: 0, - minLength: 0, - }, - { - id: 1, - value: "weak", - minDiversity: 2, - minLength: 10, - }, - { - id: 2, - value: "medium", - minDiversity: 3, - minLength: 12, - }, - { - id: 3, - value: "strong", - minDiversity: 4, - minLength: 14, - }, -]; +import PasswordInput from "../common/password-input"; interface State { registerRes: RequestState; @@ -210,57 +182,26 @@ export class Signup extends Component {
-
- -
- - {this.state.form.password && ( -
- {I18NextService.i18n.t( - this.passwordStrength as NoOptionI18nKeys - )} -
- )} -
+
+
-
- -
- -
+
+
- {siteView.local_site.registration_mode == "RequireApplication" && ( + {siteView.local_site.registration_mode === "RequireApplication" && ( <>
@@ -411,25 +352,6 @@ export class Signup extends Component { ); } - get passwordStrength(): string | undefined { - const password = this.state.form.password; - return password - ? passwordStrength(password, passwordStrengthOptions).value - : undefined; - } - - get passwordColorClass(): string { - const strength = this.passwordStrength; - - if (strength && ["weak", "medium"].includes(strength)) { - return "text-warning"; - } else if (strength == "strong") { - return "text-success"; - } else { - return "text-danger"; - } - } - async handleRegisterSubmit(i: Signup, event: any) { event.preventDefault(); const { diff --git a/src/shared/components/person/password-change.tsx b/src/shared/components/person/password-change.tsx index 565f55e6..1a60c968 100644 --- a/src/shared/components/person/password-change.tsx +++ b/src/shared/components/person/password-change.tsx @@ -6,6 +6,7 @@ import { HttpService, I18NextService, UserService } from "../../services"; import { RequestState } from "../../services/HttpService"; import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; +import PasswordInput from "../common/password-input"; interface State { passwordChangeRes: RequestState; @@ -60,37 +61,22 @@ export class PasswordChange extends Component { passwordChangeForm() { return (
-
- -
- -
+
+
-
- -
- -
+
+
diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx index 2858efce..d024aae2 100644 --- a/src/shared/components/person/settings.tsx +++ b/src/shared/components/person/settings.tsx @@ -40,6 +40,7 @@ import { ImageUploadForm } from "../common/image-upload-form"; import { LanguageSelect } from "../common/language-select"; import { ListingTypeSelect } from "../common/listing-type-select"; import { MarkdownTextArea } from "../common/markdown-textarea"; +import PasswordInput from "../common/password-input"; import { SearchableSelect } from "../common/searchable-select"; import { SortSelect } from "../common/sort-select"; import Tabs from "../common/tabs"; @@ -318,59 +319,30 @@ export class Settings extends Component { <>

{I18NextService.i18n.t("change_password")}

-
- -
- -
+
+
-
- -
- -
+
+
-
- -
- -
+
+

-
+ {this.state.deleteAccountShowConfirm && ( <> -
+
- + )} -
+ ); @@ -1225,7 +1204,8 @@ export class Settings extends Component { i.setState(s => ((s.deleteAccountForm.password = event.target.value), s)); } - async handleDeleteAccount(i: Settings) { + async handleDeleteAccount(i: Settings, event: Event) { + event.preventDefault(); const password = i.state.deleteAccountForm.password; if (password) { i.setState({ deleteAccountRes: { state: "loading" } });