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