diff --git a/packages/fakeyou.com/src/common/Analytics.ts b/packages/fakeyou.com/src/common/Analytics.ts new file mode 100644 index 0000000000..49cb10af78 --- /dev/null +++ b/packages/fakeyou.com/src/common/Analytics.ts @@ -0,0 +1,78 @@ +/** + * Send Analytics to Google Analytics. + * + * Keeping track of events in a singular space lets us easily update them and keep track of what we're analyzing at a high level. + * + * Currently we use Universal Analytics (UA), but support for that ends July 2023. We need to update to Google Analytics 4 (GA4). + * + * Here is some reading material: + * - [Events usage in UA - really good resource on how to bucket events](https://support.google.com/analytics/answer/1033068) + * - [GTag events for multiple Google products: UA, GA4, etc.](https://developers.google.com/tag-platform/devguides/events) + * - [Sending events to both UA and GA4](https://support.google.com/analytics/answer/11091026) + */ +class Analytics { + + // ========== USER ========== + + static accountSignupAttempt() { + Analytics.sendCategorizedEvent('account', 'signup_attempt'); + } + + static accountSignupComplete() { + Analytics.sendCategorizedEvent('account', 'signup_complete'); + } + + static accountLoginAttempt() { + Analytics.sendCategorizedEvent('account', 'login_attempt'); + } + + static accountLoginSuccess() { + Analytics.sendCategorizedEvent('account', 'login_success'); + } + + // ========== PREMIUM ========== + + static premiumForwardToStripeCheckout() { + Analytics.sendCategorizedEvent('premium', 'forward_to_stripe_checkout'); + } + + static premiumForwardToStripePortal() { + Analytics.sendCategorizedEvent('premium', 'forward_to_stripe_portal'); + } + + + static premiumBounceToSignup() { + Analytics.sendCategorizedEvent('premium', 'bounce_to_signup'); + } + + + // ========== TTS ========== + + static ttsGenerate(modelToken: string, ttsLength: number) { + Analytics.sendCategorizedEvent('TTS', 'generate_tts', modelToken, ttsLength); + } + + static ttsClear(modelToken?: string) { + Analytics.sendCategorizedEvent('TTS', 'clear', modelToken); + } + + // ========== (impl) ========== + + private static sendEvent(actionName: string, eventLabel?: string, value?: number) { + gtag('event', actionName, { + 'event_category': undefined, + 'event_label': eventLabel, + 'value': value, + }); + } + + private static sendCategorizedEvent(eventCategory: string, actionName: string, eventLabel?: string, value?: number) { + gtag('event', actionName, { + 'event_category': eventCategory, + 'event_label': eventLabel, + 'value': value, + }); + } +} + +export { Analytics } \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/login/LoginPage.tsx b/packages/fakeyou.com/src/v2/view/login/LoginPage.tsx index becb3685e5..e855fe357c 100644 --- a/packages/fakeyou.com/src/v2/view/login/LoginPage.tsx +++ b/packages/fakeyou.com/src/v2/view/login/LoginPage.tsx @@ -11,6 +11,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUser, faKey } from "@fortawesome/free-solid-svg-icons"; import { motion } from "framer-motion"; import { container, panel } from "../../../data/animation"; +import { Analytics } from "../../../common/Analytics"; interface Props { sessionWrapper: SessionWrapper; @@ -57,14 +58,16 @@ function LoginPage(props: Props) { password: password, }; + Analytics.accountLoginAttempt(); + const response = await CreateSession(request); if (CreateSessionIsError(response)) { setErrorMessage(response.error_message); } else if (CreateSessionIsSuccess(response)) { - console.log("querying new session"); props.querySessionAction(); props.querySessionSubscriptionsAction(); + Analytics.accountLoginSuccess(); history.push("/"); } diff --git a/packages/fakeyou.com/src/v2/view/premium/PricingPage.tsx b/packages/fakeyou.com/src/v2/view/premium/PricingPage.tsx index a0539af8e2..e64cffb63e 100644 --- a/packages/fakeyou.com/src/v2/view/premium/PricingPage.tsx +++ b/packages/fakeyou.com/src/v2/view/premium/PricingPage.tsx @@ -18,6 +18,7 @@ import { import { motion } from "framer-motion"; import { container, item, panel } from "../../../data/animation"; import { FakeYouFrontendEnvironment } from "@storyteller/components/src/env/FakeYouFrontendEnvironment"; +import { Analytics } from "../../../common/Analytics"; interface Props { sessionWrapper: SessionWrapper; @@ -54,11 +55,14 @@ function PricingPage(props: Props) { ): Promise => { if (!props.sessionWrapper.isLoggedIn()) { // TODO: This needs to bring the user back to purchase flow. + Analytics.premiumBounceToSignup(); history.push("/signup"); return false; } else if (props.sessionSubscriptionsWrapper.hasPaidFeatures()) { + Analytics.premiumForwardToStripePortal(); return await beginStripePortalFlow(); } else { + Analytics.premiumForwardToStripeCheckout(); return await beginStripeCheckoutFlow(internal_plan_key); } }; diff --git a/packages/fakeyou.com/src/v2/view/signup/SignupPage.tsx b/packages/fakeyou.com/src/v2/view/signup/SignupPage.tsx index 60d00ea3ef..a87d51bac1 100644 --- a/packages/fakeyou.com/src/v2/view/signup/SignupPage.tsx +++ b/packages/fakeyou.com/src/v2/view/signup/SignupPage.tsx @@ -10,6 +10,7 @@ import { Link } from "react-router-dom"; import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; import { motion } from "framer-motion"; import { container, panel } from "../../../data/animation"; +import { Analytics } from "../../../common/Analytics"; enum FieldTriState { EMPTY_FALSE, @@ -166,6 +167,8 @@ function SignupPage(props: Props) { ): Promise => { ev.preventDefault(); + Analytics.accountSignupAttempt(); + if ( !usernameValid || !emailValid || @@ -194,9 +197,10 @@ function SignupPage(props: Props) { setUsernameInvalidReason(response.error_fields["username"] || ""); } } else if (CreateAccountIsSuccess(response)) { - console.log("querying new session"); props.querySessionCallback(); + Analytics.accountSignupComplete(); + // TODO: Switch to functional component. window.location.href = "/"; } diff --git a/packages/fakeyou.com/src/v2/view/tts/tts_model_list/TtsModelListPage.tsx b/packages/fakeyou.com/src/v2/view/tts/tts_model_list/TtsModelListPage.tsx index 8eda936c75..ae82ca0f47 100644 --- a/packages/fakeyou.com/src/v2/view/tts/tts_model_list/TtsModelListPage.tsx +++ b/packages/fakeyou.com/src/v2/view/tts/tts_model_list/TtsModelListPage.tsx @@ -48,6 +48,7 @@ import { motion } from "framer-motion"; import { container, panel } from "../../../../data/animation"; import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; import { TtsPageHero } from "./TtsPageHero"; +import { Analytics } from "../../../../common/Analytics"; export interface EnqueueJobResponsePayload { success: boolean; @@ -192,11 +193,7 @@ function TtsModelListPage(props: Props) { const response = await GenerateTtsAudio(request); - gtag('event', 'generate_tts', { - 'event_category': 'TTS', - 'event_label': modelToken, - 'value': props.textBuffer.length, - }); + Analytics.ttsGenerate(modelToken, props.textBuffer.length); if (GenerateTtsAudioIsOk(response)) { setMaybeTtsError(undefined); @@ -211,6 +208,9 @@ function TtsModelListPage(props: Props) { const handleClearClick = (ev: React.FormEvent) => { ev.preventDefault(); props.clearTextBuffer(); + + Analytics.ttsClear(props.maybeSelectedTtsModel?.model_token); + return false; };