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>
This commit is contained in:
Alec Armbruster 2023-06-26 14:54:38 -04:00 committed by Jay Sitter
parent f5be909d64
commit 950dfad659
4 changed files with 149 additions and 22 deletions

View file

@ -5,6 +5,7 @@ export default async ({ res }: { res: Response }) => {
res.send(`User-Agent: * res.send(`User-Agent: *
Disallow: /login Disallow: /login
Disallow: /login_reset
Disallow: /settings Disallow: /settings
Disallow: /create_community Disallow: /create_community
Disallow: /create_post Disallow: /create_post

View file

@ -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));
}
}
}

View file

@ -1,7 +1,7 @@
import { myAuth, setIsoData } from "@utils/app"; import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { NavLink } from "inferno-router";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
@ -105,18 +105,12 @@ export class Login extends Component<any, State> {
required required
maxLength={60} maxLength={60}
/> />
<button <NavLink
type="button" className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
onClick={linkEvent(this, this.handlePasswordReset)} to="/login_reset"
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")}
> >
{I18NextService.i18n.t("forgot_password")} {I18NextService.i18n.t("forgot_password")}
</button> </NavLink>
</div> </div>
</div> </div>
{this.state.showTotp && ( {this.state.showTotp && (
@ -214,15 +208,4 @@ export class Login extends Component<any, State> {
i.state.form.password = event.target.value; i.state.form.password = event.target.value;
i.setState(i.state); 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"));
}
}
}
} }

View file

@ -7,6 +7,7 @@ import { Home } from "./components/home/home";
import { Instances } from "./components/home/instances"; import { Instances } from "./components/home/instances";
import { Legal } from "./components/home/legal"; import { Legal } from "./components/home/legal";
import { Login } from "./components/home/login"; import { Login } from "./components/home/login";
import { LoginReset } from "./components/home/login-reset";
import { Setup } from "./components/home/setup"; import { Setup } from "./components/home/setup";
import { Signup } from "./components/home/signup"; import { Signup } from "./components/home/signup";
import { Modlog } from "./components/modlog"; import { Modlog } from "./components/modlog";
@ -38,6 +39,10 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
path: `/login`, path: `/login`,
component: Login, component: Login,
}, },
{
path: `/login_reset`,
component: LoginReset,
},
{ {
path: `/signup`, path: `/signup`,
component: Signup, component: Signup,