mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 09:48:44 +00:00
Merge pull request #334 from storytold/bflat-fy-password-reset-new
This commit is contained in:
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?
|
||||
<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 };
|
||||
+64
@@ -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 };
|
||||
Reference in new issue
Block a user