diff --git a/src/frontend/src/features/analytics/hooks/useAnalytics.ts b/src/frontend/src/features/analytics/hooks/useAnalytics.ts index 7149c54d..36ac5e6e 100644 --- a/src/frontend/src/features/analytics/hooks/useAnalytics.ts +++ b/src/frontend/src/features/analytics/hooks/useAnalytics.ts @@ -1,17 +1,28 @@ import { useEffect } from 'react' import { useLocation } from 'wouter' -import posthog from 'posthog-js' -import { ApiUser } from '@/features/auth/api/ApiUser' +import { type PostHog } from 'posthog-js' +import { type ApiUser } from '@/features/auth/api/ApiUser' +import { useUser } from '@/features/auth/api/useUser' -export const startAnalyticsSession = (data: ApiUser) => { - if (posthog._isIdentified()) return - const { id, email } = data - posthog.identify(id, { email }) +let posthog: PostHog | null = null + +const getPosthog = async () => { + if (!posthog) posthog = (await import('posthog-js')).default + return posthog } -export const terminateAnalyticsSession = () => { - if (!posthog._isIdentified()) return - posthog.reset() +export const startAnalyticsSession = (data: ApiUser) => { + getPosthog().then((ph) => { + if (ph._isIdentified()) return + const { id, email } = data + ph.identify(id, { email }) + }) +} + +export const terminateAnalyticsSession = async () => { + const ph = await getPosthog() + if (!ph._isIdentified()) return + ph.reset() } export type useAnalyticsProps = { @@ -22,18 +33,26 @@ export type useAnalyticsProps = { export const useAnalytics = ({ id, host, isDisabled }: useAnalyticsProps) => { const [location] = useLocation() + const { user } = useUser() + useEffect(() => { if (!id || !host || isDisabled) return - if (posthog.__loaded) return - posthog.init(id, { - api_host: host, - person_profiles: 'always', + getPosthog().then((ph) => { + if (ph.__loaded) return + ph.init(id, { api_host: host, person_profiles: 'always' }) }) }, [id, host, isDisabled]) + useEffect(() => { + if (!user) return + startAnalyticsSession(user) + }, [user]) + // From PostHog tutorial on PageView tracking in a Single Page Application (SPA) context. useEffect(() => { - posthog.capture('$pageview') + getPosthog().then((ph) => { + ph.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 f435cc3d..6236fb0b 100644 --- a/src/frontend/src/features/auth/api/useUser.tsx +++ b/src/frontend/src/features/auth/api/useUser.tsx @@ -2,16 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { keys } from '@/api/queryKeys' import { fetchUser } from './fetchUser' import { type ApiUser } from './ApiUser' -import { useEffect, useMemo } from 'react' -import { - startAnalyticsSession, - terminateAnalyticsSession, -} from '@/features/analytics/hooks/useAnalytics' -import { - initializeSupportSession, - terminateSupportSession, -} from '@/features/support/hooks/useSupport' -import { logoutUrl } from '../utils/logoutUrl' +import { useMemo } from 'react' import { useConfig } from '@/api/useConfig' /** @@ -45,19 +36,6 @@ export const useUser = ( enabled: !isConfigLoading, }) - useEffect(() => { - if (query?.data) { - startAnalyticsSession(query.data) - initializeSupportSession(query.data) - } - }, [query.data]) - - const logout = () => { - terminateAnalyticsSession() - terminateSupportSession() - window.location.href = logoutUrl() - } - const isLoggedIn = query.status === 'success' ? query.data !== false : undefined const isLoggedOut = isLoggedIn === false @@ -67,6 +45,5 @@ export const useUser = ( user: isLoggedOut ? undefined : (query.data as ApiUser | undefined), isLoggedIn, isLoading: query.isLoading, - logout, } } diff --git a/src/frontend/src/features/auth/utils/logout.ts b/src/frontend/src/features/auth/utils/logout.ts new file mode 100644 index 00000000..50783019 --- /dev/null +++ b/src/frontend/src/features/auth/utils/logout.ts @@ -0,0 +1,13 @@ +import { apiUrl } from '@/api/apiUrl' +import { terminateSupportSession } from '@/features/support/hooks/useSupport' +import { terminateAnalyticsSession } from '@/features/analytics/hooks/useAnalytics' + +const logoutUrl = () => { + return apiUrl('/logout') +} + +export const logout = async () => { + await terminateAnalyticsSession() + terminateSupportSession() + window.location.href = logoutUrl() +} diff --git a/src/frontend/src/features/auth/utils/logoutUrl.ts b/src/frontend/src/features/auth/utils/logoutUrl.ts deleted file mode 100644 index 6fa09fb7..00000000 --- a/src/frontend/src/features/auth/utils/logoutUrl.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { apiUrl } from '@/api/apiUrl' - -export const logoutUrl = () => { - return apiUrl('/logout') -} diff --git a/src/frontend/src/features/settings/components/SettingsDialog.tsx b/src/frontend/src/features/settings/components/SettingsDialog.tsx index 804bf84b..c329ba64 100644 --- a/src/frontend/src/features/settings/components/SettingsDialog.tsx +++ b/src/frontend/src/features/settings/components/SettingsDialog.tsx @@ -3,12 +3,13 @@ import { useLanguageLabels } from '@/i18n/useLanguageLabels' import { A, Badge, Dialog, type DialogProps, Field, H, P } from '@/primitives' import { useUser } from '@/features/auth/api/useUser' import { LoginButton } from '@/components/LoginButton' +import { logout } from '@/features/auth/utils/logout' export type SettingsDialogProps = Pick export const SettingsDialog = (props: SettingsDialogProps) => { const { t, i18n } = useTranslation('settings') - const { user, isLoggedIn, logout } = useUser() + const { user, isLoggedIn } = useUser() const { languagesList, currentLanguage } = useLanguageLabels() const userDisplay = user?.full_name && user?.email diff --git a/src/frontend/src/features/settings/components/tabs/AccountTab.tsx b/src/frontend/src/features/settings/components/tabs/AccountTab.tsx index 1109c86d..1fcf491d 100644 --- a/src/frontend/src/features/settings/components/tabs/AccountTab.tsx +++ b/src/frontend/src/features/settings/components/tabs/AccountTab.tsx @@ -9,6 +9,7 @@ import { useState } from 'react' import { LoginButton } from '@/components/LoginButton' import { useRenameParticipant } from '@/features/rooms/api/renameParticipant' import { saveUsername } from '@/stores/userChoices' +import { logout } from '@/features/auth/utils/logout' export type AccountTabProps = Pick & Pick @@ -16,7 +17,7 @@ export type AccountTabProps = Pick & export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => { const { t } = useTranslation('settings') const room = useRoomContext() - const { user, isLoggedIn, logout } = useUser() + const { user, isLoggedIn } = useUser() const { renameParticipant } = useRenameParticipant() diff --git a/src/frontend/src/features/support/hooks/useSupport.tsx b/src/frontend/src/features/support/hooks/useSupport.tsx index 211d6767..a4b3679e 100644 --- a/src/frontend/src/features/support/hooks/useSupport.tsx +++ b/src/frontend/src/features/support/hooks/useSupport.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react' import { Crisp } from 'crisp-sdk-web' -import { ApiUser } from '@/features/auth/api/ApiUser' +import { type ApiUser } from '@/features/auth/api/ApiUser' +import { useUser } from '@/features/auth/api/useUser' import { useConfig } from '@/api/useConfig' export const initializeSupportSession = (user: ApiUser) => { @@ -23,12 +24,19 @@ export type useSupportProps = { // Configure Crisp chat for real-time support across all pages. export const useSupport = ({ id, isDisabled }: useSupportProps) => { + const { user } = useUser() + useEffect(() => { if (!id || Crisp.isCrispInjected() || isDisabled) return Crisp.configure(id) Crisp.setHideOnMobile(true) }, [id, isDisabled]) + useEffect(() => { + if (!user) return + initializeSupportSession(user) + }, [user]) + return null } diff --git a/src/frontend/src/layout/Header.tsx b/src/frontend/src/layout/Header.tsx index 99fbe963..8df8bd6d 100644 --- a/src/frontend/src/layout/Header.tsx +++ b/src/frontend/src/layout/Header.tsx @@ -13,6 +13,7 @@ import { LoginButton } from '@/components/LoginButton' import { VisualOnlyTooltip } from '@/primitives/VisualOnlyTooltip' import { useLoginHint } from '@/hooks/useLoginHint' +import { logout } from '@/features/auth/utils/logout' const Logo = () => ( { const isAccessibility = useMatchesRoute('accessibility') const isTermsOfService = useMatchesRoute('termsOfService') const isRoom = useMatchesRoute('room') - const { user, isLoggedIn, logout } = useUser() + const { user, isLoggedIn } = useUser() const userLabel = user?.full_name || user?.email const loggedInTooltip = t('loggedInUserTooltip') const loggedInAriaLabel = userLabel