From 587e11de1799f5ccb34c789ea2b4ea690751af7b Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Wed, 3 Jul 2024 12:09:58 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=88=20Improvements=20to=20posthog=20an?= =?UTF-8?q?alytics=20data=20(#1189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/[locale]/(auth)/login/login-form.tsx | 18 +++----- .../(auth)/register/register-page.tsx | 9 +--- .../app/[locale]/auth/login/login-page.tsx | 5 +-- apps/web/src/components/user-provider.tsx | 17 ++++++-- apps/web/src/contexts/plan.tsx | 11 +---- apps/web/src/contexts/posthog.tsx | 18 ++------ apps/web/src/pages/api/stripe/webhook.ts | 29 +++++++++++-- apps/web/src/utils/auth.ts | 43 +++++++++++-------- packages/backend/trpc/routers/auth.ts | 15 ++++++- packages/backend/trpc/routers/polls.ts | 8 +--- 10 files changed, 93 insertions(+), 80 deletions(-) diff --git a/apps/web/src/app/[locale]/(auth)/login/login-form.tsx b/apps/web/src/app/[locale]/(auth)/login/login-form.tsx index 652281a91..a0db3fc70 100644 --- a/apps/web/src/app/[locale]/(auth)/login/login-form.tsx +++ b/apps/web/src/app/[locale]/(auth)/login/login-form.tsx @@ -7,7 +7,6 @@ import { AlertTriangleIcon, UserIcon } from "lucide-react"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; import { getProviders, signIn, useSession } from "next-auth/react"; -import { usePostHog } from "posthog-js/react"; import React from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -38,7 +37,6 @@ export function LoginForm() { const session = useSession(); const queryClient = trpc.useUtils(); const [email, setEmail] = React.useState(); - const posthog = usePostHog(); const router = useRouter(); const callbackUrl = searchParams?.get("callbackUrl") ?? "/"; @@ -128,19 +126,15 @@ export function LoginForm() { email, token: code, }); + if (!success) { throw new Error("Failed to authenticate user"); - } else { - await queryClient.invalidate(); - const s = await session.update(); - if (s?.user) { - posthog?.identify(s.user.id, { - email: s.user.email, - name: s.user.name, - }); - } - router.push(callbackUrl); } + + await queryClient.invalidate(); + await session.update(); + + router.push(callbackUrl); }} email={getValues("email")} /> diff --git a/apps/web/src/app/[locale]/(auth)/register/register-page.tsx b/apps/web/src/app/[locale]/(auth)/register/register-page.tsx index 6dff74d65..3bbc2f394 100644 --- a/apps/web/src/app/[locale]/(auth)/register/register-page.tsx +++ b/apps/web/src/app/[locale]/(auth)/register/register-page.tsx @@ -70,14 +70,7 @@ export const RegisterForm = () => { queryClient.invalidate(); - posthog?.identify(res.user.id, { - email: res.user.email, - name: res.user.name, - timeZone, - locale, - }); - - posthog?.capture("register"); + posthog?.identify(res.user.id); signIn("registration-token", { token, diff --git a/apps/web/src/app/[locale]/auth/login/login-page.tsx b/apps/web/src/app/[locale]/auth/login/login-page.tsx index db33f7161..2f3484f6d 100644 --- a/apps/web/src/app/[locale]/auth/login/login-page.tsx +++ b/apps/web/src/app/[locale]/auth/login/login-page.tsx @@ -28,10 +28,7 @@ export const LoginPage = ({ magicLink, email }: PageProps) => { const updatedSession = await session.update(); if (updatedSession) { // identify the user in posthog - posthog?.identify(updatedSession.user.id, { - email: updatedSession.user.email, - name: updatedSession.user.name, - }); + posthog?.identify(updatedSession.user.id); await trpcUtils.invalidate(); } diff --git a/apps/web/src/components/user-provider.tsx b/apps/web/src/components/user-provider.tsx index 639a67cd7..8a9315771 100644 --- a/apps/web/src/components/user-provider.tsx +++ b/apps/web/src/components/user-provider.tsx @@ -5,6 +5,8 @@ import React from "react"; import { z } from "zod"; import { useTranslation } from "@/app/i18n/client"; +import { Spinner } from "@/components/spinner"; +import { useSubscription } from "@/contexts/plan"; import { PostHogProvider } from "@/contexts/posthog"; import { PreferencesProvider } from "@/contexts/preferences"; @@ -15,6 +17,7 @@ const userSchema = z.object({ name: z.string(), email: z.string().email().nullable(), isGuest: z.boolean(), + tier: z.enum(["guest", "hobby", "pro"]), timeZone: z.string().nullish(), timeFormat: z.enum(["hours12", "hours24"]).nullish(), weekStart: z.number().min(0).max(6).nullish(), @@ -50,15 +53,22 @@ export const IfGuest = (props: { children?: React.ReactNode }) => { export const UserProvider = (props: { children?: React.ReactNode }) => { const session = useSession(); - const user = session.data?.user; + const subscription = useSubscription(); const { t } = useTranslation(); if (!user) { - return null; + return ( +
+ +
+ ); } + const isGuest = !user.email; + const tier = isGuest ? "guest" : subscription?.active ? "pro" : "hobby"; + return ( { id: user.id as string, name: user.name ?? t("guest"), email: user.email || null, - isGuest: user.email === null, + isGuest: !user.email, + tier, }, refresh: session.update, ownsObject: ({ userId }) => { diff --git a/apps/web/src/contexts/plan.tsx b/apps/web/src/contexts/plan.tsx index 8c8dbbcb6..aea2355e3 100644 --- a/apps/web/src/contexts/plan.tsx +++ b/apps/web/src/contexts/plan.tsx @@ -3,15 +3,12 @@ import { Badge } from "@rallly/ui/badge"; import React from "react"; import { Trans } from "@/components/trans"; -import { useUser } from "@/components/user-provider"; import { isSelfHosted } from "@/utils/constants"; import { trpc } from "@/utils/trpc/client"; export const useSubscription = () => { - const { user } = useUser(); - const { data } = trpc.user.subscription.useQuery(undefined, { - enabled: !isSelfHosted && user.isGuest === false, + enabled: !isSelfHosted, }); if (isSelfHosted) { @@ -20,12 +17,6 @@ export const useSubscription = () => { }; } - if (user.isGuest) { - return { - active: false, - }; - } - return data; }; diff --git a/apps/web/src/contexts/posthog.tsx b/apps/web/src/contexts/posthog.tsx index ad92d6a75..af8ffeb20 100644 --- a/apps/web/src/contexts/posthog.tsx +++ b/apps/web/src/contexts/posthog.tsx @@ -1,3 +1,4 @@ +"use client"; import posthog from "posthog-js"; import { PostHogProvider as Provider } from "posthog-js/react"; import { useMount } from "react-use"; @@ -6,15 +7,12 @@ import { useUser } from "@/components/user-provider"; type PostHogProviderProps = React.PropsWithChildren; -const PostHogProviderInner = (props: PostHogProviderProps) => { +export function PostHogProvider(props: PostHogProviderProps) { const { user } = useUser(); useMount(() => { // initalize posthog with our user id - if ( - typeof window !== "undefined" && - process.env.NEXT_PUBLIC_POSTHOG_API_KEY - ) { + if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, { api_host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST, opt_out_capturing_by_default: false, @@ -31,12 +29,4 @@ const PostHogProviderInner = (props: PostHogProviderProps) => { }); return {props.children}; -}; - -export const PostHogProvider = (props: PostHogProviderProps) => { - if (!process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { - return <>{props.children}; - } - - return ; -}; +} diff --git a/apps/web/src/pages/api/stripe/webhook.ts b/apps/web/src/pages/api/stripe/webhook.ts index f84e1d6fe..aa54fa5f8 100644 --- a/apps/web/src/pages/api/stripe/webhook.ts +++ b/apps/web/src/pages/api/stripe/webhook.ts @@ -30,7 +30,11 @@ const validatedWebhook = async (req: NextApiRequest) => { } }; -const metadataSchema = z.object({ +const checkoutMetadataSchema = z.object({ + userId: z.string(), +}); + +const subscriptionMetadataSchema = z.object({ userId: z.string(), }); @@ -59,7 +63,7 @@ async function stripeApiHandler(req: NextApiRequest, res: NextApiResponse) { break; } - const { userId } = metadataSchema.parse(checkoutSession.metadata); + const { userId } = checkoutMetadataSchema.parse(checkoutSession.metadata); if (!userId) { res.status(400).send("Missing client reference ID"); @@ -86,10 +90,13 @@ async function stripeApiHandler(req: NextApiRequest, res: NextApiResponse) { event: "upgrade", properties: { interval: subscription.items.data[0].plan.interval, + $set: { + tier: "pro", + }, }, }); } catch (e) { - Sentry.captureMessage("Failed to track upgrade event"); + Sentry.captureException(e); } break; @@ -133,6 +140,22 @@ async function stripeApiHandler(req: NextApiRequest, res: NextApiResponse) { }, }); + try { + const data = subscriptionMetadataSchema.parse(subscription.metadata); + posthog?.capture({ + event: "subscription change", + distinctId: data.userId, + properties: { + type: event.type, + $set: { + tier: isActive ? "pro" : "hobby", + }, + }, + }); + } catch (e) { + Sentry.captureException(e); + } + break; } default: diff --git a/apps/web/src/utils/auth.ts b/apps/web/src/utils/auth.ts index 5730ac92b..28e14652d 100644 --- a/apps/web/src/utils/auth.ts +++ b/apps/web/src/utils/auth.ts @@ -174,9 +174,32 @@ const getAuthOptions = (...args: GetServerSessionParams) => signOut: "/logout", error: "/auth/error", }, + events: { + signIn({ user, account }) { + posthog?.capture({ + distinctId: user.id, + event: "login", + properties: { + method: account?.provider, + $set: { + name: user.name, + email: user.email, + timeZone: user.timeZone, + locale: user.locale, + }, + }, + }); + }, + signOut({ session }) { + posthog?.capture({ + distinctId: session.user.id, + event: "logout", + }); + }, + }, callbacks: { - async signIn({ user, email, account, profile }) { - const distinctId = user.email ?? user.id; + async signIn({ user, email, profile }) { + const distinctId = user.id; // prevent sign in if email is not verified if ( profile && @@ -221,22 +244,6 @@ const getAuthOptions = (...args: GetServerSessionParams) => if (session && session.user.email === null) { await mergeGuestsIntoUser(user.id, [session.user.id]); } - - posthog?.identify({ - distinctId, - properties: { - name: user.name, - email: user.email, - }, - }); - - posthog?.capture({ - distinctId, - event: "login", - properties: { - method: account?.provider, - }, - }); } return true; diff --git a/packages/backend/trpc/routers/auth.ts b/packages/backend/trpc/routers/auth.ts index bdff2d096..755025398 100644 --- a/packages/backend/trpc/routers/auth.ts +++ b/packages/backend/trpc/routers/auth.ts @@ -77,7 +77,7 @@ export const auth = router({ locale: z.string().optional(), }), ) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const payload = await decryptToken(input.token); if (!payload) { @@ -99,6 +99,19 @@ export const auth = router({ }, }); + ctx.posthogClient?.capture({ + event: "register", + distinctId: user.id, + properties: { + $set: { + email: user.email, + name: user.name, + timeZone: input.timeZone, + locale: input.locale, + }, + }, + }); + return { ok: true, user }; }), getUserPermission: publicProcedure diff --git a/packages/backend/trpc/routers/polls.ts b/packages/backend/trpc/routers/polls.ts index a1ade619a..2144a9488 100644 --- a/packages/backend/trpc/routers/polls.ts +++ b/packages/backend/trpc/routers/polls.ts @@ -923,13 +923,7 @@ export const polls = router({ }, }); - waitUntil( - Promise.all([ - emailToHost, - ...emailsToParticipants, - ctx.posthogClient?.flushAsync(), - ]), - ); + waitUntil(Promise.all([emailToHost, ...emailsToParticipants])); } }), reopen: possiblyPublicProcedure