import { useEffect, useState } from 'react'; import { matchPath, useLocation, useNavigate, useParams, } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { setSessionId, useEventTracker, } from '@/analytics/hooks/useEventTracker'; import { useExecuteTasksOnAnyLocationChange } from '@/app/hooks/useExecuteTasksOnAnyLocationChange'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; import { AppBasePath } from '@/types/AppBasePath'; import { AppPath } from '@/types/AppPath'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { SettingsPath } from '@/types/SettingsPath'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { isDefined } from 'twenty-shared/utils'; import { AnalyticsType } from '~/generated/graphql'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation'; import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState'; import { getPageTitleFromPath } from '~/utils/title-utils'; // TODO: break down into smaller functions and / or hooks // - moved usePageChangeEffectNavigateLocation into dedicated hook export const PageChangeEffect = () => { const navigate = useNavigate(); const { isMatchingLocation } = useIsMatchingLocation(); const [previousLocation, setPreviousLocation] = useState(''); const setHotkeyScope = useSetHotkeyScope(); const location = useLocation(); const pageChangeEffectNavigateLocation = usePageChangeEffectNavigateLocation(); const eventTracker = useEventTracker(); const { initializeQueryParamState } = useInitializeQueryParamState(); //TODO: refactor useResetTableRowSelection hook to not throw when the argument `recordTableId` is an empty string // - replace CoreObjectNamePlural.Person const objectNamePlural = useParams().objectNamePlural ?? CoreObjectNamePlural.Person; const contextStoreCurrentViewId = useRecoilComponentValueV2( contextStoreCurrentViewIdComponentState, MAIN_CONTEXT_STORE_INSTANCE_ID, ); const contextStoreCurrentViewType = useRecoilComponentValueV2( contextStoreCurrentViewTypeComponentState, MAIN_CONTEXT_STORE_INSTANCE_ID, ); const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId( objectNamePlural, contextStoreCurrentViewId || '', ); const resetTableSelections = useResetTableRowSelection(recordIndexId); const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordIndexId); const { deactivateRecordTableRow } = useActiveRecordTableRow(recordIndexId); const { resetRecordSelection } = useRecordBoardSelection(recordIndexId); const { deactivateBoardCard } = useActiveRecordBoardCard(recordIndexId); const { unfocusBoardCard } = useFocusedRecordBoardCard(recordIndexId); const { executeTasksOnAnyLocationChange } = useExecuteTasksOnAnyLocationChange(); useEffect(() => { if (!previousLocation || previousLocation !== location.pathname) { setPreviousLocation(location.pathname); executeTasksOnAnyLocationChange(); } else { return; } }, [location, previousLocation, executeTasksOnAnyLocationChange]); useEffect(() => { initializeQueryParamState(); if (isDefined(pageChangeEffectNavigateLocation)) { navigate(pageChangeEffectNavigateLocation); } }, [navigate, pageChangeEffectNavigateLocation, initializeQueryParamState]); useEffect(() => { const isLeavingRecordIndexPage = !!matchPath( AppPath.RecordIndexPage, previousLocation, ); if (isLeavingRecordIndexPage) { if (contextStoreCurrentViewType === ContextStoreViewType.Table) { resetTableSelections(); unfocusRecordTableRow(); deactivateRecordTableRow(); } if (contextStoreCurrentViewType === ContextStoreViewType.Kanban) { resetRecordSelection(); deactivateBoardCard(); unfocusBoardCard(); } } }, [ isMatchingLocation, previousLocation, resetTableSelections, unfocusRecordTableRow, deactivateRecordTableRow, contextStoreCurrentViewType, resetRecordSelection, deactivateBoardCard, unfocusBoardCard, ]); useEffect(() => { switch (true) { case isMatchingLocation(AppPath.RecordIndexPage): { setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { goto: true, keyboardShortcutMenu: true, }); break; } case isMatchingLocation(AppPath.RecordShowPage): { setHotkeyScope(PageHotkeyScope.CompanyShowPage, { goto: true, keyboardShortcutMenu: true, }); break; } case isMatchingLocation(AppPath.OpportunitiesPage): { setHotkeyScope(PageHotkeyScope.OpportunitiesPage, { goto: true, keyboardShortcutMenu: true, }); break; } case isMatchingLocation(AppPath.TasksPage): { setHotkeyScope(PageHotkeyScope.TaskPage, { goto: true, keyboardShortcutMenu: true, }); break; } case isMatchingLocation(AppPath.SignInUp): { setHotkeyScope(PageHotkeyScope.SignInUp); break; } case isMatchingLocation(AppPath.Invite): { setHotkeyScope(PageHotkeyScope.SignInUp); break; } case isMatchingLocation(AppPath.CreateProfile): { setHotkeyScope(PageHotkeyScope.CreateProfile); break; } case isMatchingLocation(AppPath.CreateWorkspace): { setHotkeyScope(PageHotkeyScope.CreateWorkspace); break; } case isMatchingLocation(AppPath.SyncEmails): { setHotkeyScope(PageHotkeyScope.SyncEmail); break; } case isMatchingLocation(AppPath.InviteTeam): { setHotkeyScope(PageHotkeyScope.InviteTeam); break; } case isMatchingLocation(AppPath.PlanRequired): { setHotkeyScope(PageHotkeyScope.PlanRequired); break; } case isMatchingLocation(SettingsPath.ProfilePage, AppBasePath.Settings): { setHotkeyScope(PageHotkeyScope.ProfilePage, { goto: true, keyboardShortcutMenu: true, }); break; } case isMatchingLocation(SettingsPath.Domain, AppBasePath.Settings): { setHotkeyScope(PageHotkeyScope.Settings, { goto: false, keyboardShortcutMenu: true, }); break; } case isMatchingLocation( SettingsPath.WorkspaceMembersPage, AppBasePath.Settings, ): { setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, { goto: true, keyboardShortcutMenu: true, }); break; } } }, [isMatchingLocation, setHotkeyScope]); useEffect(() => { setTimeout(() => { setSessionId(); eventTracker(AnalyticsType['PAGEVIEW'], { name: getPageTitleFromPath(location.pathname), properties: { pathname: location.pathname, locale: navigator.language, userAgent: window.navigator.userAgent, href: window.location.href, referrer: document.referrer, timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }, }); }, 500); }, [eventTracker, location.pathname]); const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken(); const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState); useEffect(() => { if (isCaptchaScriptLoaded && isCaptchaRequiredForPath(location.pathname)) { requestFreshCaptchaToken(); } }, [isCaptchaScriptLoaded, location.pathname, requestFreshCaptchaToken]); return <>; };