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