diff --git a/_redirects b/_redirects index e768dd163a..3d3f888dee 100644 --- a/_redirects +++ b/_redirects @@ -1,6 +1,8 @@ # We can't redirect everything until legacy vocodes is dead /about* /index.html 200 /category* /index.html 200 +/checkout_cancel* /index.html 200 +/checkout_success* /index.html 200 /clone* /index.html 200 /coming-soon* /index.html 200 /contribute* /index.html 200 @@ -14,6 +16,7 @@ /old* /index.html 200 /patrons* /index.html 200 /pending* /index.html 200 +/portal_success* /index.html 200 /profile* /index.html 200 /signup* /index.html 200 /stream* /index.html 200 diff --git a/packages/components/src/api/ApiConfig.ts b/packages/components/src/api/ApiConfig.ts index 141d6dff0c..17261e8d83 100644 --- a/packages/components/src/api/ApiConfig.ts +++ b/packages/components/src/api/ApiConfig.ts @@ -461,6 +461,22 @@ class ApiConfig { //return `wss://obs.storyteller.io/obs/${twitchUsername}`; } + // =============== Premium =============== + + listActiveSubscriptions(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/v1/billing/active_subscriptions`; + } + + createStripeCheckoutRedirect(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/v1/stripe/checkout/create_redirect`; + } + + createStripePortalRedirect(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/v1/stripe/portal/create_redirect`; + } + + // =============== Helper =============== + private getScheme(): string { return this.useSsl ? "https" : "http"; } diff --git a/packages/components/src/api/premium/CreateStripeCheckoutRedirect.ts b/packages/components/src/api/premium/CreateStripeCheckoutRedirect.ts new file mode 100644 index 0000000000..83b67234e3 --- /dev/null +++ b/packages/components/src/api/premium/CreateStripeCheckoutRedirect.ts @@ -0,0 +1,58 @@ +import { ApiConfig } from "@storyteller/components"; + +export interface CreateStripeCheckoutRedirectRequest { + internal_plan_key: string, +} + +export interface CreateStripeCheckoutRedirectSuccessResponse { + success: boolean, + stripe_checkout_redirect_url: string, +} + +export interface CreateStripeCheckoutRedirectErrorResponse { + success: boolean, +} + +type CreateStripeCheckoutRedirectResponse = CreateStripeCheckoutRedirectSuccessResponse | CreateStripeCheckoutRedirectErrorResponse; + +export function CreateStripeCheckoutRedirectIsSuccess(response: CreateStripeCheckoutRedirectResponse): response is CreateStripeCheckoutRedirectSuccessResponse { + return response?.success === true; +} + +export function CreateStripeCheckoutRedirectIsError(response: CreateStripeCheckoutRedirectResponse): response is CreateStripeCheckoutRedirectErrorResponse { + return response?.success === false; +} + +export async function CreateStripeCheckoutRedirect(internal_plan_key: string) : Promise +{ + const endpoint = new ApiConfig().createStripeCheckoutRedirect(); + + const request : CreateStripeCheckoutRedirectRequest = { + internal_plan_key: internal_plan_key, + }; + + return await fetch(endpoint, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(request), + }) + .then(res => res.json()) + .then(res => { + if (!res) { + return { success : false }; + } + + if (res && 'success' in res) { + return res; + } else { + return { success : false }; + } + }) + .catch(e => { + return { success : false }; + }); +} diff --git a/packages/components/src/api/premium/CreateStripePortalRedirect.ts b/packages/components/src/api/premium/CreateStripePortalRedirect.ts new file mode 100644 index 0000000000..1ff23ce5a9 --- /dev/null +++ b/packages/components/src/api/premium/CreateStripePortalRedirect.ts @@ -0,0 +1,58 @@ +import { ApiConfig } from "@storyteller/components"; + +export interface CreateStripePortalRedirectRequest { + portal_config_id?: string, +} + +export interface CreateStripePortalRedirectSuccessResponse { + success: boolean, + stripe_portal_redirect_url: string, +} + +export interface CreateStripePortalRedirectErrorResponse { + success: boolean, +} + +type CreateStripePortalRedirectResponse = CreateStripePortalRedirectSuccessResponse | CreateStripePortalRedirectErrorResponse; + +export function CreateStripePortalRedirectIsSuccess(response: CreateStripePortalRedirectResponse): response is CreateStripePortalRedirectSuccessResponse { + return response?.success === true; +} + +export function CreateStripePortalRedirectIsError(response: CreateStripePortalRedirectResponse): response is CreateStripePortalRedirectErrorResponse { + return response?.success === false; +} + +export async function CreateStripePortalRedirect(portal_config_id?: string) : Promise +{ + const endpoint = new ApiConfig().createStripePortalRedirect(); + + const request : CreateStripePortalRedirectRequest = { + portal_config_id: portal_config_id, + }; + + return await fetch(endpoint, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(request), + }) + .then(res => res.json()) + .then(res => { + if (!res) { + return { success : false }; + } + + if (res && 'success' in res) { + return res; + } else { + return { success : false }; + } + }) + .catch(e => { + return { success : false }; + }); +} diff --git a/packages/components/src/api/premium/ListActiveSubscriptions.ts b/packages/components/src/api/premium/ListActiveSubscriptions.ts new file mode 100644 index 0000000000..d39fad91da --- /dev/null +++ b/packages/components/src/api/premium/ListActiveSubscriptions.ts @@ -0,0 +1,55 @@ +import { ApiConfig } from "@storyteller/components"; + +export interface ListActiveSubscriptionsSuccessResponse { + success: boolean, + maybe_loyalty_program?: string, + active_subscriptions: ActiveSubscription[], +} + +export interface ActiveSubscription { + namespace: string, + product_slug: string, +} + +export interface ListActiveSubscriptionsErrorResponse { + success: boolean, +} + +type ListActiveSubscriptionsResponse = ListActiveSubscriptionsSuccessResponse | ListActiveSubscriptionsErrorResponse; + +export function ListActiveSubscriptionsIsSuccess(response: ListActiveSubscriptionsResponse): response is ListActiveSubscriptionsSuccessResponse { + return response?.success === true; +} + +export function ListActiveSubscriptionsIsError(response: ListActiveSubscriptionsResponse): response is ListActiveSubscriptionsErrorResponse { + return response?.success === false; +} + +export async function ListActiveSubscriptions() : Promise +{ + const endpoint = new ApiConfig().listActiveSubscriptions(); + + return await fetch(endpoint, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) + .then(res => res.json()) + .then(res => { + if (!res) { + return { success : false }; + } + + if (res && 'success' in res) { + return res; + } else { + return { success : false }; + } + }) + .catch(e => { + return { success : false }; + }); +} diff --git a/packages/components/src/constants/ThirdPartyLinks.ts b/packages/components/src/constants/ThirdPartyLinks.ts index e25de2e5be..49cf770110 100644 --- a/packages/components/src/constants/ThirdPartyLinks.ts +++ b/packages/components/src/constants/ThirdPartyLinks.ts @@ -6,10 +6,14 @@ class ThirdPartyLinks { // Old FakeYou Discord invite link: https://discord.gg/H72KFXm public static readonly FAKEYOU_DISCORD = "https://discord.gg/fakeyou"; - // A link to Twitter that will open a "Follow" prompt + // A link to FakeYou Twitter that will open a "Follow" prompt public static readonly FAKEYOU_TWITTER_WITH_FOLLOW_INTENT = "https://twitter.com/intent/follow?screen_name=FakeYouApp"; public static readonly FAKEYOU_PATREON = "https://www.patreon.com/fakeyou"; + + // A link to echelon's Twitter that will open a "Follow" prompt + // Additional followers may convince VC's that our founder has "clout" (one could hope) + public static readonly ECHELON_TWITTER_WITH_FOLLOW_INTENT = "https://twitter.com/intent/follow?screen_name=echelon"; } export { ThirdPartyLinks } \ No newline at end of file diff --git a/packages/components/src/elements/EchelonTwitterLink.tsx b/packages/components/src/elements/EchelonTwitterLink.tsx new file mode 100644 index 0000000000..a775515ab6 --- /dev/null +++ b/packages/components/src/elements/EchelonTwitterLink.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { faTwitter } from "@fortawesome/free-brands-svg-icons"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ThirdPartyLinks } from "../constants/ThirdPartyLinks"; + +interface Props { + hideIcon?: boolean; + iconBeforeText?: boolean; + title?: string; + children?: string | Element | Array; // Optional link text, child elements, etc. +} + +function EchelonTwitterLink(props: Props) { + let linkBody = + props.children === undefined ? <>echelon : <>{props.children}; + + const showIcon = !(props.hideIcon ? true : false); + const iconBeforeText = props.iconBeforeText ? true : false; + + const linkTitle = props.title ? props.title : "echelon's Twitter"; + + if (showIcon) { + linkBody = iconBeforeText ? ( + <> + {" "} + {linkBody} + + ) : ( + <> + {linkBody}{" "} + + + ); + } + + return ( + + {linkBody} + + ); +} + +export { EchelonTwitterLink }; diff --git a/packages/components/src/session/SessionSubscriptionsWrapper.ts b/packages/components/src/session/SessionSubscriptionsWrapper.ts new file mode 100644 index 0000000000..a72da017f0 --- /dev/null +++ b/packages/components/src/session/SessionSubscriptionsWrapper.ts @@ -0,0 +1,38 @@ +import { ListActiveSubscriptions, ListActiveSubscriptionsIsSuccess, ListActiveSubscriptionsSuccessResponse } from "../api/premium/ListActiveSubscriptions"; + +export class SessionSubscriptionsWrapper { + listActiveSubscriptionResponse?: ListActiveSubscriptionsSuccessResponse; + + private constructor(listActiveSubscriptionsSuccessResponse?: ListActiveSubscriptionsSuccessResponse) { + if (listActiveSubscriptionsSuccessResponse !== undefined) { + this.listActiveSubscriptionResponse = listActiveSubscriptionsSuccessResponse; + } + } + + public static async lookupActiveSubscriptions() : Promise { + let response = await ListActiveSubscriptions(); + if (ListActiveSubscriptionsIsSuccess(response)) { + return new SessionSubscriptionsWrapper(response); + } else { + return SessionSubscriptionsWrapper.emptySubscriptions(); + } + } + + public static emptySubscriptions() : SessionSubscriptionsWrapper { + return new SessionSubscriptionsWrapper(); + } + + public hasFreeOrPaidPremiumFeatures() : boolean { + return this.hasLoyaltyProgram() || this.hasPaidFeatures(); + } + + + public hasLoyaltyProgram() : boolean { + return !!this.listActiveSubscriptionResponse?.maybe_loyalty_program; + } + + public hasPaidFeatures() : boolean { + const subs = this.listActiveSubscriptionResponse?.active_subscriptions || []; + return subs.length > 0; + } +} \ No newline at end of file diff --git a/packages/fakeyou/src/App.tsx b/packages/fakeyou/src/App.tsx index ebc92ce08c..71c39f7f26 100644 --- a/packages/fakeyou/src/App.tsx +++ b/packages/fakeyou/src/App.tsx @@ -10,6 +10,7 @@ import { Language } from "@storyteller/components/src/i18n/Language"; import { BrowserRouter, Route, Switch } from "react-router-dom"; import { NewVocodesContainer } from "./v2/view/NewVocodesContainer"; import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; import { TtsInferenceJob, TtsInferenceJobStateResponsePayload, @@ -92,6 +93,7 @@ interface State { // Rollout of vocodes 2.0 enableAlpha: boolean; sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; // Show flash notice of vocodes name change isShowingVocodesNotice: boolean; @@ -154,7 +156,9 @@ class App extends React.Component { this.state = { enableAlpha: enableAlpha, migrationMode: migrationMode, + sessionWrapper: SessionWrapper.emptySession(), + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper.emptySubscriptions(), isShowingVocodesNotice: props.flashVocodesNotice, @@ -194,11 +198,13 @@ class App extends React.Component { } async componentDidMount() { - await this.querySession(); await this.queryLanguage(); + await this.querySession(); + await this.querySessionSubscriptions(); setInterval(async () => { await this.querySession(); + await this.querySessionSubscriptions(); }, 60000); // TODO: Use websockets, this is dumb setInterval(() => { @@ -213,6 +219,13 @@ class App extends React.Component { }); }; + querySessionSubscriptions = async () => { + const sessionSubscriptionsWrapper = await SessionSubscriptionsWrapper.lookupActiveSubscriptions(); + this.setState({ + sessionSubscriptionsWrapper: sessionSubscriptionsWrapper, + }); + }; + queryLanguage = async () => { let locale = await DetectLocale(); if (DetectLocaleIsOk(locale)) { @@ -645,6 +658,7 @@ class App extends React.Component {
- Built by echelon in Atlanta. + Built by in Atlanta.
diff --git a/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx b/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx index c8113b8a61..a1856ee94f 100644 --- a/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx +++ b/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx @@ -11,6 +11,7 @@ import { NewTopNavFc } from "./NewTopNavFc"; import { ProfileEditFc } from "./profile/profile_edit/ProfileEditFc"; import { ProfileFc } from "./profile/profile_view/ProfileFc"; import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; import { SignupPage } from "./signup/SignupPage"; import { Switch, Route } from "react-router-dom"; import { TermsPage } from "./about/terms_page/TermsPage"; @@ -56,11 +57,17 @@ import { Language } from "@storyteller/components/src/i18n/Language"; import { VoiceCloneRequestPage } from "./clone_voice_requests/VoiceCloneRequestPage"; import { VocodesPage } from "./vocodes/VocodesPage"; import { UploadVocoderPage } from "./upload/UploadVocoderPage"; +import { PricingPage } from "./premium/PricingPage"; +import { CheckoutSuccessPage } from "./premium/CheckoutSuccessPage"; +import { CheckoutCancelPage } from "./premium/CheckoutCancelPage"; +import { PortalSuccessPage } from "./premium/PortalSuccessPage"; interface Props { sessionWrapper: SessionWrapper; querySessionAction: () => void; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; + isShowingVocodesNotice: boolean; clearVocodesNotice: () => void; @@ -174,14 +181,31 @@ class NewVocodesContainer extends React.Component { /> - {/* - + { }} + sessionWrapper={this.props.sessionWrapper} + sessionSubscriptionsWrapper={this.props.sessionSubscriptionsWrapper} + /> + + + + {}} + sessionWrapper={this.props.sessionWrapper} + /> + + + {}} + sessionWrapper={this.props.sessionWrapper} + /> + + + {}} sessionWrapper={this.props.sessionWrapper} /> - */} diff --git a/packages/fakeyou/src/v2/view/premium/CheckoutCancelPage.tsx b/packages/fakeyou/src/v2/view/premium/CheckoutCancelPage.tsx new file mode 100644 index 0000000000..a0156e4f8a --- /dev/null +++ b/packages/fakeyou/src/v2/view/premium/CheckoutCancelPage.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { motion } from "framer-motion"; +import { container, item, image } from "../../../data/animation"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; + +interface Props { + sessionWrapper: SessionWrapper; + querySessionCallback: () => void; +} + +function CheckoutCancelPage(props: Props) { + return ( + +
+
+
+
+ +
+
+
+
+
+ + Oh no! + +
+
+ + It's okay if you don't want to support FakeYou right now, but we'd + appreciate it if you reconsider us in the future. + +
+ + Paid plans go directly to helping us afford more GPUs, engineering, + and research talent. + +
+ + We're trying to build a film and music production system that you + can use to make any content you dream up. Please consider supporting + us monetarily. + +
+
+
+
+
+ + Back to FakeYou + +
+
+
+
+ + + Select a Premium Plan + +
+
+
+
+ +
+ ); +} + +export { CheckoutCancelPage }; diff --git a/packages/fakeyou/src/v2/view/premium/CheckoutSuccessPage.tsx b/packages/fakeyou/src/v2/view/premium/CheckoutSuccessPage.tsx new file mode 100644 index 0000000000..d96a798c80 --- /dev/null +++ b/packages/fakeyou/src/v2/view/premium/CheckoutSuccessPage.tsx @@ -0,0 +1,73 @@ + +import React from "react"; +import { Link } from "react-router-dom"; +import { motion } from "framer-motion"; +import { container, item, image, panel } from "../../../data/animation"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; + +interface Props { + sessionWrapper: SessionWrapper; + querySessionCallback: () => void; +} + +function CheckoutSuccessPage(props: Props) { + return ( + +
+
+
+
+ +
+
+
+
+
+ + Thank you! + +
+
+
+ + We're grateful you're helping FakeYou build the future creative + suite. We're building tools for small creators and have an eye + on building a full AI-powered Hollywood and music production + studio. + +
+ + Your support helps us immensely! + +
+ + Please give it a few minutes for your purchase to take effect. + +
+
+
+
+
+ + Back to FakeYou + +
+
+
+
+ +
+ ); +} + +export { CheckoutSuccessPage }; diff --git a/packages/fakeyou/src/v2/view/premium/PortalSuccessPage.tsx b/packages/fakeyou/src/v2/view/premium/PortalSuccessPage.tsx new file mode 100644 index 0000000000..a212393e5d --- /dev/null +++ b/packages/fakeyou/src/v2/view/premium/PortalSuccessPage.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { motion } from "framer-motion"; +import { container, item, image, panel } from "../../../data/animation"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; + +interface Props { + sessionWrapper: SessionWrapper; + querySessionCallback: () => void; +} + +function PortalSuccessPage(props: Props) { + return ( + +
+
+
+
+ +
+
+
+
+
+ + Thank You! + +
+
+ + Your support means a lot to us. + + + Please continue to enjoy FakeYou! + +
+
+
+
+
+ + Back to FakeYou + +
+
+
+
+ +
+ ); +} + +export { PortalSuccessPage }; diff --git a/packages/fakeyou/src/v2/view/premium/PricingPage.tsx b/packages/fakeyou/src/v2/view/premium/PricingPage.tsx new file mode 100644 index 0000000000..a77171ca4e --- /dev/null +++ b/packages/fakeyou/src/v2/view/premium/PricingPage.tsx @@ -0,0 +1,636 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link } from "react-router-dom"; +import { faCheck } from "@fortawesome/free-solid-svg-icons"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; +import { FAKEYOU_PRICES as FYP } from "@storyteller/fakeyou/src/data/PriceTiers"; +import { + CreateStripeCheckoutRedirect, + CreateStripeCheckoutRedirectIsError, + CreateStripeCheckoutRedirectIsSuccess, +} from "@storyteller/components/src/api/premium/CreateStripeCheckoutRedirect"; +import { + CreateStripePortalRedirect, + CreateStripePortalRedirectIsError, + CreateStripePortalRedirectIsSuccess, +} from "@storyteller/components/src/api/premium/CreateStripePortalRedirect"; + +interface Props { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +function PricingPage(props: Props) { + + const beginStripeCheckoutFlow = async (internal_plan_key: string) : Promise => { + const response = await CreateStripeCheckoutRedirect(internal_plan_key); + if (CreateStripeCheckoutRedirectIsSuccess(response)) { + window.location.href = response.stripe_checkout_redirect_url; + } else if (CreateStripeCheckoutRedirectIsError(response)) { + // TODO + } + return false; + } + + const beginStripePortalFlow = async () : Promise => { + const response = await CreateStripePortalRedirect(); + if (CreateStripePortalRedirectIsSuccess(response)) { + window.location.href = response.stripe_portal_redirect_url; + } else if (CreateStripePortalRedirectIsError(response)) { + // TODO + } + return false; + } + + const beginStripeFlow = async (internal_plan_key: string) : Promise => { + if (props.sessionSubscriptionsWrapper.hasPaidFeatures()) { + return await beginStripePortalFlow(); + } else { + return await beginStripeCheckoutFlow(internal_plan_key); + } + }; + + const hasPaidPremium = props.sessionSubscriptionsWrapper.hasPaidFeatures(); + + let freeButtonText = 'Use for free'; + let plusButtonText = 'Buy Plus'; + let proButtonText = 'Buy Pro'; + let eliteButtonText = 'Buy Elite'; + + if (hasPaidPremium) { + freeButtonText = 'Unsubscribe'; + plusButtonText = 'Switch to Plus'; + proButtonText = 'Switch to Pro'; + eliteButtonText = 'Switch to Elite'; + } + + return ( +
+
+

Pricing

+

+ By purchasing FakeYou premium, you help us build more! +

+
+
+
+ {/* Starter Tier */} +
+
+

+ {FYP.starter.tier} +

+ + {freeButtonText} + +

+ ${FYP.starter.price} + /month +

+
    + +
  • + {FYP.starter.priority.title} +
  • + {FYP.starter.priority.features.map((e: any) => { + return ( +
  • + + {e} + {" "} + (registered users) +
  • + ); + })} + +
  • {FYP.starter.tts.title}
  • + {FYP.starter.tts.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + {/*
  • {FYP.starter.vcweb.title}
  • + {FYP.starter.vcweb.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • + {FYP.starter.vcapp.title}{" "} + (registered users) +
  • + {FYP.starter.vcapp.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })}*/} +
  • {FYP.starter.w2l.title}
  • + {FYP.starter.w2l.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
+ + {/* Basic Tier (For Latin American countries) */} + {/*
+
+

+ {FYP.basic.tier} +

+ + Buy {FYP.basic.tier} + +

+ ${FYP.basic.price} + /month +

+
    +
  • {FYP.basic.tts.title}
  • + {FYP.basic.tts.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.basic.vcweb.title}
  • + {FYP.basic.vcweb.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.basic.vcapp.title}
  • + {FYP.basic.vcapp.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.basic.w2l.title}
  • + {FYP.basic.w2l.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.basic.priority.title}
  • + {FYP.basic.priority.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
*/} + + {/* Plus Tier */} +
+
+

{FYP.plus.tier}

+ +

+ ${FYP.plus.price} + /month +

+
    + +
  • {FYP.plus.priority.title}
  • + {FYP.plus.priority.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + + +
  • {FYP.plus.tts.title}
  • + {FYP.plus.tts.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + {/*
  • {FYP.plus.vcweb.title}
  • + {FYP.plus.vcweb.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.plus.vcapp.title}
  • + {FYP.plus.vcapp.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })}*/} +
  • {FYP.plus.w2l.title}
  • + {FYP.plus.w2l.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
+ + {/* Pro Tier */} +
+
+

{FYP.pro.tier}

+ +

+ ${FYP.pro.price} + /month +

+
    + +
  • {FYP.pro.priority.title}
  • + {FYP.pro.priority.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + + +
  • {FYP.pro.tts.title}
  • + {FYP.pro.tts.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + {/*
  • {FYP.pro.vcweb.title}
  • + {FYP.pro.vcweb.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.pro.vcapp.title}
  • + {FYP.pro.vcapp.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })}*/} +
  • {FYP.pro.w2l.title}
  • + {FYP.pro.w2l.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + {/*
  • {FYP.pro.api.title}
  • + {FYP.pro.api.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })}*/} +
+
+
+ + {/* Elite Tier */} +
+
+

+ {FYP.elite.tier} +

+ +

+ ${FYP.elite.price} + /month +

+
    + +
  • {FYP.elite.priority.title}
  • + {FYP.elite.priority.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + +
  • {FYP.elite.tts.title}
  • + {FYP.elite.tts.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + {/*
  • {FYP.elite.vcweb.title}
  • + {FYP.elite.vcweb.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
  • {FYP.elite.vcapp.title}
  • + {FYP.elite.vcapp.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })}*/} +
  • {FYP.elite.w2l.title}
  • + {FYP.elite.w2l.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} + {/*
  • {FYP.elite.api.title}
  • + {FYP.elite.api.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })}*/} +
  • {FYP.elite.commercial.title}
  • + {FYP.elite.commercial.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
+
+ + {/* Starter Tier (to show for Latin American countries) */} + {/*
+
+
+

+ {FYP.starter.tier} +

+ +

+ ${FYP.starter.price} + /month +

+
+ + + Use for free + + +
+
+
    +
  • {FYP.starter.tts.title}
  • + {FYP.starter.tts.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
    +
  • {FYP.starter.vcweb.title}
  • + {FYP.starter.vcweb.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
+
    +
  • + {FYP.starter.vcapp.title}{" "} + (registered users) +
  • + {FYP.starter.vcapp.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
+
    +
  • {FYP.starter.w2l.title}
  • + {FYP.starter.w2l.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
    +
  • + {FYP.starter.priority.title}{" "} + (registered users) +
  • + {FYP.starter.priority.features.map((e: any) => { + return ( +
  • + + {e} +
  • + ); + })} +
+
+
+
+
*/} +
+
+ ); +} + +export { PricingPage }; diff --git a/packages/fakeyou/src/v2/view/pricing/Pricing.tsx b/packages/fakeyou/src/v2/view/pricing/Pricing.tsx deleted file mode 100644 index 62f8125b3a..0000000000 --- a/packages/fakeyou/src/v2/view/pricing/Pricing.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link } from "react-router-dom"; -import { - faCheckCircle as TRUE, - faTimesCircle as FALSE, - faCheck, -} from "@fortawesome/free-solid-svg-icons"; -import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; -import { - FAKEYOU_PRICES as FYP, - STORYTELLER_PRICES as STP, -} from "@storyteller/fakeyou/src/data/PriceTiers"; -import { - distance, - pricing1, - pricing2, - pricing3, - delay2, - duration, -} from "../../../data/animation"; - -const Fade = require("react-reveal/Fade"); - -interface Props { - sessionWrapper: SessionWrapper; - querySessionCallback: () => void; -} - -function PricingPage(props: Props) { - function capitalize(word: string) { - return word.charAt(0).toUpperCase() + word.slice(1); - } - - const [FY_TIERS, ST_TIERS] = [Object.keys(FYP), Object.keys(STP)]; - - const [FY_FEATURES, ST_FEATURES] = [ - Object.keys(FYP.free.features), - Object.keys(STP.free.features), - ]; - - let current_tiers: string[], current_features: string[], current_list: any; - - const site = window.location.hostname; - - switch (site) { - case "fakeyou": - [current_tiers, current_features, current_list] = [ - FY_TIERS, - FY_FEATURES, - FYP, - ]; - break; - case "storyteller": - [current_tiers, current_features, current_list] = [ - ST_TIERS, - ST_FEATURES, - STP, - ]; - break; - default: - [current_tiers, current_features, current_list] = [ - FY_TIERS, - FY_FEATURES, - FYP, - ]; - break; - } - - return ( -
- -
-

FakeYou Pricing

-
-
- -
-
-
- -
-

Free

-

Great for hobbyists

- - Create free TTS - -

- ${current_list.free.price} - /month -

-
    -
  • - - Unlimited TTS Generation -
  • -
  • - - Up to 10 seconds audio -
  • -
-
-
-
-
- -
-

Basic

-

Great for hobbyists

- - Buy Basic - -

- ${current_list.basic.price} - /month -

-
    -
  • - - Unlimited TTS Generation -
  • -
  • - - Up to 30 seconds audio -
  • -
  • - - Priority processing -
  • -
-
-
-
-
- -
-

Pro

-

Great for hobbyists

- - Buy Pro - -

- ${current_list.pro.price} - /month -

-
    -
  • - - Unlimited TTS Generation -
  • -
  • - - Up to 1 minute audio -
  • -
  • - - Priority processing -
  • -
  • - - Generate mp3 file -
  • -
  • - - For Commercial Use -
  • -
-
-
-
-
-
- - -
-
- - - - - - {current_tiers.map((e) => { - return ( - - ); - })} - - - - - - - - - - - - {current_features.map((e, i) => { - return ( - - - - - - - - - - ); - })} - -
Feature List - {capitalize(e)} -
Price${current_list.free.price}/month${current_list.basic.price}/month${current_list.pro.price}/month
{e} - - - - - -
-
-
-
-
- ); -} - -export { PricingPage };