Compare commits

...
Author SHA1 Message Date
bosiraphael fea82c9fbb wip 2026-03-31 17:37:45 +02:00
10 changed files with 328 additions and 218 deletions
@@ -3,6 +3,7 @@ import { ONBOARDING_PATHS } from '@/auth/constants/OnboardingPaths';
import { ONGOING_USER_CREATION_PATHS } from '@/auth/constants/OngoingUserCreationPaths';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { returnToPathState } from '@/auth/states/returnToPathState';
import { isValidReturnToPath } from '@/auth/utils/isValidReturnToPath';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
@@ -10,7 +11,6 @@ import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMeta
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
import { isValidReturnToPath } from '@/auth/utils/isValidReturnToPath';
import { isNonEmptyString } from '@sniptt/guards';
import { useLocation, useParams } from 'react-router-dom';
import { AppPath, SettingsPath } from 'twenty-shared/types';
@@ -1,7 +1,6 @@
import { safeRemoveLocalStorageItems } from '@/auth/utils/safeRemoveLocalStorageItems';
const SESSION_KEYS_TO_CLEAR = [
'lastVisitedObjectMetadataItemIdState',
'lastVisitedViewPerObjectMetadataItemState',
'playgroundApiKeyState',
'ai/agentChatDraftsByThreadIdState',
@@ -3,7 +3,6 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { getViewType } from '@/context-store/utils/getViewType';
import { useSetLastVisitedObjectMetadataId } from '@/navigation/hooks/useSetLastVisitedObjectMetadataId';
import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
@@ -29,9 +28,6 @@ export const MainContextStoreProviderEffect = ({
const { setLastVisitedViewForObjectMetadataNamePlural } =
useSetLastVisitedViewForObjectMetadataNamePlural();
const { setLastVisitedObjectMetadataId } =
useSetLastVisitedObjectMetadataId();
const [contextStoreCurrentViewId, setContextStoreCurrentViewId] =
useAtomComponentState(
contextStoreCurrentViewIdComponentState,
@@ -69,15 +65,10 @@ export const MainContextStoreProviderEffect = ({
objectNamePlural: objectMetadataItem.namePlural,
viewId: viewId ?? '',
});
setLastVisitedObjectMetadataId({
objectMetadataItemId: objectMetadataItem.id,
});
}, [
contextStoreCurrentObjectMetadataItemId,
objectMetadataItem,
setContextStoreCurrentObjectMetadataItemId,
setLastVisitedObjectMetadataId,
setLastVisitedViewForObjectMetadataNamePlural,
viewId,
]);
@@ -23,6 +23,12 @@ export const IsMinimalMetadataReadyEffect = () => {
metadataStoreState,
'views',
);
// oxlint-disable-next-line twenty/matching-state-variable
const metadataStoreNavigationMenuItems = useAtomFamilyStateValue(
metadataStoreState,
'navigationMenuItems',
);
const setIsMinimalMetadataReady = useSetAtomState(
isMinimalMetadataReadyState,
);
@@ -32,11 +38,15 @@ export const IsMinimalMetadataReadyEffect = () => {
const areObjectsLoaded = metadataStore.status === 'up-to-date';
const areViewsLoaded = metadataStoreViews.status === 'up-to-date';
const areNavigationMenuItemsLoaded =
metadataStoreNavigationMenuItems.status === 'up-to-date';
const isReady = !areObjectsLoaded
? false
: !hasAccessTokenPair ||
(isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded));
(isDefined(currentUser) &&
(!hasActiveWorkspace ||
(areViewsLoaded && areNavigationMenuItemsLoaded)));
if (!areObjectsLoaded) {
setIsMinimalMetadataReady(false);
@@ -52,6 +62,7 @@ export const IsMinimalMetadataReadyEffect = () => {
currentWorkspace,
metadataStore.status,
metadataStoreViews.status,
metadataStoreNavigationMenuItems.status,
setIsMinimalMetadataReady,
]);
@@ -9,11 +9,12 @@ import { useOpenRecordsSearchPageInSidePanel } from '@/side-panel/hooks/useOpenR
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useNavigate } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import {
type IconComponent,
IconList,
@@ -67,7 +68,7 @@ export const MobileNavigationBar = () => {
);
setCurrentMobileNavigationDrawer('main');
if (isSettingsPage) {
if (isSettingsPage && isDefined(defaultHomePagePath)) {
navigate(defaultHomePagePath);
}
},
@@ -1,5 +1,7 @@
import { currentUserState } from '@/auth/states/currentUserState';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@@ -7,8 +9,11 @@ import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { renderHook, waitFor } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { createElement, useEffect, type ReactNode } from 'react';
import { AppPath } from 'twenty-shared/types';
import { AppPath, NavigationMenuItemType, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import {
type NavigationMenuItem,
ViewKey,
ViewOpenRecordIn,
ViewType,
ViewVisibility,
@@ -16,24 +21,65 @@ import {
import { mockedUserData } from '~/testing/mock-data/users';
import { getTestEnrichedObjectMetadataItemsMock } from '~/testing/utils/getTestEnrichedObjectMetadataItemsMock';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
import { setTestViewsInMetadataStore } from '~/testing/utils/setTestViewsInMetadataStore';
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
import { setTestViewsInMetadataStore } from '~/testing/utils/setTestViewsInMetadataStore';
const Wrapper = ({ children }: { children: ReactNode }) =>
createElement(JotaiProvider, { store: jotaiStore }, children);
const renderHooks = ({
const setNavigationMenuItemsInStore = (items: NavigationMenuItem[]) => {
jotaiStore.set(metadataStoreState.atomFamily('navigationMenuItems'), {
current: items,
draft: [],
status: 'up-to-date',
});
};
const makeObjectNavItem = (
overrides: Partial<NavigationMenuItem> & {
id: string;
targetObjectMetadataId: string;
position: number;
},
): NavigationMenuItem => ({
type: NavigationMenuItemType.OBJECT as unknown as NavigationMenuItem['type'],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
});
const makeViewNavItem = (
overrides: Partial<NavigationMenuItem> & {
id: string;
viewId: string;
position: number;
},
): NavigationMenuItem => ({
type: NavigationMenuItemType.VIEW as unknown as NavigationMenuItem['type'],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
});
const companyMetadata = getMockObjectMetadataItemOrThrow('company');
const personMetadata = getMockObjectMetadataItemOrThrow('person');
const setupStore = ({
withCurrentUser,
withExistingView,
views,
navigationMenuItems,
}: {
withCurrentUser: boolean;
withExistingView: boolean;
views: Parameters<typeof setTestViewsInMetadataStore>[1];
navigationMenuItems: NavigationMenuItem[];
}) => {
setTestObjectMetadataItemsInMetadataStore(
jotaiStore,
getTestEnrichedObjectMetadataItemsMock(),
);
jotaiStore.set(isMinimalMetadataReadyState.atom, true);
const { result } = renderHook(
() => {
const setCurrentUser = useSetAtomState(currentUserState);
@@ -42,34 +88,8 @@ const renderHooks = ({
);
useEffect(() => {
if (withExistingView) {
setTestViewsInMetadataStore(jotaiStore, [
{
id: 'viewId',
name: 'Test View',
objectMetadataId: getMockObjectMetadataItemOrThrow('company').id,
type: ViewType.TABLE,
key: null,
isCompact: false,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
viewFields: [],
viewFieldGroups: [],
viewGroups: [],
viewSorts: [],
viewFilters: [],
viewFilterGroups: [],
kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '',
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
shouldHideEmptyGroups: false,
},
]);
} else {
setTestViewsInMetadataStore(jotaiStore, []);
}
setTestViewsInMetadataStore(jotaiStore, views);
setNavigationMenuItemsInStore(navigationMenuItems);
if (withCurrentUser) {
setCurrentUser(mockedUserData);
@@ -79,53 +99,214 @@ const renderHooks = ({
return useDefaultHomePagePath();
},
{
wrapper: Wrapper,
},
{ wrapper: Wrapper },
);
return { result };
};
const makeView = (
id: string,
objectMetadataId: string,
overrides?: { key?: ViewKey | null },
) => ({
id,
name: 'Test View',
objectMetadataId,
type: ViewType.TABLE,
key: overrides?.key ?? null,
isCompact: false,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
viewFields: [],
viewFieldGroups: [],
viewGroups: [],
viewSorts: [],
viewFilters: [],
viewFilterGroups: [],
kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '',
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
shouldHideEmptyGroups: false,
});
describe('useDefaultHomePagePath', () => {
it('should return proper path when no currentUser', async () => {
const { result } = renderHooks({
it('should return SignInUp when no currentUser', async () => {
const { result } = setupStore({
withCurrentUser: false,
withExistingView: false,
views: [],
navigationMenuItems: [],
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp);
});
});
it('should return proper path when no currentUser and existing view', async () => {
const { result } = renderHooks({
withCurrentUser: false,
withExistingView: true,
it('should return first OBJECT nav item path', async () => {
const personView = makeView('personViewId', personMetadata.id, {
key: ViewKey.INDEX,
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp);
});
});
it('should return proper path when currentUser is defined', async () => {
const { result } = renderHooks({
const { result } = setupStore({
withCurrentUser: true,
withExistingView: false,
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual('/objects/companies');
});
});
it('should return proper path when currentUser is defined and view exists', async () => {
const { result } = renderHooks({
withCurrentUser: true,
withExistingView: true,
views: [personView],
navigationMenuItems: [
makeObjectNavItem({
id: 'nav-person',
targetObjectMetadataId: personMetadata.id,
position: 0,
}),
makeObjectNavItem({
id: 'nav-company',
targetObjectMetadataId: companyMetadata.id,
position: 1,
}),
],
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(
'/objects/companies?viewId=viewId',
'/objects/people?viewId=personViewId',
);
});
});
it('should return first VIEW nav item path', async () => {
const companyView = makeView('companyViewId', companyMetadata.id);
const { result } = setupStore({
withCurrentUser: true,
views: [companyView],
navigationMenuItems: [
makeViewNavItem({
id: 'nav-view',
viewId: 'companyViewId',
position: 0,
}),
],
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(
'/objects/companies?viewId=companyViewId',
);
});
});
it('should respect position ordering', async () => {
const companyView = makeView('companyViewId', companyMetadata.id, {
key: ViewKey.INDEX,
});
const personView = makeView('personViewId', personMetadata.id, {
key: ViewKey.INDEX,
});
const { result } = setupStore({
withCurrentUser: true,
views: [companyView, personView],
navigationMenuItems: [
makeObjectNavItem({
id: 'nav-company',
targetObjectMetadataId: companyMetadata.id,
position: 5,
}),
makeObjectNavItem({
id: 'nav-person',
targetObjectMetadataId: personMetadata.id,
position: 1,
}),
],
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(
'/objects/people?viewId=personViewId',
);
});
});
it('should return Settings profile page when no navigable items', async () => {
const { result } = setupStore({
withCurrentUser: true,
views: [],
navigationMenuItems: [],
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(
getSettingsPath(SettingsPath.ProfilePage),
);
});
});
it('should return undefined when metadata is not yet ready', async () => {
setTestObjectMetadataItemsInMetadataStore(
jotaiStore,
getTestEnrichedObjectMetadataItemsMock(),
);
jotaiStore.set(isMinimalMetadataReadyState.atom, false);
const { result } = renderHook(
() => {
const setCurrentUser = useSetAtomState(currentUserState);
const setCurrentUserWorkspace = useSetAtomState(
currentUserWorkspaceState,
);
useEffect(() => {
setCurrentUser(mockedUserData);
setCurrentUserWorkspace(mockedUserData.currentUserWorkspace);
}, [setCurrentUser, setCurrentUserWorkspace]);
return useDefaultHomePagePath();
},
{ wrapper: Wrapper },
);
await waitFor(() => {
expect(result.current.defaultHomePagePath).toBeUndefined();
});
});
it('should skip FOLDER and LINK items', async () => {
const personView = makeView('personViewId', personMetadata.id, {
key: ViewKey.INDEX,
});
const { result } = setupStore({
withCurrentUser: true,
views: [personView],
navigationMenuItems: [
{
id: 'nav-folder',
type: NavigationMenuItemType.FOLDER,
position: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as NavigationMenuItem,
{
id: 'nav-link',
type: NavigationMenuItemType.LINK,
position: 1,
link: 'https://example.com',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as NavigationMenuItem,
makeObjectNavItem({
id: 'nav-person',
targetObjectMetadataId: personMetadata.id,
position: 2,
}),
],
});
await waitFor(() => {
expect(result.current.defaultHomePagePath).toEqual(
'/objects/people?viewId=personViewId',
);
});
});
@@ -1,91 +1,84 @@
import { isNonEmptyString } from '@sniptt/guards';
import { useMemo } from 'react';
import {
AppPath,
NavigationMenuItemType,
SettingsPath,
} from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { currentUserState } from '@/auth/states/currentUserState';
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { type ObjectPathInfo } from '@/navigation/types/ObjectPathInfo';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { filterReadableActiveObjectMetadataItems } from '@/object-metadata/utils/filterReadableActiveObjectMetadataItems';
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
import { navigationMenuItemsSelector } from '@/navigation-menu-item/common/states/navigationMenuItemsSelector';
import { filterAndSortNavigationMenuItems } from '@/navigation-menu-item/common/utils/filterAndSortNavigationMenuItems';
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/common/utils/filterWorkspaceNavigationMenuItems';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
import isEmpty from 'lodash.isempty';
import { useCallback, useMemo } from 'react';
import { AppPath, SettingsPath } from 'twenty-shared/types';
import { getAppPath, getSettingsPath, isDefined } from 'twenty-shared/utils';
import { useStore } from 'jotai';
export const useDefaultHomePagePath = () => {
const store = useStore();
const currentUser = useAtomStateValue(currentUserState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const readableNonSystemObjectMetadataItems = useMemo(
() =>
filterReadableActiveObjectMetadataItems(
activeObjectMetadataItems,
objectPermissionsByObjectMetadataId,
)
.filter((item) => !item.isSystem)
.sort((a, b) => a.nameSingular.localeCompare(b.nameSingular)),
[activeObjectMetadataItems, objectPermissionsByObjectMetadataId],
);
const getActiveObjectMetadataItemMatchingId = useCallback(
(objectMetadataId: string) => {
return readableNonSystemObjectMetadataItems.find(
(item) => item.id === objectMetadataId,
);
},
[readableNonSystemObjectMetadataItems],
);
const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector);
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
const views = useAtomStateValue(viewsSelector);
const isMinimalMetadataReady = useAtomStateValue(isMinimalMetadataReadyState);
const getFirstView = useCallback(
(objectMetadataItemId: string | undefined | null) => {
return views.find(
(view) => view.objectMetadataId === objectMetadataItemId,
);
},
[views],
);
const firstNavigableLink = useMemo(() => {
const workspaceItems =
filterWorkspaceNavigationMenuItems(navigationMenuItems);
const firstObjectPathInfo = useMemo<ObjectPathInfo | null>(() => {
const [firstObjectMetadataItem] = readableNonSystemObjectMetadataItems;
if (!isDefined(firstObjectMetadataItem)) {
return null;
}
const view = getFirstView(firstObjectMetadataItem?.id);
return { objectMetadataItem: firstObjectMetadataItem, view };
}, [getFirstView, readableNonSystemObjectMetadataItems]);
const getDefaultObjectPathInfo = useCallback(() => {
const lastVisitedObjectMetadataItemId = store.get(
lastVisitedObjectMetadataItemIdState.atom,
const sortedItems = filterAndSortNavigationMenuItems(
workspaceItems,
views,
objectMetadataItems,
);
const lastVisitedObjectMetadataItem = isDefined(
lastVisitedObjectMetadataItemId,
)
? getActiveObjectMetadataItemMatchingId(lastVisitedObjectMetadataItemId)
: undefined;
for (const item of sortedItems) {
if (
item.type !== NavigationMenuItemType.OBJECT &&
item.type !== NavigationMenuItemType.VIEW
) {
continue;
}
if (isDefined(lastVisitedObjectMetadataItem)) {
return {
view: getFirstView(lastVisitedObjectMetadataItemId),
objectMetadataItem: lastVisitedObjectMetadataItem,
};
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
item,
objectMetadataItems,
views,
);
if (
!isDefined(objectMetadataItem) ||
!getObjectPermissionsForObject(
objectPermissionsByObjectMetadataId,
objectMetadataItem.id,
).canReadObjectRecords
) {
continue;
}
const link = getNavigationMenuItemComputedLink(
item,
objectMetadataItems,
views,
);
if (isNonEmptyString(link)) {
return link;
}
}
return firstObjectPathInfo;
return null;
}, [
firstObjectPathInfo,
getActiveObjectMetadataItemMatchingId,
getFirstView,
store,
navigationMenuItems,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
views,
]);
const defaultHomePagePath = useMemo(() => {
@@ -93,29 +86,16 @@ export const useDefaultHomePagePath = () => {
return AppPath.SignInUp;
}
if (isEmpty(readableNonSystemObjectMetadataItems)) {
return getSettingsPath(SettingsPath.ProfilePage);
if (isNonEmptyString(firstNavigableLink)) {
return firstNavigableLink;
}
const defaultObjectPathInfo = getDefaultObjectPathInfo();
if (!isDefined(defaultObjectPathInfo)) {
return AppPath.NotFound;
if (!isMinimalMetadataReady) {
return undefined;
}
const namePlural = defaultObjectPathInfo.objectMetadataItem?.namePlural;
const viewId = defaultObjectPathInfo.view?.id;
return getAppPath(
AppPath.RecordIndexPage,
{ objectNamePlural: namePlural },
viewId ? { viewId } : undefined,
);
}, [
currentUser,
getDefaultObjectPathInfo,
readableNonSystemObjectMetadataItems,
]);
return getSettingsPath(SettingsPath.ProfilePage);
}, [currentUser, firstNavigableLink, isMinimalMetadataReady]);
return { defaultHomePagePath };
};
@@ -1,37 +0,0 @@
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useStore } from 'jotai';
export const useSetLastVisitedObjectMetadataId = () => {
const store = useStore();
const setLastVisitedObjectMetadataId = useCallback(
({ objectMetadataItemId }: { objectMetadataItemId: string }) => {
const objectMetadataItems = store.get(objectMetadataItemsSelector.atom);
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === objectMetadataItemId,
);
const lastVisitedObjectMetadataItemId = store.get(
lastVisitedObjectMetadataItemIdState.atom,
);
if (
isDefined(objectMetadataItem) &&
lastVisitedObjectMetadataItemId !== objectMetadataItemId
) {
store.set(
lastVisitedObjectMetadataItemIdState.atom,
objectMetadataItemId,
);
}
},
[store],
);
return {
setLastVisitedObjectMetadataId,
};
};
@@ -1,9 +0,0 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const lastVisitedObjectMetadataItemIdState = createAtomState<
string | null
>({
key: 'lastVisitedObjectMetadataItemIdState',
defaultValue: null,
useLocalStorage: true,
});
@@ -1,7 +0,0 @@
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type View } from '@/views/types/View';
export type ObjectPathInfo = {
objectMetadataItem: EnrichedObjectMetadataItem;
view: View | undefined;
};