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";
16 } from "lemmy-js-client";
17 import { Subscription } from "rxjs";
18 import { i18n } from "../../i18next";
19 import { UserService, WebSocketService } from "../../services";
30 import { HtmlTags } from "../common/html-tags";
31 import { Icon, Spinner } from "../common/icon";
32 import { MarkdownTextArea } from "../common/markdown-textarea";
34 const passwordStrengthOptions: Options<string> = [
66 password_verify?: string;
68 captcha_uuid?: string;
69 captcha_answer?: string;
73 registerLoading: boolean;
74 captcha?: GetCaptchaResponse;
75 captchaPlaying: boolean;
76 siteRes: GetSiteResponse;
79 export class Signup extends Component<any, State> {
80 private isoData = setIsoData(this.context);
81 private subscription?: Subscription;
82 private audio?: HTMLAudioElement;
88 registerLoading: false,
89 captchaPlaying: false,
90 siteRes: this.isoData.site_res,
93 constructor(props: any, context: any) {
94 super(props, context);
96 this.handleAnswerChange = this.handleAnswerChange.bind(this);
98 this.parseMessage = this.parseMessage.bind(this);
99 this.subscription = wsSubscribe(this.parseMessage);
102 WebSocketService.Instance.send(wsClient.getCaptcha());
106 componentWillUnmount() {
108 this.subscription?.unsubscribe();
112 get documentTitle(): string {
113 let siteView = this.state.siteRes.site_view;
114 return `${this.titleName(siteView)} - ${siteView.site.name}`;
117 titleName(siteView: SiteView): string {
119 siteView.local_site.private_instance ? "apply_to_join" : "sign_up"
123 get isLemmyMl(): boolean {
124 return isBrowser() && window.location.hostname == "lemmy.ml";
129 <div className="container-lg">
131 title={this.documentTitle}
132 path={this.context.router.route.match.url}
134 <div className="row">
135 <div className="col-12 col-lg-6 offset-lg-3">
136 {this.registerForm()}
144 let siteView = this.state.siteRes.site_view;
146 <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
147 <h5>{this.titleName(siteView)}</h5>
150 <div className="form-group row">
151 <div className="mt-2 mb-0 alert alert-warning" role="alert">
152 <T i18nKey="lemmy_ml_registration_message">
153 #<a href={joinLemmyUrl}>#</a>
159 <div className="form-group row">
161 className="col-sm-2 col-form-label"
162 htmlFor="register-username"
167 <div className="col-sm-10">
170 id="register-username"
171 className="form-control"
172 value={this.state.form.username}
173 onInput={linkEvent(this, this.handleRegisterUsernameChange)}
176 pattern="[a-zA-Z0-9_]+"
177 title={i18n.t("community_reqs")}
182 <div className="form-group row">
183 <label className="col-sm-2 col-form-label" htmlFor="register-email">
186 <div className="col-sm-10">
190 className="form-control"
192 siteView.local_site.require_email_verification
196 value={this.state.form.email}
198 onInput={linkEvent(this, this.handleRegisterEmailChange)}
199 required={siteView.local_site.require_email_verification}
202 {!siteView.local_site.require_email_verification &&
203 this.state.form.email &&
204 !validEmail(this.state.form.email) && (
205 <div className="mt-2 mb-0 alert alert-warning" role="alert">
206 <Icon icon="alert-triangle" classes="icon-inline mr-2" />
207 {i18n.t("no_password_reset")}
213 <div className="form-group row">
215 className="col-sm-2 col-form-label"
216 htmlFor="register-password"
220 <div className="col-sm-10">
223 id="register-password"
224 value={this.state.form.password}
225 autoComplete="new-password"
226 onInput={linkEvent(this, this.handleRegisterPasswordChange)}
229 className="form-control"
232 {this.state.form.password && (
233 <div className={this.passwordColorClass}>
234 {i18n.t(this.passwordStrength as I18nKeys)}
240 <div className="form-group row">
242 className="col-sm-2 col-form-label"
243 htmlFor="register-verify-password"
245 {i18n.t("verify_password")}
247 <div className="col-sm-10">
250 id="register-verify-password"
251 value={this.state.form.password_verify}
252 autoComplete="new-password"
253 onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
255 className="form-control"
261 {siteView.local_site.registration_mode ==
262 RegistrationMode.RequireApplication && (
264 <div className="form-group row">
265 <div className="offset-sm-2 col-sm-10">
266 <div className="mt-2 alert alert-warning" role="alert">
267 <Icon icon="alert-triangle" classes="icon-inline mr-2" />
268 {i18n.t("fill_out_application")}
270 {siteView.local_site.application_question && (
273 dangerouslySetInnerHTML={mdToHtml(
274 siteView.local_site.application_question
281 <div className="form-group row">
283 className="col-sm-2 col-form-label"
284 htmlFor="application_answer"
288 <div className="col-sm-10">
290 onContentChange={this.handleAnswerChange}
291 hideNavigationWarnings
300 {this.state.captcha && (
301 <div className="form-group row">
302 <label className="col-sm-2" htmlFor="register-captcha">
303 <span className="mr-2">{i18n.t("enter_code")}</span>
306 className="btn btn-secondary"
307 onClick={linkEvent(this, this.handleRegenCaptcha)}
308 aria-label={i18n.t("captcha")}
310 <Icon icon="refresh-cw" classes="icon-refresh-cw" />
314 <div className="col-sm-6">
317 className="form-control"
318 id="register-captcha"
319 value={this.state.form.captcha_answer}
322 this.handleRegisterCaptchaAnswerChange
329 {siteView.local_site.enable_nsfw && (
330 <div className="form-group row">
331 <div className="col-sm-10">
332 <div className="form-check">
334 className="form-check-input"
335 id="register-show-nsfw"
337 checked={this.state.form.show_nsfw}
338 onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
341 className="form-check-label"
342 htmlFor="register-show-nsfw"
344 {i18n.t("show_nsfw")}
355 className="form-control honeypot"
357 value={this.state.form.honeypot}
358 onInput={linkEvent(this, this.handleHoneyPotChange)}
360 <div className="form-group row">
361 <div className="col-sm-10">
362 <button type="submit" className="btn btn-secondary">
363 {this.state.registerLoading ? (
366 this.titleName(siteView)
376 let captchaRes = this.state.captcha?.ok;
377 return captchaRes ? (
378 <div className="col-sm-4">
381 className="rounded-top img-fluid"
382 src={this.captchaPngSrc(captchaRes)}
383 style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
384 alt={i18n.t("captcha")}
388 className="rounded-bottom btn btn-sm btn-secondary btn-block"
389 style="border-top-right-radius: 0; border-top-left-radius: 0;"
390 title={i18n.t("play_captcha_audio")}
391 onClick={linkEvent(this, this.handleCaptchaPlay)}
393 disabled={this.state.captchaPlaying}
395 <Icon icon="play" classes="icon-play" />
405 get passwordStrength(): string | undefined {
406 let password = this.state.form.password;
408 ? passwordStrength(password, passwordStrengthOptions).value
412 get passwordColorClass(): string {
413 let strength = this.passwordStrength;
415 if (strength && ["weak", "medium"].includes(strength)) {
416 return "text-warning";
417 } else if (strength == "strong") {
418 return "text-success";
420 return "text-danger";
424 handleRegisterSubmit(i: Signup, event: any) {
425 event.preventDefault();
426 i.setState({ registerLoading: true });
427 let cForm = i.state.form;
428 if (cForm.username && cForm.password && cForm.password_verify) {
429 let form: Register = {
430 username: cForm.username,
431 password: cForm.password,
432 password_verify: cForm.password_verify,
434 show_nsfw: cForm.show_nsfw,
435 captcha_uuid: cForm.captcha_uuid,
436 captcha_answer: cForm.captcha_answer,
437 honeypot: cForm.honeypot,
438 answer: cForm.answer,
440 WebSocketService.Instance.send(wsClient.register(form));
444 handleRegisterUsernameChange(i: Signup, event: any) {
445 i.state.form.username = event.target.value;
449 handleRegisterEmailChange(i: Signup, event: any) {
450 i.state.form.email = event.target.value;
451 if (i.state.form.email == "") {
452 i.state.form.email = undefined;
457 handleRegisterPasswordChange(i: Signup, event: any) {
458 i.state.form.password = event.target.value;
462 handleRegisterPasswordVerifyChange(i: Signup, event: any) {
463 i.state.form.password_verify = event.target.value;
467 handleRegisterShowNsfwChange(i: Signup, event: any) {
468 i.state.form.show_nsfw = event.target.checked;
472 handleRegisterCaptchaAnswerChange(i: Signup, event: any) {
473 i.state.form.captcha_answer = event.target.value;
477 handleAnswerChange(val: string) {
478 this.setState(s => ((s.form.answer = val), s));
481 handleHoneyPotChange(i: Signup, event: any) {
482 i.state.form.honeypot = event.target.value;
486 handleRegenCaptcha(i: Signup) {
488 i.setState({ captchaPlaying: false });
489 WebSocketService.Instance.send(wsClient.getCaptcha());
492 handleCaptchaPlay(i: Signup) {
493 // This was a bad bug, it should only build the new audio on a new file.
494 // Replays would stop prematurely if this was rebuilt every time.
495 let captchaRes = i.state.captcha?.ok;
498 let base64 = `data:audio/wav;base64,${captchaRes.wav}`;
499 i.audio = new Audio(base64);
502 i.setState({ captchaPlaying: true });
504 i.audio.addEventListener("ended", () => {
506 i.audio.currentTime = 0;
507 i.setState({ captchaPlaying: false });
514 captchaPngSrc(captcha: CaptchaResponse) {
515 return `data:image/png;base64,${captcha.png}`;
518 parseMessage(msg: any) {
519 let op = wsUserOp(msg);
522 toast(i18n.t(msg.error), "danger");
523 this.setState(s => ((s.form.captcha_answer = undefined), s));
524 // Refetch another captcha
525 // WebSocketService.Instance.send(wsClient.getCaptcha());
528 if (op == UserOperation.Register) {
529 let data = wsJsonToRes<LoginResponse>(msg);
530 // Only log them in if a jwt was set
532 UserService.Instance.login(data);
533 this.props.history.push("/communities");
536 if (data.verify_email_sent) {
537 toast(i18n.t("verify_email_sent"));
539 if (data.registration_created) {
540 toast(i18n.t("registration_application_sent"));
542 this.props.history.push("/");
544 } else if (op == UserOperation.GetCaptcha) {
545 let data = wsJsonToRes<GetCaptchaResponse>(msg);
547 this.setState({ captcha: data });
548 this.setState(s => ((s.form.captcha_uuid = data.ok?.uuid), s));
550 } else if (op == UserOperation.PasswordReset) {
551 toast(i18n.t("reset_password_mail_sent"));
552 } else if (op == UserOperation.GetSite) {
553 let data = wsJsonToRes<GetSiteResponse>(msg);
554 this.setState({ siteRes: data });