mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-03 20:26:03 +02:00
📈 Improvements to posthog analytics data (#1189)
This commit is contained in:
parent
278713d57f
commit
587e11de17
10 changed files with 93 additions and 80 deletions
|
@ -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<string>();
|
||||
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")}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isGuest = !user.email;
|
||||
const tier = isGuest ? "guest" : subscription?.active ? "pro" : "hobby";
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
|
@ -66,7 +76,8 @@ export const UserProvider = (props: { children?: React.ReactNode }) => {
|
|||
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 }) => {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <Provider client={posthog}>{props.children}</Provider>;
|
||||
};
|
||||
|
||||
export const PostHogProvider = (props: PostHogProviderProps) => {
|
||||
if (!process.env.NEXT_PUBLIC_POSTHOG_API_KEY) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
return <PostHogProviderInner {...props} />;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -77,7 +77,7 @@ export const auth = router({
|
|||
locale: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const payload = await decryptToken<RegistrationTokenPayload>(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
|
||||
|
|
|
@ -923,13 +923,7 @@ export const polls = router({
|
|||
},
|
||||
});
|
||||
|
||||
waitUntil(
|
||||
Promise.all([
|
||||
emailToHost,
|
||||
...emailsToParticipants,
|
||||
ctx.posthogClient?.flushAsync(),
|
||||
]),
|
||||
);
|
||||
waitUntil(Promise.all([emailToHost, ...emailsToParticipants]));
|
||||
}
|
||||
}),
|
||||
reopen: possiblyPublicProcedure
|
||||
|
|
Loading…
Add table
Reference in a new issue