mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-25 22:01:13 +00:00
Make working password inputs
This commit is contained in:
parent
497fb7014f
commit
e60af0fbf8
6 changed files with 254 additions and 240 deletions
161
src/shared/components/common/password-input.tsx
Normal file
161
src/shared/components/common/password-input.tsx
Normal file
|
@ -0,0 +1,161 @@
|
|||
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";
|
||||
|
||||
interface PasswordInputProps {
|
||||
id: string;
|
||||
value?: string;
|
||||
onInput: FormEventHandler<HTMLInputElement>;
|
||||
className?: string;
|
||||
showStrength?: boolean;
|
||||
cols?: number | null;
|
||||
label?: string;
|
||||
showForgotLink?: boolean;
|
||||
}
|
||||
|
||||
interface PasswordInputState {
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
const passwordStrengthOptions: Options<string> = [
|
||||
{
|
||||
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<PasswordInputProps, PasswordInputState> {
|
||||
state: PasswordInputState = {
|
||||
show: false,
|
||||
};
|
||||
|
||||
constructor(props: PasswordInputProps, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
props: {
|
||||
id,
|
||||
value,
|
||||
onInput,
|
||||
className,
|
||||
showStrength,
|
||||
cols = 10,
|
||||
label,
|
||||
showForgotLink,
|
||||
},
|
||||
state: { show },
|
||||
} = this;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classNames(className, {
|
||||
row: !!cols,
|
||||
})}
|
||||
>
|
||||
{label && (
|
||||
<label
|
||||
className={`col-sm-${12 - (cols ?? 0)} col-form-label`}
|
||||
htmlFor={id}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div
|
||||
className={classNames({
|
||||
[`col-sm-${cols}`]: !!cols,
|
||||
})}
|
||||
>
|
||||
<div className="input-group">
|
||||
<input
|
||||
type={show ? "text" : "password"}
|
||||
className="form-control"
|
||||
aria-describedby={id}
|
||||
autoComplete="on"
|
||||
onInput={onInput}
|
||||
value={value}
|
||||
required
|
||||
maxLength={60}
|
||||
minLength={showStrength ? 10 : 0}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-outline-dark"
|
||||
type="button"
|
||||
id={id}
|
||||
onClick={linkEvent(this, handleToggleShow)}
|
||||
>
|
||||
Button
|
||||
</button>
|
||||
</div>
|
||||
{showForgotLink && (
|
||||
<NavLink
|
||||
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
|
||||
to="/login_reset"
|
||||
>
|
||||
{I18NextService.i18n.t("forgot_password")}
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showStrength && value && (
|
||||
<div className={this.passwordColorClass}>
|
||||
{I18NextService.i18n.t(this.passwordStrength as NoOptionI18nKeys)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
|
@ -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<LoginResponse>;
|
||||
|
@ -90,28 +90,14 @@ export class Login extends Component<any, State> {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="login-password">
|
||||
{I18NextService.i18n.t("password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="login-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleLoginPasswordChange)}
|
||||
className="form-control"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
maxLength={60}
|
||||
/>
|
||||
<NavLink
|
||||
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
|
||||
to="/login_reset"
|
||||
>
|
||||
{I18NextService.i18n.t("forgot_password")}
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="login-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleLoginPasswordChange)}
|
||||
label={I18NextService.i18n.t("password") ?? undefined}
|
||||
showForgotLink
|
||||
/>
|
||||
</div>
|
||||
{this.state.showTotp && (
|
||||
<div className="mb-3 row">
|
||||
|
|
|
@ -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<any, State> {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="password">
|
||||
{I18NextService.i18n.t("password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
className="form-control"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
minLength={10}
|
||||
maxLength={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
label={I18NextService.i18n.t("password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="verify-password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
className="form-control"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
minLength={10}
|
||||
maxLength={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="verify-password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
label={I18NextService.i18n.t("verify_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<div className="col-sm-10">
|
||||
|
|
|
@ -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<string> = [
|
||||
{
|
||||
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<LoginResponse>;
|
||||
|
@ -210,57 +182,26 @@ export class Signup extends Component<any, State> {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
className="col-sm-2 col-form-label"
|
||||
htmlFor="register-password"
|
||||
>
|
||||
{I18NextService.i18n.t("password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="register-password"
|
||||
value={this.state.form.password}
|
||||
autoComplete="new-password"
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
minLength={10}
|
||||
maxLength={60}
|
||||
className="form-control"
|
||||
required
|
||||
/>
|
||||
{this.state.form.password && (
|
||||
<div className={this.passwordColorClass}>
|
||||
{I18NextService.i18n.t(
|
||||
this.passwordStrength as NoOptionI18nKeys
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="register-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
showStrength
|
||||
label={I18NextService.i18n.t("password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
className="col-sm-2 col-form-label"
|
||||
htmlFor="register-verify-password"
|
||||
>
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="register-verify-password"
|
||||
value={this.state.form.password_verify}
|
||||
autoComplete="new-password"
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
maxLength={60}
|
||||
className="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="register-verify-password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
label={I18NextService.i18n.t("verify_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{siteView.local_site.registration_mode == "RequireApplication" && (
|
||||
{siteView.local_site.registration_mode === "RequireApplication" && (
|
||||
<>
|
||||
<div className="mb-3 row">
|
||||
<div className="offset-sm-2 col-sm-10">
|
||||
|
@ -411,25 +352,6 @@ export class Signup extends Component<any, State> {
|
|||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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<LoginResponse>;
|
||||
|
@ -60,37 +61,22 @@ export class PasswordChange extends Component<any, State> {
|
|||
passwordChangeForm() {
|
||||
return (
|
||||
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="new-password">
|
||||
{I18NextService.i18n.t("new_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
id="new-password"
|
||||
type="password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||
className="form-control"
|
||||
required
|
||||
maxLength={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="new-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||
showStrength
|
||||
label={I18NextService.i18n.t("new_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
id="verify-password"
|
||||
type="password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||
className="form-control"
|
||||
required
|
||||
maxLength={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||
label={I18NextService.i18n.t("verify_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<div className="col-sm-10">
|
||||
|
|
|
@ -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,33 @@ export class Settings extends Component<any, SettingsState> {
|
|||
<>
|
||||
<h2 className="h5">{I18NextService.i18n.t("change_password")}</h2>
|
||||
<form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-5 col-form-label" htmlFor="user-password">
|
||||
{I18NextService.i18n.t("new_password")}
|
||||
</label>
|
||||
<div className="col-sm-7">
|
||||
<input
|
||||
type="password"
|
||||
id="user-password"
|
||||
className="form-control"
|
||||
value={this.state.changePasswordForm.new_password}
|
||||
autoComplete="new-password"
|
||||
maxLength={60}
|
||||
onInput={linkEvent(this, this.handleNewPasswordChange)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="new-password"
|
||||
value={this.state.changePasswordForm.new_password}
|
||||
onInput={linkEvent(this, this.handleNewPasswordChange)}
|
||||
cols={7}
|
||||
showStrength
|
||||
label={I18NextService.i18n.t("new_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
className="col-sm-5 col-form-label"
|
||||
htmlFor="user-verify-password"
|
||||
>
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<div className="col-sm-7">
|
||||
<input
|
||||
type="password"
|
||||
id="user-verify-password"
|
||||
className="form-control"
|
||||
value={this.state.changePasswordForm.new_password_verify}
|
||||
autoComplete="new-password"
|
||||
maxLength={60}
|
||||
onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="verify-new-password"
|
||||
value={this.state.changePasswordForm.new_password_verify}
|
||||
onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
|
||||
cols={7}
|
||||
label={I18NextService.i18n.t("verify_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
className="col-sm-5 col-form-label"
|
||||
htmlFor="user-old-password"
|
||||
>
|
||||
{I18NextService.i18n.t("old_password")}
|
||||
</label>
|
||||
<div className="col-sm-7">
|
||||
<input
|
||||
type="password"
|
||||
id="user-old-password"
|
||||
className="form-control"
|
||||
value={this.state.changePasswordForm.old_password}
|
||||
autoComplete="new-password"
|
||||
maxLength={60}
|
||||
onInput={linkEvent(this, this.handleOldPasswordChange)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="user-old-password"
|
||||
value={this.state.changePasswordForm.old_password}
|
||||
onInput={linkEvent(this, this.handleOldPasswordChange)}
|
||||
cols={7}
|
||||
label={I18NextService.i18n.t("old_password") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-3">
|
||||
<button
|
||||
|
@ -825,19 +800,22 @@ export class Settings extends Component<any, SettingsState> {
|
|||
</button>
|
||||
{this.state.deleteAccountShowConfirm && (
|
||||
<>
|
||||
<div className="my-2 alert alert-danger" role="alert">
|
||||
<label
|
||||
className="my-2 alert alert-danger d-block"
|
||||
role="alert"
|
||||
htmlFor="password-delete-account"
|
||||
>
|
||||
{I18NextService.i18n.t("delete_account_confirm")}
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
</label>
|
||||
<PasswordInput
|
||||
id="password-delete-account"
|
||||
value={this.state.deleteAccountForm.password}
|
||||
autoComplete="new-password"
|
||||
maxLength={60}
|
||||
onInput={linkEvent(
|
||||
this,
|
||||
this.handleDeleteAccountPasswordChange
|
||||
)}
|
||||
className="form-control my-2"
|
||||
className="my-2"
|
||||
cols={null}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-danger me-4"
|
||||
|
|
Loading…
Reference in a new issue