1 import { Options, passwordStrength } from "check-password-strength";
2 import { I18nKeys } from "i18next";
3 import { Component, linkEvent } from "inferno";
4 import { T } from "inferno-i18next-dess";
12 } from "lemmy-js-client";
13 import { Subscription } from "rxjs";
14 import { i18n } from "../../i18next";
15 import { UserService, WebSocketService } from "../../services";
29 import { HtmlTags } from "../common/html-tags";
30 import { Icon, Spinner } from "../common/icon";
31 import { MarkdownTextArea } from "../common/markdown-textarea";
33 const passwordStrengthOptions: Options<string> = [
61 registerForm: Register;
62 registerLoading: boolean;
63 captcha: GetCaptchaResponse;
64 captchaPlaying: boolean;
68 export class Signup extends Component<any, State> {
69 private isoData = setIsoData(this.context);
70 private subscription: Subscription;
71 private audio: HTMLAudioElement;
77 password_verify: undefined,
79 captcha_uuid: undefined,
80 captcha_answer: undefined,
84 registerLoading: false,
86 captchaPlaying: false,
87 site_view: this.isoData.site_res.site_view,
90 constructor(props: any, context: any) {
91 super(props, context);
93 this.state = this.emptyState;
94 this.handleAnswerChange = this.handleAnswerChange.bind(this);
96 this.parseMessage = this.parseMessage.bind(this);
97 this.subscription = wsSubscribe(this.parseMessage);
100 WebSocketService.Instance.send(wsClient.getCaptcha());
104 componentWillUnmount() {
106 this.subscription.unsubscribe();
110 get documentTitle(): string {
111 return `${this.titleName} - ${this.state.site_view.site.name}`;
114 get titleName(): string {
116 this.state.site_view.site.private_instance ? "apply_to_join" : "sign_up"
120 get isLemmyMl(): boolean {
121 return isBrowser() && window.location.hostname == "lemmy.ml";
126 <div class="container">
128 title={this.documentTitle}
129 path={this.context.router.route.match.url}
132 <div class="col-12 col-lg-6 offset-lg-3">{this.registerForm()}</div>
140 <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
141 <h5>{this.titleName}</h5>
144 <div class="form-group row">
145 <div class="mt-2 mb-0 alert alert-warning" role="alert">
146 <T i18nKey="lemmy_ml_registration_message">
147 #<a href={joinLemmyUrl}>#</a>
153 <div class="form-group row">
154 <label class="col-sm-2 col-form-label" htmlFor="register-username">
158 <div class="col-sm-10">
161 id="register-username"
163 value={this.state.registerForm.username}
164 onInput={linkEvent(this, this.handleRegisterUsernameChange)}
167 pattern="[a-zA-Z0-9_]+"
168 title={i18n.t("community_reqs")}
173 <div class="form-group row">
174 <label class="col-sm-2 col-form-label" htmlFor="register-email">
177 <div class="col-sm-10">
183 this.state.site_view.site.require_email_verification
187 value={this.state.registerForm.email}
189 onInput={linkEvent(this, this.handleRegisterEmailChange)}
190 required={this.state.site_view.site.require_email_verification}
193 {!this.state.site_view.site.require_email_verification &&
194 !validEmail(this.state.registerForm.email) && (
195 <div class="mt-2 mb-0 alert alert-warning" role="alert">
196 <Icon icon="alert-triangle" classes="icon-inline mr-2" />
197 {i18n.t("no_password_reset")}
203 <div class="form-group row">
204 <label class="col-sm-2 col-form-label" htmlFor="register-password">
207 <div class="col-sm-10">
210 id="register-password"
211 value={this.state.registerForm.password}
212 autoComplete="new-password"
213 onInput={linkEvent(this, this.handleRegisterPasswordChange)}
219 {this.state.registerForm.password && (
220 <div class={this.passwordColorClass}>
221 {i18n.t(this.passwordStrength as I18nKeys)}
227 <div class="form-group row">
229 class="col-sm-2 col-form-label"
230 htmlFor="register-verify-password"
232 {i18n.t("verify_password")}
234 <div class="col-sm-10">
237 id="register-verify-password"
238 value={this.state.registerForm.password_verify}
239 autoComplete="new-password"
240 onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
248 {this.state.site_view.site.require_application && (
250 <div class="form-group row">
251 <div class="offset-sm-2 col-sm-10">
252 <div class="mt-2 alert alert-warning" role="alert">
253 <Icon icon="alert-triangle" classes="icon-inline mr-2" />
254 {i18n.t("fill_out_application")}
258 dangerouslySetInnerHTML={mdToHtml(
259 this.state.site_view.site.application_question || ""
265 <div class="form-group row">
267 class="col-sm-2 col-form-label"
268 htmlFor="application_answer"
272 <div class="col-sm-10">
274 onContentChange={this.handleAnswerChange}
275 hideNavigationWarnings
282 {this.state.captcha && (
283 <div class="form-group row">
284 <label class="col-sm-2" htmlFor="register-captcha">
285 <span class="mr-2">{i18n.t("enter_code")}</span>
288 class="btn btn-secondary"
289 onClick={linkEvent(this, this.handleRegenCaptcha)}
290 aria-label={i18n.t("captcha")}
292 <Icon icon="refresh-cw" classes="icon-refresh-cw" />
296 <div class="col-sm-6">
300 id="register-captcha"
301 value={this.state.registerForm.captcha_answer}
304 this.handleRegisterCaptchaAnswerChange
311 {this.state.site_view.site.enable_nsfw && (
312 <div class="form-group row">
313 <div class="col-sm-10">
314 <div class="form-check">
316 class="form-check-input"
317 id="register-show-nsfw"
319 checked={this.state.registerForm.show_nsfw}
320 onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
322 <label class="form-check-label" htmlFor="register-show-nsfw">
323 {i18n.t("show_nsfw")}
334 class="form-control honeypot"
336 value={this.state.registerForm.honeypot}
337 onInput={linkEvent(this, this.handleHoneyPotChange)}
339 <div class="form-group row">
340 <div class="col-sm-10">
341 <button type="submit" class="btn btn-secondary">
342 {this.state.registerLoading ? <Spinner /> : this.titleName}
352 <div class="col-sm-4">
353 {this.state.captcha.ok && (
356 class="rounded-top img-fluid"
357 src={this.captchaPngSrc()}
358 style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
359 alt={i18n.t("captcha")}
361 {this.state.captcha.ok.wav && (
363 class="rounded-bottom btn btn-sm btn-secondary btn-block"
364 style="border-top-right-radius: 0; border-top-left-radius: 0;"
365 title={i18n.t("play_captcha_audio")}
366 onClick={linkEvent(this, this.handleCaptchaPlay)}
368 disabled={this.state.captchaPlaying}
370 <Icon icon="play" classes="icon-play" />
379 get passwordStrength() {
380 return passwordStrength(
381 this.state.registerForm.password,
382 passwordStrengthOptions
386 get passwordColorClass(): string {
387 let strength = this.passwordStrength;
389 if (["weak", "medium"].includes(strength)) {
390 return "text-warning";
391 } else if (strength == "strong") {
392 return "text-success";
394 return "text-danger";
398 handleRegisterSubmit(i: Signup, event: any) {
399 event.preventDefault();
400 i.state.registerLoading = true;
402 WebSocketService.Instance.send(wsClient.register(i.state.registerForm));
405 handleRegisterUsernameChange(i: Signup, event: any) {
406 i.state.registerForm.username = event.target.value;
410 handleRegisterEmailChange(i: Signup, event: any) {
411 i.state.registerForm.email = event.target.value;
412 if (i.state.registerForm.email == "") {
413 i.state.registerForm.email = undefined;
418 handleRegisterPasswordChange(i: Signup, event: any) {
419 i.state.registerForm.password = event.target.value;
423 handleRegisterPasswordVerifyChange(i: Signup, event: any) {
424 i.state.registerForm.password_verify = event.target.value;
428 handleRegisterShowNsfwChange(i: Signup, event: any) {
429 i.state.registerForm.show_nsfw = event.target.checked;
433 handleRegisterCaptchaAnswerChange(i: Signup, event: any) {
434 i.state.registerForm.captcha_answer = event.target.value;
438 handleAnswerChange(val: string) {
439 this.state.registerForm.answer = val;
440 this.setState(this.state);
443 handleHoneyPotChange(i: Signup, event: any) {
444 i.state.registerForm.honeypot = event.target.value;
448 handleRegenCaptcha(i: Signup) {
450 i.state.captchaPlaying = false;
452 WebSocketService.Instance.send(wsClient.getCaptcha());
455 handleCaptchaPlay(i: Signup) {
456 // This was a bad bug, it should only build the new audio on a new file.
457 // Replays would stop prematurely if this was rebuilt every time.
458 if (i.audio == null) {
459 let base64 = `data:audio/wav;base64,${i.state.captcha.ok.wav}`;
460 i.audio = new Audio(base64);
465 i.state.captchaPlaying = true;
468 i.audio.addEventListener("ended", () => {
469 i.audio.currentTime = 0;
470 i.state.captchaPlaying = false;
476 return `data:image/png;base64,${this.state.captcha.ok.png}`;
479 parseMessage(msg: any) {
480 let op = wsUserOp(msg);
483 toast(i18n.t(msg.error), "danger");
484 this.state = this.emptyState;
485 this.state.registerForm.captcha_answer = undefined;
486 // Refetch another captcha
487 // WebSocketService.Instance.send(wsClient.getCaptcha());
488 this.setState(this.state);
491 if (op == UserOperation.Register) {
492 let data = wsJsonToRes<LoginResponse>(msg).data;
493 this.state = this.emptyState;
494 this.setState(this.state);
495 // Only log them in if a jwt was set
497 UserService.Instance.login(data);
498 WebSocketService.Instance.send(
503 this.props.history.push("/communities");
505 if (data.verify_email_sent) {
506 toast(i18n.t("verify_email_sent"));
508 if (data.registration_created) {
509 toast(i18n.t("registration_application_sent"));
511 this.props.history.push("/");
513 } else if (op == UserOperation.GetCaptcha) {
514 let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
516 this.state.captcha = data;
517 this.state.registerForm.captcha_uuid = data.ok.uuid;
518 this.setState(this.state);
520 } else if (op == UserOperation.PasswordReset) {
521 toast(i18n.t("reset_password_mail_sent"));
522 } else if (op == UserOperation.GetSite) {
523 let data = wsJsonToRes<GetSiteResponse>(msg).data;
524 this.state.site_view = data.site_view;
525 this.setState(this.state);