diff --git a/packages/features/ee/event-tracking/lib/posthog/provider.tsx b/packages/features/ee/event-tracking/lib/posthog/provider.tsx index c4277138af..2cb6e29c8f 100644 --- a/packages/features/ee/event-tracking/lib/posthog/provider.tsx +++ b/packages/features/ee/event-tracking/lib/posthog/provider.tsx @@ -2,17 +2,25 @@ import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; function Provider({ children }: { children: React.ReactNode }) { + const initializeOnce = useRef(false); + useEffect(() => { - if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return; + if (!process.env.NEXT_PUBLIC_POSTHOG_KEY || initializeOnce.current) return; + + initializeOnce.current = true; + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com", autocapture: false, persistence: "memory", - request_batching: false, - // Enable debug mode in development + request_batching: true, + capture_pageview: false, + capture_pageleave: false, + disable_session_recording: true, + advanced_disable_decide: true, loaded: (posthog) => { if (process.env.NODE_ENV === "development") posthog.debug(); }, diff --git a/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx b/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx index 4722c790dc..ab9111d0d3 100644 --- a/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx +++ b/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx @@ -1,6 +1,7 @@ // eslint-disable-next-line no-restricted-imports -import { noop } from "lodash"; +import type { PostHog } from "posthog-js"; import { usePostHog as usePostHogLib } from "posthog-js/react"; +import { useEffect, useRef } from "react"; import { z } from "zod"; import dayjs from "@calcom/dayjs"; @@ -8,26 +9,31 @@ import { WEBSITE_URL } from "@calcom/lib/constants"; import { useHasTeamPlan, useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan"; import { trpc } from "@calcom/trpc/react"; -// eslint-disable-next-line turbo/no-undeclared-env-vars export const isPostHogEnabled = z.string().min(1).safeParse(process.env.NEXT_PUBLIC_POSTHOG_KEY).success; -type PostHogHook = { +// Define the return type for our hook +type PostHogMethods = { capture: (event: string, properties?: Record) => void; - identify: (distinctId: string, properties?: Record) => void; + identify: (distinctId?: string, properties?: Record) => void; reset: () => void; }; -const usePostHogHook: () => PostHogHook = isPostHogEnabled +// Internal hook type that includes the methods we need +type InternalPostHogHook = Pick; + +const usePostHogHook: () => InternalPostHogHook = isPostHogEnabled ? usePostHogLib : () => ({ - identify: noop, - capture: noop, - reset: noop, + identify: () => undefined, + capture: () => undefined, + reset: () => undefined, + register: () => undefined, + get_distinct_id: () => "", }); -const usePostHog = () => { +const usePostHog = (): PostHogMethods => { const posthog = usePostHogHook(); - const { data } = trpc.viewer.me.useQuery(); + const { data: userData } = trpc.viewer.me.useQuery(); const { data: statsData } = trpc.viewer.myStats.useQuery(undefined, { trpc: { context: { @@ -37,37 +43,67 @@ const usePostHog = () => { }); const { hasPaidPlan } = useHasPaidPlan(); const { hasTeamPlan } = useHasTeamPlan(); + const lastPropertiesString = useRef(null); - const identify = async () => { - if (!data || !statsData) return; + useEffect(() => { + if (!userData || !statsData) return; - posthog.identify(String(data.id), { - distinctId: String(data.id), - ...(data && data?.name && { name: data.name }), - ...(data && data?.email && { email: data.email }), - created_at: String(dayjs(data?.createdDate).unix()), - //keys should be snake cased - user_name: data?.username, - link: `${WEBSITE_URL}/${data?.username}`, - identity_provider: data?.identityProvider, - timezone: data?.timeZone, - locale: data?.locale, + const properties = { + name: userData.name, + email: userData.email, + created_at: String(dayjs(userData.createdDate).unix()), + user_name: userData.username, + link: `${WEBSITE_URL}/${userData.username}`, + identity_provider: userData.identityProvider, + timezone: userData.timeZone, + locale: userData.locale, has_paid_plan: hasPaidPlan, has_team_plan: hasTeamPlan, - metadata: data?.metadata, - completed_onboarding: data?.completedOnboarding, + completed_onboarding: userData.completedOnboarding, + has_orgs_plan: !!userData.organizationId, + organization: userData.organization?.slug, + is_premium: userData.isPremium, sum_of_bookings: statsData?.sumOfBookings, sum_of_calendars: statsData?.sumOfCalendars, sum_of_teams: statsData?.sumOfTeams, - has_orgs_plan: !!data?.organizationId, - organization: data?.organization?.slug, sum_of_event_types: statsData?.sumOfEventTypes, sum_of_team_event_types: statsData?.sumOfTeamEventTypes, - is_premium: data?.isPremium, - }); - }; + }; - return { identify, capture: posthog.capture, reset: posthog.reset }; + // Clean properties + const cleanProperties = Object.fromEntries(Object.entries(properties).filter(([_, v]) => v != null)); + + // Create a string representation of the properties for comparison + const propertiesString = JSON.stringify(cleanProperties); + + // Only set properties if they've changed + if (propertiesString !== lastPropertiesString.current) { + lastPropertiesString.current = propertiesString; + // Use register instead of identify to avoid duplicate calls + posthog.register(cleanProperties); + // Only identify once + if (!posthog.get_distinct_id()) { + posthog.identify(String(userData.id)); + } + } + }, [userData, statsData, hasPaidPlan, hasTeamPlan, posthog]); + + return { + capture: (event: string, properties?: Record) => { + if (posthog.get_distinct_id()) { + posthog.capture(event, properties); + } + }, + identify: (distinctId?: string, properties?: Record) => { + if (distinctId) { + posthog.identify(distinctId, properties); + } + }, + reset: () => { + lastPropertiesString.current = null; + posthog.reset(); + }, + }; }; export default usePostHog;