redeem beta key and moderation page updates

init redeem beta key page

redeem beta key page

beta key redeem success page

redeem beta key and moderation page updates

redirects back to redeem page after login and signup

removed unused
This commit is contained in:
BFlat committed 2024-05-17 02:52:27 +07:00
1 parent 474934d2f0
commit ef66b8ac3d
11 files changed
+413 -153

No files matched your search

@@ -18,5 +18,5 @@ export const CreateBetaKey = MakeRequest<
{}
>({
method: "POST",
routingFunction: () => "/v1/beta_keys/new",
routingFunction: () => "/v1/beta_keys/create",
});
@@ -0,0 +1,19 @@
import MakeRequest from "../MakeRequest";
export interface RedeemBetaKeyRequest {
beta_key: string;
}
export interface RedeemBetaKeyResponse {
success: boolean;
}
export const RedeemBetaKey = MakeRequest<
string,
RedeemBetaKeyRequest,
RedeemBetaKeyResponse,
{}
>({
method: "POST",
routingFunction: () => "/v1/beta_keys/redeem",
});
@@ -31,7 +31,10 @@ import { useInferenceJobs, useLocalize } from "hooks";
import { Logout } from "@storyteller/components/src/api/session/Logout";
import { Button } from "components/common";
import { WebUrl } from "common/WebUrl";
import { WebsiteConfig, Website } from "@storyteller/components/src/env/GetWebsite";
import {
WebsiteConfig,
Website,
} from "@storyteller/components/src/env/GetWebsite";
import { useDomainConfig } from "context/DomainConfigContext";
interface SideNavProps {
@@ -59,6 +62,8 @@ export default function SideNav({
const isOnLoginPage = window.location.pathname.includes("/login");
const isOnSignUpPage = window.location.pathname.includes("/signup");
const isOnStudioPage = window.location.pathname.includes("/studio");
const isOnBetaKeyRedeemPage =
window.location.pathname.includes("/beta-key/redeem");
const domain: WebsiteConfig = useDomainConfig();
let history = useHistory();
@@ -112,7 +117,8 @@ export default function SideNav({
const shouldNotShowSidebar =
(!isLoggedIn && (isOnLandingPage || isOnLoginPage || isOnSignUpPage)) ||
isOnStudioPage ||
(domain.website === Website.StorytellerAi && isOnLandingPage);
(domain.website === Website.StorytellerAi && isOnLandingPage) ||
isOnBetaKeyRedeemPage;
const shouldShowSidebar = windowWidth >= 992 && !shouldNotShowSidebar;
const sidebarClassName = `sidebar ${
shouldShowSidebar ? "visible" : ""
@@ -277,36 +283,35 @@ export default function SideNav({
{t("videoWorkflow")}
</NavLink>
</li>
{
// <li>
// <NavLink
// to="/studio"
// activeClassName="active-link"
// onClick={handleNavLinkClick}
// >
// <FontAwesomeIcon
// icon={faCameraMovie}
// className="sidebar-heading-icon"
// />
// Storyteller Studio
// {/* {t("videoStorytellerStudio")} */}
// </NavLink>
// </li>
// <li>
// <NavLink
// to="/engine-compositor"
// activeClassName="active-link"
// onClick={handleNavLinkClick}
// >
// <FontAwesomeIcon
// icon={faTransporter}
// className="sidebar-heading-icon"
// />
// Engine Compositor
// {/* {t("videoStorytellerStudio")} */}
// </NavLink>
// </li>
{
// <li>
// <NavLink
// to="/studio"
// activeClassName="active-link"
// onClick={handleNavLinkClick}
// >
// <FontAwesomeIcon
// icon={faCameraMovie}
// className="sidebar-heading-icon"
// />
// Storyteller Studio
// {/* {t("videoStorytellerStudio")} */}
// </NavLink>
// </li>
// <li>
// <NavLink
// to="/engine-compositor"
// activeClassName="active-link"
// onClick={handleNavLinkClick}
// >
// <FontAwesomeIcon
// icon={faTransporter}
// className="sidebar-heading-icon"
// />
// Engine Compositor
// {/* {t("videoStorytellerStudio")} */}
// </NavLink>
// </li>
}
</>
);
@@ -479,11 +484,7 @@ export default function SideNav({
</NavLink>
</li>
<li className="mb-3">
<a
href={GetDiscordLink()}
target="_blank"
rel="noreferrer"
>
<a href={GetDiscordLink()} target="_blank" rel="noreferrer">
<FontAwesomeIcon
icon={faDiscord}
className="sidebar-heading-icon"
@@ -48,6 +48,8 @@ export default function TopNav({
const isOnLoginPage = window.location.pathname.includes("/login");
const isOnSignUpPage = window.location.pathname.includes("/signup");
const isOnStudioPage = window.location.pathname.includes("/studio");
const isOnBetaKeyRedeemPage =
window.location.pathname.includes("/beta-key/redeem");
const { open } = useModal();
const openModal = () => open({ component: InferenceJobsModal });
@@ -192,15 +194,21 @@ export default function TopNav({
const pageContentWrapper = document.getElementById("page-content-wrapper");
if (pageContentWrapper) {
if (domain.titlePart === "Storyteller AI" && isOnLandingPage) {
if (
(domain.titlePart === "Storyteller AI" && isOnLandingPage) ||
isOnBetaKeyRedeemPage
) {
pageContentWrapper.style.padding = "0px";
} else {
pageContentWrapper.style.padding = "";
}
}
}, [domain.titlePart, isOnLandingPage]);
}, [domain.titlePart, isOnLandingPage, isOnBetaKeyRedeemPage]);
if (domain.titlePart === "Storyteller AI" && isOnLandingPage) {
if (
(domain.titlePart === "Storyteller AI" && isOnLandingPage) ||
isOnBetaKeyRedeemPage
) {
return null;
}
@@ -136,6 +136,8 @@ import DevUploadAlt from "./pages/dev_upload/DevUploadAlt";
import { ModerationTokenInfoPage } from "./pages/moderation/ModerationTokenInfoPage";
import StyleVideo from "./pages/style-video";
import CreateBetaKeyPage from "./pages/beta_key/CreateBetaKeyPage";
import RedeemBetaKeyPage from "./pages/beta_key/RedeemBetaKeyPage";
import RedeemSuccessPage from "./pages/beta_key/RedeemSuccessPage";
interface Props {
sessionWrapper: SessionWrapper;
@@ -1129,6 +1131,14 @@ class PageContainer extends React.Component<
<CreateBetaKeyPage />
</Route>
<Route path="/beta-key/redeem/success">
<RedeemSuccessPage />
</Route>
<Route path="/beta-key/redeem">
<RedeemBetaKeyPage />
</Route>
{/*
// <Route path="/engine-compositor">
// <EngineCompositor
@@ -0,0 +1,28 @@
@import "scss/colours.scss";
.redeem-container {
max-width: 600px !important;
}
.redeem-button {
font-size: 20px !important;
font-weight: 500;
color: #fff;
background-color: $primary;
border-radius: 1rem;
cursor: pointer;
transition: all 0.2s;
padding: 14px;
width: 300px;
border: 4px solid lighten($primary, 12%);
&:hover {
background-color: lighten($primary, 5%);
color: #fff;
border: 4px solid lighten($primary, 12%);
}
&:active,
&:focus {
border: 4px solid lighten($primary, 12%);
}
}
@@ -0,0 +1,139 @@
import { Button, Container, Label, Panel, TempInput } from "components/common";
import React, { useState } from "react";
import "./BetaKey.scss";
import { faKey } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { RedeemBetaKey } from "@storyteller/components/src/api/beta_key/RedeemBetaKey";
import { useHistory } from "react-router-dom";
import { useSession } from "hooks";
export default function RedeemBetaKeyPage() {
const [key, setKey] = useState("");
const [loading, setLoading] = useState(false);
const [showAlert, setShowAlert] = useState(false);
const history = useHistory();
const { loggedIn } = useSession();
const handleGoToSuccess = () => {
history.push("/beta-key/redeem/success");
};
const handleRedeemBetaKey = async () => {
try {
setLoading(true);
const response = await RedeemBetaKey("", {
beta_key: key,
});
if (response.success) {
handleGoToSuccess();
} else {
console.log("Failed to redeem beta key.");
setShowAlert(true);
}
} catch (error) {
console.error("Failed to redeem beta key.", error);
setShowAlert(true);
} finally {
setLoading(false);
}
};
if (!loggedIn) {
return (
<Container type="panel" className="narrow-container">
<div className="d-flex flex-column align-items-center justify-content-center vh-100 gap-4">
<img
src="/fakeyou/Storyteller-Logo-1.png"
alt="Storyteller Logo"
style={{ maxWidth: "280px" }}
/>
<Panel padding={true}>
<div className="d-flex flex-column align-items-center py-4">
<h4 className="fw-bold mb-1 text-center">
You need an account to redeem a beta key.
</h4>
<p className="opacity-75 text-center">
Please login or create an account to proceed with redeeming a
beta key.
</p>
<div className="d-flex gap-2 mt-2">
<Button
label="Sign Up"
className="mt-3"
to="/signup?redirect=/beta-key/redeem/"
variant="secondary"
/>
<Button
label="Login"
className="mt-3"
to="/login?redirect=/beta-key/redeem/"
/>
</div>
</div>
</Panel>
</div>
</Container>
);
}
return (
<>
<Container type="panel" className="redeem-container">
<div className="d-flex flex-column align-items-center justify-content-center vh-100 gap-4">
<div className="px-4 d-flex flex-column align-items-center">
<img
src="/fakeyou/Storyteller-Logo-1.png"
alt="Storyteller Logo"
className="mb-5 pb-3 pb-lg-5"
style={{ maxWidth: "100%" }}
/>
<div className="d-flex flex-column align-items-center">
<h4 className="fw-bold mb-1 text-center">
<FontAwesomeIcon icon={faKey} className="me-2 fs-5" />
Redeem Studio Beta Key
</h4>
<p className="opacity-75 text-center">
Enter your beta key to get access to Storyteller Studio Beta.
</p>
</div>
</div>
<Panel padding={true}>
<div className="w-100 d-flex justify-content-center fs-5">
<Label label="Enter your key:" />
</div>
<TempInput
placeholder="Enter your beta key here..."
value={key}
onChange={e => {
setKey(e.target.value);
setShowAlert(false);
}}
required={true}
className="text-center w-100 fs-5"
/>
{showAlert && (
<p className="text-center text-red">
Invalid beta key! Please try again.
</p>
)}
<div className="d-flex flex-column align-items-center">
<Button
label="Redeem Beta Key"
className="redeem-button mt-3"
onClick={handleRedeemBetaKey}
isLoading={loading}
disabled={key.length === 0}
/>
</div>
</Panel>
</div>
</Container>
</>
);
}
@@ -0,0 +1,39 @@
import { Button, Container } from "components/common";
import React from "react";
import "./BetaKey.scss";
export default function RedeemSuccessPage() {
const handleEnterStudio = () => {
window.location.href = "https://studio.storyteller.ai";
};
return (
<>
<Container type="panel" className="redeem-container">
<div className="d-flex flex-column align-items-center justify-content-center vh-100">
<div className="px-4 d-flex flex-column align-items-center">
<img
src="/mascot/kitsune_pose7.webp"
alt="Success Mascot"
style={{ maxWidth: "100%" }}
/>
<div className="d-flex flex-column align-items-center">
<h2 className="fw-bold mb-1">Redemption Success</h2>
<p className="opacity-75 text-center fs-5">
You now have access to Storyteller Studio Beta!
</p>
</div>
</div>
<div className="d-flex flex-column align-items-center">
<Button
label="Enter Studio"
className="redeem-button mt-4"
onClick={handleEnterStudio}
/>
</div>
</div>
</Container>
</>
);
}
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Link, useHistory } from "react-router-dom";
import { Link, useHistory, useLocation } from "react-router-dom";
import {
CreateSession,
CreateSessionIsError,
@@ -29,6 +29,9 @@ function LoginPage(props: Props) {
const [password, setPassword] = useState("");
const [usernameOrEmail, setUsernameOrEmail] = useState("");
const [errorMessage, setErrorMessage] = useState("");
let location = useLocation();
const queryParams = new URLSearchParams(location.search);
const redirectUrl = queryParams.get("redirect") || "/";
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
@@ -72,7 +75,7 @@ function LoginPage(props: Props) {
props.querySessionAction();
props.querySessionSubscriptionsAction();
Analytics.accountLoginSuccess();
history.push("/");
history.push(redirectUrl);
}
return false;
@@ -11,7 +11,15 @@ import {
faTags,
faUsers,
} from "@fortawesome/free-solid-svg-icons";
import { faMagnifyingGlass, faUserAlt } from "@fortawesome/pro-solid-svg-icons";
import {
faGift,
faKey,
faList,
faMagnifyingGlass,
faUserAlt,
} from "@fortawesome/pro-solid-svg-icons";
import { Button, Container, Panel } from "components/common";
import PageHeader from "components/layout/PageHeader";
interface Props {
sessionWrapper: SessionWrapper;
@@ -23,124 +31,125 @@ function ModerationPage(props: Props) {
}
return (
<div>
<div className="container py-5">
<h1 className=" fw-bold text-center text-lg-start">
Moderation Controls
</h1>
</div>
<Container type="panel" className="narrow-container">
<PageHeader title="Moderation Controls" />
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Lookup, Stats, and Editing</h2>
<div className="py-6">
<div className="d-flex flex-column gap-3">
<Link
to="/moderation/token_info"
className="btn btn-success w-100"
>
<FontAwesomeIcon icon={faMagnifyingGlass} className="me-2" />
Token Info Lookup
</Link>
</div>
<div className="d-flex flex-column gap-4">
<Panel padding={true}>
<h4 className="fw-bold mb-3">Lookup, Stats, and Editing</h4>
<div className="d-flex flex-column gap-3">
<Link to="/moderation/token_info" className="btn btn-success w-100">
<FontAwesomeIcon icon={faMagnifyingGlass} className="me-2" />
Token Info Lookup
</Link>
</div>
</div>
</div>
</Panel>
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Users</h2>
<div className="py-6">
<div className="d-flex flex-column gap-3">
<Link to="/moderation/ip_bans" className="btn btn-secondary w-100">
<FontAwesomeIcon icon={faBan} className="me-2" />
IP Bans
</Link>
<Link to="/moderation/user_feature_flags" className="btn btn-secondary w-100">
<FontAwesomeIcon icon={faUserAlt} className="me-2" />
User Feature Flags
</Link>
</div>
<Panel padding={true}>
<h4 className="fw-bold mb-3">Studio Beta Keys</h4>
<div className="d-flex flex-column gap-3">
<Button
to="/beta-key/create"
label="Create Beta Keys"
icon={faKey}
/>
<Button
icon={faGift}
to="/beta-key/redeem"
label="Redeem Beta Key Page"
variant="secondary"
/>
<Button
icon={faList}
label="List of Beta Keys Page"
disabled={true}
variant="secondary"
/>
</div>
</div>
</div>
</Panel>
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Emergency</h2>
<div className="py-6">
<div className="d-flex flex-column gap-3">
<Link
to="/moderation/job_control"
className="btn btn-primary w-100"
>
<FontAwesomeIcon icon={faListCheck} className="me-2" />
Job Control
</Link>
</div>
<Panel padding={true}>
<h4 className="fw-bold mb-3">Users</h4>
<div className="d-flex flex-column gap-3">
<Link to="/moderation/ip_bans" className="btn btn-secondary w-100">
<FontAwesomeIcon icon={faBan} className="me-2" />
IP Bans
</Link>
<Link
to="/moderation/user_feature_flags"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faUserAlt} className="me-2" />
User Feature Flags
</Link>
</div>
</div>
</div>
</Panel>
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Legacy and Deprecated Pages</h2>
<div className="py-6">
<div className="d-flex flex-column gap-3">
<Panel padding={true}>
<h4 className="fw-bold mb-3">Emergency</h4>
<Link
to="/moderation/job_stats"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faListCheck} className="me-2" />
Job Stats (Old TTS + W2L)
</Link>
<Link
to="/moderation/user/list"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faUsers} className="me-2" />
User List
</Link>
<Link
to={WebUrl.moderationTtsCategoryList()}
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faTags} className="me-2" />
Manage TTS Categories
</Link>
<Link
to="/moderation/approve/w2l_templates"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faClipboardCheck} className="me-2" />
Unapproved W2L Templates
</Link>
<Link
to="/moderation/voice_stats"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faMicrophone} className="me-2" />
Voice Stats
</Link>
</div>
<div className="d-flex flex-column gap-3">
<Link
to="/moderation/job_control"
className="btn btn-primary w-100"
>
<FontAwesomeIcon icon={faListCheck} className="me-2" />
Job Control
</Link>
</div>
</div>
</Panel>
<Panel padding={true}>
<h4 className="fw-bold mb-3">Legacy and Deprecated Pages</h4>
<div className="d-flex flex-column gap-3">
<Link
to="/moderation/job_stats"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faListCheck} className="me-2" />
Job Stats (Old TTS + W2L)
</Link>
<Link
to="/moderation/user/list"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faUsers} className="me-2" />
User List
</Link>
<Link
to={WebUrl.moderationTtsCategoryList()}
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faTags} className="me-2" />
Manage TTS Categories
</Link>
<Link
to="/moderation/approve/w2l_templates"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faClipboardCheck} className="me-2" />
Unapproved W2L Templates
</Link>
<Link
to="/moderation/voice_stats"
className="btn btn-secondary w-100"
>
<FontAwesomeIcon icon={faMicrophone} className="me-2" />
Voice Stats
</Link>
</div>
</Panel>
</div>
<div className="container pb-5">
<p>
More mod controls will be added in the future: user roles, activity
tracking, timed bans, account bans, etc.
</p>
</div>
</div>
<p className="mt-5 opacity-75">
More mod controls will be added in the future: user roles, activity
tracking, timed bans, account bans, etc.
</p>
</Container>
);
}
@@ -8,7 +8,7 @@ import {
} from "@storyteller/components/src/api/user/CreateAccount";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUser, faEnvelope, faKey } from "@fortawesome/free-solid-svg-icons";
import { Link, useHistory } from "react-router-dom";
import { Link, useHistory, useLocation } from "react-router-dom";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Analytics } from "../../../../common/Analytics";
@@ -35,6 +35,8 @@ interface Props {
function SignupPage(props: Props) {
let history = useHistory();
const domain = useDomainConfig();
let location = useLocation();
const queryParams = new URLSearchParams(location.search);
const parsedQueryString = queryString.parse(window.location.search);
@@ -264,7 +266,9 @@ function SignupPage(props: Props) {
return await BeginStripeCheckoutFlow(maybeInternalPlanKey);
}
let redirectUrl = WebUrl.pricingPageWithReferer("signup");
const redirectLink = queryParams.get("redirect");
let redirectUrl = redirectLink || WebUrl.pricingPageWithReferer("signup");
history.push(redirectUrl);
return true;