diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index b049e0b7..47f6f351 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,11 +1,11 @@ import '@livekit/components-styles' import '@/styles/index.css' -import { Suspense, useEffect } from 'react' +import { Suspense } from 'react' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { QueryClientProvider } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useLang } from 'hoofd' -import { Switch, Route, useLocation } from 'wouter' +import { Switch, Route } from 'wouter' import { I18nProvider } from 'react-aria-components' import { Layout } from './layout/Layout' import { NotFoundScreen } from './components/NotFoundScreen' @@ -13,28 +13,16 @@ import { routes } from './routes' import './i18n/init' import { silenceLiveKitLogs } from '@/utils/livekit.ts' import { queryClient } from '@/api/queryClient' -import posthog from 'posthog-js' +import { useAnalytics } from '@/features/analytics/hooks/useAnalytics' function App() { const { i18n } = useTranslation() - const [location] = useLocation() useLang(i18n.language) const isProduction = import.meta.env.PROD silenceLiveKitLogs(isProduction) - // We're on a free tier, so we need to limit the number of events we send to PostHog. - // Be frugal with event tracking, even though we could filter them out later if necessary. - if (isProduction) { - posthog.init('phc_RPYko028Oqtj0c9exLIWwrlrjLxSdxT0ntW0Lam4iom', { - api_host: 'https://eu.i.posthog.com', - person_profiles: 'always', - }) - } - - useEffect(() => { - posthog.capture('$pageview') - }, [location]) + useAnalytics() return ( diff --git a/src/frontend/src/features/analytics/hooks/useAnalytics.tsx b/src/frontend/src/features/analytics/hooks/useAnalytics.tsx new file mode 100644 index 00000000..23f213bd --- /dev/null +++ b/src/frontend/src/features/analytics/hooks/useAnalytics.tsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react' +import { useLocation } from 'wouter' +import posthog from 'posthog-js' +import { ApiUser } from '@/features/auth/api/ApiUser' + +const ANALYTICS_ID = 'phc_RPYko028Oqtj0c9exLIWwrlrjLxSdxT0ntW0Lam4iom' +const ANALYTICS_HOST = 'https://eu.i.posthog.com' + +export const startAnalyticsSession = (data: ApiUser) => { + if (posthog._isIdentified()) return + const { id, email } = data + posthog.identify(id, { email }) +} + +export const terminateAnalyticsSession = () => { + if (!posthog._isIdentified()) return + posthog.reset() +} + +export const useAnalytics = () => { + const [location] = useLocation() + + const isProduction = import.meta.env.PROD + + useEffect(() => { + // We're on a free tier, so we need to limit the number of events we send to PostHog. + // Be frugal with event tracking, even though we could filter them out later if necessary. + if (!isProduction) return + if (posthog.__loaded) return + posthog.init(ANALYTICS_ID, { + api_host: ANALYTICS_HOST, + person_profiles: 'always', + }) + }, [isProduction]) + + // From PostHog tutorial on PageView tracking in a Single Page Application (SPA) context. + useEffect(() => { + posthog.capture('$pageview') + }, [location]) + + return null +} diff --git a/src/frontend/src/features/auth/api/useUser.tsx b/src/frontend/src/features/auth/api/useUser.tsx index c3b1dac3..5c7b06df 100644 --- a/src/frontend/src/features/auth/api/useUser.tsx +++ b/src/frontend/src/features/auth/api/useUser.tsx @@ -3,10 +3,10 @@ import { keys } from '@/api/queryKeys' import { fetchUser } from './fetchUser' import { type ApiUser } from './ApiUser' import { useEffect } from 'react' -import posthog from 'posthog-js' +import { startAnalyticsSession } from '@/features/analytics/hooks/useAnalytics' /** - * returns info about currently logged in user + * returns info about currently logged-in user * * `isLoggedIn` is undefined while query is loading and true/false when it's done */ @@ -18,8 +18,8 @@ export const useUser = () => { }) useEffect(() => { - if (query.data && query.data.id && !posthog._isIdentified()) { - posthog.identify(query.data.id, { email: query.data.email }) + if (query?.data) { + startAnalyticsSession(query.data) } }, [query.data]) diff --git a/src/frontend/src/layout/Header.tsx b/src/frontend/src/layout/Header.tsx index 6233d201..09f8918a 100644 --- a/src/frontend/src/layout/Header.tsx +++ b/src/frontend/src/layout/Header.tsx @@ -9,8 +9,8 @@ import { useMatchesRoute } from '@/navigation/useMatchesRoute' import { Feedback } from '@/components/Feedback' import { Menu } from '@/primitives/Menu' import { MenuList } from '@/primitives/MenuList' -import posthog from 'posthog-js' import { ProConnectButton } from '@/components/ProConnectButton' +import { terminateAnalyticsSession } from '@/features/analytics/hooks/useAnalytics' export const Header = () => { const { t } = useTranslation() @@ -81,7 +81,7 @@ export const Header = () => { items={[{ value: 'logout', label: t('logout') }]} onAction={(value) => { if (value === 'logout') { - posthog.reset() + terminateAnalyticsSession() window.location.href = logoutUrl() } }}