From 921cd976a4aae54ccb0bc855da36980aa91a7c4b Mon Sep 17 00:00:00 2001 From: Alec Armbruster <35377827+alectrocute@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:54:38 -0400 Subject: [PATCH] Move password reset form to separate route, view (#1390) * rework password reset form * make self-suggested changes * cleaning * validate in handlePasswordReset as well * update submodule * partially make suggested changes * make suggested changes * resolve merge conflicts * resolve merge conflicts * resolve merge conflicts --------- Co-authored-by: Dessalines <dessalines@users.noreply.github.com> --- src/server/handlers/robots-handler.ts | 1 + src/shared/components/home/login-reset.tsx | 138 +++++++++++++++++++++ src/shared/components/home/login.tsx | 27 +--- src/shared/routes.ts | 5 + 4 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 src/shared/components/home/login-reset.tsx diff --git a/src/server/handlers/robots-handler.ts b/src/server/handlers/robots-handler.ts index 7271095..80678aa 100644 --- a/src/server/handlers/robots-handler.ts +++ b/src/server/handlers/robots-handler.ts @@ -5,6 +5,7 @@ export default async ({ res }: { res: Response }) => { res.send(`User-Agent: * Disallow: /login + Disallow: /login_reset Disallow: /settings Disallow: /create_community Disallow: /create_post diff --git a/src/shared/components/home/login-reset.tsx b/src/shared/components/home/login-reset.tsx new file mode 100644 index 0000000..c96255d --- /dev/null +++ b/src/shared/components/home/login-reset.tsx @@ -0,0 +1,138 @@ +import { setIsoData } from "@utils/app"; +import { capitalizeFirstLetter, validEmail } from "@utils/helpers"; +import { Component, linkEvent } from "inferno"; +import { GetSiteResponse } from "lemmy-js-client"; +import { HttpService, I18NextService, UserService } from "../../services"; +import { toast } from "../../toast"; +import { HtmlTags } from "../common/html-tags"; +import { Spinner } from "../common/icon"; + +interface State { + form: { + email: string; + loading: boolean; + }; + siteRes: GetSiteResponse; +} + +export class LoginReset extends Component<any, State> { + private isoData = setIsoData(this.context); + + state: State = { + form: { + email: "", + loading: false, + }, + siteRes: this.isoData.site_res, + }; + + constructor(props: any, context: any) { + super(props, context); + } + + componentDidMount() { + if (UserService.Instance.myUserInfo) { + this.context.router.history.push("/"); + } + } + + get documentTitle(): string { + return `${capitalizeFirstLetter( + I18NextService.i18n.t("forgot_password") + )} - ${this.state.siteRes.site_view.site.name}`; + } + + render() { + return ( + <div className="container-lg"> + <HtmlTags + title={this.documentTitle} + path={this.context.router.route.match.url} + /> + <div className="col-12 col-lg-6 col-md-8 m-auto"> + {this.loginResetForm()} + </div> + </div> + ); + } + + loginResetForm() { + return ( + <form onSubmit={linkEvent(this, this.handlePasswordReset)}> + <h5> + {capitalizeFirstLetter(I18NextService.i18n.t("forgot_password"))} + </h5> + + <div className="form-group row"> + <label className="col-form-label"> + {I18NextService.i18n.t("no_password_reset")} + </label> + </div> + + <div className="form-group row mt-2"> + <label + className="col-sm-2 col-form-label" + htmlFor="login-reset-email" + > + {I18NextService.i18n.t("email")} + </label> + + <div className="col-sm-10"> + <input + type="text" + className="form-control" + id="login-reset-email" + value={this.state.form.email} + onInput={linkEvent(this, this.handleEmailInputChange)} + autoComplete="email" + required + minLength={3} + /> + </div> + </div> + + <div className="form-group row mt-3"> + <div className="col-sm-10"> + <button + type="button" + onClick={linkEvent(this, this.handlePasswordReset)} + className="btn btn-secondary" + disabled={ + !validEmail(this.state.form.email) || this.state.form.loading + } + > + {this.state.form.loading ? ( + <Spinner /> + ) : ( + I18NextService.i18n.t("reset_password") + )} + </button> + </div> + </div> + </form> + ); + } + + handleEmailInputChange(i: LoginReset, event: any) { + i.setState(s => ((s.form.email = event.target.value.trim()), s)); + } + + async handlePasswordReset(i: LoginReset, event: any) { + event.preventDefault(); + + const email = i.state.form.email; + + if (email && validEmail(email)) { + i.setState(s => ((s.form.loading = true), s)); + + const res = await HttpService.client.passwordReset({ email }); + + if (res.state == "success") { + toast(I18NextService.i18n.t("reset_password_mail_sent")); + i.context.router.history.push("/login"); + } + + i.setState(s => ((s.form.loading = false), s)); + } + } +} diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index 4dd6466..397288e 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -1,7 +1,7 @@ import { myAuth, setIsoData } from "@utils/app"; import { isBrowser } from "@utils/browser"; -import { validEmail } from "@utils/helpers"; import { Component, linkEvent } from "inferno"; +import { NavLink } from "inferno-router"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { I18NextService, UserService } from "../../services"; import { HttpService, RequestState } from "../../services/HttpService"; @@ -105,18 +105,12 @@ export class Login extends Component<any, State> { required maxLength={60} /> - <button - type="button" - onClick={linkEvent(this, this.handlePasswordReset)} - className="btn p-0 btn-link d-inline-block float-right text-muted small fw-bold pointer-events not-allowed" - disabled={ - !!this.state.form.username_or_email && - !validEmail(this.state.form.username_or_email) - } - title={I18NextService.i18n.t("no_password_reset")} + <NavLink + className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed" + to="/login_reset" > {I18NextService.i18n.t("forgot_password")} - </button> + </NavLink> </div> </div> {this.state.showTotp && ( @@ -214,15 +208,4 @@ export class Login extends Component<any, State> { i.state.form.password = event.target.value; i.setState(i.state); } - - async handlePasswordReset(i: Login, event: any) { - event.preventDefault(); - const email = i.state.form.username_or_email; - if (email) { - const res = await HttpService.client.passwordReset({ email }); - if (res.state == "success") { - toast(I18NextService.i18n.t("reset_password_mail_sent")); - } - } - } } diff --git a/src/shared/routes.ts b/src/shared/routes.ts index 6e3ed49..01325af 100644 --- a/src/shared/routes.ts +++ b/src/shared/routes.ts @@ -7,6 +7,7 @@ import { Home } from "./components/home/home"; import { Instances } from "./components/home/instances"; import { Legal } from "./components/home/legal"; import { Login } from "./components/home/login"; +import { LoginReset } from "./components/home/login-reset"; import { Setup } from "./components/home/setup"; import { Signup } from "./components/home/signup"; import { Modlog } from "./components/modlog"; @@ -38,6 +39,10 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [ path: `/login`, component: Login, }, + { + path: `/login_reset`, + component: LoginReset, + }, { path: `/signup`, component: Signup, -- 2.44.1