From 087a4e384dbfbc807f178b638e4fbe7427954f75 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 1 Nov 2025 20:05:17 +0100 Subject: [PATCH] Fix view favorite making the app crash (#15517) On new workspaces, as they come without the "view" object which not part of engine metadata, favorites pointing to views make the app crashes. This fixes it --- .../CurrentWorkspaceMemberFavorites.tsx | 15 +++-- .../CurrentWorkspaceMemberOrphanFavorites.tsx | 13 ++++- .../FavoriteNavigationDrawerItem.tsx | 50 ----------------- .../FavoriteNavigationDrawerSubItem.tsx | 55 ------------------- .../getFavoriteSecondaryLabel.test.ts | 31 +++++++++++ .../utils/getFavoriteSecondaryLabel.ts | 23 ++++++++ 6 files changed, 75 insertions(+), 112 deletions(-) delete mode 100644 packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerItem.tsx delete mode 100644 packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerSubItem.tsx create mode 100644 packages/twenty-front/src/modules/favorites/utils/__tests__/getFavoriteSecondaryLabel.test.ts create mode 100644 packages/twenty-front/src/modules/favorites/utils/getFavoriteSecondaryLabel.ts diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx index 3c08fd74302..8904961754e 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx @@ -1,6 +1,5 @@ import { FavoriteFolderNavigationDrawerItemDropdown } from '@/favorites/components/FavoriteFolderNavigationDrawerItemDropdown'; import { FavoriteIcon } from '@/favorites/components/FavoriteIcon'; -import { FavoriteNavigationDrawerSubItem } from '@/favorites/components/FavoriteNavigationDrawerSubItem'; import { FavoritesDroppable } from '@/favorites/components/FavoritesDroppable'; import { FAVORITE_FOLDER_DELETE_MODAL_ID } from '@/favorites/constants/FavoriteFolderDeleteModalId'; import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext'; @@ -8,8 +7,10 @@ import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useDeleteFavoriteFolder } from '@/favorites/hooks/useDeleteFavoriteFolder'; import { useRenameFavoriteFolder } from '@/favorites/hooks/useRenameFavoriteFolder'; import { openFavoriteFolderIdsState } from '@/favorites/states/openFavoriteFolderIdsState'; +import { getFavoriteSecondaryLabel } from '@/favorites/utils/getFavoriteSecondaryLabel'; import { isLocationMatchingFavorite } from '@/favorites/utils/isLocationMatchingFavorite'; import { type ProcessedFavorite } from '@/favorites/utils/sortFavorites'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; @@ -19,13 +20,14 @@ import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpe import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer'; +import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { Droppable } from '@hello-pangea/dnd'; import { useContext, useState } from 'react'; import { createPortal } from 'react-dom'; import { useLocation } from 'react-router-dom'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { IconFolder, IconFolderOpen, IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; @@ -43,6 +45,7 @@ export const CurrentWorkspaceMemberFavorites = ({ folder, isGroup, }: CurrentWorkspaceMemberFavoritesProps) => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const currentPath = useLocation().pathname; const currentViewPath = useLocation().pathname + useLocation().search; const { isDragging } = useContext(FavoritesDragContext); @@ -196,8 +199,12 @@ export const CurrentWorkspaceMemberFavorites = ({ index={index} isInsideScrollableContainer itemComponent={ - } to={isDragging ? undefined : favorite.link} diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx index d9867079722..8d3e30db57c 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx @@ -1,14 +1,17 @@ import { FavoriteIcon } from '@/favorites/components/FavoriteIcon'; -import { FavoriteNavigationDrawerItem } from '@/favorites/components/FavoriteNavigationDrawerItem'; import { FavoritesDroppable } from '@/favorites/components/FavoritesDroppable'; import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { getFavoriteSecondaryLabel } from '@/favorites/utils/getFavoriteSecondaryLabel'; import { isLocationMatchingFavorite } from '@/favorites/utils/isLocationMatchingFavorite'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; +import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import styled from '@emotion/styled'; import { useContext } from 'react'; import { useLocation } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; import { IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; @@ -21,6 +24,7 @@ const StyledOrphanFavoritesContainer = styled.div` `; export const CurrentWorkspaceMemberOrphanFavorites = () => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const { sortedFavorites: favorites } = useFavorites(); const { deleteFavorite } = useDeleteFavorite(); const currentPath = useLocation().pathname; @@ -42,8 +46,11 @@ export const CurrentWorkspaceMemberOrphanFavorites = () => { isInsideScrollableContainer={true} itemComponent={ - } active={isLocationMatchingFavorite( diff --git a/packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerItem.tsx b/packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerItem.tsx deleted file mode 100644 index d03167971e3..00000000000 --- a/packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerItem.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { type ProcessedFavorite } from '@/favorites/utils/sortFavorites'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; - -type FavoriteNavigationDrawerItemProps = { - favorite: ProcessedFavorite; - label: string; - Icon?: React.ComponentProps['Icon']; - to?: string; - active?: boolean; - rightOptions?: React.ComponentProps< - typeof NavigationDrawerItem - >['rightOptions']; - isDragging?: boolean; - triggerEvent?: React.ComponentProps< - typeof NavigationDrawerItem - >['triggerEvent']; -}; - -export const FavoriteNavigationDrawerItem = ({ - favorite, - label, - Icon, - to, - active, - rightOptions, - isDragging, - triggerEvent, -}: FavoriteNavigationDrawerItemProps) => { - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: favorite.objectNameSingular, - }); - - if (!favorite.objectNameSingular) { - return null; - } - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerSubItem.tsx b/packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerSubItem.tsx deleted file mode 100644 index 4692aee4746..00000000000 --- a/packages/twenty-front/src/modules/favorites/components/FavoriteNavigationDrawerSubItem.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { type ProcessedFavorite } from '@/favorites/utils/sortFavorites'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; - -type FavoriteNavigationDrawerSubItemProps = { - favorite: ProcessedFavorite; - label: string; - Icon?: React.ComponentProps['Icon']; - to?: string; - active?: boolean; - subItemState?: React.ComponentProps< - typeof NavigationDrawerSubItem - >['subItemState']; - rightOptions?: React.ComponentProps< - typeof NavigationDrawerSubItem - >['rightOptions']; - isDragging?: boolean; - triggerEvent?: React.ComponentProps< - typeof NavigationDrawerSubItem - >['triggerEvent']; -}; - -export const FavoriteNavigationDrawerSubItem = ({ - favorite, - label, - Icon, - to, - active, - subItemState, - rightOptions, - isDragging, - triggerEvent, -}: FavoriteNavigationDrawerSubItemProps) => { - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: favorite.objectNameSingular || '', - }); - - if (!favorite.objectNameSingular) { - return null; - } - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/favorites/utils/__tests__/getFavoriteSecondaryLabel.test.ts b/packages/twenty-front/src/modules/favorites/utils/__tests__/getFavoriteSecondaryLabel.test.ts new file mode 100644 index 00000000000..1cfa403b103 --- /dev/null +++ b/packages/twenty-front/src/modules/favorites/utils/__tests__/getFavoriteSecondaryLabel.test.ts @@ -0,0 +1,31 @@ +import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +import { getFavoriteSecondaryLabel } from '../getFavoriteSecondaryLabel'; + +describe('getFavoriteSecondaryLabel', () => { + it('should return "View" for view object', () => { + const result = getFavoriteSecondaryLabel({ + objectMetadataItems: generatedMockObjectMetadataItems, + favoriteObjectNameSingular: 'view', + }); + + expect(result).toBe('View'); + }); + + it('should return labelSingular for matching object metadata item', () => { + const result = getFavoriteSecondaryLabel({ + objectMetadataItems: generatedMockObjectMetadataItems, + favoriteObjectNameSingular: 'person', + }); + + expect(result).toBe('Person'); + }); + + it('should return undefined when object metadata item is not found', () => { + const result = getFavoriteSecondaryLabel({ + objectMetadataItems: generatedMockObjectMetadataItems, + favoriteObjectNameSingular: 'nonexistent', + }); + + expect(result).toBeUndefined(); + }); +}); diff --git a/packages/twenty-front/src/modules/favorites/utils/getFavoriteSecondaryLabel.ts b/packages/twenty-front/src/modules/favorites/utils/getFavoriteSecondaryLabel.ts new file mode 100644 index 00000000000..37891ab48d1 --- /dev/null +++ b/packages/twenty-front/src/modules/favorites/utils/getFavoriteSecondaryLabel.ts @@ -0,0 +1,23 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +type GetFavoriteSecondaryLabelProps = { + objectMetadataItems: Pick< + ObjectMetadataItem, + 'nameSingular' | 'labelSingular' + >[]; + favoriteObjectNameSingular: string; +}; + +export const getFavoriteSecondaryLabel = ({ + objectMetadataItems, + favoriteObjectNameSingular, +}: GetFavoriteSecondaryLabelProps) => { + if (favoriteObjectNameSingular === 'view') { + return 'View'; + } + + return objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.nameSingular === favoriteObjectNameSingular, + )?.labelSingular; +};