]> Untitled Git - lemmy.git/blob - ui/src/components/login.tsx
Merge branch 'feature/frontend-a11y' of https://github.com/richardj/lemmy into richar...
[lemmy.git] / ui / src / components / login.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from 'rxjs';
3 import { retryWhen, delay, take } from 'rxjs/operators';
4 import {
5   LoginForm,
6   RegisterForm,
7   LoginResponse,
8   UserOperation,
9   PasswordResetForm,
10   GetSiteResponse,
11   WebSocketJsonResponse,
12 } from '../interfaces';
13 import { WebSocketService, UserService } from '../services';
14 import { wsJsonToRes, validEmail, toast } from '../utils';
15 import { i18n } from '../i18next';
16 import { T } from 'inferno-i18next';
17
18 interface State {
19   loginForm: LoginForm;
20   registerForm: RegisterForm;
21   loginLoading: boolean;
22   registerLoading: boolean;
23   enable_nsfw: boolean;
24 }
25
26 export class Login extends Component<any, State> {
27   private subscription: Subscription;
28
29   emptyState: State = {
30     loginForm: {
31       username_or_email: undefined,
32       password: undefined,
33     },
34     registerForm: {
35       username: undefined,
36       password: undefined,
37       password_verify: undefined,
38       admin: false,
39       show_nsfw: false,
40     },
41     loginLoading: false,
42     registerLoading: false,
43     enable_nsfw: undefined,
44   };
45
46   constructor(props: any, context: any) {
47     super(props, context);
48
49     this.state = this.emptyState;
50
51     this.subscription = WebSocketService.Instance.subject
52       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
53       .subscribe(
54         msg => this.parseMessage(msg),
55         err => console.error(err),
56         () => console.log('complete')
57       );
58
59     WebSocketService.Instance.getSite();
60   }
61
62   componentWillUnmount() {
63     this.subscription.unsubscribe();
64   }
65
66   render() {
67     return (
68       <div class="container">
69         <div class="row">
70           <div class="col-12 col-lg-6 mb-4">{this.loginForm()}</div>
71           <div class="col-12 col-lg-6">{this.registerForm()}</div>
72         </div>
73       </div>
74     );
75   }
76
77   loginForm() {
78     return (
79       <div>
80         <form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
81           <h5>{ i18n.t('login') }</h5>
82           <div class="form-group row">
83             <label class="col-sm-2 col-form-label" for="login-email-or-username">
84                 { i18n.t('email_or_username') }
85             </label>
86             <div class="col-sm-10">
87               <input
88                 type="text"
89                 class="form-control"
90                 id="login-email-or-username"
91                 value={this.state.loginForm.username_or_email}
92                 onInput={linkEvent(this, this.handleLoginUsernameChange)}
93                 required
94                 minLength={3}
95               />
96             </div>
97           </div>
98           <div class="form-group row">
99             <label class="col-sm-2 col-form-label" for="login-password">
100                 { i18n.t('password') }
101             </label>
102             <div class="col-sm-10">
103               <input
104                 type="password"
105                 id="login-password"
106                 value={this.state.loginForm.password}
107                 onInput={linkEvent(this, this.handleLoginPasswordChange)}
108                 class="form-control"
109                 required
110               />
111               <button
112                 disabled={!validEmail(this.state.loginForm.username_or_email)}
113                 onClick={linkEvent(this, this.handlePasswordReset)}
114                 className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold"
115               >
116                 { i18n.t('forgot_password') }
117               </button>
118             </div>
119           </div>
120           <div class="form-group row">
121             <div class="col-sm-10">
122               <button type="submit" class="btn btn-secondary">
123                 {this.state.loginLoading ? (
124                   <svg class="icon icon-spinner spin">
125                     <use xlinkHref="#icon-spinner"></use>
126                   </svg>
127                 ) : (
128                   i18n.t('login')
129                 )}
130               </button>
131             </div>
132           </div>
133         </form>
134       </div>
135     );
136   }
137   registerForm() {
138     return (
139       <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
140         <h2>
141           { i18n.t('sign_up') }
142         </h2>
143
144         <div class="form-group row">
145           <label class="col-sm-2 col-form-label" for="register-username">
146             { i18n.t('username') }
147           </label>
148
149           <div class="col-sm-10">
150             <input
151               type="text"
152               id="register-username"
153               class="form-control"
154               value={this.state.registerForm.username}
155               onInput={linkEvent(this, this.handleRegisterUsernameChange)}
156               required
157               minLength={3}
158               maxLength={20}
159               pattern="[a-zA-Z0-9_]+"
160             />
161           </div>
162         </div>
163
164         <div class="form-group row">
165           <label class="col-sm-2 col-form-label" for="register-email">
166             { i18n.t('email') }
167           </label>
168           <div class="col-sm-10">
169             <input
170               type="email"
171               id="register-email"
172               class="form-control"
173               placeholder={i18n.t('optional')}
174               value={this.state.registerForm.email}
175               onInput={linkEvent(this, this.handleRegisterEmailChange)}
176               minLength={3}
177             />
178           </div>
179         </div>
180
181         <div class="form-group row">
182           <label class="col-sm-2 col-form-label" for="register-password">
183             { i18n.t('password') }
184           </label>
185           <div class="col-sm-10">
186             <input
187               type="password"
188               id="register-password"
189               value={this.state.registerForm.password}
190               onInput={linkEvent(this, this.handleRegisterPasswordChange)}
191               class="form-control"
192               required
193             />
194           </div>
195         </div>
196
197         <div class="form-group row">
198           <label class="col-sm-2 col-form-label" for="register-verify-password">
199             { i18n.t('verify_password') }
200           </label>
201           <div class="col-sm-10">
202             <input
203               type="password"
204               id="register-verify-password"
205               value={this.state.registerForm.password_verify}
206               onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
207               class="form-control"
208               required
209             />
210           </div>
211         </div>
212
213         { this.state.enable_nsfw && (
214           <div class="form-group row">
215             <div class="col-sm-10">
216               <div class="form-check">
217                 <input
218                   class="form-check-input"
219                   id="register-show-nsfw"
220                   type="checkbox"
221                   checked={this.state.registerForm.show_nsfw}
222                   onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
223                 />
224                 <label class="form-check-label" for="register-show-nsfw">
225                     { i18n.t('show_nsfw') }
226                 </label>
227               </div>
228             </div>
229           </div>
230         )}
231         <div class="form-group row">
232           <div class="col-sm-10">
233             <button type="submit" class="btn btn-secondary">
234               {this.state.registerLoading ? (
235                 <svg class="icon icon-spinner spin">
236                   <use xlinkHref="#icon-spinner"></use>
237                 </svg>
238               ) : (
239                 i18n.t('sign_up')
240               )}
241             </button>
242           </div>
243         </div>
244       </form>
245     );
246   }
247
248   handleLoginSubmit(i: Login, event: any) {
249     event.preventDefault();
250     i.state.loginLoading = true;
251     i.setState(i.state);
252     WebSocketService.Instance.login(i.state.loginForm);
253   }
254
255   handleLoginUsernameChange(i: Login, event: any) {
256     i.state.loginForm.username_or_email = event.target.value;
257     i.setState(i.state);
258   }
259
260   handleLoginPasswordChange(i: Login, event: any) {
261     i.state.loginForm.password = event.target.value;
262     i.setState(i.state);
263   }
264
265   handleRegisterSubmit(i: Login, event: any) {
266     event.preventDefault();
267     i.state.registerLoading = true;
268     i.setState(i.state);
269
270     WebSocketService.Instance.register(i.state.registerForm);
271   }
272
273   handleRegisterUsernameChange(i: Login, event: any) {
274     i.state.registerForm.username = event.target.value;
275     i.setState(i.state);
276   }
277
278   handleRegisterEmailChange(i: Login, event: any) {
279     i.state.registerForm.email = event.target.value;
280     if (i.state.registerForm.email == '') {
281       i.state.registerForm.email = undefined;
282     }
283     i.setState(i.state);
284   }
285
286   handleRegisterPasswordChange(i: Login, event: any) {
287     i.state.registerForm.password = event.target.value;
288     i.setState(i.state);
289   }
290
291   handleRegisterPasswordVerifyChange(i: Login, event: any) {
292     i.state.registerForm.password_verify = event.target.value;
293     i.setState(i.state);
294   }
295
296   handleRegisterShowNsfwChange(i: Login, event: any) {
297     i.state.registerForm.show_nsfw = event.target.checked;
298     i.setState(i.state);
299   }
300
301   handlePasswordReset(i: Login) {
302     event.preventDefault();
303     let resetForm: PasswordResetForm = {
304       email: i.state.loginForm.username_or_email,
305     };
306     WebSocketService.Instance.passwordReset(resetForm);
307   }
308
309   parseMessage(msg: WebSocketJsonResponse) {
310     let res = wsJsonToRes(msg);
311     if (msg.error) {
312       toast(i18n.t(msg.error), 'danger');
313       this.state = this.emptyState;
314       this.setState(this.state);
315       return;
316     } else {
317       if (res.op == UserOperation.Login) {
318         let data = res.data as LoginResponse;
319         this.state = this.emptyState;
320         this.setState(this.state);
321         UserService.Instance.login(data);
322         toast(i18n.t('logged_in'));
323         this.props.history.push('/');
324       } else if (res.op == UserOperation.Register) {
325         let data = res.data as LoginResponse;
326         this.state = this.emptyState;
327         this.setState(this.state);
328         UserService.Instance.login(data);
329         this.props.history.push('/communities');
330       } else if (res.op == UserOperation.PasswordReset) {
331         toast(i18n.t('reset_password_mail_sent'));
332       } else if (res.op == UserOperation.GetSite) {
333         let data = res.data as GetSiteResponse;
334         this.state.enable_nsfw = data.site.enable_nsfw;
335         this.setState(this.state);
336         document.title = `${i18n.t('login')} - ${
337           WebSocketService.Instance.site.name
338         }`;
339       }
340     }
341   }
342 }