mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 19:41:32 +00:00
Merge pull request #708 from storytold/feaute/app-state-api
implemented AppState API, replaced old code
This commit is contained in:
17 files changed
+591
-444
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import MakeRequest from "../MakeRequest";
|
||||
import { UserDetailsLight } from "../_common/UserDetailsLight";
|
||||
|
||||
export interface UserLocale {
|
||||
full_language_tags: [string, string];
|
||||
language_codes: [string, string];
|
||||
}
|
||||
|
||||
export interface ServerAlert {
|
||||
maybe_category: string;
|
||||
maybe_message: string;
|
||||
}
|
||||
|
||||
export interface ActiveSubscription {
|
||||
namespace: string;
|
||||
product_slug: string;
|
||||
}
|
||||
|
||||
export interface PremiumStatus {
|
||||
active_subscriptions: ActiveSubscription[];
|
||||
has_free_premium: boolean;
|
||||
has_paid_premium: boolean;
|
||||
has_premium: boolean;
|
||||
maybe_loyalty_program: string | null;
|
||||
}
|
||||
|
||||
export type FeatureFlags =
|
||||
| "explore_media"
|
||||
| "studio"
|
||||
| "upload_3d"
|
||||
| "video_style_transfer";
|
||||
|
||||
export interface UserPermissions {
|
||||
feature_flags: FeatureFlags[];
|
||||
is_moderator: boolean;
|
||||
legacy_permission_flags: {
|
||||
can_approve_w2l_templates: boolean;
|
||||
can_ban_users: boolean;
|
||||
can_delete_other_users_tts_models: boolean;
|
||||
can_delete_other_users_tts_results: boolean;
|
||||
can_delete_other_users_w2l_results: boolean;
|
||||
can_delete_other_users_w2l_templates: boolean;
|
||||
can_delete_own_account: boolean;
|
||||
can_delete_own_tts_models: boolean;
|
||||
can_delete_own_tts_results: boolean;
|
||||
can_delete_own_w2l_results: boolean;
|
||||
can_delete_own_w2l_templates: boolean;
|
||||
can_delete_users: boolean;
|
||||
can_edit_other_users_profiles: boolean;
|
||||
can_edit_other_users_tts_models: boolean;
|
||||
can_edit_other_users_w2l_templates: boolean;
|
||||
can_upload_tts_models: boolean;
|
||||
can_upload_w2l_templates: boolean;
|
||||
can_use_tts: boolean;
|
||||
can_use_w2l: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetAppStateRequest {}
|
||||
|
||||
export interface GetAppStateResponse {
|
||||
is_banned: boolean;
|
||||
is_logged_in: boolean;
|
||||
locale: UserLocale;
|
||||
maybe_alert: ServerAlert | null;
|
||||
maybe_premium: PremiumStatus | null;
|
||||
maybe_user_info: UserDetailsLight | null;
|
||||
permissions: UserPermissions;
|
||||
refresh_interval_millis: number;
|
||||
server_info: {
|
||||
build_sha: string;
|
||||
build_sha_short: string;
|
||||
hostname: string;
|
||||
};
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export const GetAppState = MakeRequest<
|
||||
string,
|
||||
GetAppStateRequest,
|
||||
GetAppStateResponse,
|
||||
{}
|
||||
>({
|
||||
method: "GET",
|
||||
routingFunction: () => `/v1/app_state`,
|
||||
});
|
||||
@@ -11,26 +11,35 @@ const FAKEYOU_NAMESPACE = "fakeyou";
|
||||
export class SessionSubscriptionsWrapper {
|
||||
listActiveSubscriptionResponse?: ListActiveSubscriptionsSuccessResponse;
|
||||
|
||||
private constructor(listActiveSubscriptionsSuccessResponse?: ListActiveSubscriptionsSuccessResponse) {
|
||||
private constructor(
|
||||
listActiveSubscriptionsSuccessResponse?: ListActiveSubscriptionsSuccessResponse
|
||||
) {
|
||||
if (listActiveSubscriptionsSuccessResponse !== undefined) {
|
||||
this.listActiveSubscriptionResponse = listActiveSubscriptionsSuccessResponse;
|
||||
this.listActiveSubscriptionResponse =
|
||||
listActiveSubscriptionsSuccessResponse;
|
||||
}
|
||||
}
|
||||
|
||||
public static async lookupActiveSubscriptions() : Promise<SessionSubscriptionsWrapper> {
|
||||
public static async lookupActiveSubscriptions(): Promise<SessionSubscriptionsWrapper> {
|
||||
let response = await ListActiveSubscriptions();
|
||||
if (ListActiveSubscriptionsIsSuccess(response)) {
|
||||
return new SessionSubscriptionsWrapper(response);
|
||||
return SessionSubscriptionsWrapper.wrapResponse(response);
|
||||
} else {
|
||||
return SessionSubscriptionsWrapper.emptySubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
public static emptySubscriptions() : SessionSubscriptionsWrapper {
|
||||
public static emptySubscriptions(): SessionSubscriptionsWrapper {
|
||||
return new SessionSubscriptionsWrapper();
|
||||
}
|
||||
|
||||
public ttsMaximumLength() : number {
|
||||
public static wrapResponse(
|
||||
sessionStateResponse: ListActiveSubscriptionsSuccessResponse
|
||||
): SessionSubscriptionsWrapper {
|
||||
return new SessionSubscriptionsWrapper(sessionStateResponse);
|
||||
}
|
||||
|
||||
public ttsMaximumLength(): number {
|
||||
if (this.hasActiveEliteSubscription()) {
|
||||
return 4096;
|
||||
} else if (this.hasActiveProSubscription()) {
|
||||
@@ -43,37 +52,50 @@ export class SessionSubscriptionsWrapper {
|
||||
return 1024;
|
||||
}
|
||||
|
||||
public hasFreeOrPaidPremiumFeatures() : boolean {
|
||||
public hasFreeOrPaidPremiumFeatures(): boolean {
|
||||
return this.hasLoyaltyProgram() || this.hasPaidFeatures();
|
||||
}
|
||||
|
||||
public hasLoyaltyProgram() : boolean {
|
||||
public hasLoyaltyProgram(): boolean {
|
||||
return !!this.listActiveSubscriptionResponse?.maybe_loyalty_program;
|
||||
}
|
||||
|
||||
public hasPaidFeatures() : boolean {
|
||||
const subs = this.listActiveSubscriptionResponse?.active_subscriptions || [];
|
||||
public hasPaidFeatures(): boolean {
|
||||
const subs =
|
||||
this.listActiveSubscriptionResponse?.active_subscriptions || [];
|
||||
return subs.length > 0;
|
||||
}
|
||||
|
||||
public hasActivePlusSubscription() : boolean {
|
||||
let maybePlan = FakeYouFrontendEnvironment.getInstance().useProductionStripePlans() ?
|
||||
this.findActiveSubscription(FAKEYOU_NAMESPACE, "fakeyou_plus") :
|
||||
this.findActiveSubscription(FAKEYOU_NAMESPACE, "development_fakeyou_plus");
|
||||
public hasActivePlusSubscription(): boolean {
|
||||
let maybePlan =
|
||||
FakeYouFrontendEnvironment.getInstance().useProductionStripePlans()
|
||||
? this.findActiveSubscription(FAKEYOU_NAMESPACE, "fakeyou_plus")
|
||||
: this.findActiveSubscription(
|
||||
FAKEYOU_NAMESPACE,
|
||||
"development_fakeyou_plus"
|
||||
);
|
||||
return maybePlan !== undefined;
|
||||
}
|
||||
|
||||
public hasActiveProSubscription() : boolean {
|
||||
let maybePlan = FakeYouFrontendEnvironment.getInstance().useProductionStripePlans() ?
|
||||
this.findActiveSubscription(FAKEYOU_NAMESPACE, "fakeyou_pro") :
|
||||
this.findActiveSubscription(FAKEYOU_NAMESPACE, "development_fakeyou_pro");
|
||||
public hasActiveProSubscription(): boolean {
|
||||
let maybePlan =
|
||||
FakeYouFrontendEnvironment.getInstance().useProductionStripePlans()
|
||||
? this.findActiveSubscription(FAKEYOU_NAMESPACE, "fakeyou_pro")
|
||||
: this.findActiveSubscription(
|
||||
FAKEYOU_NAMESPACE,
|
||||
"development_fakeyou_pro"
|
||||
);
|
||||
return maybePlan !== undefined;
|
||||
}
|
||||
|
||||
public hasActiveEliteSubscription() : boolean {
|
||||
let maybePlan = FakeYouFrontendEnvironment.getInstance().useProductionStripePlans() ?
|
||||
this.findActiveSubscription(FAKEYOU_NAMESPACE, "fakeyou_elite") :
|
||||
this.findActiveSubscription(FAKEYOU_NAMESPACE, "development_fakeyou_elite");
|
||||
public hasActiveEliteSubscription(): boolean {
|
||||
let maybePlan =
|
||||
FakeYouFrontendEnvironment.getInstance().useProductionStripePlans()
|
||||
? this.findActiveSubscription(FAKEYOU_NAMESPACE, "fakeyou_elite")
|
||||
: this.findActiveSubscription(
|
||||
FAKEYOU_NAMESPACE,
|
||||
"development_fakeyou_elite"
|
||||
);
|
||||
return maybePlan !== undefined;
|
||||
}
|
||||
|
||||
@@ -95,7 +117,7 @@ export class SessionSubscriptionsWrapper {
|
||||
const subs =
|
||||
this.listActiveSubscriptionResponse?.active_subscriptions || [];
|
||||
return subs.find(
|
||||
(sub) => sub.namespace === namespace && sub.product_slug === product_slug
|
||||
sub => sub.namespace === namespace && sub.product_slug === product_slug
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,13 @@ import "./AppNew.scss";
|
||||
import "scss/custom-bootstrap.scss";
|
||||
|
||||
import React from "react";
|
||||
import { ApiConfig } from "@storyteller/components";
|
||||
import Cookies from "universal-cookie";
|
||||
import {
|
||||
DetectLocale,
|
||||
DetectLocaleIsOk,
|
||||
} from "@storyteller/components/src/api/locale/DetectLocale";
|
||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import PageContainer from "./v2/view/PageContainer";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { TtsInferenceJob } from "@storyteller/components/src/jobs/TtsInferenceJobs";
|
||||
import { W2lInferenceJob } from "@storyteller/components/src/jobs/W2lInferenceJobs";
|
||||
import { FAKEYOU_MERGED_TRANSLATIONS } from "./_i18n/FakeYouTranslations";
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import deepEqual from "deep-equal";
|
||||
import {
|
||||
AvailableLanguageKey,
|
||||
AVAILABLE_LANGUAGE_MAP,
|
||||
ENGLISH_LANGUAGE,
|
||||
} from "./_i18n/AvailableLanguageMap";
|
||||
import { VoiceConversionModelListItem } from "@storyteller/components/src/api/voice_conversion/ListVoiceConversionModels";
|
||||
import HttpBackend from "i18next-http-backend";
|
||||
|
||||
@@ -77,33 +62,7 @@ enum MigrationMode {
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
// Migration Mode
|
||||
// migrationMode: MigrationMode;
|
||||
|
||||
// Rollout of vocodes 2.0
|
||||
// enableAlpha: boolean;
|
||||
|
||||
sessionFetched: boolean;
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
|
||||
// Locale + show flash notice to Spanish speakers
|
||||
//localeLanguageCodes: string[],
|
||||
//localeFullLanguageTags: string[],
|
||||
// isShowingLanguageNotice: boolean;
|
||||
// displayLanguage: Language;
|
||||
// primaryLanguageCode: string;
|
||||
|
||||
// isShowingTwitchTtsNotice: boolean;
|
||||
// isShowingPleaseFollowNotice: boolean;
|
||||
|
||||
// An improved notice for "new" languages asking users to help.
|
||||
// isShowingBootstrapLanguageNotice: boolean;
|
||||
|
||||
// Current text entered
|
||||
textBuffer: string;
|
||||
|
||||
// voiceConversionModels: VoiceConversionModelListItem[];
|
||||
maybeSelectedVoiceConversionModel?: VoiceConversionModelListItem;
|
||||
}
|
||||
|
||||
@@ -113,11 +72,6 @@ class App extends React.Component<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
sessionFetched: false,
|
||||
sessionWrapper: SessionWrapper.emptySession(),
|
||||
sessionSubscriptionsWrapper:
|
||||
SessionSubscriptionsWrapper.emptySubscriptions(),
|
||||
|
||||
textBuffer: "",
|
||||
|
||||
maybeSelectedVoiceConversionModel: undefined,
|
||||
@@ -141,132 +95,6 @@ class App extends React.Component<Props, State> {
|
||||
require("./v2/view/_css/footer.scss");
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await this.queryLanguage();
|
||||
await this.querySession();
|
||||
await this.querySessionSubscriptions();
|
||||
|
||||
setInterval(async () => {
|
||||
// See warnings in the following methods when adding new methods
|
||||
// that affect global "state"
|
||||
await this.querySession();
|
||||
await this.querySessionSubscriptions();
|
||||
}, 60000);
|
||||
// TODO: Use websockets, this is dumb
|
||||
}
|
||||
|
||||
querySession = async () => {
|
||||
// WARNING: Making setState calls in this scope without checking existing
|
||||
// state can cause the whole site to refresh/worsen UX. Double check if
|
||||
// state needs to be set here, or if instead can be refreshed locally on
|
||||
// the page where the new "state" needed
|
||||
const sessionWrapper = await SessionWrapper.lookupSession();
|
||||
const username = sessionWrapper.getDisplayName();
|
||||
const cookies = new Cookies();
|
||||
|
||||
if (username !== undefined) {
|
||||
// Track only logged-in users (for now)
|
||||
PosthogClient.enablePosthog();
|
||||
PosthogClient.setUsername(username);
|
||||
cookies.set("logged_in_username", username, {
|
||||
path: "/",
|
||||
expires: new Date(Date.now() + 3 * 86400000),
|
||||
});
|
||||
window.dataLayer.push({
|
||||
user_id: username,
|
||||
});
|
||||
} else {
|
||||
cookies.remove("logged_in_username", { path: "/" });
|
||||
}
|
||||
|
||||
if (!deepEqual(sessionWrapper, this.state.sessionWrapper)) {
|
||||
this.setState({
|
||||
sessionWrapper: sessionWrapper,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
querySessionSubscriptions = async () => {
|
||||
// WARNING: Making setState calls in this scope without checking existing
|
||||
// state can cause the whole site to refresh/worsen UX. Double check if
|
||||
// state needs to be set here, or if instead can be refreshed locally on
|
||||
// the page where the new "state" needed
|
||||
|
||||
if (this.state.sessionFetched === false) {
|
||||
this.setState({ sessionFetched: true });
|
||||
}
|
||||
|
||||
const cookies = new Cookies();
|
||||
|
||||
const sessionSubscriptionsWrapper =
|
||||
await SessionSubscriptionsWrapper.lookupActiveSubscriptions();
|
||||
|
||||
const plan = sessionSubscriptionsWrapper.getActiveProductSlug();
|
||||
if (plan !== undefined) {
|
||||
cookies.set("logged_in_user_plan", plan, {
|
||||
path: "/",
|
||||
expires: new Date(Date.now() + 3 * 86400000),
|
||||
});
|
||||
} else {
|
||||
cookies.remove("logged_in_user_plan", { path: "/" });
|
||||
}
|
||||
|
||||
if (
|
||||
!deepEqual(
|
||||
sessionSubscriptionsWrapper,
|
||||
this.state.sessionSubscriptionsWrapper
|
||||
)
|
||||
) {
|
||||
this.setState({
|
||||
sessionSubscriptionsWrapper: sessionSubscriptionsWrapper,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
queryLanguage = async () => {
|
||||
let locale = await DetectLocale();
|
||||
if (DetectLocaleIsOk(locale)) {
|
||||
// NB: We treat the language preference as being the order in the array.
|
||||
// As of 2023-01-14, the backend does not handle quality values / q-weights,
|
||||
// so these may be slightly wrong. An adjustment to the server will fix this.
|
||||
|
||||
let preferredLanguage = ENGLISH_LANGUAGE;
|
||||
|
||||
for (let languageCode of locale.language_codes) {
|
||||
let maybeLanguage =
|
||||
AVAILABLE_LANGUAGE_MAP[languageCode as AvailableLanguageKey];
|
||||
|
||||
if (maybeLanguage !== undefined) {
|
||||
preferredLanguage = maybeLanguage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i18n.changeLanguage(preferredLanguage.languageCode);
|
||||
i18n2.changeLanguage(preferredLanguage.languageCode);
|
||||
}
|
||||
};
|
||||
|
||||
logoutSession = () => {
|
||||
const api = new ApiConfig();
|
||||
const endpointUrl = api.logout();
|
||||
|
||||
fetch(endpointUrl, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
.then(_raw_response => {
|
||||
this.querySession();
|
||||
this.querySessionSubscriptions();
|
||||
})
|
||||
.catch(e => {
|
||||
/* Ignore. */
|
||||
});
|
||||
};
|
||||
|
||||
setTextBuffer = (textBuffer: string) => {
|
||||
this.setState({ textBuffer: textBuffer });
|
||||
};
|
||||
@@ -280,36 +108,17 @@ class App extends React.Component<Props, State> {
|
||||
<BrowserRouter>
|
||||
<div id="main" className="bg-gradient">
|
||||
<div id="viewable">
|
||||
{/* This is the old vocodes1.0-compatible username and version switch
|
||||
<MigrationTopNav
|
||||
enableAlpha={this.state.enableAlpha}
|
||||
sessionWrapper={this.state.sessionWrapper}
|
||||
querySessionAction={this.querySession}
|
||||
/>
|
||||
*/}
|
||||
|
||||
<div className="migrationComponentWrapper">
|
||||
<CoreServicesProvider
|
||||
{...{
|
||||
querySession: this.querySession,
|
||||
querySubscriptions: this.querySessionSubscriptions,
|
||||
state: this.state,
|
||||
}}
|
||||
>
|
||||
<Switch>
|
||||
<Route path="/">
|
||||
<PageContainer
|
||||
textBuffer={this.state.textBuffer}
|
||||
setTextBuffer={this.setTextBuffer}
|
||||
clearTextBuffer={this.clearTextBuffer}
|
||||
maybeSelectedVoiceConversionModel={
|
||||
this.state.maybeSelectedVoiceConversionModel
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
|
||||
<FooterNav sessionWrapper={this.state.sessionWrapper} />
|
||||
<CoreServicesProvider>
|
||||
<PageContainer
|
||||
textBuffer={this.state.textBuffer}
|
||||
setTextBuffer={this.setTextBuffer}
|
||||
clearTextBuffer={this.clearTextBuffer}
|
||||
maybeSelectedVoiceConversionModel={
|
||||
this.state.maybeSelectedVoiceConversionModel
|
||||
}
|
||||
/>
|
||||
<FooterNav />
|
||||
</CoreServicesProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,52 @@
|
||||
import { useState } from "react";
|
||||
import { useChanger, useSession } from "hooks";
|
||||
import { useContext, useState } from "react";
|
||||
import { useChanger } from "hooks";
|
||||
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
|
||||
import { CreateSession, CreateSessionResponse } from "@storyteller/components/src/api/account/CreateSession";
|
||||
import {
|
||||
CreateSession,
|
||||
CreateSessionResponse,
|
||||
} from "@storyteller/components/src/api/account/CreateSession";
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
interface Props {
|
||||
onSuccess?: (x?:any) => any,
|
||||
status: FetchStatus,
|
||||
statusSet: (x: FetchStatus) => void,
|
||||
onSuccess?: (x?: any) => any;
|
||||
status: FetchStatus;
|
||||
statusSet: (x: FetchStatus) => void;
|
||||
}
|
||||
|
||||
export default function useLogin({ onSuccess, status, statusSet }: Props) {
|
||||
const [errorType,errorTypeSet] = useState("");
|
||||
const { querySession, querySubscriptions } = useSession();
|
||||
const [errorType, errorTypeSet] = useState("");
|
||||
const { queryAppState } = useContext(AppStateContext);
|
||||
const { allAreValid, setProps, state, update } = useChanger({
|
||||
password: { value: "" },
|
||||
usernameOrEmail: { value: "" }
|
||||
usernameOrEmail: { value: "" },
|
||||
});
|
||||
|
||||
const login = () => {
|
||||
errorTypeSet("");
|
||||
statusSet(FetchStatus.in_progress);
|
||||
// if (allAreValid()) {
|
||||
CreateSession("",{
|
||||
username_or_email: state.usernameOrEmail.value,
|
||||
password: state.password.value,
|
||||
}).then((res: CreateSessionResponse) => {
|
||||
if (!res.success && res.error_type) {
|
||||
statusSet(FetchStatus.error);
|
||||
errorTypeSet(res.error_type);
|
||||
} else if (res.success) {
|
||||
statusSet(FetchStatus.success);
|
||||
querySession();
|
||||
querySubscriptions();
|
||||
if (onSuccess) onSuccess(res);
|
||||
}
|
||||
});
|
||||
};
|
||||
CreateSession("", {
|
||||
username_or_email: state.usernameOrEmail.value,
|
||||
password: state.password.value,
|
||||
}).then((res: CreateSessionResponse) => {
|
||||
if (!res.success && res.error_type) {
|
||||
statusSet(FetchStatus.error);
|
||||
errorTypeSet(res.error_type);
|
||||
} else if (res.success) {
|
||||
statusSet(FetchStatus.success);
|
||||
queryAppState();
|
||||
if (onSuccess) onSuccess(res);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return { allAreValid, errorType, errorTypeSet, login, setProps, state, update };
|
||||
};
|
||||
return {
|
||||
allAreValid,
|
||||
errorType,
|
||||
errorTypeSet,
|
||||
login,
|
||||
setProps,
|
||||
state,
|
||||
update,
|
||||
};
|
||||
}
|
||||
@@ -1,23 +1,27 @@
|
||||
import { useChanger, useSession } from "hooks";
|
||||
import { useContext } from "react";
|
||||
import { useChanger } from "hooks";
|
||||
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
|
||||
import { CreateAccount, CreateAccountResponse } from "@storyteller/components/src/api/account/CreateAccount";
|
||||
import {
|
||||
CreateAccount,
|
||||
CreateAccountResponse,
|
||||
} from "@storyteller/components/src/api/account/CreateAccount";
|
||||
import { InputValidation, ValidatorCallbacks } from "common/InputValidation";
|
||||
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
enum EmailReasons {
|
||||
EmailTaken = "emailTaken",
|
||||
Invalid = "invalid",
|
||||
TooShort = "tooShort",
|
||||
BadInput = "badInput"
|
||||
BadInput = "badInput",
|
||||
}
|
||||
|
||||
enum PasswordReasons {
|
||||
TooShort = "tooShort"
|
||||
TooShort = "tooShort",
|
||||
}
|
||||
|
||||
enum PasswordConfirmReasons {
|
||||
PasswordMismatch = "passwordMismatch",
|
||||
TooShort = "tooShort"
|
||||
TooShort = "tooShort",
|
||||
}
|
||||
|
||||
enum UsernameReasons {
|
||||
@@ -26,14 +30,14 @@ enum UsernameReasons {
|
||||
IsReserved = "isReserved",
|
||||
IsTaken = "isTaken",
|
||||
TooLong = "tooLong",
|
||||
TooShort = "tooShort"
|
||||
TooShort = "tooShort",
|
||||
}
|
||||
|
||||
const emailErrors = {
|
||||
emailTaken: "Email is taken",
|
||||
invalid: "Email is invalid",
|
||||
tooShort: "Email is too short",
|
||||
badInput: "Bad email input, try another"
|
||||
badInput: "Bad email input, try another",
|
||||
};
|
||||
|
||||
const passwordErrors = {
|
||||
@@ -42,7 +46,7 @@ const passwordErrors = {
|
||||
|
||||
const passwordConfirmErrors = {
|
||||
passwordMismatch: "Passwords do not match",
|
||||
tooShort: "Password is too short"
|
||||
tooShort: "Password is too short",
|
||||
};
|
||||
|
||||
const usernameErrors = {
|
||||
@@ -51,42 +55,45 @@ const usernameErrors = {
|
||||
isReserved: "Username is reserved",
|
||||
isTaken: "Username is taken",
|
||||
tooLong: "Username is too long",
|
||||
tooShort: "Username is too short"
|
||||
tooShort: "Username is too short",
|
||||
};
|
||||
|
||||
const emailValidator = ({ inputValue }: ValidatorCallbacks) => {
|
||||
let reason = "";
|
||||
let validation = InputValidation.Neutral;
|
||||
|
||||
if (inputValue.length > 1) {
|
||||
if (inputValue.length < 3) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = EmailReasons.TooShort;
|
||||
} else if (!inputValue.includes("@")) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = EmailReasons.Invalid;
|
||||
} else {
|
||||
validation = InputValidation.Valid;
|
||||
}
|
||||
|
||||
if (inputValue.length > 1) {
|
||||
if (inputValue.length < 3) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = EmailReasons.TooShort;
|
||||
} else if (!inputValue.includes("@")) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = EmailReasons.Invalid;
|
||||
} else {
|
||||
validation = InputValidation.Valid;
|
||||
}
|
||||
}
|
||||
|
||||
return { value: inputValue, reason, validation };
|
||||
};
|
||||
|
||||
const passwordConfirmValidator = ({ inputValue, state }: ValidatorCallbacks) => {
|
||||
const passwordConfirmValidator = ({
|
||||
inputValue,
|
||||
state,
|
||||
}: ValidatorCallbacks) => {
|
||||
const password = state.password;
|
||||
let validation = InputValidation.Neutral;
|
||||
let reason = "";
|
||||
|
||||
if (inputValue.length > 1) {
|
||||
if (inputValue !== password.value) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = PasswordConfirmReasons.PasswordMismatch;
|
||||
} else {
|
||||
validation = InputValidation.Valid;
|
||||
reason = "";
|
||||
}
|
||||
if (inputValue.length > 1) {
|
||||
if (inputValue !== password.value) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = PasswordConfirmReasons.PasswordMismatch;
|
||||
} else {
|
||||
validation = InputValidation.Valid;
|
||||
reason = "";
|
||||
}
|
||||
}
|
||||
|
||||
return { value: inputValue, reason, validation };
|
||||
};
|
||||
@@ -108,14 +115,20 @@ const passwordValidator = ({ inputValue, state }: ValidatorCallbacks) => {
|
||||
|
||||
if (inputValue !== passwordConfirm.value) {
|
||||
confirmValidation = InputValidation.Invalid;
|
||||
confirmReason = PasswordConfirmReasons.PasswordMismatch
|
||||
confirmReason = PasswordConfirmReasons.PasswordMismatch;
|
||||
} else {
|
||||
confirmValidation = InputValidation.Valid;
|
||||
confirmReason = "";
|
||||
}
|
||||
}
|
||||
|
||||
const additional = { passwordConfirm: { ...passwordConfirm, reason: confirmReason, validation: confirmValidation } }
|
||||
const additional = {
|
||||
passwordConfirm: {
|
||||
...passwordConfirm,
|
||||
reason: confirmReason,
|
||||
validation: confirmValidation,
|
||||
},
|
||||
};
|
||||
|
||||
return { additional, value: inputValue, reason, validation };
|
||||
};
|
||||
@@ -123,14 +136,14 @@ const passwordValidator = ({ inputValue, state }: ValidatorCallbacks) => {
|
||||
const usernameValidator = ({ inputValue }: ValidatorCallbacks) => {
|
||||
let reason = "";
|
||||
let validation = InputValidation.Neutral;
|
||||
|
||||
|
||||
if (inputValue.length > 1) {
|
||||
if (inputValue.length < 3) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = UsernameReasons.TooShort
|
||||
reason = UsernameReasons.TooShort;
|
||||
} else if (inputValue.length > 15) {
|
||||
validation = InputValidation.Invalid;
|
||||
reason = UsernameReasons.TooLong
|
||||
reason = UsernameReasons.TooLong;
|
||||
} else {
|
||||
validation = InputValidation.Valid;
|
||||
}
|
||||
@@ -140,73 +153,98 @@ const usernameValidator = ({ inputValue }: ValidatorCallbacks) => {
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onSuccess?: (x?:any) => any,
|
||||
status: FetchStatus,
|
||||
statusSet: (x: FetchStatus) => void,
|
||||
onSuccess?: (x?: any) => any;
|
||||
status: FetchStatus;
|
||||
statusSet: (x: FetchStatus) => void;
|
||||
}
|
||||
|
||||
export default function useSignup({ onSuccess, status, statusSet }: Props) {
|
||||
const { querySession } = useSession();
|
||||
const { queryAppState } = useContext(AppStateContext);
|
||||
const { allAreValid, setProps, state, update } = useChanger({
|
||||
email: {
|
||||
errorText: emailErrors,
|
||||
validator: emailValidator,
|
||||
value: ""
|
||||
value: "",
|
||||
},
|
||||
password: {
|
||||
errorText: passwordErrors,
|
||||
validator: passwordValidator,
|
||||
value: ""
|
||||
value: "",
|
||||
},
|
||||
passwordConfirm: {
|
||||
errorText: passwordConfirmErrors,
|
||||
validator: passwordConfirmValidator,
|
||||
value: ""
|
||||
value: "",
|
||||
},
|
||||
username: {
|
||||
errorText: usernameErrors,
|
||||
validator: usernameValidator,
|
||||
value: ""
|
||||
value: "",
|
||||
},
|
||||
});
|
||||
|
||||
const signup = () => {
|
||||
statusSet(FetchStatus.in_progress);
|
||||
CreateAccount("",{
|
||||
CreateAccount("", {
|
||||
username: state.username.value,
|
||||
email_address: state.email.value,
|
||||
password: state.password.value,
|
||||
password_confirmation: state.passwordConfirm.value,
|
||||
}).then((res: CreateAccountResponse) => {
|
||||
if (res && res.error_fields && res.error_type) {
|
||||
statusSet(FetchStatus.error);
|
||||
statusSet(FetchStatus.error);
|
||||
if (res.error_fields.email_address) {
|
||||
switch (res.error_type) {
|
||||
case "BadInput": return update({ name: "email", reason: EmailReasons.BadInput, validation: InputValidation.Invalid });
|
||||
case "EmailTaken": return update({ name: "email", reason: EmailReasons.EmailTaken, validation: InputValidation.Invalid });
|
||||
case "BadInput":
|
||||
return update({
|
||||
name: "email",
|
||||
reason: EmailReasons.BadInput,
|
||||
validation: InputValidation.Invalid,
|
||||
});
|
||||
case "EmailTaken":
|
||||
return update({
|
||||
name: "email",
|
||||
reason: EmailReasons.EmailTaken,
|
||||
validation: InputValidation.Invalid,
|
||||
});
|
||||
}
|
||||
return;
|
||||
} else if (res.error_fields.password) {
|
||||
switch (res.error_type) {
|
||||
case "TooShort": update({ name: "password", reason: PasswordReasons.TooShort, validation: InputValidation.Invalid });
|
||||
}
|
||||
case "TooShort":
|
||||
update({
|
||||
name: "password",
|
||||
reason: PasswordReasons.TooShort,
|
||||
validation: InputValidation.Invalid,
|
||||
});
|
||||
}
|
||||
} else if (res.error_fields?.username) {
|
||||
let updateUsername = (reason: UsernameReasons) => update({ reason, name: "username", validation: InputValidation.Invalid });
|
||||
let updateUsername = (reason: UsernameReasons) =>
|
||||
update({
|
||||
reason,
|
||||
name: "username",
|
||||
validation: InputValidation.Invalid,
|
||||
});
|
||||
switch (res.error_fields.username) {
|
||||
case "invalid username characters": return updateUsername(UsernameReasons.InvalidCharacters);
|
||||
case "username is too long": return updateUsername(UsernameReasons.TooLong);
|
||||
case "username is taken": return updateUsername(UsernameReasons.IsTaken);
|
||||
case "username is reserved": return updateUsername(UsernameReasons.IsReserved);
|
||||
case "username contains slurs": return updateUsername(UsernameReasons.ContainsSlurs);
|
||||
}
|
||||
case "invalid username characters":
|
||||
return updateUsername(UsernameReasons.InvalidCharacters);
|
||||
case "username is too long":
|
||||
return updateUsername(UsernameReasons.TooLong);
|
||||
case "username is taken":
|
||||
return updateUsername(UsernameReasons.IsTaken);
|
||||
case "username is reserved":
|
||||
return updateUsername(UsernameReasons.IsReserved);
|
||||
case "username contains slurs":
|
||||
return updateUsername(UsernameReasons.ContainsSlurs);
|
||||
}
|
||||
}
|
||||
} else if (res.success) {
|
||||
statusSet(FetchStatus.success);
|
||||
querySession();
|
||||
queryAppState();
|
||||
if (onSuccess) onSuccess(res);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return { allAreValid, setProps, signup, state, update };
|
||||
};
|
||||
}
|
||||
@@ -37,8 +37,7 @@ import { GetDiscordLink } from "@storyteller/components/src/env/GetDiscordLink";
|
||||
import { WebUrl } from "common/WebUrl";
|
||||
|
||||
export default function TopNav() {
|
||||
const { querySession, querySubscriptions, sessionWrapper, user } =
|
||||
useSession();
|
||||
const { queryAppState, sessionWrapper, user } = useSession();
|
||||
const domain = useDomainConfig();
|
||||
let history = useHistory();
|
||||
const [isMobileSearchBarVisible, setIsMobileSearchBarVisible] =
|
||||
@@ -141,8 +140,7 @@ export default function TopNav() {
|
||||
|
||||
const logoutHandler = async () => {
|
||||
await Logout();
|
||||
querySession();
|
||||
querySubscriptions();
|
||||
queryAppState();
|
||||
history.push("/");
|
||||
};
|
||||
|
||||
@@ -152,8 +150,8 @@ export default function TopNav() {
|
||||
let displayName = user.display_name;
|
||||
let username = user.username;
|
||||
let emailHash = user.email_gravatar_hash;
|
||||
let avatarIndex = user.core_info.default_avatar.image_index;
|
||||
let backgroundColorIndex = user.core_info.default_avatar.color_index;
|
||||
let avatarIndex = user.core_info?.default_avatar?.image_index || 0;
|
||||
let backgroundColorIndex = user.core_info?.default_avatar?.color_index || 0;
|
||||
|
||||
profileDropdown = (
|
||||
<ProfileDropdown
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import React, { createContext, useEffect, useState } from "react";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import Cookies from "universal-cookie";
|
||||
import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient";
|
||||
import deepEqual from "deep-equal";
|
||||
import { i18n2 } from "App";
|
||||
import {
|
||||
AvailableLanguageKey,
|
||||
AVAILABLE_LANGUAGE_MAP,
|
||||
ENGLISH_LANGUAGE,
|
||||
} from "_i18n/AvailableLanguageMap";
|
||||
|
||||
import {
|
||||
GetAppState,
|
||||
GetAppStateResponse,
|
||||
} from "@storyteller/components/src/api/app_state/GetAppState";
|
||||
import { useInterval } from "hooks";
|
||||
|
||||
interface AppStatusProviderProps {
|
||||
children: any;
|
||||
debug?: string;
|
||||
}
|
||||
|
||||
interface CurrentAppState {
|
||||
appState: GetAppStateResponse;
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptions: SessionSubscriptionsWrapper;
|
||||
}
|
||||
|
||||
interface AppStateUtilities extends CurrentAppState {
|
||||
queryAppState: () => void;
|
||||
}
|
||||
|
||||
export const emptyAppState: GetAppStateResponse = {
|
||||
is_banned: false,
|
||||
is_logged_in: false,
|
||||
locale: {
|
||||
full_language_tags: ["en-US", "en"],
|
||||
language_codes: ["en", "en"],
|
||||
},
|
||||
maybe_alert: null,
|
||||
maybe_premium: null,
|
||||
maybe_user_info: null,
|
||||
permissions: {
|
||||
feature_flags: [],
|
||||
is_moderator: false,
|
||||
legacy_permission_flags: {
|
||||
can_approve_w2l_templates: false,
|
||||
can_ban_users: false,
|
||||
can_delete_other_users_tts_models: false,
|
||||
can_delete_other_users_tts_results: false,
|
||||
can_delete_other_users_w2l_results: false,
|
||||
can_delete_other_users_w2l_templates: false,
|
||||
can_delete_own_account: false,
|
||||
can_delete_own_tts_models: false,
|
||||
can_delete_own_tts_results: false,
|
||||
can_delete_own_w2l_results: false,
|
||||
can_delete_own_w2l_templates: false,
|
||||
can_delete_users: false,
|
||||
can_edit_other_users_profiles: false,
|
||||
can_edit_other_users_tts_models: false,
|
||||
can_edit_other_users_w2l_templates: false,
|
||||
can_upload_tts_models: false,
|
||||
can_upload_w2l_templates: false,
|
||||
can_use_tts: false,
|
||||
can_use_w2l: false,
|
||||
},
|
||||
},
|
||||
server_info: {
|
||||
build_sha: "",
|
||||
build_sha_short: "",
|
||||
hostname: "",
|
||||
},
|
||||
success: false,
|
||||
refresh_interval_millis: 60000,
|
||||
};
|
||||
|
||||
const emptyState: CurrentAppState = {
|
||||
appState: emptyAppState,
|
||||
sessionWrapper: SessionWrapper.emptySession(),
|
||||
sessionSubscriptions: SessionSubscriptionsWrapper.emptySubscriptions(),
|
||||
};
|
||||
|
||||
export const AppStateContext = createContext<AppStateUtilities>({
|
||||
...emptyState,
|
||||
queryAppState: () => {},
|
||||
});
|
||||
|
||||
export default function AppStateProvider({
|
||||
children,
|
||||
debug,
|
||||
}: AppStatusProviderProps) {
|
||||
const [stateAndWrappers, stateAndWrappersSet] =
|
||||
useState<CurrentAppState>(emptyState);
|
||||
const [keepAlive, keepAliveSet] = useState(true);
|
||||
|
||||
const onTick = () => {
|
||||
if (debug) {
|
||||
console.log("💾 App State Loop", stateAndWrappers);
|
||||
}
|
||||
GetAppState("", {}).then((res: GetAppStateResponse) => {
|
||||
if (res.success) {
|
||||
if (debug) {
|
||||
console.log("App State fetch success ✅", res);
|
||||
}
|
||||
|
||||
// compare response and state without server info as server host can change
|
||||
// that way we only write state when there are meaningful state updates
|
||||
|
||||
const { server_info: unusedEventInfo, ...oldState } =
|
||||
stateAndWrappers.appState;
|
||||
const { server_info: unusedResInfo, ...updatedState } = res;
|
||||
const isUpdated = !deepEqual(oldState, updatedState);
|
||||
|
||||
if (isUpdated) {
|
||||
const newSessionWrapper = SessionWrapper.wrapResponse({
|
||||
success: true,
|
||||
logged_in: res.is_logged_in,
|
||||
...(res.is_logged_in && res.maybe_user_info && res.permissions
|
||||
? {
|
||||
user: {
|
||||
user_token: res.maybe_user_info.user_token,
|
||||
username: res.maybe_user_info.username,
|
||||
display_name: res.maybe_user_info.display_name,
|
||||
email_gravatar_hash: res.maybe_user_info.gravatar_hash,
|
||||
can_access_studio:
|
||||
res.permissions.feature_flags.includes("studio"),
|
||||
maybe_feature_flags: res.permissions.feature_flags,
|
||||
...res.permissions.legacy_permission_flags,
|
||||
},
|
||||
}
|
||||
: { user: null }),
|
||||
});
|
||||
|
||||
const newSubsciptions = SessionSubscriptionsWrapper.wrapResponse({
|
||||
success: true,
|
||||
active_subscriptions: res.maybe_premium
|
||||
? res.maybe_premium.active_subscriptions
|
||||
: [],
|
||||
...(res.maybe_premium?.maybe_loyalty_program
|
||||
? {
|
||||
maybe_loyalty_program:
|
||||
res.maybe_premium.maybe_loyalty_program,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
const cookies = new Cookies();
|
||||
|
||||
stateAndWrappersSet({
|
||||
appState: res,
|
||||
sessionWrapper: newSessionWrapper,
|
||||
sessionSubscriptions: newSubsciptions,
|
||||
});
|
||||
|
||||
keepAliveSet(false);
|
||||
|
||||
if (res.maybe_user_info) {
|
||||
if (debug) {
|
||||
console.log("🌻 Logged in");
|
||||
}
|
||||
PosthogClient.enablePosthog();
|
||||
PosthogClient.setUsername(res.maybe_user_info.username);
|
||||
cookies.set("logged_in_username", res.maybe_user_info.username, {
|
||||
path: "/",
|
||||
expires: new Date(Date.now() + 3 * 86400000),
|
||||
});
|
||||
// @ts-ignore
|
||||
window.dataLayer.push({
|
||||
user_id: res.maybe_user_info.username,
|
||||
});
|
||||
} else {
|
||||
if (debug) {
|
||||
console.log("🥀 Logged out", res);
|
||||
}
|
||||
cookies.remove("logged_in_username", { path: "/" });
|
||||
}
|
||||
|
||||
// this is just a copy/paste of code from App.tsx. I'll probably rewrite it whem I understand it more -V
|
||||
|
||||
let preferredLanguage = ENGLISH_LANGUAGE;
|
||||
|
||||
for (let languageCode of res.locale.language_codes) {
|
||||
let maybeLanguage =
|
||||
AVAILABLE_LANGUAGE_MAP[languageCode as AvailableLanguageKey];
|
||||
|
||||
if (maybeLanguage !== undefined) {
|
||||
preferredLanguage = maybeLanguage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i18n2.changeLanguage(preferredLanguage.languageCode);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const queryAppState = () => {
|
||||
if (debug) {
|
||||
console.log("❓ Manual App State query");
|
||||
}
|
||||
onTick();
|
||||
};
|
||||
|
||||
useInterval({
|
||||
debug,
|
||||
interval: stateAndWrappers.appState.refresh_interval_millis,
|
||||
onTick,
|
||||
locked: !keepAlive,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!keepAlive) {
|
||||
keepAliveSet(true);
|
||||
}
|
||||
}, [keepAlive]);
|
||||
|
||||
return (
|
||||
<AppStateContext.Provider
|
||||
{...{
|
||||
value: { ...stateAndWrappers, queryAppState },
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppStateContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default, AppStateContext, emptyAppState } from "./AppStateProvider";
|
||||
+16
-29
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AppStateProvider,
|
||||
InferenceJobsProvider,
|
||||
ModalProvider,
|
||||
NotificationProvider,
|
||||
@@ -10,37 +11,23 @@ import ServerStatusChecker from "./ServerStatusChecker";
|
||||
|
||||
interface Props {
|
||||
children?: any;
|
||||
querySession: any;
|
||||
querySubscriptions: any;
|
||||
state: any;
|
||||
}
|
||||
|
||||
export default function CoreServicesProvider({
|
||||
children,
|
||||
querySession,
|
||||
querySubscriptions,
|
||||
state,
|
||||
}: Props) {
|
||||
const sessionProps = {
|
||||
querySession: querySession,
|
||||
querySubscriptions,
|
||||
sessionSubscriptions: state.sessionSubscriptionsWrapper,
|
||||
sessionWrapper: state.sessionWrapper,
|
||||
sessionFetched: state.sessionFetched,
|
||||
};
|
||||
|
||||
export default function CoreServicesProvider({ children }: Props) {
|
||||
return (
|
||||
<SessionProvider {...sessionProps}>
|
||||
<InferenceJobsProvider>
|
||||
<NotificationProvider>
|
||||
<ServerStatusChecker />
|
||||
<ModalProvider>
|
||||
<SignUpQuestionnaireProvider>
|
||||
{children}
|
||||
</SignUpQuestionnaireProvider>
|
||||
</ModalProvider>
|
||||
</NotificationProvider>
|
||||
</InferenceJobsProvider>
|
||||
</SessionProvider>
|
||||
<AppStateProvider>
|
||||
<SessionProvider>
|
||||
<InferenceJobsProvider>
|
||||
<NotificationProvider>
|
||||
<ServerStatusChecker />
|
||||
<ModalProvider>
|
||||
<SignUpQuestionnaireProvider>
|
||||
{children}
|
||||
</SignUpQuestionnaireProvider>
|
||||
</ModalProvider>
|
||||
</NotificationProvider>
|
||||
</InferenceJobsProvider>
|
||||
</SessionProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
}
|
||||
+18
-20
@@ -1,4 +1,4 @@
|
||||
import React, { createContext } from "react";
|
||||
import React, { createContext, useContext } from "react";
|
||||
import ModalLayer from "components/providers/ModalProvider/ModalLayer";
|
||||
import { ModalConfig, useModalState } from "hooks";
|
||||
import AccountModal from "components/layout/AccountModal";
|
||||
@@ -7,6 +7,11 @@ import { StudioRolloutHostnameAllowed } from "@storyteller/components/src/utils/
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { StyleVideoNotAvailable } from "v2/view/_common/StyleVideoNotAvailable";
|
||||
import { GetAppStateResponse } from "@storyteller/components/src/api/app_state/GetAppState";
|
||||
import {
|
||||
AppStateContext,
|
||||
emptyAppState,
|
||||
} from "components/providers/AppStateProvider";
|
||||
|
||||
export interface AccountModalMessages {
|
||||
loginMessage?: string;
|
||||
@@ -19,11 +24,11 @@ export interface AccountModalEvents {
|
||||
}
|
||||
|
||||
export interface SessionUtilities {
|
||||
querySession: () => void;
|
||||
querySubscriptions: () => void;
|
||||
queryAppState: () => void;
|
||||
}
|
||||
|
||||
interface SessionContextType extends SessionUtilities {
|
||||
appState: GetAppStateResponse;
|
||||
canAccessStudio: () => boolean;
|
||||
canEditTtsModel: (creatorUserToken: string) => boolean;
|
||||
canEditMediaFile: (creatorUserToken?: string) => boolean;
|
||||
@@ -46,11 +51,8 @@ interface SessionContextType extends SessionUtilities {
|
||||
userTokenMatch: (token: string) => boolean;
|
||||
}
|
||||
|
||||
interface SessionProviderProps extends SessionUtilities {
|
||||
interface SessionProviderProps {
|
||||
children?: any;
|
||||
sessionFetched: boolean;
|
||||
sessionSubscriptions: SessionSubscriptionsWrapper;
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
// Functions are initially No-ops/dummies so that they are never undefined and never have to be called conditionally.
|
||||
@@ -60,6 +62,7 @@ interface SessionProviderProps extends SessionUtilities {
|
||||
// const thingy = new SessionWrapper()
|
||||
|
||||
export const SessionContext = createContext<SessionContextType>({
|
||||
appState: emptyAppState,
|
||||
canAccessStudio: () => false,
|
||||
canEditTtsModel: () => false,
|
||||
canEditMediaFile: () => false,
|
||||
@@ -74,19 +77,14 @@ export const SessionContext = createContext<SessionContextType>({
|
||||
open: () => {},
|
||||
},
|
||||
userTokenMatch: () => false,
|
||||
querySession: () => {},
|
||||
querySubscriptions: () => {},
|
||||
queryAppState: () => {},
|
||||
sessionWrapper: SessionWrapper.emptySession(),
|
||||
});
|
||||
|
||||
export default function SessionProvider({
|
||||
children,
|
||||
querySession,
|
||||
querySubscriptions,
|
||||
sessionFetched,
|
||||
sessionSubscriptions,
|
||||
sessionWrapper,
|
||||
}: SessionProviderProps) {
|
||||
export default function SessionProvider({ children }: SessionProviderProps) {
|
||||
const { appState, sessionSubscriptions, sessionWrapper, queryAppState } =
|
||||
useContext(AppStateContext);
|
||||
|
||||
const sessionResponse = sessionWrapper?.sessionStateResponse || {
|
||||
logged_in: false,
|
||||
user: null,
|
||||
@@ -143,6 +141,7 @@ export default function SessionProvider({
|
||||
<SessionContext.Provider
|
||||
{...{
|
||||
value: {
|
||||
appState,
|
||||
canAccessStudio,
|
||||
canEditTtsModel,
|
||||
canEditMediaFile,
|
||||
@@ -150,9 +149,8 @@ export default function SessionProvider({
|
||||
loggedInOrModal,
|
||||
loggedIn,
|
||||
modal,
|
||||
querySession,
|
||||
querySubscriptions,
|
||||
sessionFetched,
|
||||
queryAppState,
|
||||
sessionFetched: appState.success,
|
||||
sessionSubscriptions,
|
||||
sessionWrapper,
|
||||
studioAccessCheck,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AppStateProvider from "./AppStateProvider";
|
||||
import CoreServicesProvider from "./CoreServicesProvider";
|
||||
import InferenceJobsProvider, {
|
||||
InferenceJobsContext,
|
||||
@@ -8,6 +9,7 @@ import SessionProvider from "./SessionProvider";
|
||||
import SignUpQuestionnaireProvider from "./SignUpQuestionnaireProvider";
|
||||
|
||||
export {
|
||||
AppStateProvider,
|
||||
CoreServicesProvider,
|
||||
InferenceJobsContext,
|
||||
InferenceJobsProvider,
|
||||
|
||||
@@ -1,44 +1,35 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { StatusAlertCheck, StatusAlertCheckResponse } from "@storyteller/components/src/api/server/StatusAlertCheck";
|
||||
import { useInterval, useNotifications } from "hooks";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useNotifications } from "hooks";
|
||||
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
export default function useStatusPoll() {
|
||||
const [serverStatus, serverStatusSet] = useState<StatusAlertCheckResponse>({});
|
||||
const [initialized,initializedSet] = useState(false);
|
||||
const [downAlerted,downAlertedSet] = useState(false);
|
||||
const [upAlerted,upAlertedSet] = useState(false);
|
||||
const [downAlerted, downAlertedSet] = useState(false);
|
||||
const [upAlerted, upAlertedSet] = useState(false);
|
||||
const notifications = useNotifications();
|
||||
const defaultInt = 60000;
|
||||
const interval = Math.max(defaultInt, serverStatus.refresh_interval_millis || defaultInt);
|
||||
|
||||
const onTick = () => {
|
||||
StatusAlertCheck("",{})
|
||||
.then((res: StatusAlertCheckResponse) => {
|
||||
serverStatusSet(res);
|
||||
});
|
||||
};
|
||||
const {
|
||||
appState: { maybe_alert },
|
||||
} = useContext(AppStateContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
initializedSet(true);
|
||||
onTick();
|
||||
}
|
||||
if (serverStatus.maybe_alert && !downAlerted) {
|
||||
if (maybe_alert && !downAlerted) {
|
||||
downAlertedSet(true);
|
||||
upAlertedSet(false);
|
||||
notifications.create({
|
||||
autoRemove: false,
|
||||
title: "Server down for maintenence",
|
||||
content: "Please check back shortly"
|
||||
content: "Please check back shortly",
|
||||
});
|
||||
} else if (!serverStatus.maybe_alert && downAlerted && !upAlerted) {
|
||||
} else if (!maybe_alert && downAlerted && !upAlerted) {
|
||||
upAlertedSet(true);
|
||||
downAlertedSet(false);
|
||||
notifications.create({ autoRemove: false, title: "Server is back online", });
|
||||
notifications.create({
|
||||
autoRemove: false,
|
||||
title: "Server is back online",
|
||||
});
|
||||
}
|
||||
},[downAlerted,initialized,notifications,serverStatus,upAlerted]);
|
||||
}, [downAlerted, notifications, maybe_alert, upAlerted]);
|
||||
|
||||
useInterval({ interval, onTick, locked: !initialized, });
|
||||
|
||||
return serverStatus;
|
||||
};
|
||||
return maybe_alert;
|
||||
}
|
||||
@@ -92,8 +92,8 @@ function CreateCommentForm(props: Props) {
|
||||
let gravatar = <span />;
|
||||
let username = user?.username;
|
||||
let emailHash = user?.email_gravatar_hash;
|
||||
let avatarIndex = user?.core_info.default_avatar.image_index;
|
||||
let backgroundColorIndex = user?.core_info.default_avatar.color_index;
|
||||
let avatarIndex = user?.core_info?.default_avatar.image_index || 0;
|
||||
let backgroundColorIndex = user?.core_info?.default_avatar.color_index || 0;
|
||||
|
||||
if (gravatarHash !== undefined) {
|
||||
gravatar = (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { GitSha } from "@storyteller/components/src/elements/GitSha";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { ModerationIcon } from "../_icons/ModerationIcon";
|
||||
import { WebUrl } from "../../../common/WebUrl";
|
||||
@@ -13,39 +12,20 @@ import {
|
||||
faRedditAlien,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
|
||||
import {
|
||||
GetServerInfo,
|
||||
GetServerInfoIsOk,
|
||||
GetServerInfoSuccessResponse,
|
||||
} from "@storyteller/components/src/api/server/GetServerInfo";
|
||||
import { useLocalize } from "hooks";
|
||||
import { Container } from "components/common";
|
||||
import { useDomainConfig } from "context/DomainConfigContext";
|
||||
import { GetDiscordLink } from "@storyteller/components/src/env/GetDiscordLink";
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
function FooterNav(props: Props) {
|
||||
function FooterNav() {
|
||||
const {
|
||||
appState: { server_info },
|
||||
sessionWrapper,
|
||||
} = useContext(AppStateContext);
|
||||
const domain = useDomainConfig();
|
||||
const { t } = useLocalize("Footer");
|
||||
const [serverInfo, setServerInfo] = useState<
|
||||
GetServerInfoSuccessResponse | undefined
|
||||
>(undefined);
|
||||
const location = useLocation();
|
||||
|
||||
const getServerInfo = useCallback(async () => {
|
||||
const response = await GetServerInfo();
|
||||
if (GetServerInfoIsOk(response)) {
|
||||
setServerInfo(response);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getServerInfo();
|
||||
}, [getServerInfo]);
|
||||
|
||||
//let myDataLink = WebUrl.signupPage();
|
||||
|
||||
//if (props.sessionWrapper.isLoggedIn()) {
|
||||
@@ -55,7 +35,7 @@ function FooterNav(props: Props) {
|
||||
|
||||
let moderationLink = <span />;
|
||||
|
||||
if (props.sessionWrapper.canBanUsers()) {
|
||||
if (sessionWrapper.canBanUsers()) {
|
||||
moderationLink = (
|
||||
<div className="mb-4 mb-lg-0 me-0 me-lg-4">
|
||||
<Link to={WebUrl.moderationMain()}>
|
||||
@@ -69,15 +49,13 @@ function FooterNav(props: Props) {
|
||||
let serverGitSha = <></>;
|
||||
|
||||
if (
|
||||
serverInfo !== undefined &&
|
||||
!!serverInfo.server_build_sha &&
|
||||
serverInfo.server_build_sha !== "undefined"
|
||||
server_info !== undefined &&
|
||||
!!server_info.build_sha_short &&
|
||||
server_info.build_sha_short !== "undefined"
|
||||
) {
|
||||
serverGitSha = (
|
||||
<div className="d-flex flex-column flex-lg-row align-items-center">
|
||||
<div className="git-sha">
|
||||
API: {serverInfo.server_build_sha.substring(0, 8)}
|
||||
</div>
|
||||
<div className="git-sha">API: {server_info.build_sha_short}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import {
|
||||
CreateSession,
|
||||
@@ -18,7 +18,7 @@ import Panel from "components/common/Panel";
|
||||
import { useDomainConfig } from "context/DomainConfigContext";
|
||||
import ScrollingSceneCarousel from "../landing/storyteller/PostlaunchLanding/ScrollingSceneCarousel";
|
||||
import { InjectScript } from "common/InjectScript";
|
||||
import { useSession } from "hooks";
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
// NB: Google Sign In requires a global javascript function
|
||||
declare global {
|
||||
@@ -28,7 +28,7 @@ declare global {
|
||||
function LoginPage() {
|
||||
let history = useHistory();
|
||||
const domain = useDomainConfig();
|
||||
const { querySession, querySubscriptions, sessionWrapper } = useSession();
|
||||
const { sessionWrapper, queryAppState } = useContext(AppStateContext);
|
||||
const [password, setPassword] = useState("");
|
||||
const [usernameOrEmail, setUsernameOrEmail] = useState("");
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
@@ -80,8 +80,7 @@ function LoginPage() {
|
||||
if (CreateSessionIsError(response)) {
|
||||
setErrorMessage(response.error_message);
|
||||
} else if (CreateSessionIsSuccess(response)) {
|
||||
querySession();
|
||||
querySubscriptions();
|
||||
queryAppState();
|
||||
Analytics.accountLoginSuccess();
|
||||
history.push(redirectUrl);
|
||||
}
|
||||
|
||||
+4
-5
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
|
||||
import Container from "components/common/Container";
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
RedeemResetPassword,
|
||||
RedeemResetPasswordIsSuccess,
|
||||
} from "@storyteller/components/src/api/user/RedeemResetPassword";
|
||||
import { useSession } from "hooks";
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
// TODO(bt,2023-11-12): Localize error messages
|
||||
const ERR_CODE_NOT_SET = "password reset code is not set";
|
||||
@@ -23,7 +23,7 @@ const ERR_BACKEND =
|
||||
|
||||
function PasswordResetVerificationPage() {
|
||||
const history = useHistory();
|
||||
const { querySession, querySubscriptions, sessionWrapper } = useSession();
|
||||
const { sessionWrapper, queryAppState } = useContext(AppStateContext);
|
||||
|
||||
usePrefixedDocumentTitle("Password Reset Verification");
|
||||
|
||||
@@ -130,8 +130,7 @@ function PasswordResetVerificationPage() {
|
||||
|
||||
if (RedeemResetPasswordIsSuccess(response)) {
|
||||
setBackendError(undefined);
|
||||
querySession();
|
||||
querySubscriptions();
|
||||
queryAppState();
|
||||
history.push("/");
|
||||
} else {
|
||||
setBackendError(ERR_BACKEND);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { t } from "i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
import {
|
||||
@@ -19,7 +19,7 @@ import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClie
|
||||
import Panel from "components/common/Panel";
|
||||
import { useDomainConfig } from "context/DomainConfigContext";
|
||||
import ScrollingSceneCarousel from "../landing/storyteller/PostlaunchLanding/ScrollingSceneCarousel";
|
||||
import { useSession } from "hooks";
|
||||
import { AppStateContext } from "components/providers/AppStateProvider";
|
||||
|
||||
enum FieldTriState {
|
||||
EMPTY_FALSE,
|
||||
@@ -31,7 +31,7 @@ function SignupPage() {
|
||||
let history = useHistory();
|
||||
const domain = useDomainConfig();
|
||||
let location = useLocation();
|
||||
const { querySession, querySubscriptions, sessionWrapper } = useSession();
|
||||
const { sessionWrapper, queryAppState } = useContext(AppStateContext);
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
|
||||
const parsedQueryString = queryString.parse(window.location.search);
|
||||
@@ -242,8 +242,7 @@ function SignupPage() {
|
||||
setUsernameInvalidReason(reason);
|
||||
}
|
||||
} else if (CreateAccountIsSuccess(response)) {
|
||||
querySession();
|
||||
querySubscriptions();
|
||||
queryAppState();
|
||||
|
||||
Analytics.accountSignupComplete();
|
||||
|
||||
|
||||
Reference in new issue
Block a user