]> Untitled Git - lemmy-ui.git/blob - src/shared/components/common/password-input.tsx
fix submodule error
[lemmy-ui.git] / src / shared / components / common / password-input.tsx
1 import { Options, passwordStrength } from "check-password-strength";
2 import classNames from "classnames";
3 import { NoOptionI18nKeys } from "i18next";
4 import { Component, FormEventHandler, linkEvent } from "inferno";
5 import { NavLink } from "inferno-router";
6 import { I18NextService } from "../../services";
7 import { Icon } from "./icon";
8
9 interface PasswordInputProps {
10   id: string;
11   value?: string;
12   onInput: FormEventHandler<HTMLInputElement>;
13   className?: string;
14   showStrength?: boolean;
15   label?: string | null;
16   showForgotLink?: boolean;
17   isNew?: boolean;
18 }
19
20 interface PasswordInputState {
21   show: boolean;
22 }
23
24 const passwordStrengthOptions: Options<string> = [
25   {
26     id: 0,
27     value: "very_weak",
28     minDiversity: 0,
29     minLength: 0,
30   },
31   {
32     id: 1,
33     value: "weak",
34     minDiversity: 2,
35     minLength: 10,
36   },
37   {
38     id: 2,
39     value: "medium",
40     minDiversity: 3,
41     minLength: 12,
42   },
43   {
44     id: 3,
45     value: "strong",
46     minDiversity: 4,
47     minLength: 14,
48   },
49 ];
50
51 function handleToggleShow(i: PasswordInput) {
52   i.setState(prev => ({
53     ...prev,
54     show: !prev.show,
55   }));
56 }
57
58 class PasswordInput extends Component<PasswordInputProps, PasswordInputState> {
59   state: PasswordInputState = {
60     show: false,
61   };
62
63   constructor(props: PasswordInputProps, context: any) {
64     super(props, context);
65   }
66
67   render() {
68     const {
69       props: {
70         id,
71         value,
72         onInput,
73         className,
74         showStrength,
75         label,
76         showForgotLink,
77         isNew,
78       },
79       state: { show },
80     } = this;
81
82     return (
83       <>
84         <div className={classNames("row", className)}>
85           {label && (
86             <label className="col-sm-2 col-form-label" htmlFor={id}>
87               {label}
88             </label>
89           )}
90           <div className={`col-sm-${label ? 10 : 12}`}>
91             <div className="input-group">
92               <input
93                 type={show ? "text" : "password"}
94                 className="form-control"
95                 aria-describedby={id}
96                 autoComplete={isNew ? "new-password" : "current-password"}
97                 onInput={onInput}
98                 value={value}
99                 required
100                 maxLength={60}
101                 minLength={10}
102               />
103               <button
104                 className="btn btn-outline-dark"
105                 type="button"
106                 id={id}
107                 onClick={linkEvent(this, handleToggleShow)}
108                 aria-label={I18NextService.i18n.t(
109                   `${show ? "show" : "hide"}_password`,
110                 )}
111                 data-tippy-content={I18NextService.i18n.t(
112                   `${show ? "show" : "hide"}_password`,
113                 )}
114               >
115                 <Icon icon={`eye${show ? "-slash" : ""}`} inline />
116               </button>
117             </div>
118             {showStrength && value && (
119               <div className={this.passwordColorClass}>
120                 {I18NextService.i18n.t(
121                   this.passwordStrength as NoOptionI18nKeys,
122                 )}
123               </div>
124             )}
125             {showForgotLink && (
126               <NavLink
127                 className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
128                 to="/login_reset"
129               >
130                 {I18NextService.i18n.t("forgot_password")}
131               </NavLink>
132             )}
133           </div>
134         </div>
135       </>
136     );
137   }
138
139   get passwordStrength(): string | undefined {
140     const password = this.props.value;
141     return password
142       ? passwordStrength(password, passwordStrengthOptions).value
143       : undefined;
144   }
145
146   get passwordColorClass(): string {
147     const strength = this.passwordStrength;
148
149     if (strength && ["weak", "medium"].includes(strength)) {
150       return "text-warning";
151     } else if (strength === "strong") {
152       return "text-success";
153     } else {
154       return "text-danger";
155     }
156   }
157 }
158
159 export default PasswordInput;