mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-25 13:51:13 +00:00
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
This commit is contained in:
parent
eaeea374be
commit
faeb66954c
8 changed files with 264 additions and 245 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit 3638cde3b3d59a969872d5f8e65f80faa9d3ab1c
|
Subproject commit a1a19aea1ad7d91195775a5ccea62ccc9076a2c7
|
|
@ -258,5 +258,12 @@
|
||||||
<path d="M8.72046 10.6397L14.9999 7.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8.72046 10.6397L14.9999 7.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M8.70605 13.353L15 16.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8.70605 13.353L15 16.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="icon-eye" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</symbol>
|
||||||
|
<symbol id="icon-eye-slash" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
|
||||||
|
</symbol>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
157
src/shared/components/common/password-input.tsx
Normal file
157
src/shared/components/common/password-input.tsx
Normal file
|
@ -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<HTMLInputElement>;
|
||||||
|
className?: string;
|
||||||
|
showStrength?: boolean;
|
||||||
|
label?: string | null;
|
||||||
|
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,
|
||||||
|
label,
|
||||||
|
showForgotLink,
|
||||||
|
},
|
||||||
|
state: { show },
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classNames("row", className)}>
|
||||||
|
{label && (
|
||||||
|
<label className="col-sm-2 col-form-label" htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div className={`col-sm-${label ? 10 : 12}`}>
|
||||||
|
<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={10}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-dark"
|
||||||
|
type="button"
|
||||||
|
id={id}
|
||||||
|
onClick={linkEvent(this, handleToggleShow)}
|
||||||
|
aria-label={I18NextService.i18n.t(
|
||||||
|
`${show ? "show" : "hide"}_password`
|
||||||
|
)}
|
||||||
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
|
`${show ? "show" : "hide"}_password`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon icon={`eye${show ? "-slash" : ""}`} inline />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{showStrength && value && (
|
||||||
|
<div className={this.passwordColorClass}>
|
||||||
|
{I18NextService.i18n.t(
|
||||||
|
this.passwordStrength as NoOptionI18nKeys
|
||||||
|
)}
|
||||||
|
</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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { myAuth, setIsoData } from "@utils/app";
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser } from "@utils/browser";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { NavLink } from "inferno-router";
|
|
||||||
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import { HttpService, RequestState } from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
loginRes: RequestState<LoginResponse>;
|
loginRes: RequestState<LoginResponse>;
|
||||||
|
@ -90,28 +90,14 @@ export class Login extends Component<any, State> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="login-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("password")}
|
id="login-password"
|
||||||
</label>
|
value={this.state.form.password}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleLoginPasswordChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("password")}
|
||||||
type="password"
|
showForgotLink
|
||||||
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>
|
</div>
|
||||||
{this.state.showTotp && (
|
{this.state.showTotp && (
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import { HttpService, RequestState } from "../../services/HttpService";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
import { SiteForm } from "./site-form";
|
import { SiteForm } from "./site-form";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -121,41 +122,21 @@ export class Setup extends Component<any, State> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("password")}
|
id="password"
|
||||||
</label>
|
value={this.state.form.password}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("password")}
|
||||||
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>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("verify_password")}
|
id="verify-password"
|
||||||
</label>
|
value={this.state.form.password_verify}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("verify_password")}
|
||||||
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>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { myAuth, setIsoData } from "@utils/app";
|
import { myAuth, setIsoData } from "@utils/app";
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser } from "@utils/browser";
|
||||||
import { validEmail } from "@utils/helpers";
|
import { validEmail } from "@utils/helpers";
|
||||||
import { Options, passwordStrength } from "check-password-strength";
|
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import {
|
import {
|
||||||
|
@ -20,33 +18,7 @@ import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
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,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
registerRes: RequestState<LoginResponse>;
|
registerRes: RequestState<LoginResponse>;
|
||||||
|
@ -210,57 +182,26 @@ export class Signup extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label
|
<PasswordInput
|
||||||
className="col-sm-2 col-form-label"
|
id="register-password"
|
||||||
htmlFor="register-password"
|
value={this.state.form.password}
|
||||||
>
|
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||||
{I18NextService.i18n.t("password")}
|
showStrength
|
||||||
</label>
|
label={I18NextService.i18n.t("password")}
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label
|
<PasswordInput
|
||||||
className="col-sm-2 col-form-label"
|
id="register-verify-password"
|
||||||
htmlFor="register-verify-password"
|
value={this.state.form.password_verify}
|
||||||
>
|
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||||
{I18NextService.i18n.t("verify_password")}
|
label={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>
|
</div>
|
||||||
|
|
||||||
{siteView.local_site.registration_mode == "RequireApplication" && (
|
{siteView.local_site.registration_mode === "RequireApplication" && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="offset-sm-2 col-sm-10">
|
<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) {
|
async handleRegisterSubmit(i: Signup, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { HttpService, I18NextService, UserService } from "../../services";
|
||||||
import { RequestState } from "../../services/HttpService";
|
import { RequestState } from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
passwordChangeRes: RequestState<LoginResponse>;
|
passwordChangeRes: RequestState<LoginResponse>;
|
||||||
|
@ -60,37 +61,22 @@ export class PasswordChange extends Component<any, State> {
|
||||||
passwordChangeForm() {
|
passwordChangeForm() {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="new-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("new_password")}
|
id="new-password"
|
||||||
</label>
|
value={this.state.form.password}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||||
<input
|
showStrength
|
||||||
id="new-password"
|
label={I18NextService.i18n.t("new_password")}
|
||||||
type="password"
|
/>
|
||||||
value={this.state.form.password}
|
|
||||||
onInput={linkEvent(this, this.handlePasswordChange)}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("verify_password")}
|
id="password"
|
||||||
</label>
|
value={this.state.form.password_verify}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("verify_password")}
|
||||||
id="verify-password"
|
/>
|
||||||
type="password"
|
|
||||||
value={this.state.form.password_verify}
|
|
||||||
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { ImageUploadForm } from "../common/image-upload-form";
|
||||||
import { LanguageSelect } from "../common/language-select";
|
import { LanguageSelect } from "../common/language-select";
|
||||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
import { SearchableSelect } from "../common/searchable-select";
|
import { SearchableSelect } from "../common/searchable-select";
|
||||||
import { SortSelect } from "../common/sort-select";
|
import { SortSelect } from "../common/sort-select";
|
||||||
import Tabs from "../common/tabs";
|
import Tabs from "../common/tabs";
|
||||||
|
@ -318,59 +319,30 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
<>
|
<>
|
||||||
<h2 className="h5">{I18NextService.i18n.t("change_password")}</h2>
|
<h2 className="h5">{I18NextService.i18n.t("change_password")}</h2>
|
||||||
<form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
|
<form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-5 col-form-label" htmlFor="user-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("new_password")}
|
id="new-password"
|
||||||
</label>
|
value={this.state.changePasswordForm.new_password}
|
||||||
<div className="col-sm-7">
|
onInput={linkEvent(this, this.handleNewPasswordChange)}
|
||||||
<input
|
showStrength
|
||||||
type="password"
|
label={I18NextService.i18n.t("new_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>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label
|
<PasswordInput
|
||||||
className="col-sm-5 col-form-label"
|
id="verify-new-password"
|
||||||
htmlFor="user-verify-password"
|
value={this.state.changePasswordForm.new_password_verify}
|
||||||
>
|
onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
|
||||||
{I18NextService.i18n.t("verify_password")}
|
label={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>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label
|
<PasswordInput
|
||||||
className="col-sm-5 col-form-label"
|
id="user-old-password"
|
||||||
htmlFor="user-old-password"
|
value={this.state.changePasswordForm.old_password}
|
||||||
>
|
onInput={linkEvent(this, this.handleOldPasswordChange)}
|
||||||
{I18NextService.i18n.t("old_password")}
|
label={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>
|
</div>
|
||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
<button
|
<button
|
||||||
|
@ -816,8 +788,12 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="input-group mb-3">
|
<form
|
||||||
|
className="mb-3"
|
||||||
|
onSubmit={linkEvent(this, this.handleDeleteAccount)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="btn d-block btn-danger"
|
className="btn d-block btn-danger"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
|
@ -828,24 +804,26 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
</button>
|
</button>
|
||||||
{this.state.deleteAccountShowConfirm && (
|
{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")}
|
{I18NextService.i18n.t("delete_account_confirm")}
|
||||||
</div>
|
</label>
|
||||||
<input
|
<PasswordInput
|
||||||
type="password"
|
id="password-delete-account"
|
||||||
value={this.state.deleteAccountForm.password}
|
value={this.state.deleteAccountForm.password}
|
||||||
autoComplete="new-password"
|
|
||||||
maxLength={60}
|
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleDeleteAccountPasswordChange
|
this.handleDeleteAccountPasswordChange
|
||||||
)}
|
)}
|
||||||
className="form-control my-2"
|
className="my-2"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
type="submit"
|
||||||
className="btn btn-danger me-4"
|
className="btn btn-danger me-4"
|
||||||
disabled={!this.state.deleteAccountForm.password}
|
disabled={!this.state.deleteAccountForm.password}
|
||||||
onClick={linkEvent(this, this.handleDeleteAccount)}
|
|
||||||
>
|
>
|
||||||
{this.state.deleteAccountRes.state === "loading" ? (
|
{this.state.deleteAccountRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
@ -855,6 +833,7 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
|
type="button"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleDeleteAccountShowConfirmToggle
|
this.handleDeleteAccountShowConfirmToggle
|
||||||
|
@ -864,7 +843,7 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1225,7 +1204,8 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
|
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;
|
const password = i.state.deleteAccountForm.password;
|
||||||
if (password) {
|
if (password) {
|
||||||
i.setState({ deleteAccountRes: { state: "loading" } });
|
i.setState({ deleteAccountRes: { state: "loading" } });
|
||||||
|
|
Loading…
Reference in a new issue