From ddfbe259036d50b357e3955282796ac9e804b0db Mon Sep 17 00:00:00 2001 From: abhi-slash-git <99098806+abhi-slash-git@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:46:25 -0500 Subject: [PATCH] feat: adding posthog libs for node + js (#16862) * adding posthog libs for node + js * updating lock * move to init posthog client - pageview component * adding identify in shell * removing test event captures * add signout tracking to dropdown as it was missed in merge --------- Co-authored-by: sean Co-authored-by: Bailey Pumfleet --- .env.example | 5 ++ apps/web/lib/app-providers-app-dir.tsx | 5 +- apps/web/lib/app-providers.tsx | 16 ++++- apps/web/modules/signup-view.tsx | 6 +- apps/web/package.json | 2 + .../features/auth/lib/next-auth-options.ts | 43 +++++++----- packages/features/bookings/Booker/Booker.tsx | 4 +- .../lib/posthog/postHogClient.ts | 40 +++++++++++ .../event-tracking/lib/posthog/provider.tsx | 25 +++++++ .../lib/posthog/providerDynamic.tsx | 14 ++++ .../lib/posthog/userPostHog.tsx | 66 +++++++++++++++++++ .../lib/posthog/web/PostHogPageView.tsx | 25 +++++++ .../components/CreateEventTypeDialog.tsx | 4 ++ packages/features/insights/filters/index.tsx | 2 +- .../appDir/SettingsLayoutAppDirClient.tsx | 14 ++-- .../settings/layouts/SettingsLayout.tsx | 14 ++-- packages/features/shell/Shell.tsx | 21 ++++-- .../shell/user-dropdown/UserDropdown.tsx | 8 ++- packages/types/environment.d.ts | 2 + turbo.json | 2 + 20 files changed, 271 insertions(+), 47 deletions(-) create mode 100644 packages/features/ee/event-tracking/lib/posthog/postHogClient.ts create mode 100644 packages/features/ee/event-tracking/lib/posthog/provider.tsx create mode 100644 packages/features/ee/event-tracking/lib/posthog/providerDynamic.tsx create mode 100644 packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx create mode 100644 packages/features/ee/event-tracking/lib/posthog/web/PostHogPageView.tsx diff --git a/.env.example b/.env.example index 5eec893084..40a8a67df2 100644 --- a/.env.example +++ b/.env.example @@ -102,6 +102,11 @@ NEXT_PUBLIC_INTERCOM_APP_ID= # Secret to enable Intercom Identity Verification INTERCOM_SECRET= +# Posthog Config +NEXT_PUBLIC_POSTHOG_KEY= + +NEXT_PUBLIC_POSTHOG_HOST= + # Zendesk Config NEXT_PUBLIC_ZENDESK_KEY= diff --git a/apps/web/lib/app-providers-app-dir.tsx b/apps/web/lib/app-providers-app-dir.tsx index abfe2aec22..28c60de233 100644 --- a/apps/web/lib/app-providers-app-dir.tsx +++ b/apps/web/lib/app-providers-app-dir.tsx @@ -14,6 +14,7 @@ import type { ReactNode } from "react"; import { useEffect } from "react"; import CacheProvider from "react-inlinesvg/provider"; +import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic"; import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider"; import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic"; import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic"; @@ -299,7 +300,9 @@ const AppProviders = (props: PageWrapperProps) => { return ( - {RemainingProviders} + + {RemainingProviders} + ); }; diff --git a/apps/web/lib/app-providers.tsx b/apps/web/lib/app-providers.tsx index 5d794d975c..3b11aee2b2 100644 --- a/apps/web/lib/app-providers.tsx +++ b/apps/web/lib/app-providers.tsx @@ -7,11 +7,13 @@ import type { SSRConfig } from "next-i18next"; import { appWithTranslation } from "next-i18next"; import { ThemeProvider } from "next-themes"; import type { AppProps as NextAppProps, AppProps as NextJsAppProps } from "next/app"; +import dynamic from "next/dynamic"; import type { ParsedUrlQuery } from "querystring"; import type { PropsWithChildren, ReactNode } from "react"; import { useEffect } from "react"; import CacheProvider from "react-inlinesvg/provider"; +import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic"; import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider"; import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic"; import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic"; @@ -56,6 +58,13 @@ export type AppProps = Omit< err?: Error; }; +const PostHogPageView = dynamic( + () => import("@calcom/features/ee/event-tracking/lib/posthog/web/PostHogPageView"), + { + ssr: false, + } +); + type AppPropsWithChildren = AppProps & { children: ReactNode; }; @@ -319,7 +328,12 @@ const AppProviders = (props: AppPropsWithChildren) => { return ( - {RemainingProviders} + + + + {RemainingProviders} + + ); }; diff --git a/apps/web/modules/signup-view.tsx b/apps/web/modules/signup-view.tsx index d37d4677ec..29404cc1fb 100644 --- a/apps/web/modules/signup-view.tsx +++ b/apps/web/modules/signup-view.tsx @@ -624,7 +624,7 @@ export default function Signup({ -
+
{IS_CALCOM && ( <>
@@ -675,7 +675,7 @@ export default function Signup({
)} -
+
Cal.com Booking Page {FEATURES.map((feature) => ( <> -
+
{t(feature.title)} diff --git a/apps/web/package.json b/apps/web/package.json index 2687229f0f..6ede7a5a3a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -109,6 +109,8 @@ "nodemailer": "^6.7.8", "nuqs": "^1.20.0", "otplib": "^12.0.1", + "posthog-js": "^1.164.1", + "posthog-node": "^4.2.0", "qrcode": "^1.5.1", "raw-body": "^2.5.1", "react": "^18.2.0", diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index 296d536558..3feb979677 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -10,6 +10,7 @@ import GoogleProvider from "next-auth/providers/google"; import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService"; import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg"; +import postHogClient from "@calcom/features/ee/event-tracking/lib/posthog/postHogClient"; import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider"; import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml"; @@ -947,24 +948,32 @@ export const getOptions = ({ // this is a workaround – in the future once we move to use the Account model in the DB // we should use NextAuth's isNewUser flag instead: https://next-auth.js.org/configuration/events#signin const isNewUser = new Date(user.createdDate) > new Date(Date.now() - 10 * 60 * 1000); - if ((isENVDev || IS_CALCOM) && process.env.DUB_API_KEY && isNewUser) { - const clickId = getDubId(); - // check if there's a clickId (dub_id) cookie set by @dub/analytics - if (clickId) { - // here we use waitUntil – meaning this code will run async to not block the main thread - waitUntil( - // if so, send a lead event to Dub - // @see https://d.to/conversions/next-auth - dub.track.lead({ - clickId, - eventName: "Sign Up", - customerId: user.id.toString(), - customerName: user.name, - customerEmail: user.email, - customerAvatar: user.image, - }) - ); + if ((isENVDev || IS_CALCOM) && isNewUser) { + if (process.env.DUB_API_KEY) { + const clickId = getDubId(); + // check if there's a clickId (dub_id) cookie set by @dub/analytics + if (clickId) { + // here we use waitUntil – meaning this code will run async to not block the main thread + waitUntil( + // if so, send a lead event to Dub + // @see https://d.to/conversions/next-auth + dub.track.lead({ + clickId, + eventName: "Sign Up", + customerId: user.id.toString(), + customerName: user.name, + customerEmail: user.email, + customerAvatar: user.image, + }) + ); + } } + + postHogClient().capture(user.id.toString(), "Sign Up", { + email: user.email, + name: user.name, + username: user.username, + }); } }, }, diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx index 6f4090b315..032bbfeba6 100644 --- a/packages/features/bookings/Booker/Booker.tsx +++ b/packages/features/bookings/Booker/Booker.tsx @@ -275,7 +275,7 @@ const BookerComponent = ({ data-testid="booker-container" className={classNames( ...getBookerSizeClassNames(layout, bookerState, hideEventTypeDetails), - `bg-default dark:bg-muted grid max-w-full items-start dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row`, + `bg-default dark:bg-muted grid max-w-full items-start sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row dark:[color-scheme:dark]`, // We remove border only when the content covers entire viewport. Because in embed, it can almost never be the case that it covers entire viewport, we show the border there (layout === BookerLayouts.MONTH_VIEW || isEmbed) && "border-subtle rounded-md", !isEmbed && "sm:transition-[width] sm:duration-300", @@ -411,7 +411,7 @@ const BookerComponent = ({ layout === BookerLayouts.COLUMN_VIEW } className={classNames( - "border-subtle rtl:border-default flex h-full w-full flex-col overflow-x-auto px-5 py-3 pb-0 rtl:border-r ltr:md:border-l", + "border-subtle rtl:border-default flex h-full w-full flex-col overflow-x-auto px-5 py-3 pb-0 ltr:md:border-l rtl:border-r", layout === BookerLayouts.MONTH_VIEW && "h-full overflow-hidden md:w-[var(--booker-timeslots-width)]", layout !== BookerLayouts.MONTH_VIEW && "sticky top-0" diff --git a/packages/features/ee/event-tracking/lib/posthog/postHogClient.ts b/packages/features/ee/event-tracking/lib/posthog/postHogClient.ts new file mode 100644 index 0000000000..f85d1e23b6 --- /dev/null +++ b/packages/features/ee/event-tracking/lib/posthog/postHogClient.ts @@ -0,0 +1,40 @@ +// eslint-disable-next-line no-restricted-imports +import { noop } from "lodash"; +import { PostHog, type PostHog as PostHogType } from "posthog-node"; + +let postHog: { + capture: PostHogType["capture"]; + identify: PostHogType["identify"]; +}; + +// eslint-disable-next-line turbo/no-undeclared-env-vars +if (process.env.NEXT_PUBLIC_POSTHOG_KEY) { + // eslint-disable-next-line turbo/no-undeclared-env-vars + postHog = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + // eslint-disable-next-line turbo/no-undeclared-env-vars + host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + flushAt: 1, + }); +} else { + postHog = { + capture: noop, + identify: noop, + }; +} + +export function postHogClient() { + function capture(distinctId: string, event: string, properties?: Record) { + postHog?.capture({ distinctId, event, properties }); + } + + function identify(distinctId: string, properties?: Record) { + postHog?.identify({ distinctId, properties }); + } + + return { + capture, + identify, + }; +} + +export default postHogClient; diff --git a/packages/features/ee/event-tracking/lib/posthog/provider.tsx b/packages/features/ee/event-tracking/lib/posthog/provider.tsx new file mode 100644 index 0000000000..c4277138af --- /dev/null +++ b/packages/features/ee/event-tracking/lib/posthog/provider.tsx @@ -0,0 +1,25 @@ +"use client"; + +import posthog from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; +import { useEffect } from "react"; + +function Provider({ children }: { children: React.ReactNode }) { + useEffect(() => { + if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return; + 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 + loaded: (posthog) => { + if (process.env.NODE_ENV === "development") posthog.debug(); + }, + }); + }, []); + + return {children}; +} + +export default Provider; diff --git a/packages/features/ee/event-tracking/lib/posthog/providerDynamic.tsx b/packages/features/ee/event-tracking/lib/posthog/providerDynamic.tsx new file mode 100644 index 0000000000..1aba44d170 --- /dev/null +++ b/packages/features/ee/event-tracking/lib/posthog/providerDynamic.tsx @@ -0,0 +1,14 @@ +import dynamic from "next/dynamic"; +import { Fragment } from "react"; + +const initPostProvider = () => { + // eslint-disable-next-line turbo/no-undeclared-env-vars + if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) { + return Fragment; + } + + return dynamic(() => import("./provider")); +}; + +const DynamicPostHogProvider = initPostProvider(); +export default DynamicPostHogProvider; diff --git a/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx b/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx new file mode 100644 index 0000000000..2c66d58266 --- /dev/null +++ b/packages/features/ee/event-tracking/lib/posthog/userPostHog.tsx @@ -0,0 +1,66 @@ +// eslint-disable-next-line no-restricted-imports +import { noop } from "lodash"; +import { usePostHog as usePostHogLib } from "posthog-js/react"; +import { z } from "zod"; + +import dayjs from "@calcom/dayjs"; +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 = { + capture: (event: string, properties?: Record) => void; + identify: (distinctId: string, properties?: Record) => void; + reset: () => void; +}; + +const usePostHogHook: () => PostHogHook = isPostHogEnabled + ? usePostHogLib + : () => ({ + identify: noop, + capture: noop, + reset: noop, + }); + +const usePostHog = () => { + const posthog = usePostHogHook(); + const { data } = trpc.viewer.me.useQuery(); + const { hasPaidPlan } = useHasPaidPlan(); + const { hasTeamPlan } = useHasTeamPlan(); + + const identify = async () => { + if (!data) 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, + has_paid_plan: hasPaidPlan, + has_team_plan: hasTeamPlan, + metadata: data?.metadata, + completed_onboarding: data?.completedOnboarding, + sum_of_bookings: data?.sumOfBookings, + sum_of_calendars: data?.sumOfCalendars, + sum_of_teams: data?.sumOfTeams, + has_orgs_plan: !!data?.organizationId, + organization: data?.organization?.slug, + sum_of_event_types: data?.sumOfEventTypes, + sum_of_team_event_types: data?.sumOfTeamEventTypes, + is_premium: data?.isPremium, + }); + }; + + return { identify, capture: posthog.capture, reset: posthog.reset }; +}; + +export default usePostHog; diff --git a/packages/features/ee/event-tracking/lib/posthog/web/PostHogPageView.tsx b/packages/features/ee/event-tracking/lib/posthog/web/PostHogPageView.tsx new file mode 100644 index 0000000000..9bf99fcf73 --- /dev/null +++ b/packages/features/ee/event-tracking/lib/posthog/web/PostHogPageView.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { usePathname, useSearchParams } from "next/navigation"; +import { usePostHog } from "posthog-js/react"; +import { useEffect } from "react"; + +export default function PostHogPageView() { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const posthog = usePostHog(); + useEffect(() => { + // Track pageviews + if (pathname && posthog) { + let url = window.origin + pathname; + if (searchParams.toString()) { + url = `${url}?${searchParams.toString()}`; + } + posthog.capture("$pageview", { + $current_url: url, + }); + } + }, [pathname, searchParams, posthog]); + + return null; +} diff --git a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx index 7f63c753ef..5f49ff80ca 100644 --- a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx +++ b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx @@ -14,6 +14,8 @@ import { Button, Dialog, DialogClose, DialogContent, DialogFooter, showToast } f import CreateEventTypeForm from "./CreateEventTypeForm"; +import usePostHog from "../../ee/event-tracking/lib/posthog/userPostHog"; + // this describes the uniform data needed to create a new event type on Profile or Team export interface EventTypeParent { teamId: number | null | undefined; // if undefined, then it's a profile @@ -60,6 +62,7 @@ export default function CreateEventTypeDialog({ membershipRole: MembershipRole | null | undefined; }[]; }) { + const postHog = usePostHog(); const { t } = useLocale(); const router = useRouter(); const orgBranding = useOrgBranding(); @@ -137,6 +140,7 @@ export default function CreateEventTypeDialog({ form={form} isManagedEventType={isManagedEventType} handleSubmit={(values) => { + postHog.capture("Event Created Frontend"); createMutation.mutate(values); }} SubmitButton={SubmitButton} diff --git a/packages/features/insights/filters/index.tsx b/packages/features/insights/filters/index.tsx index 0cb29db884..ff35e54804 100644 --- a/packages/features/insights/filters/index.tsx +++ b/packages/features/insights/filters/index.tsx @@ -23,7 +23,7 @@ const ClearFilters = () => { color="secondary" target="_blank" rel="noreferrer" - className="min-w-24 h-[38px] border-0" + className="h-[38px] min-w-24 border-0" onClick={() => { clearFilters(); }}> diff --git a/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx b/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx index 2ff2f87b5a..d9bd8ba74b 100644 --- a/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx +++ b/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx @@ -228,9 +228,9 @@ const BackButtonInSidebar = ({ name }: { name: string }) => { data-testid={`vertical-tab-${name}`}> - + {name} @@ -313,7 +313,7 @@ const TeamListCollapsible = () => { {!team.parentId && ( {team.name )} @@ -463,7 +463,7 @@ const SettingsSidebarContainer = ({ {tab && tab.icon && ( )} {!tab.icon && tab?.avatar && ( @@ -508,7 +508,7 @@ const SettingsSidebarContainer = ({ {tab && tab.icon && ( )} )} )} diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index 058a5d927d..b85a57f409 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -227,9 +227,9 @@ const BackButtonInSidebar = ({ name }: { name: string }) => { data-testid={`vertical-tab-${name}`}> - + {name} @@ -310,7 +310,7 @@ const TeamListCollapsible = () => { {!team.parentId && ( {team.name )} @@ -457,7 +457,7 @@ const SettingsSidebarContainer = ({ {tab && tab.icon && ( )} {!tab.icon && tab?.avatar && ( @@ -502,7 +502,7 @@ const SettingsSidebarContainer = ({ {tab && tab.icon && ( )} )} )} diff --git a/packages/features/shell/Shell.tsx b/packages/features/shell/Shell.tsx index 932c7860c9..eee2479cdb 100644 --- a/packages/features/shell/Shell.tsx +++ b/packages/features/shell/Shell.tsx @@ -1,7 +1,7 @@ import { useSession } from "next-auth/react"; import { usePathname, useRouter } from "next/navigation"; import type { Dispatch, ReactElement, ReactNode, SetStateAction } from "react"; -import React, { cloneElement, Fragment } from "react"; +import React, { cloneElement, useEffect } from "react"; import { Toaster } from "react-hot-toast"; import { useRedirectToLoginIfUnauthenticated } from "@calcom/features/auth/lib/hooks/useRedirectToLoginIfUnauthenticated"; @@ -16,6 +16,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { ButtonState, useNotifications } from "@calcom/lib/hooks/useNotifications"; import { Button, ErrorBoundary, HeadSeo, SkeletonText } from "@calcom/ui"; +import usePostHog from "../ee/event-tracking/lib/posthog/userPostHog"; import { SideBarContainer } from "./SideBar"; import { TopNavContainer } from "./TopNav"; import { BannerContainer } from "./banners/LayoutBanner"; @@ -26,13 +27,19 @@ import { useAppTheme } from "./useAppTheme"; const Layout = (props: LayoutProps) => { const { banners, bannersHeight } = useBanners(); const pathname = usePathname(); - + const postHog = usePostHog(); const isFullPageWithoutSidebar = pathname?.startsWith("/apps/routing-forms/reporting/"); const pageTitle = typeof props.heading === "string" && !props.title ? props.heading : props.title; useBootIntercom(); useFormbricks(); + useEffect(() => { + if (!props.isPublic) { + postHog.identify(); + } + }, [props.isPublic, postHog]); + return ( <> {!props.withoutSeo && ( @@ -47,7 +54,7 @@ const Layout = (props: LayoutProps) => { -
+
{banners && !props.isPlatformUser && !isFullPageWithoutSidebar && ( )} @@ -58,7 +65,7 @@ const Layout = (props: LayoutProps) => { ) : ( )} -
+
@@ -172,7 +179,7 @@ export function ShellMain(props: LayoutProps) { {props.heading && (

@@ -180,7 +187,7 @@ export function ShellMain(props: LayoutProps) {

)} {props.subtitle && ( -

+

{!isLocaleReady ? : props.subtitle}

)} @@ -236,7 +243,7 @@ function MainContainer({ ...props }: LayoutProps) { return ( -
+
{/* show top navigation for md and smaller (tablet and phones) */} {TopNavContainerProp}
diff --git a/packages/features/shell/user-dropdown/UserDropdown.tsx b/packages/features/shell/user-dropdown/UserDropdown.tsx index 8539497728..dfdb070a66 100644 --- a/packages/features/shell/user-dropdown/UserDropdown.tsx +++ b/packages/features/shell/user-dropdown/UserDropdown.tsx @@ -21,6 +21,7 @@ import { // TODO (Platform): we shouldnt be importing from web here import { useGetUserAttributes } from "@calcom/web/components/settings/platform/hooks/useGetUserAttributes"; +import usePostHog from "../../ee/event-tracking/lib/posthog/userPostHog"; import FreshChatProvider from "../../ee/support/lib/freshchat/FreshChatProvider"; interface UserDropdownProps { @@ -31,6 +32,7 @@ export function UserDropdown({ small }: UserDropdownProps) { const { t } = useLocale(); const { data: user } = useMeQuery(); const pathname = usePathname(); + const posthog = usePostHog(); const isPlatformPages = pathname?.startsWith("/settings/platform"); useEffect(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -204,7 +206,11 @@ export function UserDropdown({ small }: UserDropdownProps) { type="button" StartIcon="log-out" aria-hidden="true" - onClick={() => signOut({ callbackUrl: "/auth/logout" })}> + onClick={() => { + posthog.capture("sign_out"); + posthog.reset(); + signOut({ callbackUrl: "/auth/logout" }); + }}> {t("sign_out")} diff --git a/packages/types/environment.d.ts b/packages/types/environment.d.ts index b01eda5672..491cdeedf6 100644 --- a/packages/types/environment.d.ts +++ b/packages/types/environment.d.ts @@ -33,6 +33,8 @@ declare namespace NodeJS { readonly PAYMENT_FEE_PERCENTAGE: number | undefined; readonly PAYMENT_FEE_FIXED: number | undefined; readonly NEXT_PUBLIC_INTERCOM_APP_ID: string | undefined; + readonly NEXT_PUBLIC_POSTHOG_KEY: string | undefined; + readonly NEXT_PUBLIC_POSTHOG_HOST: string | undefined; readonly TANDEM_CLIENT_ID: string | undefined; readonly TANDEM_CLIENT_SECRET: string | undefined; readonly TANDEM_BASE_URL: string | undefined; diff --git a/turbo.json b/turbo.json index f8e7b45b8a..a76a271955 100644 --- a/turbo.json +++ b/turbo.json @@ -456,6 +456,8 @@ "NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED", "NEXT_PUBLIC_API_V2_ROOT_URL", "NEXT_PUBLIC_VAPID_PUBLIC_KEY", + "NEXT_PUBLIC_POSTHOG_KEY", + "NEXT_PUBLIC_POSTHOG_HOST", "VAPID_PRIVATE_KEY", "HUDDLE01_API_TOKEN" ]