Integrate NavigationMenuItem with feature flag support (#17268)
## Implement Navigation Menu Items Frontend Implements the frontend for navigation menu items, the new system replacing favorites. ### Changes - Added GraphQL fragments and queries for navigation menu items - Added hooks for managing navigation menu items (create, update, delete, sorting, filtering) - Updated components to use navigation menu items instead of favorites - Added test coverage for utility functions ### Migration Note The favorites and navigation menu item modules currently exist in parallel. The favorites code will be removed once all data has been migrated to navigation menu items. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Replaces Favorites with feature-flagged `NavigationMenuItem` across frontend and backend, while keeping Favorites as fallback until migration completes. > > - UI: new `navigation-menu-item` components (folders, orphan items, drag provider/droppable, icons, skeleton), dispatcher components to switch from Favorites, and updated “Add to favorites” action to create `NavigationMenuItem` when `IS_NAVIGATION_MENU_ITEM_ENABLED` > - DnD: shared `validateAndExtractFolderId` and droppable id utils moved to `ui/layout/draggable-list`; favorites DnD updated to use shared utils > - GraphQL (client): add fragments, queries, mutations, hooks (create/update/delete/find), and generated types; added `RecordIdentifier` and `targetRecordIdentifier` on `NavigationMenuItem` > - Prefetch: new prefetch state/effect for navigation menu items; skip favorites prefetch when flag enabled > - Backend: add DTOs (`NavigationMenuItem`, `RecordIdentifier`), resolver `targetRecordIdentifier` field, service logic to fetch record identifiers with permission-aware access and image signing, `getRecordImageIdentifier` util, entity relation to `view`, and migration adding FK on `viewId` > - Feature flags & seeding: add `IS_NAVIGATION_MENU_ITEM_ENABLED` to enums, dev seeder enables it; standard app seeds workspace navigation menu items instead of favorites when flag on > - Tests: add unit tests for sorting/labels/folder id and related utils > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c99746f08b9f84fc8cec4fcc3a7d7afb8ea92db7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { PrefetchRunFavoriteQueriesEffect } from '@/prefetch/components/PrefetchRunFavoriteQueriesEffect';
|
||||
import { PrefetchRunNavigationMenuItemQueriesEffect } from '@/prefetch/components/PrefetchRunNavigationMenuItemQueriesEffect';
|
||||
import React from 'react';
|
||||
|
||||
export const PrefetchDataProvider = ({ children }: React.PropsWithChildren) => {
|
||||
return (
|
||||
<>
|
||||
<PrefetchRunFavoriteQueriesEffect />
|
||||
<PrefetchRunNavigationMenuItemQueriesEffect />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
+15
-2
@@ -16,11 +16,16 @@ import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState
|
||||
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const PrefetchRunFavoriteQueriesEffect = () => {
|
||||
const isNavigationMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
|
||||
);
|
||||
const showAuthModal = useShowAuthModal();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
@@ -55,14 +60,22 @@ export const PrefetchRunFavoriteQueriesEffect = () => {
|
||||
objectNameSingular: CoreObjectNameSingular.Favorite,
|
||||
filter: findAllFavoritesOperationSignature.variables.filter,
|
||||
recordGqlFields: findAllFavoritesOperationSignature.fields,
|
||||
skip: showAuthModal || isSettingsPage || !isWorkspaceActive,
|
||||
skip:
|
||||
showAuthModal ||
|
||||
isSettingsPage ||
|
||||
!isWorkspaceActive ||
|
||||
isNavigationMenuItemEnabled,
|
||||
});
|
||||
|
||||
const { records: favoriteFolders } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
|
||||
filter: findAllFavoriteFoldersOperationSignature.variables.filter,
|
||||
recordGqlFields: findAllFavoriteFoldersOperationSignature.fields,
|
||||
skip: showAuthModal || isSettingsPage || !isWorkspaceActive,
|
||||
skip:
|
||||
showAuthModal ||
|
||||
isSettingsPage ||
|
||||
!isWorkspaceActive ||
|
||||
isNavigationMenuItemEnabled,
|
||||
});
|
||||
|
||||
const setPrefetchFavoritesState = useRecoilCallback(
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
|
||||
import { prefetchNavigationMenuItemsState } from '@/prefetch/states/prefetchNavigationMenuItemsState';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import {
|
||||
useFindManyNavigationMenuItemsQuery,
|
||||
type NavigationMenuItem,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const PrefetchRunNavigationMenuItemQueriesEffect = () => {
|
||||
const isNavigationMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
const showAuthModal = useShowAuthModal();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const isWorkspaceActive =
|
||||
currentWorkspace?.activationStatus === WorkspaceActivationStatus.ACTIVE;
|
||||
|
||||
const setIsPrefetchNavigationMenuItemsLoaded = useSetRecoilState(
|
||||
prefetchIsLoadedFamilyState(PrefetchKey.AllNavigationMenuItems),
|
||||
);
|
||||
|
||||
const { data, loading } = useFindManyNavigationMenuItemsQuery({
|
||||
skip:
|
||||
showAuthModal ||
|
||||
isSettingsPage ||
|
||||
!isWorkspaceActive ||
|
||||
!isNavigationMenuItemEnabled,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
const setPrefetchNavigationMenuItemsState = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(navigationMenuItems: NavigationMenuItem[]) => {
|
||||
const existingNavigationMenuItems = snapshot
|
||||
.getLoadable(prefetchNavigationMenuItemsState)
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(existingNavigationMenuItems, navigationMenuItems)) {
|
||||
set(prefetchNavigationMenuItemsState, navigationMenuItems);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && isDefined(data?.navigationMenuItems)) {
|
||||
setPrefetchNavigationMenuItemsState(data.navigationMenuItems);
|
||||
setIsPrefetchNavigationMenuItemsLoaded(true);
|
||||
}
|
||||
}, [
|
||||
data,
|
||||
loading,
|
||||
setPrefetchNavigationMenuItemsState,
|
||||
setIsPrefetchNavigationMenuItemsLoaded,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
Reference in New Issue
Block a user