password reset forms

This commit is contained in:
Brandon Thomas committed 2023-11-12 03:18:30 -05:00
1 parent 7ab4694e68
commit b1daae95ee
6 files changed
+185 -9

No files matched your search

+1
View File
@@ -23,6 +23,7 @@
/news* /index.html 200
/obs_configs* /index.html 200
/old* /index.html 200
/password-reset* /index.html 200
/patrons* /index.html 200
/pending* /index.html 200
/portal_success* /index.html 200
+8
View File
@@ -103,6 +103,14 @@ class ApiConfig {
return `${this.getApiOrigin()}/v1/login`;
}
passwordResetRequest(): string {
return `${this.getApiOrigin()}/v1/password_reset/request`;
}
passwordResetRedeem(): string {
return `${this.getApiOrigin()}/v1/password_reset/redeem`;
}
logout(): string {
return `${this.getApiOrigin()}/v1/logout`;
}
@@ -0,0 +1,57 @@
import { ApiConfig } from "@storyteller/components";
export interface RequestResetPasswordRequest {
reset_token: string,
new_password: string,
new_password_validation: string,
}
export interface RequestResetPasswordSuccessResponse {
success: boolean,
}
export interface RequestResetPasswordErrorResponse {
success: boolean,
error_type: string,
error_fields: { [key: string]: string; },
}
type RequestResetPasswordResponse = RequestResetPasswordSuccessResponse | RequestResetPasswordErrorResponse;
export function RequestResetPasswordIsSuccess(response: RequestResetPasswordResponse): response is RequestResetPasswordSuccessResponse {
return response?.success === true;
}
export function RequestResetPasswordIsError(response: RequestResetPasswordResponse): response is RequestResetPasswordErrorResponse {
return response?.success === false;
}
export async function RequestResetPassword(request: RequestResetPasswordRequest) : Promise<RequestResetPasswordResponse>
{
const endpoint = new ApiConfig().passwordResetRedeem();
return await fetch(endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false };
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -0,0 +1,55 @@
import { ApiConfig } from "@storyteller/components";
export interface RequestResetPasswordRequest {
username_or_email: string,
}
export interface RequestResetPasswordSuccessResponse {
success: boolean,
}
export interface RequestResetPasswordErrorResponse {
success: boolean,
error_type: string,
error_fields: { [key: string]: string; },
}
type RequestResetPasswordResponse = RequestResetPasswordSuccessResponse | RequestResetPasswordErrorResponse;
export function RequestResetPasswordIsSuccess(response: RequestResetPasswordResponse): response is RequestResetPasswordSuccessResponse {
return response?.success === true;
}
export function RequestResetPasswordIsError(response: RequestResetPasswordResponse): response is RequestResetPasswordErrorResponse {
return response?.success === false;
}
export async function RequestResetPassword(request: RequestResetPasswordRequest) : Promise<RequestResetPasswordResponse>
{
const endpoint = new ApiConfig().passwordResetRequest();
return await fetch(endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false };
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { Link, useHistory } from "react-router-dom";
import {
CreateSession,
CreateSessionIsError,
@@ -132,11 +132,11 @@ function LoginPage(props: Props) {
onChange={handlePasswordChange}
/>
</div>
{/* <p className="d-flex flex-column flex-lg-row gap-2">
<p className="d-flex flex-column flex-lg-row gap-2">
<Link to="/password-reset" className="text-link form-text">
Forgot your password?
</Link>
</p> */}
</p>
</div>
</div>
<button className="btn btn-primary w-100 mt-4">Login</button>
@@ -1,6 +1,6 @@
import React from "react";
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { Link, useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
@@ -8,6 +8,9 @@ import Input from "components/common/Input";
import { Button } from "components/common";
import Panel from "components/common/Panel";
import { faEnvelope } from "@fortawesome/pro-solid-svg-icons";
import {
RequestResetPassword, RequestResetPasswordIsSuccess,
} from "@storyteller/components/src/api/user/RequestResetPassword";
interface Props {
sessionWrapper: SessionWrapper;
@@ -16,11 +19,61 @@ interface Props {
function PasswordResetEmailPage(props: Props) {
let history = useHistory();
usePrefixedDocumentTitle("Reset Password");
const [emailOrUsername, setEmailOrUsername] = useState("");
const [isSent, setIsSent] = useState(false);
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Reset Password");
const handleChange = (
ev: React.FormEvent<HTMLInputElement>
) => {
const value = (ev.target as HTMLInputElement).value;
setEmailOrUsername(value);
}
const handleSubmit = async (
ev: React.FormEvent<HTMLButtonElement>
) : Promise<boolean> => {
ev.preventDefault();
const value = emailOrUsername.trim();
if (value.length < 4) {
return false;
}
const request = {
username_or_email: value
};
const response = await RequestResetPassword(request);
if (RequestResetPasswordIsSuccess(response)) {
setIsSent(true);
}
return false;
}
if (isSent) {
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Password Reset Sent"
subText="If you entered a valid email or username, an email with instructions to reset your password has been sent your way. "
panel={false}
showBackButton={true}
/>
<Link to={"/password-reset/verify"}>
Click here once you have the code.
</Link>
</Container>
);
}
return (
<Container type="panel" className="login-panel">
@@ -36,12 +89,14 @@ function PasswordResetEmailPage(props: Props) {
<form>
<div className="d-flex flex-column gap-4">
<Input
label="Enter Email"
label="Enter Email or Username"
icon={faEnvelope}
placeholder="Email address"
placeholder="Email address or username"
value={emailOrUsername}
onChange={handleChange}
/>
<Button label="Reset Password" onClick={() => {}} />
<Button label="Reset Password" onClick={handleSubmit} />
</div>
</form>
</Panel>