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';
13 WebSocketJsonResponse,
15 } from '../interfaces';
16 import { WebSocketService, UserService } from '../services';
17 import { wsJsonToRes, validEmail, toast } from '../utils';
18 import { i18n } from '../i18next';
22 registerForm: RegisterForm;
23 loginLoading: boolean;
24 registerLoading: boolean;
25 captcha: GetCaptchaResponse;
26 captchaPlaying: boolean;
30 export class Login extends Component<any, State> {
31 private subscription: Subscription;
35 username_or_email: undefined,
41 password_verify: undefined,
44 captcha_uuid: undefined,
45 captcha_answer: undefined,
48 registerLoading: false,
50 captchaPlaying: false,
54 creator_id: undefined,
56 creator_name: undefined,
57 number_of_users: undefined,
58 number_of_posts: undefined,
59 number_of_comments: undefined,
60 number_of_communities: undefined,
61 enable_downvotes: undefined,
62 open_registration: undefined,
63 enable_nsfw: undefined,
67 constructor(props: any, context: any) {
68 super(props, context);
70 this.state = this.emptyState;
72 this.subscription = WebSocketService.Instance.subject
73 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
75 msg => this.parseMessage(msg),
76 err => console.error(err),
77 () => console.log('complete')
80 WebSocketService.Instance.getSite();
81 WebSocketService.Instance.getCaptcha();
84 componentWillUnmount() {
85 this.subscription.unsubscribe();
88 get documentTitle(): string {
89 if (this.state.site.name) {
90 return `${i18n.t('login')} - ${this.state.site.name}`;
98 <div class="container">
99 <Helmet title={this.documentTitle} />
101 <div class="col-12 col-lg-6 mb-4">{this.loginForm()}</div>
102 <div class="col-12 col-lg-6">{this.registerForm()}</div>
111 <form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
112 <h5>{i18n.t('login')}</h5>
113 <div class="form-group row">
115 class="col-sm-2 col-form-label"
116 htmlFor="login-email-or-username"
118 {i18n.t('email_or_username')}
120 <div class="col-sm-10">
124 id="login-email-or-username"
125 value={this.state.loginForm.username_or_email}
126 onInput={linkEvent(this, this.handleLoginUsernameChange)}
132 <div class="form-group row">
133 <label class="col-sm-2 col-form-label" htmlFor="login-password">
136 <div class="col-sm-10">
140 value={this.state.loginForm.password}
141 onInput={linkEvent(this, this.handleLoginPasswordChange)}
145 {validEmail(this.state.loginForm.username_or_email) && (
148 onClick={linkEvent(this, this.handlePasswordReset)}
149 className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold"
151 {i18n.t('forgot_password')}
156 <div class="form-group row">
157 <div class="col-sm-10">
158 <button type="submit" class="btn btn-secondary">
159 {this.state.loginLoading ? (
160 <svg class="icon icon-spinner spin">
161 <use xlinkHref="#icon-spinner"></use>
176 <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
177 <h5>{i18n.t('sign_up')}</h5>
179 <div class="form-group row">
180 <label class="col-sm-2 col-form-label" htmlFor="register-username">
184 <div class="col-sm-10">
187 id="register-username"
189 value={this.state.registerForm.username}
190 onInput={linkEvent(this, this.handleRegisterUsernameChange)}
194 pattern="[a-zA-Z0-9_]+"
199 <div class="form-group row">
200 <label class="col-sm-2 col-form-label" htmlFor="register-email">
203 <div class="col-sm-10">
208 placeholder={i18n.t('optional')}
209 value={this.state.registerForm.email}
210 onInput={linkEvent(this, this.handleRegisterEmailChange)}
213 {!validEmail(this.state.registerForm.email) && (
214 <div class="mt-2 mb-0 alert alert-light" role="alert">
215 <svg class="icon icon-inline mr-2">
216 <use xlinkHref="#icon-alert-triangle"></use>
218 {i18n.t('no_password_reset')}
224 <div class="form-group row">
225 <label class="col-sm-2 col-form-label" htmlFor="register-password">
228 <div class="col-sm-10">
231 id="register-password"
232 value={this.state.registerForm.password}
233 autoComplete="new-password"
234 onInput={linkEvent(this, this.handleRegisterPasswordChange)}
241 <div class="form-group row">
243 class="col-sm-2 col-form-label"
244 htmlFor="register-verify-password"
246 {i18n.t('verify_password')}
248 <div class="col-sm-10">
251 id="register-verify-password"
252 value={this.state.registerForm.password_verify}
253 autoComplete="new-password"
254 onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
261 {this.state.captcha && (
262 <div class="form-group row">
263 <label class="col-sm-2" htmlFor="register-captcha">
264 <span class="mr-2">{i18n.t('enter_code')}</span>
267 class="btn btn-secondary"
268 onClick={linkEvent(this, this.handleRegenCaptcha)}
270 <svg class="icon icon-refresh-cw">
271 <use xlinkHref="#icon-refresh-cw"></use>
276 <div class="col-sm-6">
280 id="register-captcha"
281 value={this.state.registerForm.captcha_answer}
284 this.handleRegisterCaptchaAnswerChange
291 {this.state.site.enable_nsfw && (
292 <div class="form-group row">
293 <div class="col-sm-10">
294 <div class="form-check">
296 class="form-check-input"
297 id="register-show-nsfw"
299 checked={this.state.registerForm.show_nsfw}
300 onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
302 <label class="form-check-label" htmlFor="register-show-nsfw">
303 {i18n.t('show_nsfw')}
309 <div class="form-group row">
310 <div class="col-sm-10">
311 <button type="submit" class="btn btn-secondary">
312 {this.state.registerLoading ? (
313 <svg class="icon icon-spinner spin">
314 <use xlinkHref="#icon-spinner"></use>
328 <div class="col-sm-4">
329 {this.state.captcha.ok && (
332 class="rounded-top img-fluid"
333 src={this.captchaPngSrc()}
334 style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
336 {this.state.captcha.ok.wav && (
338 class="rounded-bottom btn btn-sm btn-secondary btn-block"
339 style="border-top-right-radius: 0; border-top-left-radius: 0;"
340 title={i18n.t('play_captcha_audio')}
341 onClick={linkEvent(this, this.handleCaptchaPlay)}
343 disabled={this.state.captchaPlaying}
345 <svg class="icon icon-play">
346 <use xlinkHref="#icon-play"></use>
356 handleLoginSubmit(i: Login, event: any) {
357 event.preventDefault();
358 i.state.loginLoading = true;
360 WebSocketService.Instance.login(i.state.loginForm);
363 handleLoginUsernameChange(i: Login, event: any) {
364 i.state.loginForm.username_or_email = event.target.value;
368 handleLoginPasswordChange(i: Login, event: any) {
369 i.state.loginForm.password = event.target.value;
373 handleRegisterSubmit(i: Login, event: any) {
374 event.preventDefault();
375 i.state.registerLoading = true;
377 WebSocketService.Instance.register(i.state.registerForm);
380 handleRegisterUsernameChange(i: Login, event: any) {
381 i.state.registerForm.username = event.target.value;
385 handleRegisterEmailChange(i: Login, event: any) {
386 i.state.registerForm.email = event.target.value;
387 if (i.state.registerForm.email == '') {
388 i.state.registerForm.email = undefined;
393 handleRegisterPasswordChange(i: Login, event: any) {
394 i.state.registerForm.password = event.target.value;
398 handleRegisterPasswordVerifyChange(i: Login, event: any) {
399 i.state.registerForm.password_verify = event.target.value;
403 handleRegisterShowNsfwChange(i: Login, event: any) {
404 i.state.registerForm.show_nsfw = event.target.checked;
408 handleRegisterCaptchaAnswerChange(i: Login, event: any) {
409 i.state.registerForm.captcha_answer = event.target.value;
413 handleRegenCaptcha(_i: Login, _event: any) {
414 event.preventDefault();
415 WebSocketService.Instance.getCaptcha();
418 handlePasswordReset(i: Login) {
419 event.preventDefault();
420 let resetForm: PasswordResetForm = {
421 email: i.state.loginForm.username_or_email,
423 WebSocketService.Instance.passwordReset(resetForm);
426 handleCaptchaPlay(i: Login) {
427 event.preventDefault();
428 let snd = new Audio('data:audio/wav;base64,' + i.state.captcha.ok.wav);
430 i.state.captchaPlaying = true;
432 snd.addEventListener('ended', () => {
434 i.state.captchaPlaying = false;
435 i.setState(this.state);
440 return `data:image/png;base64,${this.state.captcha.ok.png}`;
443 parseMessage(msg: WebSocketJsonResponse) {
444 let res = wsJsonToRes(msg);
446 toast(i18n.t(msg.error), 'danger');
447 this.state = this.emptyState;
448 this.state.registerForm.captcha_answer = undefined;
449 // Refetch another captcha
450 WebSocketService.Instance.getCaptcha();
451 this.setState(this.state);
454 if (res.op == UserOperation.Login) {
455 let data = res.data as LoginResponse;
456 this.state = this.emptyState;
457 this.setState(this.state);
458 UserService.Instance.login(data);
459 WebSocketService.Instance.userJoin();
460 toast(i18n.t('logged_in'));
461 this.props.history.push('/');
462 } else if (res.op == UserOperation.Register) {
463 let data = res.data as LoginResponse;
464 this.state = this.emptyState;
465 this.setState(this.state);
466 UserService.Instance.login(data);
467 WebSocketService.Instance.userJoin();
468 this.props.history.push('/communities');
469 } else if (res.op == UserOperation.GetCaptcha) {
470 let data = res.data as GetCaptchaResponse;
472 this.state.captcha = data;
473 this.state.registerForm.captcha_uuid = data.ok.uuid;
474 this.setState(this.state);
476 } else if (res.op == UserOperation.PasswordReset) {
477 toast(i18n.t('reset_password_mail_sent'));
478 } else if (res.op == UserOperation.GetSite) {
479 let data = res.data as GetSiteResponse;
480 this.state.site = data.site;
481 this.setState(this.state);