Files
twenty/packages/twenty-front/src/modules/navigation-menu-item/hooks/useCreateNavigationMenuItem.ts
T
Charles Bochet 6b48f197d4 feat: deprecate WorkspaceFavorite in favor of NavigationMenuItem (#18624)
## Summary

- **Removes the entire `modules/favorites/` directory** (~66 files,
~5000 lines deleted) — components, hooks, states, types, utils, tests,
and the favorite-folder-picker sub-module
- **Eliminates the dual-write pattern** where creating a favorite also
created a NavigationMenuItem — all consumers now use
`useCreateNavigationMenuItem` directly
- **Removes `IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED` feature flag
checks** from ~12 files, always taking the NavigationMenuItem code path
- **Cleans up backend dual-writes** in `object-metadata.service.ts` and
`twenty-standard-application.service.ts` that were creating Favorite
records alongside NavigationMenuItems
- **Updates prefetch system** to only load NavigationMenuItems (removes
favorites prefetch effects and states)
- **Cleans up test infrastructure** — updates Storybook decorators, mock
data, and graphql mocks to remove favorites references

### What was intentionally kept
- **Backend entity definitions** (`FavoriteWorkspaceEntity`,
`FavoriteFolderWorkspaceEntity`) — these define the database schema and
need a proper database migration to remove
- **Cascade deletion listeners** — still needed to clean up existing
Favorite data in workspaces that haven't been fully migrated
- **v1.18 migration commands** — needed for workspaces upgrading from
older versions

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:45:40 +01:00

91 lines
2.8 KiB
TypeScript

import { isDefined } from 'twenty-shared/utils';
import { useMutation } from '@apollo/client/react';
import { CreateNavigationMenuItemDocument } from '~/generated-metadata/graphql';
import { useNavigationMenuItemsData } from '@/navigation-menu-item/hooks/useNavigationMenuItemsData';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
export const useCreateNavigationMenuItem = () => {
const { navigationMenuItems, currentWorkspaceMemberId } =
useNavigationMenuItemsData();
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
const [createNavigationMenuItemMutation] = useMutation(
CreateNavigationMenuItemDocument,
{
refetchQueries: ['FindManyNavigationMenuItems'],
},
);
const createNavigationMenuItem = async (
targetRecord: ObjectRecord,
targetObjectNameSingular: string,
folderId?: string,
) => {
const isView = targetObjectNameSingular === 'view';
if (isView) {
const relevantItems = folderId
? navigationMenuItems.filter((item) => item.folderId === folderId)
: navigationMenuItems.filter(
(item) =>
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
);
const maxPosition = Math.max(
...relevantItems.map((item) => item.position),
0,
);
await createNavigationMenuItemMutation({
variables: {
input: {
viewId: targetRecord.id,
userWorkspaceId: currentWorkspaceMemberId,
folderId,
position: maxPosition + 1,
},
},
});
} else {
const objectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === targetObjectNameSingular,
);
if (!isDefined(objectMetadataItem)) {
throw new Error(
`Object metadata item not found for nameSingular: ${targetObjectNameSingular}`,
);
}
const relevantItems = folderId
? navigationMenuItems.filter((item) => item.folderId === folderId)
: navigationMenuItems.filter(
(item) =>
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
);
const maxPosition = Math.max(
...relevantItems.map((item) => item.position),
0,
);
await createNavigationMenuItemMutation({
variables: {
input: {
targetRecordId: targetRecord.id,
targetObjectMetadataId: objectMetadataItem.id,
userWorkspaceId: currentWorkspaceMemberId,
folderId,
position: maxPosition + 1,
},
},
});
}
};
return { createNavigationMenuItem };
};