import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
UserOperation,
PasswordResetForm,
GetSiteResponse,
+ GetCaptchaResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, validEmail, toast } from '../utils';
import { i18n } from '../i18next';
registerForm: RegisterForm;
loginLoading: boolean;
registerLoading: boolean;
- enable_nsfw: boolean;
+ captcha: GetCaptchaResponse;
+ captchaPlaying: boolean;
+ site: Site;
}
export class Login extends Component<any, State> {
password_verify: undefined,
admin: false,
show_nsfw: false,
+ captcha_uuid: undefined,
+ captcha_answer: undefined,
},
loginLoading: false,
registerLoading: false,
- enable_nsfw: undefined,
+ captcha: undefined,
+ captchaPlaying: false,
+ site: {
+ id: undefined,
+ name: undefined,
+ creator_id: undefined,
+ published: undefined,
+ creator_name: undefined,
+ number_of_users: undefined,
+ number_of_posts: undefined,
+ number_of_comments: undefined,
+ number_of_communities: undefined,
+ enable_downvotes: undefined,
+ open_registration: undefined,
+ enable_nsfw: undefined,
+ },
};
constructor(props: any, context: any) {
);
WebSocketService.Instance.getSite();
+ WebSocketService.Instance.getCaptcha();
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site.name) {
+ return `${i18n.t('login')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 col-lg-6 mb-4">{this.loginForm()}</div>
<div class="col-12 col-lg-6">{this.registerForm()}</div>
class="form-control"
required
/>
- <button
- disabled={!validEmail(this.state.loginForm.username_or_email)}
- onClick={linkEvent(this, this.handlePasswordReset)}
- className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold"
- >
- {i18n.t('forgot_password')}
- </button>
+ {validEmail(this.state.loginForm.username_or_email) && (
+ <button
+ type="button"
+ onClick={linkEvent(this, this.handlePasswordReset)}
+ className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold"
+ >
+ {i18n.t('forgot_password')}
+ </button>
+ )}
</div>
</div>
<div class="form-group row">
</div>
);
}
+
registerForm() {
return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
onInput={linkEvent(this, this.handleRegisterEmailChange)}
minLength={3}
/>
+ {!validEmail(this.state.registerForm.email) && (
+ <div class="mt-2 mb-0 alert alert-light" role="alert">
+ <svg class="icon icon-inline mr-2">
+ <use xlinkHref="#icon-alert-triangle"></use>
+ </svg>
+ {i18n.t('no_password_reset')}
+ </div>
+ )}
</div>
</div>
type="password"
id="register-password"
value={this.state.registerForm.password}
+ autoComplete="new-password"
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
class="form-control"
required
type="password"
id="register-verify-password"
value={this.state.registerForm.password_verify}
+ autoComplete="new-password"
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
class="form-control"
required
</div>
</div>
- {this.state.enable_nsfw && (
+ {this.state.captcha && (
+ <div class="form-group row">
+ <label class="col-sm-2" htmlFor="register-captcha">
+ <span class="mr-2">{i18n.t('enter_code')}</span>
+ <button
+ type="button"
+ class="btn btn-secondary"
+ onClick={linkEvent(this, this.handleRegenCaptcha)}
+ >
+ <svg class="icon icon-refresh-cw">
+ <use xlinkHref="#icon-refresh-cw"></use>
+ </svg>
+ </button>
+ </label>
+ {this.showCaptcha()}
+ <div class="col-sm-6">
+ <input
+ type="text"
+ class="form-control"
+ id="register-captcha"
+ value={this.state.registerForm.captcha_answer}
+ onInput={linkEvent(
+ this,
+ this.handleRegisterCaptchaAnswerChange
+ )}
+ required
+ />
+ </div>
+ </div>
+ )}
+ {this.state.site.enable_nsfw && (
<div class="form-group row">
<div class="col-sm-10">
<div class="form-check">
);
}
+ showCaptcha() {
+ return (
+ <div class="col-sm-4">
+ {this.state.captcha.ok && (
+ <>
+ <img
+ class="rounded-top img-fluid"
+ src={this.captchaPngSrc()}
+ style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
+ />
+ {this.state.captcha.ok.wav && (
+ <button
+ class="rounded-bottom btn btn-sm btn-secondary btn-block"
+ style="border-top-right-radius: 0; border-top-left-radius: 0;"
+ title={i18n.t('play_captcha_audio')}
+ onClick={linkEvent(this, this.handleCaptchaPlay)}
+ type="button"
+ disabled={this.state.captchaPlaying}
+ >
+ <svg class="icon icon-play">
+ <use xlinkHref="#icon-play"></use>
+ </svg>
+ </button>
+ )}
+ </>
+ )}
+ </div>
+ );
+ }
+
handleLoginSubmit(i: Login, event: any) {
event.preventDefault();
i.state.loginLoading = true;
event.preventDefault();
i.state.registerLoading = true;
i.setState(i.state);
-
WebSocketService.Instance.register(i.state.registerForm);
}
i.setState(i.state);
}
+ handleRegisterCaptchaAnswerChange(i: Login, event: any) {
+ i.state.registerForm.captcha_answer = event.target.value;
+ i.setState(i.state);
+ }
+
+ handleRegenCaptcha(_i: Login, _event: any) {
+ event.preventDefault();
+ WebSocketService.Instance.getCaptcha();
+ }
+
handlePasswordReset(i: Login) {
event.preventDefault();
let resetForm: PasswordResetForm = {
WebSocketService.Instance.passwordReset(resetForm);
}
+ handleCaptchaPlay(i: Login) {
+ event.preventDefault();
+ let snd = new Audio('data:audio/wav;base64,' + i.state.captcha.ok.wav);
+ snd.play();
+ i.state.captchaPlaying = true;
+ i.setState(i.state);
+ snd.addEventListener('ended', () => {
+ snd.currentTime = 0;
+ i.state.captchaPlaying = false;
+ i.setState(this.state);
+ });
+ }
+
+ captchaPngSrc() {
+ return `data:image/png;base64,${this.state.captcha.ok.png}`;
+ }
+
parseMessage(msg: WebSocketJsonResponse) {
let res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
this.state = this.emptyState;
+ this.state.registerForm.captcha_answer = undefined;
+ // Refetch another captcha
+ WebSocketService.Instance.getCaptcha();
this.setState(this.state);
return;
} else {
this.state = this.emptyState;
this.setState(this.state);
UserService.Instance.login(data);
+ WebSocketService.Instance.userJoin();
toast(i18n.t('logged_in'));
this.props.history.push('/');
} else if (res.op == UserOperation.Register) {
this.state = this.emptyState;
this.setState(this.state);
UserService.Instance.login(data);
+ WebSocketService.Instance.userJoin();
this.props.history.push('/communities');
+ } else if (res.op == UserOperation.GetCaptcha) {
+ let data = res.data as GetCaptchaResponse;
+ if (data.ok) {
+ this.state.captcha = data;
+ this.state.registerForm.captcha_uuid = data.ok.uuid;
+ this.setState(this.state);
+ }
} else if (res.op == UserOperation.PasswordReset) {
toast(i18n.t('reset_password_mail_sent'));
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- this.state.enable_nsfw = data.site.enable_nsfw;
+ this.state.site = data.site;
this.setState(this.state);
- document.title = `${i18n.t('login')} - ${
- WebSocketService.Instance.site.name
- }`;
}
}
}