From c3dad32a2e27c1bfad16cbc1235dd1a0e68fff38 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 09:55:33 +0200 Subject: [PATCH 01/21] add posthog identification --- package-lock.json | 40 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/pages/_app.tsx | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb83625..3275b0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "next": "^13.3.0", "next-auth": "^4.21.0", "posthog-js": "^1.53.4", + "posthog-node": "^3.1.0", "react": "18.2.0", "react-dom": "18.2.0", "react-media-recorder": "^1.6.6", @@ -5858,6 +5859,26 @@ "rrweb-snapshot": "^1.1.14" } }, + "node_modules/posthog-node": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-3.1.0.tgz", + "integrity": "sha512-hXhkDWigzNYgkfLpd3HbfCD0h1/zP19pPXEba2Daf6xCerrFxc7ixMDtXwCQMXOmJNvrcoVMvrjULpfKHN11vA==", + "dependencies": { + "axios": "^0.27.0" + }, + "engines": { + "node": ">=15.0.0" + } + }, + "node_modules/posthog-node/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/preact": { "version": "10.13.2", "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz", @@ -11451,6 +11472,25 @@ "rrweb-snapshot": "^1.1.14" } }, + "posthog-node": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-3.1.0.tgz", + "integrity": "sha512-hXhkDWigzNYgkfLpd3HbfCD0h1/zP19pPXEba2Daf6xCerrFxc7ixMDtXwCQMXOmJNvrcoVMvrjULpfKHN11vA==", + "requires": { + "axios": "^0.27.0" + }, + "dependencies": { + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + } + } + }, "preact": { "version": "10.13.2", "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz", diff --git a/package.json b/package.json index 9067ab7..6131e7c 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "next": "^13.3.0", "next-auth": "^4.21.0", "posthog-js": "^1.53.4", + "posthog-node": "^3.1.0", "react": "18.2.0", "react-dom": "18.2.0", "react-media-recorder": "^1.6.6", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index b70ce29..c59dbf5 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,14 +1,15 @@ import { type AppType } from "next/app"; import { type Session } from "next-auth"; -import { SessionProvider } from "next-auth/react"; +import { SessionProvider, useSession } from "next-auth/react"; import { api } from "~/utils/api"; import "~/styles/globals.css"; import CrispChat from "~/components/CrispChat"; import posthog from "posthog-js"; -import { PostHogProvider } from "posthog-js/react"; +import { PostHogProvider, usePostHog } from "posthog-js/react"; import { env } from "~/env.mjs"; +import { type ReactNode, useEffect } from "react"; // Check that PostHog is client-side (used to handle Next.js SSR) if (typeof window !== "undefined") { @@ -28,11 +29,38 @@ const MyApp: AppType<{ session: Session | null }> = ({ return ( - + + + ); }; +const PostHogIdentificationWrapper = ({ + children, +}: { + children: ReactNode; +}) => { + const { data: session, status } = useSession(); + const posthog = usePostHog(); + + useEffect(() => { + if (!posthog) return; + if (status === "authenticated") { + const { id, name, email, stripeSubscriptionStatus } = session?.user; + posthog.identify(id, { + name, + email, + stripeSubscriptionStatus, + }); + } else if (status === "unauthenticated") { + posthog.reset(); + } + }, [posthog, session, status]); + + return
{children}
; +}; + export default api.withTRPC(MyApp); From 9ad8bcf507dae85ae4d1fa52fa7a1b368609813e Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 12:52:31 +0200 Subject: [PATCH 02/21] add sign-in and sign-out events --- package-lock.json | 14 +++++++------- package.json | 2 +- src/server/auth.ts | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3275b0f..e871aa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "micro": "^10.0.1", "micro-cors": "^0.1.1", "next": "^13.3.0", - "next-auth": "^4.21.0", + "next-auth": "^4.22.1", "posthog-js": "^1.53.4", "posthog-node": "^3.1.0", "react": "18.2.0", @@ -5350,9 +5350,9 @@ } }, "node_modules/next-auth": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.22.0.tgz", - "integrity": "sha512-08+kjnDoE7aQ52O996x6cwA3ffc2CbHIkrCgLYhbE+aDIJBKI0oA9UbIEIe19/+ODYJgpAHHOtJx4izmsgaVag==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.22.1.tgz", + "integrity": "sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==", "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", @@ -11147,9 +11147,9 @@ } }, "next-auth": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.22.0.tgz", - "integrity": "sha512-08+kjnDoE7aQ52O996x6cwA3ffc2CbHIkrCgLYhbE+aDIJBKI0oA9UbIEIe19/+ODYJgpAHHOtJx4izmsgaVag==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.22.1.tgz", + "integrity": "sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==", "requires": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", diff --git a/package.json b/package.json index 6131e7c..ddcd95e 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "micro": "^10.0.1", "micro-cors": "^0.1.1", "next": "^13.3.0", - "next-auth": "^4.21.0", + "next-auth": "^4.22.1", "posthog-js": "^1.53.4", "posthog-node": "^3.1.0", "react": "18.2.0", diff --git a/src/server/auth.ts b/src/server/auth.ts index d98f7f0..b4e3589 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -9,6 +9,7 @@ import GitHubProvider from "next-auth/providers/github"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { env } from "~/env.mjs"; import { prisma } from "~/server/db"; +import { PostHog } from "posthog-node"; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` @@ -67,6 +68,44 @@ export const authOptions: NextAuthOptions = { * @see https://next-auth.js.org/providers/github */ ], + events: { + async signIn(message) { + const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { + host: env.NEXT_PUBLIC_POSTHOG_HOST, + }); + + client.capture({ + distinctId: message.user.id, + event: "user logged in", + properties: { + provider: message.account?.provider, + isNewUser: message.isNewUser, + }, + }); + + await client.shutdownAsync(); + }, + async signOut(message) { + const session = message.session as unknown as { + id: string; + sessionToken: string; + userId: string; + expires: Date; + }; + if (!session?.userId) return; + + const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { + host: env.NEXT_PUBLIC_POSTHOG_HOST, + }); + + client.capture({ + distinctId: session.userId, + event: "user logged out", + }); + + await client.shutdownAsync(); + }, + }, pages: { signIn: "/sign-in", }, From ba7766f2f693306ac0f7895a9b42909c2b69da27 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 13:13:43 +0200 Subject: [PATCH 03/21] add posthog to trpc context and add view video event --- src/server/api/routers/video.ts | 21 ++++++++++++++++++--- src/server/api/trpc.ts | 2 ++ src/server/posthog.ts | 6 ++++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/server/posthog.ts diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index 7e5ab3f..d952b8e 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -41,8 +41,8 @@ export const videoRouter = createTRPCRouter({ get: publicProcedure .input(z.object({ videoId: z.string() })) .query(async ({ ctx, input }) => { - const { s3 } = ctx; - const video = await ctx.prisma.video.findUnique({ + const { s3, posthog, session, prisma } = ctx; + const video = await prisma.video.findUnique({ where: { id: input.videoId, }, @@ -54,10 +54,25 @@ export const videoRouter = createTRPCRouter({ throw new TRPCError({ code: "NOT_FOUND" }); } - if (video.userId !== ctx?.session?.user.id && !video.sharing) { + if (!session || (video.userId !== session?.user.id && !video.sharing)) { throw new TRPCError({ code: "FORBIDDEN" }); } + posthog.capture({ + distinctId: session.user.id, + event: "viewing video", + properties: { + videoId: video.id, + videoCreatedAt: video.createdAt, + videoUpdatedAt: video.updatedAt, + videoUser: video.user.id, + videoSharing: video.sharing, + videoDeleteAfterLinkExpires: video.delete_after_link_expires, + videoShareLinkExpiresAt: video.shareLinkExpiresAt, + }, + }); + void posthog.shutdownAsync(); + const getObjectCommand = new GetObjectCommand({ Bucket: env.AWS_BUCKET_NAME, Key: video.userId + "/" + video.id, diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 9224b68..904eb5b 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -41,6 +41,7 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => { session: opts.session, prisma, s3, + posthog, stripe, req: opts.req, res: opts.res, @@ -79,6 +80,7 @@ import { ZodError } from "zod"; import { s3 } from "~/server/aws/s3"; import { stripe } from "~/server/stripe"; import { type NextApiRequest, type NextApiResponse } from "next"; +import { posthog } from "~/server/posthog"; const t = initTRPC.context().create({ transformer: superjson, diff --git a/src/server/posthog.ts b/src/server/posthog.ts new file mode 100644 index 0000000..1c9869e --- /dev/null +++ b/src/server/posthog.ts @@ -0,0 +1,6 @@ +import { PostHog } from "posthog-node"; +import { env } from "~/env.mjs"; + +export const posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { + host: env.NEXT_PUBLIC_POSTHOG_HOST, +}); From ea5166c41627dba381f1666cdc9cfd212c179c9e Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 13:17:52 +0200 Subject: [PATCH 04/21] add viewing video list event --- src/server/api/routers/video.ts | 51 ++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index d952b8e..8f17396 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -15,29 +15,40 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { TRPCError } from "@trpc/server"; export const videoRouter = createTRPCRouter({ - getAll: protectedProcedure.query(async ({ ctx }) => { - const videos = await ctx.prisma.video.findMany({ - where: { - userId: ctx.session.user.id, - }, - }); + getAll: protectedProcedure.query( + async ({ ctx: { prisma, session, s3, posthog } }) => { + const videos = await prisma.video.findMany({ + where: { + userId: session.user.id, + }, + }); - const videosWithThumbnailUrl = await Promise.all( - videos.map(async (video) => { - const thumbnailUrl = await getSignedUrl( - ctx.s3, - new GetObjectCommand({ - Bucket: env.AWS_BUCKET_NAME, - Key: video.userId + "/" + video.id + "-thumbnail", - }) - ); + posthog.capture({ + distinctId: session.user.id, + event: "viewing video list", + properties: { + videoAmount: videos.length, + }, + }); + void posthog.shutdownAsync(); - return { ...video, thumbnailUrl }; - }) - ); + const videosWithThumbnailUrl = await Promise.all( + videos.map(async (video) => { + const thumbnailUrl = await getSignedUrl( + s3, + new GetObjectCommand({ + Bucket: env.AWS_BUCKET_NAME, + Key: video.userId + "/" + video.id + "-thumbnail", + }) + ); - return videosWithThumbnailUrl; - }), + return { ...video, thumbnailUrl }; + }) + ); + + return videosWithThumbnailUrl; + } + ), get: publicProcedure .input(z.object({ videoId: z.string() })) .query(async ({ ctx, input }) => { From 9eb531a3e8c2d9f86328ef7f3878ef8b5e18e2fe Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 13:22:46 +0200 Subject: [PATCH 05/21] add video upload events --- src/server/api/routers/video.ts | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index 8f17396..c5202dc 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -95,20 +95,29 @@ export const videoRouter = createTRPCRouter({ }), getUploadUrl: protectedProcedure .input(z.object({ key: z.string() })) - .mutation(async ({ ctx, input }) => { + .mutation(async ({ ctx: { prisma, session, s3, posthog }, input }) => { const { key } = input; - const { s3 } = ctx; - const videos = await ctx.prisma.video.findMany({ + const videos = await prisma.video.findMany({ where: { - userId: ctx.session.user.id, + userId: session.user.id, }, }); if ( videos.length >= 10 && - ctx.session.user.stripeSubscriptionStatus !== "active" + session.user.stripeSubscriptionStatus !== "active" ) { + posthog.capture({ + distinctId: session.user.id, + event: "hit video upload limit", + properties: { + videoAmount: videos.length, + stripeSubscriptionStatus: session.user.stripeSubscriptionStatus, + }, + }); + void posthog.shutdownAsync(); + throw new TRPCError({ code: "FORBIDDEN", message: @@ -116,9 +125,19 @@ export const videoRouter = createTRPCRouter({ }); } - const video = await ctx.prisma.video.create({ + posthog.capture({ + distinctId: session.user.id, + event: "uploading video", + properties: { + videoAmount: videos.length, + stripeSubscriptionStatus: session.user.stripeSubscriptionStatus, + }, + }); + void posthog.shutdownAsync(); + + const video = await prisma.video.create({ data: { - userId: ctx.session.user.id, + userId: session.user.id, title: key, }, }); @@ -127,7 +146,7 @@ export const videoRouter = createTRPCRouter({ s3, new PutObjectCommand({ Bucket: env.AWS_BUCKET_NAME, - Key: ctx.session.user.id + "/" + video.id, + Key: session.user.id + "/" + video.id, }) ); From 78f6005942b854e12dfbb3a1f8824795d90fc13e Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:30:32 +0200 Subject: [PATCH 06/21] add update video setSharing event --- src/server/api/routers/video.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index c5202dc..2c86ef5 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -167,11 +167,11 @@ export const videoRouter = createTRPCRouter({ }), setSharing: protectedProcedure .input(z.object({ videoId: z.string(), sharing: z.boolean() })) - .mutation(async ({ ctx, input }) => { - const updateVideo = await ctx.prisma.video.updateMany({ + .mutation(async ({ ctx: { prisma, session, posthog }, input }) => { + const updateVideo = await prisma.video.updateMany({ where: { id: input.videoId, - userId: ctx.session.user.id, + userId: session.user.id, }, data: { sharing: input.sharing, @@ -182,6 +182,16 @@ export const videoRouter = createTRPCRouter({ throw new TRPCError({ code: "FORBIDDEN" }); } + posthog.capture({ + distinctId: session.user.id, + event: "update video setSharing", + properties: { + videoId: input.videoId, + videoSharing: input.sharing, + }, + }); + void posthog.shutdownAsync(); + return { success: true, updateVideo, From 8f40b1279f63eeabb1749d21cb5bedd29dbb53ce Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:31:45 +0200 Subject: [PATCH 07/21] add update video delete_after_link_expires event --- src/server/api/routers/video.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index 2c86ef5..7db2759 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -201,11 +201,11 @@ export const videoRouter = createTRPCRouter({ .input( z.object({ videoId: z.string(), delete_after_link_expires: z.boolean() }) ) - .mutation(async ({ ctx, input }) => { - const updateVideo = await ctx.prisma.video.updateMany({ + .mutation(async ({ ctx: { prisma, session, posthog }, input }) => { + const updateVideo = await prisma.video.updateMany({ where: { id: input.videoId, - userId: ctx.session.user.id, + userId: session.user.id, }, data: { delete_after_link_expires: input.delete_after_link_expires, @@ -216,6 +216,16 @@ export const videoRouter = createTRPCRouter({ throw new TRPCError({ code: "FORBIDDEN" }); } + posthog.capture({ + distinctId: session.user.id, + event: "update video delete_after_link_expires", + properties: { + videoId: input.videoId, + delete_after_link_expires: input.delete_after_link_expires, + }, + }); + void posthog.shutdownAsync(); + return { success: true, updateVideo, From fe2081bdea9dca39584887a628fdfcfea7773288 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:33:21 +0200 Subject: [PATCH 08/21] add update video shareLinkExpiresAt event --- src/server/api/routers/video.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index 7db2759..5cb3ab2 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -238,11 +238,11 @@ export const videoRouter = createTRPCRouter({ shareLinkExpiresAt: z.nullable(z.date()), }) ) - .mutation(async ({ ctx, input }) => { - const updateVideo = await ctx.prisma.video.updateMany({ + .mutation(async ({ ctx: { prisma, session, posthog }, input }) => { + const updateVideo = await prisma.video.updateMany({ where: { id: input.videoId, - userId: ctx.session.user.id, + userId: session.user.id, }, data: { shareLinkExpiresAt: input.shareLinkExpiresAt, @@ -253,6 +253,16 @@ export const videoRouter = createTRPCRouter({ throw new TRPCError({ code: "FORBIDDEN" }); } + posthog.capture({ + distinctId: session.user.id, + event: "update video shareLinkExpiresAt", + properties: { + videoId: input.videoId, + shareLinkExpiresAt: input.shareLinkExpiresAt, + }, + }); + void posthog.shutdownAsync(); + return { success: true, updateVideo, From 2fe910feec0a64b3d0e1d37be6b5a338ef79c42d Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:34:15 +0200 Subject: [PATCH 09/21] add update video title event --- src/server/api/routers/video.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index 5cb3ab2..0b56a73 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -275,11 +275,11 @@ export const videoRouter = createTRPCRouter({ title: z.string(), }) ) - .mutation(async ({ ctx, input }) => { - const updateVideo = await ctx.prisma.video.updateMany({ + .mutation(async ({ ctx: { prisma, session, posthog }, input }) => { + const updateVideo = await prisma.video.updateMany({ where: { id: input.videoId, - userId: ctx.session.user.id, + userId: session.user.id, }, data: { title: input.title, @@ -290,6 +290,16 @@ export const videoRouter = createTRPCRouter({ throw new TRPCError({ code: "FORBIDDEN" }); } + posthog.capture({ + distinctId: session.user.id, + event: "update video title", + properties: { + videoId: input.videoId, + title: input.title, + }, + }); + void posthog.shutdownAsync(); + return { success: true, updateVideo, From f3603a7240fd1c68fda3321481ee818c7945fd3b Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:36:02 +0200 Subject: [PATCH 10/21] add delete video event --- src/server/api/routers/video.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/server/api/routers/video.ts b/src/server/api/routers/video.ts index 0b56a73..18f260b 100644 --- a/src/server/api/routers/video.ts +++ b/src/server/api/routers/video.ts @@ -311,11 +311,11 @@ export const videoRouter = createTRPCRouter({ videoId: z.string(), }) ) - .mutation(async ({ ctx, input }) => { - const deleteVideo = await ctx.prisma.video.deleteMany({ + .mutation(async ({ ctx: { prisma, session, s3, posthog }, input }) => { + const deleteVideo = await prisma.video.deleteMany({ where: { id: input.videoId, - userId: ctx.session.user.id, + userId: session.user.id, }, }); @@ -323,17 +323,26 @@ export const videoRouter = createTRPCRouter({ throw new TRPCError({ code: "FORBIDDEN" }); } - const deleteVideoObject = await ctx.s3.send( + posthog.capture({ + distinctId: session.user.id, + event: "video delete", + properties: { + videoId: input.videoId, + }, + }); + void posthog.shutdownAsync(); + + const deleteVideoObject = await s3.send( new DeleteObjectCommand({ Bucket: env.AWS_BUCKET_NAME, - Key: ctx.session.user.id + "/" + input.videoId, + Key: session.user.id + "/" + input.videoId, }) ); - const deleteThumbnailObject = await ctx.s3.send( + const deleteThumbnailObject = await s3.send( new DeleteObjectCommand({ Bucket: env.AWS_BUCKET_NAME, - Key: ctx.session.user.id + "/" + input.videoId + "-thumbnail", + Key: session.user.id + "/" + input.videoId + "-thumbnail", }) ); From 2c0a063975e0352512c4efac236b8ec6d6d4cd11 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:45:01 +0200 Subject: [PATCH 11/21] add ShareModal events --- src/components/ShareModal.tsx | 44 ++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/components/ShareModal.tsx b/src/components/ShareModal.tsx index 5b89157..b63dfa3 100644 --- a/src/components/ShareModal.tsx +++ b/src/components/ShareModal.tsx @@ -3,6 +3,7 @@ import { Fragment, useState } from "react"; import { ModernSwitch } from "~/components/ModernSwitch"; import { api, type RouterOutputs } from "~/utils/api"; import ExpireDateSelectMenu from "~/components/ExpireDateSelectMenu"; +import { usePostHog } from "posthog-js/react"; interface Props { video: RouterOutputs["video"]["get"]; @@ -11,6 +12,23 @@ interface Props { export function ShareModal({ video }: Props) { const utils = api.useContext(); const [open, setOpen] = useState(false); + const posthog = usePostHog(); + + const openModal = () => { + setOpen(true); + posthog?.capture("open video share modal", { + videoSharing: video.sharing, + videoId: video.id, + }); + }; + + const closeModal = () => { + setOpen(false); + posthog?.capture("close video share modal", { + videoSharing: video.sharing, + videoId: video.id, + }); + }; const setSharingMutation = api.video.setSharing.useMutation({ onMutate: async ({ videoId, sharing }) => { @@ -58,23 +76,24 @@ export function ShareModal({ video }: Props) { setTimeout(() => { setLinkCopied(false); }, 5000); + + posthog?.capture("public video link copied", { + videoSharing: video.sharing, + videoId: video.id, + }); }; return ( <> setOpen(true)} + onClick={openModal} className="ml-4 cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]" > Share - setOpen(false)} - > + - {/*
*/} - {/* Share link with search engines*/} - {/* console.log("test")}*/} - {/* />*/} - {/*
*/} - {/*
*/} - {/* Embed code*/} - {/* */} - {/*
*/} ) : null} From 51eaa538f1a78e1b8a00653da208e229e32da48d Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:49:31 +0200 Subject: [PATCH 12/21] add profileMenu events --- src/components/ProfileMenu.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/ProfileMenu.tsx b/src/components/ProfileMenu.tsx index a99021e..80248cd 100644 --- a/src/components/ProfileMenu.tsx +++ b/src/components/ProfileMenu.tsx @@ -3,12 +3,26 @@ import { Fragment } from "react"; import { signOut, useSession } from "next-auth/react"; import { useRouter } from "next/router"; import { api } from "~/utils/api"; +import { usePostHog } from "posthog-js/react"; export default function ProfileMenu() { const { mutateAsync: createBillingPortalSession } = api.stripe.createBillingPortalSession.useMutation(); const { push } = useRouter(); const { data: session } = useSession(); + const posthog = usePostHog(); + + const openBillingSettings = () => { + void createBillingPortalSession().then(({ billingPortalUrl }) => { + if (billingPortalUrl) { + void push(billingPortalUrl); + } + }); + + posthog?.capture("billing settings opened", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + }); + }; return ( @@ -34,15 +48,7 @@ export default function ProfileMenu() { {({ active }) => (
{ - void createBillingPortalSession().then( - ({ billingPortalUrl }) => { - if (billingPortalUrl) { - void push(billingPortalUrl); - } - } - ); - }} + onClick={openBillingSettings} className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${ active ? "bg-gray-100" : "" }`} From 9be8efeadfa7b19c2c6001369a3f01c58c20c0ec Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:53:58 +0200 Subject: [PATCH 13/21] add download video event --- src/components/VideoMoreMenu.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/VideoMoreMenu.tsx b/src/components/VideoMoreMenu.tsx index e43c802..da469e8 100644 --- a/src/components/VideoMoreMenu.tsx +++ b/src/components/VideoMoreMenu.tsx @@ -8,6 +8,7 @@ import { TrashIcon, } from "@radix-ui/react-icons"; import { useRouter } from "next/router"; +import { usePostHog } from "posthog-js/react"; interface Props { video: RouterOutputs["video"]["get"]; @@ -18,6 +19,7 @@ export default function VideoMoreMenu({ video }: Props) { const [renameMenuOpen, setRenameMenuOpen] = useState(false); const utils = api.useContext(); const router = useRouter(); + const posthog = usePostHog(); const items = [ { @@ -42,7 +44,10 @@ export default function VideoMoreMenu({ video }: Props) { a.download = video.title; a.click(); }); - //window.location.href = response.url; + }); + + posthog?.capture("download existing video", { + videoId: video.id, }); }, }, From 96296e8b4c971a5f341bef3095d22fdcb090a2e0 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:59:01 +0200 Subject: [PATCH 14/21] add newvideomenu events --- src/components/NewVideoMenu.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/NewVideoMenu.tsx b/src/components/NewVideoMenu.tsx index e311f15..f574a3f 100644 --- a/src/components/NewVideoMenu.tsx +++ b/src/components/NewVideoMenu.tsx @@ -5,22 +5,36 @@ import { useAtom } from "jotai"; import recordVideoModalOpen from "~/atoms/recordVideoModalOpen"; import paywallAtom from "~/atoms/paywallAtom"; import { useSession } from "next-auth/react"; +import { usePostHog } from "posthog-js/react"; export default function NewVideoMenu() { const [, setRecordOpen] = useAtom(recordVideoModalOpen); const [, setUploadOpen] = useAtom(uploadVideoModalOpen); const [, setPaywallOpen] = useAtom(paywallAtom); const { data: session } = useSession(); + const posthog = usePostHog(); const openRecordModal = () => { setRecordOpen(true); + + posthog?.capture("open record video modal", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + }); }; const openUploadModal = () => { if (session?.user.stripeSubscriptionStatus === "active") { setUploadOpen(true); + + posthog?.capture("open upload video modal", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + }); } else { setPaywallOpen(true); + + posthog?.capture("hit video upload paywall", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + }); } }; From b55f2e98ea6c655fb29fc4df8734e4323313a39d Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:02:10 +0200 Subject: [PATCH 15/21] add empty video list page CTA events --- src/pages/videos.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/pages/videos.tsx b/src/pages/videos.tsx index 2365cdd..03e5766 100644 --- a/src/pages/videos.tsx +++ b/src/pages/videos.tsx @@ -16,6 +16,7 @@ import uploadVideoModalOpen from "~/atoms/uploadVideoModalOpen"; import recordVideoModalOpen from "~/atoms/recordVideoModalOpen"; import Paywall from "~/components/Paywall"; import paywallAtom from "~/atoms/paywallAtom"; +import { usePostHog } from "posthog-js/react"; const VideoList: NextPage = () => { const [, setRecordOpen] = useAtom(recordVideoModalOpen); @@ -24,6 +25,7 @@ const VideoList: NextPage = () => { const router = useRouter(); const { status, data: session } = useSession(); const { data: videos, isLoading } = api.video.getAll.useQuery(); + const posthog = usePostHog(); if (status === "unauthenticated") { void router.replace("/sign-in"); @@ -31,13 +33,28 @@ const VideoList: NextPage = () => { const openRecordModal = () => { setRecordOpen(true); + + posthog?.capture("open record video modal", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + cta: "empty video list page", + }); }; const openUploadModal = () => { if (session?.user.stripeSubscriptionStatus === "active") { setUploadOpen(true); + + posthog?.capture("open upload video modal", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + cta: "empty video list page", + }); } else { setPaywallOpen(true); + + posthog?.capture("hit video upload paywall", { + stripeSubscriptionStatus: session?.user.stripeSubscriptionStatus, + cta: "empty video list page", + }); } }; From abd0a03cd0c3d295820e7e7f751be7f12987e2cf Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:16:30 +0200 Subject: [PATCH 16/21] add recorder events --- src/components/Recorder.tsx | 20 +++++++++++++++++++- src/components/VideoRecordModal.tsx | 4 ++++ src/components/VideoUploadModal.tsx | 8 ++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index 40bec3b..15e1718 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -15,6 +15,7 @@ import { TRPCClientError } from "@trpc/client"; import { useAtom } from "jotai/index"; import paywallAtom from "~/atoms/paywallAtom"; import recordVideoModalOpen from "~/atoms/recordVideoModalOpen"; +import { usePostHog } from "posthog-js/react"; interface Props { closeModal: () => void; @@ -45,6 +46,7 @@ export default function Recorder({ closeModal, step, setStep }: Props) { const [duration, setDuration] = useState(0); const [, setPaywallOpen] = useAtom(paywallAtom); const videoRef = useRef(null); + const posthog = usePostHog(); const handleRecording = async () => { const screenStream = await navigator.mediaDevices.getDisplayMedia({ @@ -80,6 +82,8 @@ export default function Recorder({ closeModal, step, setStep }: Props) { recorderRef.current.startRecording(); setStep("in"); + + posthog?.capture("recorder: start video recording"); }; const handleStop = () => { @@ -98,6 +102,8 @@ export default function Recorder({ closeModal, step, setStep }: Props) { }); setStep("post"); + + posthog?.capture("recorder: video recording finished"); }; const handleDelete = () => { @@ -109,6 +115,8 @@ export default function Recorder({ closeModal, step, setStep }: Props) { closeModal(); setStep("pre"); + + posthog?.capture("recorder: video deleted"); }; const handlePause = () => { @@ -119,6 +127,7 @@ export default function Recorder({ closeModal, step, setStep }: Props) { } else { recorderRef.current.pauseRecording(); } + posthog?.capture("recorder: recording paused/resumed", { pause }); setPause(!pause); } }; @@ -146,6 +155,8 @@ export default function Recorder({ closeModal, step, setStep }: Props) { "Recording - " + dayjs().format("D MMM YYYY") + ".webm"; invokeSaveAsDialog(blob, dateString); } + + posthog?.capture("recorder: video downloaded"); }; const generateThumbnail = async (video: HTMLVideoElement) => { @@ -187,6 +198,7 @@ export default function Recorder({ closeModal, step, setStep }: Props) { .then(() => { void router.push("share/" + id); setRecordOpen(false); + posthog?.capture("recorder: video uploaded"); }) .catch((err) => { console.error(err); @@ -197,6 +209,7 @@ export default function Recorder({ closeModal, step, setStep }: Props) { err.message === "Sorry, you have reached the maximum video upload limit on our free tier. Please upgrade to upload more." ) { + posthog?.capture("recorder: video upload paywall hit"); setPaywallOpen(true); } } else { @@ -325,6 +338,8 @@ export default function Recorder({ closeModal, step, setStep }: Props) {