Merge pull request #334 from storytold/bflat-fy-password-reset-new

This commit is contained in:
BFlat authored and GitHub committed 2023-11-05 10:49:48 +07:00
commit ee85ea9330
7 files changed
+299 -60

No files matched your search

@@ -20,6 +20,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
iconFlip?: boolean;
download?: boolean | string;
disabled?: boolean;
isLoading?: boolean;
}
export default function Button({
@@ -36,6 +37,7 @@ export default function Button({
iconFlip = false,
download,
disabled,
isLoading = false,
...rest
}: ButtonProps) {
let iconMarginClass = !square && label ? (iconFlip ? "ms-2" : "me-2") : "";
@@ -45,13 +47,22 @@ export default function Button({
let LabelComponent = !square ? label : null;
const SpinnerComponent = isLoading ? (
<div
className="spinner-border spinner-border-sm text-white ms-2"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
) : null;
const externalClass = rest.className || "";
const commonProps = {
className: `${externalClass} button ${small ? "button-small" : ""} ${
square ? (small ? "button-square-small" : "button-square") : ""
} button-${variant} ${full ? "w-100" : ""}`,
disabled: disabled,
disabled: disabled || isLoading,
};
delete rest.className;
@@ -60,11 +71,13 @@ export default function Button({
<>
{LabelComponent}
{IconComponent}
{SpinnerComponent}
</>
) : (
<>
{IconComponent}
{LabelComponent}
{SpinnerComponent}
</>
);
@@ -56,7 +56,11 @@ export default function PageHeader({
</div>
)}
<div className="row">
<div className="d-flex flex-column col-lg-7 justify-content-center gap-4 py-3">
<div
className={`d-flex flex-column ${
imageUrl ? "col-lg-7" : "col-12"
} justify-content-center gap-4 py-2`}
>
<div>
<h1 className="fw-bold">
{/* {icon} */}
@@ -76,7 +80,11 @@ export default function PageHeader({
</div>
)}
</div>
<div className="d-none d-lg-block col-lg-5">
<div
className={`d-none col-lg-5 ${
imageUrl ? "d-lg-block" : "d-lg-none"
}`}
>
{imageUrl && (
<img
src={imageUrl}
@@ -93,6 +93,9 @@ import TtsModelSearchPage from "./pages/tts/tts_model_search/TtsModelSearchPage"
import MediaPage from "./pages/media/MediaPage";
import { VoiceDesignerMainPage } from "./pages/voice_designer/VoiceDesignerMainPage";
import { VoiceDesignerVoiceEditPage } from "./pages/voice_designer/VoiceDesignerVoiceEditPage";
import { PasswordResetEmailPage } from "./pages/password_reset/PasswordResetEmailPage";
import { PasswordResetVerificationPage } from "./pages/password_reset/PasswordResetVerificationPage";
import { PasswordResetChangePage } from "./pages/password_reset/PasswordResetChangePage";
interface Props {
sessionWrapper: SessionWrapper;
@@ -269,6 +272,24 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/password-reset/new">
<PasswordResetChangePage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/password-reset/verify">
<PasswordResetVerificationPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/password-reset">
<PasswordResetEmailPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/profile/:username/edit">
<ProfileEditFc sessionWrapper={this.props.sessionWrapper} />
</Route>
@@ -645,9 +666,11 @@ class PageContainer extends React.Component<
</Route>
<Route path="/voice-designer">
<VoiceDesignerMainPage {...{
sessionWrapper: this.props.sessionWrapper
}}/>
<VoiceDesignerMainPage
{...{
sessionWrapper: this.props.sessionWrapper,
}}
/>
</Route>
<Route path="/generate-speech">
@@ -1,5 +1,4 @@
import React, { useState } from "react";
import { WebUrl } from "../../../../common/WebUrl";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Link, useHistory } from "react-router-dom";
import {
@@ -13,6 +12,9 @@ import { faUser, faKey } from "@fortawesome/free-solid-svg-icons";
import { Analytics } from "../../../../common/Analytics";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient";
import Container from "components/common/Container";
import Panel from "components/common/Panel";
import PageHeader from "components/layout/PageHeader";
interface Props {
sessionWrapper: SessionWrapper;
@@ -88,62 +90,59 @@ function LoginPage(props: Props) {
}
return (
<div className="pt-lg-5">
<div className="container-panel pb-5 pt-lg-5 login-panel">
<div className="panel p-3 p-lg-4 load-hidden mt-5 mt-lg-0 px-md-4">
<h1 className="panel-title fw-bold">Login</h1>
<div className="py-6">
{errorWarning}
<Container type="panel" className="login-panel">
<PageHeader
title="Login"
subText="Log into your account."
panel={false}
/>
<Panel padding={true}>
{errorWarning}
<form onSubmit={handleFormSubmit}>
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">Username or Email</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faUser} />
</span>
<input
className="form-control"
type="text"
placeholder="Username or Email"
value={usernameOrEmail}
onChange={handleUsernameOrEmailChange}
/>
</div>
{/*<p className="help"></p>*/}
</div>
<div>
<label className="sub-title">Password</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faKey} />
</span>
<input
className="form-control"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</div>
{/*<p className="help"></p>*/}
</div>
<button className="btn btn-primary w-100 mt-2">Login</button>
<p>
Don’t have an account? &nbsp;
<Link to={WebUrl.signupPage()} className="text-link">
Create an account now.
</Link>
</p>
<form onSubmit={handleFormSubmit}>
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">Username or Email</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faUser} />
</span>
<input
className="form-control"
type="text"
placeholder="Username or Email"
value={usernameOrEmail}
onChange={handleUsernameOrEmailChange}
/>
</div>
</form>
{/*<p className="help"></p>*/}
</div>
<div>
<label className="sub-title">Password</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faKey} />
</span>
<input
className="form-control"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</div>
<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>
</div>
</div>
</div>
</div>
</div>
<button className="btn btn-primary w-100 mt-4">Login</button>
</form>
</Panel>
</Container>
);
}
@@ -0,0 +1,80 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import Input from "components/common/Input";
import { Button } from "components/common";
import Panel from "components/common/Panel";
import { faKey } from "@fortawesome/pro-solid-svg-icons";
import BackButton from "components/common/BackButton";
interface Props {
sessionWrapper: SessionWrapper;
}
function PasswordResetChangePage(props: Props) {
let history = useHistory();
const [isPasswordChanged, setIsPasswordChanged] = useState(false);
const [isLoading, setIsLoading] = useState(false);
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Reset Password");
const handlePasswordChange = async () => {
// Simulate a delay using a timer (dummy timer)
setIsLoading(true);
setTimeout(() => {
setIsPasswordChanged(true);
setIsLoading(false);
}, 2000);
};
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Change Password"
subText="Set your new password."
panel={false}
/>
<Panel padding={true}>
{isPasswordChanged ? (
<div className="d-flex flex-column gap-3 align-items-start">
<p>Your password has been successfully changed!</p>
<BackButton label="Back to Login" to="/login" />
</div>
) : (
<form>
<div className="d-flex flex-column gap-4">
<Input
label="New Password"
icon={faKey}
placeholder="Enter new password"
/>
<Input
label="Confirm New Password"
icon={faKey}
placeholder="Confirm new password"
/>
<Button
label="Confirm Change"
onClick={handlePasswordChange}
isLoading={isLoading}
/>
</div>
</form>
)}
</Panel>
</Container>
);
}
export { PasswordResetChangePage };
@@ -0,0 +1,52 @@
import React from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
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";
interface Props {
sessionWrapper: SessionWrapper;
}
function PasswordResetEmailPage(props: Props) {
let history = useHistory();
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Reset Password");
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Reset Password"
subText="Enter your account's email address you'd like your password
reset information sent to."
panel={false}
showBackButton={true}
/>
<Panel padding={true}>
<form>
<div className="d-flex flex-column gap-4">
<Input
label="Enter Email"
icon={faEnvelope}
placeholder="Email address"
/>
<Button label="Reset Password" onClick={() => {}} />
</div>
</form>
</Panel>
</Container>
);
}
export { PasswordResetEmailPage };
@@ -0,0 +1,64 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import Input from "components/common/Input";
import { Button } from "components/common";
import Panel from "components/common/Panel";
import { faLock } from "@fortawesome/pro-solid-svg-icons";
interface Props {
sessionWrapper: SessionWrapper;
}
function PasswordResetVerificationPage(props: Props) {
let history = useHistory();
const [isLoading, setIsLoading] = useState(false);
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Code Verification");
const handleVerifyCode = async () => {
// Simulate a delay using a timer (dummy timer)
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Code Verification"
subText="Enter the 6-digit code sent to your email address."
panel={false}
/>
<Panel padding={true}>
<form>
<div className="d-flex flex-column gap-4">
<Input
label="Verification Code"
icon={faLock}
placeholder="Enter 6-digit code"
/>
<Button
label="Verify Code"
onClick={handleVerifyCode}
isLoading={isLoading}
/>
</div>
</form>
</Panel>
</Container>
);
}
export { PasswordResetVerificationPage };