mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-14 22:02:36 +00:00
password reset forms
This commit is contained in:
1 parent
7ab4694e68
commit
b1daae95ee
6 files changed
+185
-9
No files matched your search
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user