From 2680f1d6be871d7c900648e1ec2a5678a479d26c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Tue, 18 Mar 2025 15:37:28 +0100
Subject: [PATCH] 583 refactor useCommandMenu hook (#10984)
Closes https://github.com/twentyhq/core-team-issues/issues/583
- Split hook into smaller hooks
- Create tests
---
...cordIndexActionMenuBarAllActionsButton.tsx | 4 +-
.../calendar/components/CalendarEventRow.tsx | 5 +-
.../emails/components/EmailThreadPreview.tsx | 4 +-
.../hooks/useOpenCreateActivityDrawer.ts | 4 +-
.../activities/notes/components/NoteCard.tsx | 4 +-
.../activities/tasks/components/TaskRow.tsx | 4 +-
.../activity/components/EventRowActivity.tsx | 4 +-
.../components/CommandMenuContainer.tsx | 12 +-
...nuContextChipGroupsWithRecordSelection.tsx | 4 +-
.../components/CommandMenuTopBar.tsx | 5 +-
.../hooks/__tests__/useCommandMenu.test.tsx | 164 +----
...MenuCloseAnimationCompleteCleanup.test.tsx | 249 +++++++
.../__tests__/useCommandMenuHistory.test.tsx | 147 ++++
.../__tests__/useNavigateCommandMenu.test.tsx | 101 +++
...useOpenCalendarEventInCommandMenu.test.tsx | 89 +++
.../useOpenEmailThreadInCommandMenu.test.tsx | 89 +++
.../useOpenRecordInCommandMenu.test.tsx | 178 +++++
.../useSetGlobalCommandMenuContext.test.tsx | 157 ++++
.../__tests__/useWorkflowCommandMenu.test.tsx | 207 ++++++
.../command-menu/hooks/useCommandMenu.ts | 697 +-----------------
...ommandMenuCloseAnimationCompleteCleanup.ts | 69 ++
.../hooks/useCommandMenuContextChips.tsx | 4 +-
.../hooks/useCommandMenuHistory.ts | 103 +++
.../hooks/useCommandMenuHotKeys.ts | 16 +-
.../hooks/useNavigateCommandMenu.ts | 137 ++++
.../useOpenCalendarEventInCommandMenu.ts | 63 ++
.../hooks/useOpenEmailThreadInCommandMenu.ts | 62 ++
.../hooks/useOpenRecordInCommandMenu.ts | 158 ++++
.../useOpenRecordsSearchPageInCommandMenu.ts | 22 +
.../command-menu/hooks/useSearchRecords.tsx | 4 +-
.../hooks/useSetGlobalCommandMenuContext.ts | 70 ++
.../hooks/useWorkflowCommandMenu.ts | 124 ++++
...ndMenuWorkflowSelectTriggerTypeContent.tsx | 4 +-
.../components/MainNavigationDrawerItems.tsx | 4 +-
.../components/MobileNavigationBar.tsx | 4 +-
.../object-record/components/RecordChip.tsx | 4 +-
.../components/RecordBoardCard.tsx | 4 +-
.../RecordBoardColumnNewOpportunity.tsx | 4 +-
.../useAddNewRecordAndOpenRightDrawer.ts | 4 +-
.../hooks/useCreateNewTableRecords.ts | 4 +-
.../hooks/useOpenRecordTableCellV2.ts | 4 +-
.../WorkflowDiagramCanvasEditableEffect.tsx | 4 +-
.../WorkflowDiagramCanvasReadonlyEffect.tsx | 4 +-
.../WorkflowRunDiagramCanvasEffect.tsx | 8 +-
.../hooks/useStartNodeCreation.ts | 8 +-
...DrawerWorkflowSelectTriggerTypeContent.tsx | 4 +-
.../testing/jest/JestContextStoreSetter.tsx | 12 +-
...dataAndApolloMocksAndActionMenuWrapper.tsx | 2 +
48 files changed, 2120 insertions(+), 918 deletions(-)
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuHistory.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useNavigateCommandMenu.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenCalendarEventInCommandMenu.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenEmailThreadInCommandMenu.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenRecordInCommandMenu.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useSetGlobalCommandMenuContext.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useOpenCalendarEventInCommandMenu.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useOpenEmailThreadInCommandMenu.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useSetGlobalCommandMenuContext.ts
create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts
diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBarAllActionsButton.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBarAllActionsButton.tsx
index 572df3a1d..9ee832a68 100644
--- a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBarAllActionsButton.tsx
+++ b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBarAllActionsButton.tsx
@@ -38,11 +38,11 @@ const StyledSeparator = styled.div<{ size: 'sm' | 'md' }>`
export const RecordIndexActionMenuBarAllActionsButton = () => {
const theme = useTheme();
- const { openRootCommandMenu } = useCommandMenu();
+ const { openCommandMenu } = useCommandMenu();
return (
<>
-
+
All Actions
diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx
index 60deb9f74..414d369e0 100644
--- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx
+++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx
@@ -10,7 +10,7 @@ import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendar
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenCalendarEventInCommandMenu } from '@/command-menu/hooks/useOpenCalendarEventInCommandMenu';
import { isDefined } from 'twenty-shared';
import {
Avatar,
@@ -114,7 +114,8 @@ export const CalendarEventRow = ({
const theme = useTheme();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { displayCurrentEventCursor = false } = useContext(CalendarContext);
- const { openCalendarEventInCommandMenu } = useCommandMenu();
+ const { openCalendarEventInCommandMenu } =
+ useOpenCalendarEventInCommandMenu();
const startsAt = getCalendarEventStartDate(calendarEvent);
const endsAt = getCalendarEventEndDate(calendarEvent);
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
index 5becd7538..383f9d538 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
@@ -3,7 +3,7 @@ import { Avatar, GRAY_SCALE } from 'twenty-ui';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenEmailThreadInCommandMenu } from '@/command-menu/hooks/useOpenEmailThreadInCommandMenu';
import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
import { formatToHumanReadableDate } from '~/utils/date-utils';
@@ -68,7 +68,7 @@ type EmailThreadPreviewProps = {
};
export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
- const { openEmailThreadInCommandMenu } = useCommandMenu();
+ const { openEmailThreadInCommandMenu } = useOpenEmailThreadInCommandMenu();
const visibility = thread.visibility;
diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
index b4ea1537a..d1e02ce47 100644
--- a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
+++ b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
@@ -1,7 +1,6 @@
import { useSetRecoilState } from 'recoil';
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
@@ -13,6 +12,7 @@ import { Note } from '@/activities/types/Note';
import { NoteTarget } from '@/activities/types/NoteTarget';
import { Task } from '@/activities/types/Task';
import { TaskTarget } from '@/activities/types/TaskTarget';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
@@ -53,7 +53,7 @@ export const useOpenCreateActivityDrawer = ({
isUpsertingActivityInDBState,
);
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const openCreateActivityDrawer = async ({
targetableObjects,
diff --git a/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx b/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx
index 62cfcda79..e3019c9fd 100644
--- a/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx
+++ b/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { Note } from '@/activities/types/Note';
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
@@ -68,7 +68,7 @@ export const NoteCard = ({
note: Note;
isSingleNote: boolean;
}) => {
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const body = getActivityPreview(note?.bodyV2?.blocknote ?? null);
diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx
index 037ed8ec8..a33ce6977 100644
--- a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx
+++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx
@@ -13,7 +13,7 @@ import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { Task } from '@/activities/types/Task';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import { useCompleteTask } from '../hooks/useCompleteTask';
@@ -78,7 +78,7 @@ const StyledCheckboxContainer = styled.div`
export const TaskRow = ({ task }: { task: Task }) => {
const theme = useTheme();
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const body = getActivitySummary(task?.bodyV2?.blocknote ?? null);
diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/activity/components/EventRowActivity.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/activity/components/EventRowActivity.tsx
index 836cdf4a4..07a632c72 100644
--- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/activity/components/EventRowActivity.tsx
+++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/activity/components/EventRowActivity.tsx
@@ -5,7 +5,7 @@ import {
StyledEventRowItemAction,
StyledEventRowItemColumn,
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { isNonEmptyString } from '@sniptt/guards';
@@ -55,7 +55,7 @@ export const EventRowActivity = ({
? event.linkedRecordCachedName
: 'Untitled';
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
return (
<>
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
index af14c887d..2955ee87a 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
@@ -9,6 +9,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
@@ -54,11 +55,10 @@ export const CommandMenuContainer = ({
}: {
children: React.ReactNode;
}) => {
- const {
- toggleCommandMenu,
- closeCommandMenu,
- onCommandMenuCloseAnimationComplete,
- } = useCommandMenu();
+ const { toggleCommandMenu, closeCommandMenu } = useCommandMenu();
+
+ const { commandMenuCloseAnimationCompleteCleanup } =
+ useCommandMenuCloseAnimationCompleteCleanup();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
@@ -154,7 +154,7 @@ export const CommandMenuContainer = ({
{isCommandMenuOpened && (
0 ? openRootCommandMenu : undefined,
+ onClick: contextChips.length > 0 ? openCommandMenu : undefined,
withIconBackground: false,
}
: undefined;
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx
index 1c981e474..7f422501a 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx
@@ -7,6 +7,7 @@ import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/Command
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuContextChips } from '@/command-menu/hooks/useCommandMenuContextChips';
+import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
@@ -91,7 +92,9 @@ export const CommandMenuTopBar = () => {
const isMobile = useIsMobile();
- const { closeCommandMenu, goBackFromCommandMenu } = useCommandMenu();
+ const { closeCommandMenu } = useCommandMenu();
+
+ const { goBackFromCommandMenu } = useCommandMenuHistory();
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx
index f3db8942b..5c54c53ae 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx
@@ -8,8 +8,6 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
-import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
-import { IconList, IconSearch } from 'twenty-ui';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -53,7 +51,7 @@ describe('useCommandMenu', () => {
const { result } = renderHooks();
act(() => {
- result.current.commandMenu.openRootCommandMenu();
+ result.current.commandMenu.openCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(true);
@@ -82,164 +80,4 @@ describe('useCommandMenu', () => {
expect(result.current.isCommandMenuOpened).toBe(false);
});
-
- it('should navigate to a page', () => {
- const { result } = renderHooks();
-
- expect(result.current.commandMenuNavigationStack).toEqual([]);
- expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
- expect(result.current.commandMenuPageInfo).toEqual({
- title: undefined,
- Icon: undefined,
- instanceId: '',
- });
-
- act(() => {
- result.current.commandMenu.navigateCommandMenu({
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- });
- });
-
- expect(result.current.commandMenuNavigationStack).toEqual([
- {
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- },
- ]);
- expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
- expect(result.current.commandMenuPageInfo).toEqual({
- title: 'Search',
- Icon: IconSearch,
- instanceId: '1',
- });
-
- act(() => {
- result.current.commandMenu.navigateCommandMenu({
- page: CommandMenuPages.ViewRecord,
- pageTitle: 'Company',
- pageIcon: IconList,
- pageId: '2',
- });
- });
-
- expect(result.current.commandMenuNavigationStack).toEqual([
- {
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- },
- {
- page: CommandMenuPages.ViewRecord,
- pageTitle: 'Company',
- pageIcon: IconList,
- pageId: '2',
- },
- ]);
- expect(result.current.commandMenuPage).toBe(CommandMenuPages.ViewRecord);
- expect(result.current.commandMenuPageInfo).toEqual({
- title: 'Company',
- Icon: IconList,
- instanceId: '2',
- });
- });
-
- it('should go back from a page', () => {
- const { result } = renderHooks();
-
- act(() => {
- result.current.commandMenu.navigateCommandMenu({
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- });
- });
-
- act(() => {
- result.current.commandMenu.navigateCommandMenu({
- page: CommandMenuPages.ViewRecord,
- pageTitle: 'Company',
- pageIcon: IconList,
- pageId: '2',
- });
- });
-
- expect(result.current.commandMenuNavigationStack).toEqual([
- {
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- },
- {
- page: CommandMenuPages.ViewRecord,
- pageTitle: 'Company',
- pageIcon: IconList,
- pageId: '2',
- },
- ]);
-
- act(() => {
- result.current.commandMenu.goBackFromCommandMenu();
- });
-
- expect(result.current.commandMenuNavigationStack).toEqual([
- {
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- },
- ]);
- expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
- expect(result.current.commandMenuPageInfo).toEqual({
- title: 'Search',
- Icon: IconSearch,
- instanceId: '1',
- });
-
- act(() => {
- result.current.commandMenu.goBackFromCommandMenu();
- result.current.commandMenu.onCommandMenuCloseAnimationComplete();
- });
-
- expect(result.current.commandMenuNavigationStack).toEqual([]);
- expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
- expect(result.current.commandMenuPageInfo).toEqual({
- title: undefined,
- instanceId: '',
- Icon: undefined,
- });
- expect(result.current.isCommandMenuOpened).toBe(false);
- });
-
- it('should navigate to a page in history', () => {
- const { result } = renderHooks();
-
- act(() => {
- result.current.commandMenu.navigateCommandMenu({
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: '1',
- });
- });
-
- act(() => {
- result.current.commandMenu.navigateCommandMenuHistory(0);
- });
-
- expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
- expect(result.current.commandMenuPageInfo).toEqual({
- title: 'Search',
- Icon: IconSearch,
- instanceId: '1',
- });
- });
});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx
new file mode 100644
index 000000000..66bd00bc6
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx
@@ -0,0 +1,249 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
+import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
+import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
+import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
+import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
+import { IconList } from 'twenty-ui';
+
+const mockCloseDropdown = jest.fn();
+const mockResetContextStoreStates = jest.fn();
+const mockResetSelectedItem = jest.fn();
+const mockGoBackToPreviousHotkeyScope = jest.fn();
+const mockEmitRightDrawerCloseEvent = jest.fn();
+
+jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
+ useDropdownV2: () => ({
+ closeDropdown: mockCloseDropdown,
+ }),
+}));
+
+jest.mock('@/command-menu/hooks/useResetContextStoreStates', () => ({
+ useResetContextStoreStates: () => ({
+ resetContextStoreStates: mockResetContextStoreStates,
+ }),
+}));
+
+jest.mock('@/ui/layout/selectable-list/hooks/useSelectableList', () => ({
+ useSelectableList: () => ({
+ resetSelectedItem: mockResetSelectedItem,
+ }),
+}));
+
+jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope', () => ({
+ usePreviousHotkeyScope: () => ({
+ goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
+ }),
+}));
+
+jest.mock('@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent', () => ({
+ emitRightDrawerCloseEvent: () => {
+ mockEmitRightDrawerCloseEvent();
+ },
+}));
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const { commandMenuCloseAnimationCompleteCleanup } =
+ useCommandMenuCloseAnimationCompleteCleanup();
+
+ const commandMenuPage = useRecoilValue(commandMenuPageState);
+ const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
+ const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
+ const commandMenuSearch = useRecoilValue(commandMenuSearchState);
+ const commandMenuNavigationStack = useRecoilValue(
+ commandMenuNavigationStackState,
+ );
+ const commandMenuNavigationRecords = useRecoilValue(
+ commandMenuNavigationRecordsState,
+ );
+ const commandMenuNavigationMorphItemByPage = useRecoilValue(
+ commandMenuNavigationMorphItemByPageState,
+ );
+ const hasUserSelectedCommand = useRecoilValue(
+ hasUserSelectedCommandState,
+ );
+ const isCommandMenuClosing = useRecoilValue(isCommandMenuClosingState);
+ const viewableRecordId = useRecoilValue(viewableRecordIdState);
+
+ // Get setters for state modification in tests
+ const setCommandMenuPage = useSetRecoilState(commandMenuPageState);
+ const setCommandMenuPageInfo = useSetRecoilState(
+ commandMenuPageInfoState,
+ );
+ const setIsCommandMenuOpened = useSetRecoilState(
+ isCommandMenuOpenedState,
+ );
+ const setCommandMenuSearch = useSetRecoilState(commandMenuSearchState);
+ const setCommandMenuNavigationStack = useSetRecoilState(
+ commandMenuNavigationStackState,
+ );
+ const setCommandMenuNavigationRecords = useSetRecoilState(
+ commandMenuNavigationRecordsState,
+ );
+ const setHasUserSelectedCommand = useSetRecoilState(
+ hasUserSelectedCommandState,
+ );
+ const setIsCommandMenuClosing = useSetRecoilState(
+ isCommandMenuClosingState,
+ );
+ const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
+
+ return {
+ commandMenuCloseAnimationCompleteCleanup,
+ commandMenuPage,
+ commandMenuPageInfo,
+ isCommandMenuOpened,
+ commandMenuSearch,
+ commandMenuNavigationStack,
+ commandMenuNavigationRecords,
+ commandMenuNavigationMorphItemByPage,
+ hasUserSelectedCommand,
+ isCommandMenuClosing,
+ viewableRecordId,
+ setCommandMenuPage,
+ setCommandMenuPageInfo,
+ setIsCommandMenuOpened,
+ setCommandMenuSearch,
+ setCommandMenuNavigationStack,
+ setCommandMenuNavigationRecords,
+ setHasUserSelectedCommand,
+ setIsCommandMenuClosing,
+ setViewableRecordId,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+ return { result };
+ };
+
+ it('should reset modified states back to default values', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.setCommandMenuPage(CommandMenuPages.ViewRecord);
+ result.current.setCommandMenuPageInfo({
+ title: 'Test Record',
+ Icon: IconList,
+ instanceId: 'test-id',
+ });
+ result.current.setIsCommandMenuOpened(true);
+ result.current.setCommandMenuSearch('test search');
+ result.current.setCommandMenuNavigationStack([
+ {
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: 'Search',
+ pageIcon: IconList,
+ pageId: '1',
+ },
+ ]);
+ result.current.setCommandMenuNavigationRecords([
+ {
+ objectMetadataItem: { id: '1', nameSingular: 'Record' } as any,
+ record: { id: '1' } as any,
+ },
+ ]);
+ result.current.setHasUserSelectedCommand(true);
+ result.current.setIsCommandMenuClosing(true);
+ result.current.setViewableRecordId('record-123');
+ });
+
+ expect(result.current.commandMenuPage).toBe(CommandMenuPages.ViewRecord);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: 'Test Record',
+ Icon: IconList,
+ instanceId: 'test-id',
+ });
+ expect(result.current.isCommandMenuOpened).toBe(true);
+ expect(result.current.commandMenuSearch).toBe('test search');
+ expect(result.current.commandMenuNavigationStack).toEqual([
+ {
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: 'Search',
+ pageIcon: IconList,
+ pageId: '1',
+ },
+ ]);
+ expect(result.current.commandMenuNavigationRecords).toEqual([
+ {
+ objectMetadataItem: { id: '1', nameSingular: 'Record' } as any,
+ record: { id: '1' } as any,
+ },
+ ]);
+ expect(result.current.hasUserSelectedCommand).toBe(true);
+ expect(result.current.isCommandMenuClosing).toBe(true);
+ expect(result.current.viewableRecordId).toBe('record-123');
+
+ act(() => {
+ result.current.commandMenuCloseAnimationCompleteCleanup();
+ });
+
+ expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: undefined,
+ Icon: undefined,
+ instanceId: '',
+ });
+ expect(result.current.isCommandMenuOpened).toBe(false);
+ expect(result.current.commandMenuSearch).toBe('');
+ expect(result.current.commandMenuNavigationStack).toEqual([]);
+ expect(result.current.commandMenuNavigationRecords).toEqual([]);
+ expect(result.current.hasUserSelectedCommand).toBe(false);
+ expect(result.current.isCommandMenuClosing).toBe(false);
+ expect(result.current.viewableRecordId).toBe(null);
+ });
+
+ it('should call all dependent functions correctly', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.commandMenuCloseAnimationCompleteCleanup();
+ });
+
+ expect(mockCloseDropdown).toHaveBeenCalledTimes(1);
+ expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2);
+ expect(mockResetSelectedItem).toHaveBeenCalledTimes(1);
+ expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalledTimes(1);
+ expect(mockEmitRightDrawerCloseEvent).toHaveBeenCalledTimes(1);
+
+ expect(mockCloseDropdown).toHaveBeenCalledWith(
+ COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID,
+ );
+ expect(mockResetContextStoreStates).toHaveBeenNthCalledWith(
+ 1,
+ COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ );
+ expect(mockResetContextStoreStates).toHaveBeenNthCalledWith(
+ 2,
+ COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuHistory.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuHistory.test.tsx
new file mode 100644
index 000000000..5f1dc3ffb
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuHistory.test.tsx
@@ -0,0 +1,147 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { RecoilRoot, useRecoilValue } from 'recoil';
+
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
+import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
+import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { IconList, IconSearch } from 'twenty-ui';
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const commandMenu = useCommandMenu();
+ const commandMenuHistory = useCommandMenuHistory();
+ const commandMenuCloseAnimationCompleteCleanup =
+ useCommandMenuCloseAnimationCompleteCleanup();
+ const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
+ const commandMenuNavigationStack = useRecoilValue(
+ commandMenuNavigationStackState,
+ );
+ const commandMenuPage = useRecoilValue(commandMenuPageState);
+ const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
+
+ return {
+ commandMenu,
+ commandMenuHistory,
+ isCommandMenuOpened,
+ commandMenuNavigationStack,
+ commandMenuPage,
+ commandMenuPageInfo,
+ commandMenuCloseAnimationCompleteCleanup,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useCommandMenuHistory', () => {
+ it('should go back from a page', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.commandMenu.navigateCommandMenu({
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: 'Search',
+ pageIcon: IconSearch,
+ pageId: '1',
+ });
+ });
+
+ act(() => {
+ result.current.commandMenu.navigateCommandMenu({
+ page: CommandMenuPages.ViewRecord,
+ pageTitle: 'Company',
+ pageIcon: IconList,
+ pageId: '2',
+ });
+ });
+
+ expect(result.current.commandMenuNavigationStack).toEqual([
+ {
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: 'Search',
+ pageIcon: IconSearch,
+ pageId: '1',
+ },
+ {
+ page: CommandMenuPages.ViewRecord,
+ pageTitle: 'Company',
+ pageIcon: IconList,
+ pageId: '2',
+ },
+ ]);
+
+ act(() => {
+ result.current.commandMenuHistory.goBackFromCommandMenu();
+ });
+
+ expect(result.current.commandMenuNavigationStack).toEqual([
+ {
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: 'Search',
+ pageIcon: IconSearch,
+ pageId: '1',
+ },
+ ]);
+ expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: 'Search',
+ Icon: IconSearch,
+ instanceId: '1',
+ });
+
+ act(() => {
+ result.current.commandMenuHistory.goBackFromCommandMenu();
+ result.current.commandMenuCloseAnimationCompleteCleanup.commandMenuCloseAnimationCompleteCleanup();
+ });
+
+ expect(result.current.commandMenuNavigationStack).toEqual([]);
+ expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: undefined,
+ instanceId: '',
+ Icon: undefined,
+ });
+ expect(result.current.isCommandMenuOpened).toBe(false);
+ });
+
+ it('should navigate to a page in history', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.commandMenu.navigateCommandMenu({
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: 'Search',
+ pageIcon: IconSearch,
+ pageId: '1',
+ });
+ });
+
+ act(() => {
+ result.current.commandMenuHistory.navigateCommandMenuHistory(0);
+ });
+
+ expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: 'Search',
+ Icon: IconSearch,
+ instanceId: '1',
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useNavigateCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useNavigateCommandMenu.test.tsx
new file mode 100644
index 000000000..37d3a06c5
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useNavigateCommandMenu.test.tsx
@@ -0,0 +1,101 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
+import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { Icon123, useIcons } from 'twenty-ui';
+import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('mocked-uuid'),
+}));
+
+const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'person',
+)!;
+
+const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
+ apolloMocks: [],
+ componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ contextStoreCurrentObjectMetadataNameSingular:
+ personMockObjectMetadataItem.nameSingular,
+ contextStoreCurrentViewId: 'my-view-id',
+ contextStoreTargetedRecordsRule: {
+ mode: 'selection',
+ selectedRecordIds: [],
+ },
+ contextStoreNumberOfSelectedRecords: 0,
+ contextStoreCurrentViewType: ContextStoreViewType.Table,
+});
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const { navigateCommandMenu } = useNavigateCommandMenu();
+
+ const commandMenuPage = useRecoilValue(commandMenuPageState);
+ const commandMenuNavigationStack = useRecoilValue(
+ commandMenuNavigationStackState,
+ );
+ const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
+
+ const { getIcon } = useIcons();
+
+ return {
+ navigateCommandMenu,
+ commandMenuPage,
+ commandMenuNavigationStack,
+ commandMenuPageInfo,
+ getIcon,
+ };
+ },
+ {
+ wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useNavigateCommandMenu', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should navigate to the correct page', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.navigateCommandMenu({
+ page: CommandMenuPages.Root,
+ pageTitle: 'Root',
+ pageIcon: Icon123,
+ pageIconColor: 'red',
+ pageId: 'mocked-uuid',
+ resetNavigationStack: false,
+ });
+ });
+
+ expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
+ expect(result.current.commandMenuNavigationStack).toEqual([
+ {
+ page: CommandMenuPages.Root,
+ pageTitle: 'Root',
+ pageIcon: Icon123,
+ pageIconColor: 'red',
+ pageId: 'mocked-uuid',
+ },
+ ]);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: 'Root',
+ Icon: Icon123,
+ instanceId: 'mocked-uuid',
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenCalendarEventInCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenCalendarEventInCommandMenu.test.tsx
new file mode 100644
index 000000000..12f136c7a
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenCalendarEventInCommandMenu.test.tsx
@@ -0,0 +1,89 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { useOpenCalendarEventInCommandMenu } from '@/command-menu/hooks/useOpenCalendarEventInCommandMenu';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { IconCalendarEvent } from 'twenty-ui';
+import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('mocked-uuid'),
+}));
+
+const mockNavigateCommandMenu = jest.fn();
+jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
+ useNavigateCommandMenu: () => ({
+ navigateCommandMenu: mockNavigateCommandMenu,
+ }),
+}));
+
+const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'person',
+)!;
+
+const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
+ apolloMocks: [],
+ componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ contextStoreCurrentObjectMetadataNameSingular:
+ personMockObjectMetadataItem.nameSingular,
+ contextStoreCurrentViewId: 'my-view-id',
+ contextStoreTargetedRecordsRule: {
+ mode: 'selection',
+ selectedRecordIds: [],
+ },
+ contextStoreNumberOfSelectedRecords: 0,
+ contextStoreCurrentViewType: ContextStoreViewType.Table,
+});
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const { openCalendarEventInCommandMenu } =
+ useOpenCalendarEventInCommandMenu();
+
+ const viewableRecordId = useRecoilComponentValueV2(
+ viewableRecordIdComponentState,
+ 'mocked-uuid',
+ );
+
+ return {
+ openCalendarEventInCommandMenu,
+ viewableRecordId,
+ };
+ },
+ {
+ wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useOpenCalendarEventInCommandMenu', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should set the correct states and navigate to the calendar event page', () => {
+ const { result } = renderHooks();
+
+ const calendarEventId = 'calendar-event-123';
+
+ act(() => {
+ result.current.openCalendarEventInCommandMenu(calendarEventId);
+ });
+
+ expect(result.current.viewableRecordId).toBe(calendarEventId);
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.ViewCalendarEvent,
+ pageTitle: 'Calendar Event',
+ pageIcon: IconCalendarEvent,
+ pageId: 'mocked-uuid',
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenEmailThreadInCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenEmailThreadInCommandMenu.test.tsx
new file mode 100644
index 000000000..ea0249f03
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenEmailThreadInCommandMenu.test.tsx
@@ -0,0 +1,89 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { useOpenEmailThreadInCommandMenu } from '@/command-menu/hooks/useOpenEmailThreadInCommandMenu';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { IconMail } from 'twenty-ui';
+import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('mocked-uuid'),
+}));
+
+const mockNavigateCommandMenu = jest.fn();
+jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
+ useNavigateCommandMenu: () => ({
+ navigateCommandMenu: mockNavigateCommandMenu,
+ }),
+}));
+
+const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'person',
+)!;
+
+const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
+ apolloMocks: [],
+ componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ contextStoreCurrentObjectMetadataNameSingular:
+ personMockObjectMetadataItem.nameSingular,
+ contextStoreCurrentViewId: 'my-view-id',
+ contextStoreTargetedRecordsRule: {
+ mode: 'selection',
+ selectedRecordIds: [],
+ },
+ contextStoreNumberOfSelectedRecords: 0,
+ contextStoreCurrentViewType: ContextStoreViewType.Table,
+});
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const { openEmailThreadInCommandMenu } =
+ useOpenEmailThreadInCommandMenu();
+
+ const viewableRecordId = useRecoilComponentValueV2(
+ viewableRecordIdComponentState,
+ 'mocked-uuid',
+ );
+
+ return {
+ openEmailThreadInCommandMenu,
+ viewableRecordId,
+ };
+ },
+ {
+ wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useOpenEmailThreadInCommandMenu', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should set the correct states and navigate to the email thread page', () => {
+ const { result } = renderHooks();
+
+ const emailThreadId = 'email-thread-123';
+
+ act(() => {
+ result.current.openEmailThreadInCommandMenu(emailThreadId);
+ });
+
+ expect(result.current.viewableRecordId).toBe(emailThreadId);
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.ViewEmailThread,
+ pageTitle: 'Email Thread',
+ pageIcon: IconMail,
+ pageId: 'mocked-uuid',
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenRecordInCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenRecordInCommandMenu.test.tsx
new file mode 100644
index 000000000..63c04f1a4
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useOpenRecordInCommandMenu.test.tsx
@@ -0,0 +1,178 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useIcons } from 'twenty-ui';
+import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('mocked-uuid'),
+}));
+
+const mockNavigateCommandMenu = jest.fn();
+jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
+ useNavigateCommandMenu: () => ({
+ navigateCommandMenu: mockNavigateCommandMenu,
+ }),
+}));
+
+const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'person',
+)!;
+
+const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
+ apolloMocks: [],
+ componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ contextStoreCurrentObjectMetadataNameSingular:
+ personMockObjectMetadataItem.nameSingular,
+ contextStoreCurrentViewId: 'my-view-id',
+ contextStoreTargetedRecordsRule: {
+ mode: 'selection',
+ selectedRecordIds: [],
+ },
+ contextStoreNumberOfSelectedRecords: 0,
+ contextStoreCurrentViewType: ContextStoreViewType.Table,
+});
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
+
+ const commandMenuPage = useRecoilValue(commandMenuPageState);
+ const commandMenuNavigationMorphItemByPage = useRecoilValue(
+ commandMenuNavigationMorphItemByPageState,
+ );
+
+ const viewableRecordId = useRecoilComponentValueV2(
+ viewableRecordIdComponentState,
+ 'mocked-uuid',
+ );
+ const viewableRecordNameSingular = useRecoilComponentValueV2(
+ viewableRecordNameSingularComponentState,
+ 'mocked-uuid',
+ );
+ const currentObjectMetadataItemId = useRecoilComponentValueV2(
+ contextStoreCurrentObjectMetadataItemIdComponentState,
+ 'mocked-uuid',
+ );
+ const targetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ 'mocked-uuid',
+ );
+ const numberOfSelectedRecords = useRecoilComponentValueV2(
+ contextStoreNumberOfSelectedRecordsComponentState,
+ 'mocked-uuid',
+ );
+ const currentViewType = useRecoilComponentValueV2(
+ contextStoreCurrentViewTypeComponentState,
+ 'mocked-uuid',
+ );
+ const { getIcon } = useIcons();
+
+ return {
+ openRecordInCommandMenu,
+ viewableRecordId,
+ commandMenuPage,
+ commandMenuNavigationMorphItemByPage,
+ viewableRecordNameSingular,
+ currentObjectMetadataItemId,
+ targetedRecordsRule,
+ numberOfSelectedRecords,
+ currentViewType,
+ getIcon,
+ };
+ },
+ {
+ wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useOpenRecordInCommandMenu', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should set the correct states and navigate to the record page', () => {
+ const { result } = renderHooks();
+
+ const recordId = 'record-123';
+ const objectNameSingular = 'person';
+
+ act(() => {
+ result.current.openRecordInCommandMenu({
+ recordId,
+ objectNameSingular,
+ });
+ });
+
+ expect(result.current.viewableRecordId).toBe(recordId);
+ expect(result.current.viewableRecordNameSingular).toBe(objectNameSingular);
+ expect(result.current.currentObjectMetadataItemId).toBe(
+ personMockObjectMetadataItem.id,
+ );
+ expect(result.current.targetedRecordsRule).toEqual({
+ mode: 'selection',
+ selectedRecordIds: [recordId],
+ });
+ expect(result.current.numberOfSelectedRecords).toBe(1);
+ expect(result.current.currentViewType).toBe(ContextStoreViewType.ShowPage);
+
+ expect(result.current.commandMenuNavigationMorphItemByPage.size).toBe(1);
+ expect(
+ result.current.commandMenuNavigationMorphItemByPage.get('mocked-uuid'),
+ ).toEqual({
+ objectMetadataId: personMockObjectMetadataItem.id,
+ recordId,
+ });
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.ViewRecord,
+ pageTitle: 'Person',
+ pageIcon: result.current.getIcon(personMockObjectMetadataItem.icon),
+ pageIconColor: 'currentColor',
+ pageId: 'mocked-uuid',
+ resetNavigationStack: false,
+ });
+ });
+
+ it('should set the correct page title for a new record', () => {
+ const { result } = renderHooks();
+
+ const recordId = 'new-record-123';
+ const objectNameSingular = 'person';
+
+ act(() => {
+ result.current.openRecordInCommandMenu({
+ recordId,
+ objectNameSingular,
+ isNewRecord: true,
+ });
+ });
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.ViewRecord,
+ pageTitle: 'New Person',
+ pageIcon: result.current.getIcon(personMockObjectMetadataItem.icon),
+ pageIconColor: 'currentColor',
+ pageId: 'mocked-uuid',
+ resetNavigationStack: false,
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useSetGlobalCommandMenuContext.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useSetGlobalCommandMenuContext.test.tsx
new file mode 100644
index 000000000..c4a2e8d9d
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useSetGlobalCommandMenuContext.test.tsx
@@ -0,0 +1,157 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
+import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
+import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
+
+const mockCopyContextStoreStates = jest.fn();
+jest.mock(
+ '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates',
+ () => ({
+ useCopyContextStoreStates: () => ({
+ copyContextStoreStates: mockCopyContextStoreStates,
+ }),
+ }),
+);
+
+const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'person',
+)!;
+
+const peopleMock = getPeopleRecordConnectionMock();
+
+const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
+ apolloMocks: [],
+ componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ contextStoreCurrentObjectMetadataNameSingular:
+ personMockObjectMetadataItem.nameSingular,
+ contextStoreCurrentViewId: 'my-view-id',
+ contextStoreTargetedRecordsRule: {
+ mode: 'selection',
+ selectedRecordIds: [peopleMock[0].id, peopleMock[1].id],
+ },
+ contextStoreNumberOfSelectedRecords: 2,
+ contextStoreCurrentViewType: ContextStoreViewType.Table,
+ onInitializeRecoilSnapshot: (snapshot) => {
+ snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]);
+ snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]);
+ },
+});
+
+describe('useSetGlobalCommandMenuContext', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should reset all command menu context states', () => {
+ const { result } = renderHook(
+ () => {
+ const { setGlobalCommandMenuContext } =
+ useSetGlobalCommandMenuContext();
+
+ const targetedRecordsRule = useRecoilValue(
+ contextStoreTargetedRecordsRuleComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ );
+
+ const numberOfSelectedRecords = useRecoilValue(
+ contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ );
+
+ const filters = useRecoilValue(
+ contextStoreFiltersComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ );
+
+ const currentViewType = useRecoilValue(
+ contextStoreCurrentViewTypeComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ );
+
+ const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
+
+ const hasUserSelectedCommand = useRecoilValue(
+ hasUserSelectedCommandState,
+ );
+
+ return {
+ setGlobalCommandMenuContext,
+ targetedRecordsRule,
+ numberOfSelectedRecords,
+ filters,
+ currentViewType,
+ commandMenuPageInfo,
+ hasUserSelectedCommand,
+ };
+ },
+ {
+ wrapper,
+ },
+ );
+
+ expect(result.current.targetedRecordsRule).toEqual({
+ mode: 'selection',
+ selectedRecordIds: [peopleMock[0].id, peopleMock[1].id],
+ });
+ expect(result.current.numberOfSelectedRecords).toBe(2);
+ expect(result.current.filters).toEqual([]);
+ expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: undefined,
+ Icon: undefined,
+ instanceId: '',
+ });
+ expect(result.current.hasUserSelectedCommand).toBe(false);
+
+ act(() => {
+ result.current.setGlobalCommandMenuContext();
+ });
+
+ expect(result.current.targetedRecordsRule).toEqual({
+ mode: 'selection',
+ selectedRecordIds: [],
+ });
+ expect(result.current.numberOfSelectedRecords).toBe(0);
+ expect(result.current.filters).toEqual([]);
+ expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
+ expect(result.current.commandMenuPageInfo).toEqual({
+ title: undefined,
+ Icon: undefined,
+ instanceId: '',
+ });
+ expect(result.current.hasUserSelectedCommand).toBe(false);
+ });
+
+ it('should call copyContextStoreStates with correct parameters', () => {
+ const { result } = renderHook(() => useSetGlobalCommandMenuContext(), {
+ wrapper,
+ });
+
+ act(() => {
+ result.current.setGlobalCommandMenuContext();
+ });
+
+ expect(mockCopyContextStoreStates).toHaveBeenCalledWith({
+ instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx
new file mode 100644
index 000000000..8fdae3d2c
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx
@@ -0,0 +1,207 @@
+import { renderHook } from '@testing-library/react';
+import { useRecoilValue } from 'recoil';
+
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
+import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { t } from '@lingui/core/macro';
+import { act } from 'react';
+import { IconBolt, IconSettingsAutomation, useIcons } from 'twenty-ui';
+import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+import { useWorkflowCommandMenu } from '../useWorkflowCommandMenu';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('mocked-uuid'),
+}));
+
+const mockNavigateCommandMenu = jest.fn();
+jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
+ useNavigateCommandMenu: () => ({
+ navigateCommandMenu: mockNavigateCommandMenu,
+ }),
+}));
+
+const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'workflow',
+)!;
+
+jest.mock('@/object-metadata/hooks/useObjectMetadataItem', () => ({
+ useObjectMetadataItem: jest.fn(() => ({
+ objectMetadataItem: workflowMockObjectMetadataItem,
+ })),
+}));
+
+const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
+ apolloMocks: [],
+ componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ contextStoreCurrentObjectMetadataNameSingular:
+ workflowMockObjectMetadataItem.nameSingular,
+ contextStoreCurrentViewId: 'my-view-id',
+ contextStoreTargetedRecordsRule: {
+ mode: 'selection',
+ selectedRecordIds: [],
+ },
+ contextStoreNumberOfSelectedRecords: 0,
+ contextStoreCurrentViewType: ContextStoreViewType.Table,
+});
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const {
+ openWorkflowTriggerTypeInCommandMenu,
+ openStepSelectInCommandMenu,
+ openWorkflowEditStepInCommandMenu,
+ openWorkflowViewStepInCommandMenu,
+ } = useWorkflowCommandMenu();
+ const commandMenuPage = useRecoilValue(commandMenuPageState);
+ const commandMenuNavigationMorphItemByPage = useRecoilValue(
+ commandMenuNavigationMorphItemByPageState,
+ );
+
+ const viewableRecordId = useRecoilComponentValueV2(
+ viewableRecordIdComponentState,
+ 'mocked-uuid',
+ );
+ const viewableRecordNameSingular = useRecoilComponentValueV2(
+ viewableRecordNameSingularComponentState,
+ 'mocked-uuid',
+ );
+ const currentObjectMetadataItemId = useRecoilComponentValueV2(
+ contextStoreCurrentObjectMetadataItemIdComponentState,
+ 'mocked-uuid',
+ );
+ const targetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ 'mocked-uuid',
+ );
+ const numberOfSelectedRecords = useRecoilComponentValueV2(
+ contextStoreNumberOfSelectedRecordsComponentState,
+ 'mocked-uuid',
+ );
+ const currentViewType = useRecoilComponentValueV2(
+ contextStoreCurrentViewTypeComponentState,
+ 'mocked-uuid',
+ );
+ const workflowId = useRecoilComponentValueV2(
+ workflowIdComponentState,
+ 'mocked-uuid',
+ );
+ const { getIcon } = useIcons();
+
+ return {
+ openWorkflowTriggerTypeInCommandMenu,
+ openStepSelectInCommandMenu,
+ openWorkflowEditStepInCommandMenu,
+ openWorkflowViewStepInCommandMenu,
+ workflowId,
+ viewableRecordId,
+ commandMenuPage,
+ commandMenuNavigationMorphItemByPage,
+ viewableRecordNameSingular,
+ currentObjectMetadataItemId,
+ targetedRecordsRule,
+ numberOfSelectedRecords,
+ currentViewType,
+ getIcon,
+ };
+ },
+ {
+ wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useWorkflowCommandMenu', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should navigate to the workflow step select trigger type page', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.openWorkflowTriggerTypeInCommandMenu('test-workflow-id');
+ });
+
+ expect(result.current.workflowId).toBe('test-workflow-id');
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.WorkflowStepSelectTriggerType,
+ pageTitle: t`Trigger Type`,
+ pageIcon: IconBolt,
+ pageId: 'mocked-uuid',
+ });
+ });
+
+ it('should navigate to the workflow step select action page', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.openStepSelectInCommandMenu('test-workflow-id');
+ });
+
+ expect(result.current.workflowId).toBe('test-workflow-id');
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.WorkflowStepSelectAction,
+ pageTitle: t`Select Action`,
+ pageIcon: IconSettingsAutomation,
+ pageId: 'mocked-uuid',
+ });
+ });
+
+ it('should navigate to the workflow step edit page', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.openWorkflowEditStepInCommandMenu(
+ 'test-workflow-id',
+ 'Edit Step',
+ IconSettingsAutomation,
+ );
+ });
+
+ expect(result.current.workflowId).toBe('test-workflow-id');
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.WorkflowStepEdit,
+ pageTitle: 'Edit Step',
+ pageIcon: IconSettingsAutomation,
+ pageId: 'mocked-uuid',
+ });
+ });
+
+ it('should navigate to the workflow step view page', () => {
+ const { result } = renderHooks();
+
+ act(() => {
+ result.current.openWorkflowViewStepInCommandMenu(
+ 'test-workflow-id',
+ 'View Step',
+ IconSettingsAutomation,
+ );
+ });
+
+ expect(result.current.workflowId).toBe('test-workflow-id');
+
+ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
+ page: CommandMenuPages.WorkflowStepView,
+ pageTitle: 'View Step',
+ pageIcon: IconSettingsAutomation,
+ pageId: 'mocked-uuid',
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
index ec3a0dcb3..76b6d4e20 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
@@ -1,79 +1,17 @@
import { useRecoilCallback } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
-import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
-import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
-import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
-import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
-import {
- IconBolt,
- IconCalendarEvent,
- IconComponent,
- IconDotsVertical,
- IconMail,
- IconSearch,
- IconSettingsAutomation,
- useIcons,
-} from 'twenty-ui';
+import { IconDotsVertical } from 'twenty-ui';
-import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
-import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
-import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
-import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
-import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
-import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
-import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
-import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
-import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
-import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
-import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
-import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
-import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
-import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
-import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
-import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
-import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
-import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
-import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
-import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
-import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
-import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
-import { useTheme } from '@emotion/react';
-import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
-import { capitalize, isDefined } from 'twenty-shared';
-import { v4 } from 'uuid';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
-export type CommandMenuNavigationStackItem = {
- page: CommandMenuPages;
- pageTitle: string;
- pageIcon: IconComponent;
- pageIconColor?: string;
- pageId?: string;
-};
-
export const useCommandMenu = () => {
- const { resetSelectedItem } = useSelectableList('command-menu-list');
- const {
- setHotkeyScopeAndMemorizePreviousScope,
- goBackToPreviousHotkeyScope,
- } = usePreviousHotkeyScope();
- const { getIcon } = useIcons();
-
- const { copyContextStoreStates } = useCopyContextStoreStates();
- const { resetContextStoreStates } = useResetContextStoreStates();
-
- const { closeDropdown } = useDropdownV2();
-
- const theme = useTheme();
+ const { navigateCommandMenu } = useNavigateCommandMenu();
const closeCommandMenu = useRecoilCallback(
({ set }) =>
@@ -85,141 +23,7 @@ export const useCommandMenu = () => {
[],
);
- const onCommandMenuCloseAnimationComplete = useRecoilCallback(
- ({ set }) =>
- () => {
- closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
-
- resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
- resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
-
- set(viewableRecordIdState, null);
- set(commandMenuPageState, CommandMenuPages.Root);
- set(commandMenuPageInfoState, {
- title: undefined,
- Icon: undefined,
- instanceId: '',
- });
- set(isCommandMenuOpenedState, false);
- set(commandMenuSearchState, '');
- set(commandMenuNavigationMorphItemByPageState, new Map());
- set(commandMenuNavigationRecordsState, []);
- set(commandMenuNavigationStackState, []);
- resetSelectedItem();
- set(hasUserSelectedCommandState, false);
- goBackToPreviousHotkeyScope();
-
- emitRightDrawerCloseEvent();
- set(isCommandMenuClosingState, false);
- },
- [
- closeDropdown,
- goBackToPreviousHotkeyScope,
- resetContextStoreStates,
- resetSelectedItem,
- ],
- );
-
- const openCommandMenu = useRecoilCallback(
- ({ snapshot, set }) =>
- () => {
- const isCommandMenuOpened = snapshot
- .getLoadable(isCommandMenuOpenedState)
- .getValue();
-
- const isCommandMenuClosing = snapshot
- .getLoadable(isCommandMenuClosingState)
- .getValue();
-
- if (isCommandMenuClosing) {
- onCommandMenuCloseAnimationComplete();
- }
-
- setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
-
- if (isCommandMenuOpened) {
- return;
- }
-
- copyContextStoreStates({
- instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID,
- instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
- });
-
- set(isCommandMenuOpenedState, true);
- set(hasUserSelectedCommandState, false);
- set(isDragSelectionStartEnabledState, false);
- },
- [
- copyContextStoreStates,
- onCommandMenuCloseAnimationComplete,
- setHotkeyScopeAndMemorizePreviousScope,
- ],
- );
-
- const navigateCommandMenu = useRecoilCallback(
- ({ snapshot, set }) => {
- return ({
- page,
- pageTitle,
- pageIcon,
- pageIconColor,
- pageId,
- resetNavigationStack = false,
- }: CommandMenuNavigationStackItem & {
- resetNavigationStack?: boolean;
- }) => {
- if (!pageId) {
- pageId = v4();
- }
-
- openCommandMenu();
- set(commandMenuPageState, page);
- set(commandMenuPageInfoState, {
- title: pageTitle,
- Icon: pageIcon,
- instanceId: pageId,
- });
-
- const isCommandMenuClosing = snapshot
- .getLoadable(isCommandMenuClosingState)
- .getValue();
-
- const currentNavigationStack = isCommandMenuClosing
- ? []
- : snapshot.getLoadable(commandMenuNavigationStackState).getValue();
-
- if (resetNavigationStack) {
- set(commandMenuNavigationStackState, [
- {
- page,
- pageTitle,
- pageIcon,
- pageIconColor,
- pageId,
- },
- ]);
-
- set(commandMenuNavigationRecordsState, []);
- set(commandMenuNavigationMorphItemByPageState, new Map());
- } else {
- set(commandMenuNavigationStackState, [
- ...currentNavigationStack,
- {
- page,
- pageTitle,
- pageIcon,
- pageIconColor,
- pageId,
- },
- ]);
- }
- };
- },
- [openCommandMenu],
- );
-
- const openRootCommandMenu = useCallback(() => {
+ const openCommandMenu = useCallback(() => {
navigateCommandMenu({
page: CommandMenuPages.Root,
pageTitle: 'Command Menu',
@@ -240,503 +44,16 @@ export const useCommandMenu = () => {
if (isCommandMenuOpened) {
closeCommandMenu();
} else {
- openRootCommandMenu();
+ openCommandMenu();
}
},
- [closeCommandMenu, openRootCommandMenu],
- );
-
- const goBackFromCommandMenu = useRecoilCallback(
- ({ snapshot, set }) => {
- return () => {
- const currentNavigationStack = snapshot
- .getLoadable(commandMenuNavigationStackState)
- .getValue();
-
- const newNavigationStack = currentNavigationStack.slice(0, -1);
- const lastNavigationStackItem = newNavigationStack.at(-1);
-
- if (!isDefined(lastNavigationStackItem)) {
- closeCommandMenu();
- return;
- }
-
- set(commandMenuPageState, lastNavigationStackItem.page);
-
- set(commandMenuPageInfoState, {
- title: lastNavigationStackItem.pageTitle,
- Icon: lastNavigationStackItem.pageIcon,
- instanceId: lastNavigationStackItem.pageId,
- });
-
- set(commandMenuNavigationStackState, newNavigationStack);
-
- const currentMorphItems = snapshot
- .getLoadable(commandMenuNavigationMorphItemByPageState)
- .getValue();
-
- if (currentNavigationStack.length > 0) {
- const removedItem = currentNavigationStack.at(-1);
-
- if (isDefined(removedItem)) {
- const newMorphItems = new Map(currentMorphItems);
- newMorphItems.delete(removedItem.pageId);
- set(commandMenuNavigationMorphItemByPageState, newMorphItems);
- }
- }
-
- set(hasUserSelectedCommandState, false);
- };
- },
- [closeCommandMenu],
- );
-
- const navigateCommandMenuHistory = useRecoilCallback(({ snapshot, set }) => {
- return (pageIndex: number) => {
- const currentNavigationStack = snapshot
- .getLoadable(commandMenuNavigationStackState)
- .getValue();
-
- const newNavigationStack = currentNavigationStack.slice(0, pageIndex + 1);
-
- set(commandMenuNavigationStackState, newNavigationStack);
-
- const newNavigationStackItem = newNavigationStack.at(-1);
-
- if (!isDefined(newNavigationStackItem)) {
- throw new Error(
- `No command menu navigation stack item found for index ${pageIndex}`,
- );
- }
-
- set(commandMenuPageState, newNavigationStackItem?.page);
- set(commandMenuPageInfoState, {
- title: newNavigationStackItem?.pageTitle,
- Icon: newNavigationStackItem?.pageIcon,
- instanceId: newNavigationStackItem?.pageId,
- });
- const currentMorphItems = snapshot
- .getLoadable(commandMenuNavigationMorphItemByPageState)
- .getValue();
-
- const newMorphItems = new Map(
- Array.from(currentMorphItems.entries()).filter(([pageId]) =>
- newNavigationStack.some((item) => item.pageId === pageId),
- ),
- );
-
- set(commandMenuNavigationMorphItemByPageState, newMorphItems);
-
- set(hasUserSelectedCommandState, false);
- };
- }, []);
-
- const openRecordInCommandMenu = useRecoilCallback(
- ({ set, snapshot }) => {
- return ({
- recordId,
- objectNameSingular,
- isNewRecord = false,
- }: {
- recordId: string;
- objectNameSingular: string;
- isNewRecord?: boolean;
- }) => {
- const pageComponentInstanceId = v4();
-
- set(
- viewableRecordNameSingularComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- objectNameSingular,
- );
- set(
- viewableRecordIdComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- recordId,
- );
- set(viewableRecordIdState, recordId);
-
- const objectMetadataItem = snapshot
- .getLoadable(
- objectMetadataItemFamilySelector({
- objectName: objectNameSingular,
- objectNameType: 'singular',
- }),
- )
- .getValue();
-
- if (!objectMetadataItem) {
- throw new Error(
- `No object metadata item found for object name ${objectNameSingular}`,
- );
- }
-
- set(
- contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- objectMetadataItem.id,
- );
-
- set(
- contextStoreTargetedRecordsRuleComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- {
- mode: 'selection',
- selectedRecordIds: [recordId],
- },
- );
-
- set(
- contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- 1,
- );
-
- set(
- contextStoreCurrentViewTypeComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- ContextStoreViewType.ShowPage,
- );
-
- set(
- contextStoreCurrentViewIdComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- snapshot
- .getLoadable(
- contextStoreCurrentViewIdComponentState.atomFamily({
- instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
- }),
- )
- .getValue(),
- );
-
- const currentMorphItems = snapshot
- .getLoadable(commandMenuNavigationMorphItemByPageState)
- .getValue();
-
- const morphItemToAdd = {
- objectMetadataId: objectMetadataItem.id,
- recordId,
- };
-
- const newMorphItems = new Map([
- ...currentMorphItems,
- [pageComponentInstanceId, morphItemToAdd],
- ]);
-
- set(commandMenuNavigationMorphItemByPageState, newMorphItems);
-
- const Icon = objectMetadataItem?.icon
- ? getIcon(objectMetadataItem.icon)
- : getIcon('IconList');
-
- const IconColor = getIconColorForObjectType({
- objectType: objectMetadataItem.nameSingular,
- theme,
- });
-
- const capitalizedObjectNameSingular = capitalize(objectNameSingular);
-
- navigateCommandMenu({
- page: CommandMenuPages.ViewRecord,
- pageTitle: isNewRecord
- ? t`New ${capitalizedObjectNameSingular}`
- : capitalizedObjectNameSingular,
- pageIcon: Icon,
- pageIconColor: IconColor,
- pageId: pageComponentInstanceId,
- resetNavigationStack: false,
- });
- };
- },
- [getIcon, navigateCommandMenu, theme],
- );
-
- const openWorkflowTriggerTypeInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (workflowId: string) => {
- const pageId = v4();
-
- set(
- workflowIdComponentState.atomFamily({ instanceId: pageId }),
- workflowId,
- );
-
- navigateCommandMenu({
- page: CommandMenuPages.WorkflowStepSelectTriggerType,
- pageTitle: t`Trigger Type`,
- pageIcon: IconBolt,
- pageId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const openWorkflowActionInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (workflowId: string) => {
- const pageId = v4();
-
- set(
- workflowIdComponentState.atomFamily({ instanceId: pageId }),
- workflowId,
- );
-
- navigateCommandMenu({
- page: CommandMenuPages.WorkflowStepSelectAction,
- pageTitle: t`Select Action`,
- pageIcon: IconSettingsAutomation,
- pageId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const openWorkflowEditStepInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (workflowId: string, title: string, icon: IconComponent) => {
- const pageId = v4();
-
- set(
- workflowIdComponentState.atomFamily({ instanceId: pageId }),
- workflowId,
- );
-
- navigateCommandMenu({
- page: CommandMenuPages.WorkflowStepEdit,
- pageTitle: title,
- pageIcon: icon,
- pageId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const openWorkflowViewStepInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (workflowId: string, title: string, icon: IconComponent) => {
- const pageId = v4();
-
- set(
- workflowIdComponentState.atomFamily({ instanceId: pageId }),
- workflowId,
- );
-
- navigateCommandMenu({
- page: CommandMenuPages.WorkflowStepView,
- pageTitle: title,
- pageIcon: icon,
- pageId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const openWorkflowViewRunStepInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (workflowId: string, title: string, icon: IconComponent) => {
- const pageId = v4();
-
- set(
- workflowIdComponentState.atomFamily({ instanceId: pageId }),
- workflowId,
- );
-
- navigateCommandMenu({
- page: CommandMenuPages.WorkflowRunStepView,
- pageTitle: title,
- pageIcon: icon,
- pageId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const openRecordsSearchPage = () => {
- navigateCommandMenu({
- page: CommandMenuPages.SearchRecords,
- pageTitle: 'Search',
- pageIcon: IconSearch,
- pageId: v4(),
- });
- };
-
- const openCalendarEventInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (calendarEventId: string) => {
- const pageComponentInstanceId = v4();
-
- set(
- viewableRecordIdComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- calendarEventId,
- );
-
- // TODO: Uncomment this once we need to calendar event title in the navigation
- // const objectMetadataItem = snapshot
- // .getLoadable(objectMetadataItemsState)
- // .getValue()
- // .find(
- // ({ nameSingular }) =>
- // nameSingular === CoreObjectNameSingular.CalendarEvent,
- // );
-
- // set(
- // commandMenuNavigationMorphItemsState,
- // new Map([
- // ...snapshot
- // .getLoadable(commandMenuNavigationMorphItemsState)
- // .getValue(),
- // [
- // pageComponentInstanceId,
- // {
- // objectMetadataId: objectMetadataItem?.id,
- // recordId: calendarEventId,
- // },
- // ],
- // ]),
- // );
-
- navigateCommandMenu({
- page: CommandMenuPages.ViewCalendarEvent,
- pageTitle: 'Calendar Event',
- pageIcon: IconCalendarEvent,
- pageId: pageComponentInstanceId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const openEmailThreadInCommandMenu = useRecoilCallback(
- ({ set }) => {
- return (emailThreadId: string) => {
- const pageComponentInstanceId = v4();
-
- set(
- viewableRecordIdComponentState.atomFamily({
- instanceId: pageComponentInstanceId,
- }),
- emailThreadId,
- );
-
- // TODO: Uncomment this once we need to show the thread title in the navigation
- // const objectMetadataItem = snapshot
- // .getLoadable(objectMetadataItemsState)
- // .getValue()
- // .find(
- // ({ nameSingular }) =>
- // nameSingular === CoreObjectNameSingular.MessageThread,
- // );
-
- // set(
- // commandMenuNavigationMorphItemsState,
- // new Map([
- // ...snapshot
- // .getLoadable(commandMenuNavigationMorphItemsState)
- // .getValue(),
- // [
- // pageComponentInstanceId,
- // {
- // objectMetadataId: objectMetadataItem?.id,
- // recordId: emailThreadId,
- // },
- // ],
- // ]),
- // );
-
- navigateCommandMenu({
- page: CommandMenuPages.ViewEmailThread,
- pageTitle: 'Email Thread',
- pageIcon: IconMail,
- pageId: pageComponentInstanceId,
- });
- };
- },
- [navigateCommandMenu],
- );
-
- const setGlobalCommandMenuContext = useRecoilCallback(
- ({ set }) => {
- return () => {
- copyContextStoreStates({
- instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
- instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
- });
-
- set(
- contextStoreTargetedRecordsRuleComponentState.atomFamily({
- instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
- }),
- {
- mode: 'selection',
- selectedRecordIds: [],
- },
- );
-
- set(
- contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
- instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
- }),
- 0,
- );
-
- set(
- contextStoreFiltersComponentState.atomFamily({
- instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
- }),
- [],
- );
-
- set(
- contextStoreCurrentViewTypeComponentState.atomFamily({
- instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
- }),
- ContextStoreViewType.Table,
- );
-
- set(commandMenuPageInfoState, {
- title: undefined,
- Icon: undefined,
- instanceId: '',
- });
-
- set(hasUserSelectedCommandState, false);
- };
- },
- [copyContextStoreStates],
+ [closeCommandMenu, openCommandMenu],
);
return {
- openRootCommandMenu,
+ openCommandMenu,
closeCommandMenu,
- onCommandMenuCloseAnimationComplete,
navigateCommandMenu,
- navigateCommandMenuHistory,
- goBackFromCommandMenu,
- openRecordsSearchPage,
- openRecordInCommandMenu,
toggleCommandMenu,
- setGlobalCommandMenuContext,
- openCalendarEventInCommandMenu,
- openEmailThreadInCommandMenu,
- openWorkflowTriggerTypeInCommandMenu,
- openWorkflowActionInCommandMenu,
- openWorkflowEditStepInCommandMenu,
- openWorkflowViewStepInCommandMenu,
- openWorkflowViewRunStepInCommandMenu,
};
};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts
new file mode 100644
index 000000000..2855d7cf2
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts
@@ -0,0 +1,69 @@
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
+import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
+import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
+import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
+import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
+import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
+import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
+import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
+import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
+import { useRecoilCallback } from 'recoil';
+
+export const useCommandMenuCloseAnimationCompleteCleanup = () => {
+ const { resetSelectedItem } = useSelectableList('command-menu-list');
+
+ const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
+
+ const { resetContextStoreStates } = useResetContextStoreStates();
+
+ const { closeDropdown } = useDropdownV2();
+
+ const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback(
+ ({ set }) =>
+ () => {
+ closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
+
+ resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
+ resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
+
+ set(viewableRecordIdState, null);
+ set(commandMenuPageState, CommandMenuPages.Root);
+ set(commandMenuPageInfoState, {
+ title: undefined,
+ Icon: undefined,
+ instanceId: '',
+ });
+ set(isCommandMenuOpenedState, false);
+ set(commandMenuSearchState, '');
+ set(commandMenuNavigationMorphItemByPageState, new Map());
+ set(commandMenuNavigationRecordsState, []);
+ set(commandMenuNavigationStackState, []);
+ resetSelectedItem();
+ set(hasUserSelectedCommandState, false);
+ goBackToPreviousHotkeyScope();
+
+ emitRightDrawerCloseEvent();
+ set(isCommandMenuClosingState, false);
+ },
+ [
+ closeDropdown,
+ goBackToPreviousHotkeyScope,
+ resetContextStoreStates,
+ resetSelectedItem,
+ ],
+ );
+
+ return {
+ commandMenuCloseAnimationCompleteCleanup,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx
index ef16aac44..239b019c6 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx
@@ -1,5 +1,5 @@
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
@@ -24,7 +24,7 @@ export const useCommandMenuContextChips = () => {
commandMenuNavigationStackState,
);
- const { navigateCommandMenuHistory } = useCommandMenu();
+ const { navigateCommandMenuHistory } = useCommandMenuHistory();
const theme = useTheme();
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts
new file mode 100644
index 000000000..fd2f7da59
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts
@@ -0,0 +1,103 @@
+import { useRecoilCallback } from 'recoil';
+
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { isDefined } from 'twenty-shared';
+
+export const useCommandMenuHistory = () => {
+ const { closeCommandMenu } = useCommandMenu();
+
+ const goBackFromCommandMenu = useRecoilCallback(
+ ({ snapshot, set }) => {
+ return () => {
+ const currentNavigationStack = snapshot
+ .getLoadable(commandMenuNavigationStackState)
+ .getValue();
+
+ const newNavigationStack = currentNavigationStack.slice(0, -1);
+ const lastNavigationStackItem = newNavigationStack.at(-1);
+
+ if (!isDefined(lastNavigationStackItem)) {
+ closeCommandMenu();
+ return;
+ }
+
+ set(commandMenuPageState, lastNavigationStackItem.page);
+
+ set(commandMenuPageInfoState, {
+ title: lastNavigationStackItem.pageTitle,
+ Icon: lastNavigationStackItem.pageIcon,
+ instanceId: lastNavigationStackItem.pageId,
+ });
+
+ set(commandMenuNavigationStackState, newNavigationStack);
+
+ const currentMorphItems = snapshot
+ .getLoadable(commandMenuNavigationMorphItemByPageState)
+ .getValue();
+
+ if (currentNavigationStack.length > 0) {
+ const removedItem = currentNavigationStack.at(-1);
+
+ if (isDefined(removedItem)) {
+ const newMorphItems = new Map(currentMorphItems);
+ newMorphItems.delete(removedItem.pageId);
+ set(commandMenuNavigationMorphItemByPageState, newMorphItems);
+ }
+ }
+
+ set(hasUserSelectedCommandState, false);
+ };
+ },
+ [closeCommandMenu],
+ );
+
+ const navigateCommandMenuHistory = useRecoilCallback(({ snapshot, set }) => {
+ return (pageIndex: number) => {
+ const currentNavigationStack = snapshot
+ .getLoadable(commandMenuNavigationStackState)
+ .getValue();
+
+ const newNavigationStack = currentNavigationStack.slice(0, pageIndex + 1);
+
+ set(commandMenuNavigationStackState, newNavigationStack);
+
+ const newNavigationStackItem = newNavigationStack.at(-1);
+
+ if (!isDefined(newNavigationStackItem)) {
+ throw new Error(
+ `No command menu navigation stack item found for index ${pageIndex}`,
+ );
+ }
+
+ set(commandMenuPageState, newNavigationStackItem.page);
+ set(commandMenuPageInfoState, {
+ title: newNavigationStackItem.pageTitle,
+ Icon: newNavigationStackItem.pageIcon,
+ instanceId: newNavigationStackItem.pageId,
+ });
+ const currentMorphItems = snapshot
+ .getLoadable(commandMenuNavigationMorphItemByPageState)
+ .getValue();
+
+ const newMorphItems = new Map(
+ Array.from(currentMorphItems.entries()).filter(([pageId]) =>
+ newNavigationStack.some((item) => item.pageId === pageId),
+ ),
+ );
+
+ set(commandMenuNavigationMorphItemByPageState, newMorphItems);
+
+ set(hasUserSelectedCommandState, false);
+ };
+ }, []);
+
+ return {
+ goBackFromCommandMenu,
+ navigateCommandMenuHistory,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
index 012b804a7..6ff649965 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
@@ -1,5 +1,8 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
+import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
+import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
@@ -13,12 +16,13 @@ import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
export const useCommandMenuHotKeys = () => {
- const {
- openRecordsSearchPage,
- toggleCommandMenu,
- goBackFromCommandMenu,
- setGlobalCommandMenuContext,
- } = useCommandMenu();
+ const { toggleCommandMenu } = useCommandMenu();
+
+ const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
+
+ const { goBackFromCommandMenu } = useCommandMenuHistory();
+
+ const { setGlobalCommandMenuContext } = useSetGlobalCommandMenuContext();
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts
new file mode 100644
index 000000000..c82c84f43
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts
@@ -0,0 +1,137 @@
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
+import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
+import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
+import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
+import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
+import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
+import { useRecoilCallback } from 'recoil';
+import { IconComponent } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+export type CommandMenuNavigationStackItem = {
+ page: CommandMenuPages;
+ pageTitle: string;
+ pageIcon: IconComponent;
+ pageIconColor?: string;
+ pageId?: string;
+};
+
+export const useNavigateCommandMenu = () => {
+ const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
+
+ const { copyContextStoreStates } = useCopyContextStoreStates();
+
+ const { commandMenuCloseAnimationCompleteCleanup } =
+ useCommandMenuCloseAnimationCompleteCleanup();
+
+ const openCommandMenu = useRecoilCallback(
+ ({ snapshot, set }) =>
+ () => {
+ const isCommandMenuOpened = snapshot
+ .getLoadable(isCommandMenuOpenedState)
+ .getValue();
+
+ const isCommandMenuClosing = snapshot
+ .getLoadable(isCommandMenuClosingState)
+ .getValue();
+
+ if (isCommandMenuClosing) {
+ commandMenuCloseAnimationCompleteCleanup();
+ }
+
+ setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
+
+ if (isCommandMenuOpened) {
+ return;
+ }
+
+ copyContextStoreStates({
+ instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID,
+ instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ });
+
+ set(isCommandMenuOpenedState, true);
+ set(hasUserSelectedCommandState, false);
+ set(isDragSelectionStartEnabledState, false);
+ },
+ [
+ copyContextStoreStates,
+ commandMenuCloseAnimationCompleteCleanup,
+ setHotkeyScopeAndMemorizePreviousScope,
+ ],
+ );
+
+ const navigateCommandMenu = useRecoilCallback(
+ ({ snapshot, set }) => {
+ return ({
+ page,
+ pageTitle,
+ pageIcon,
+ pageIconColor,
+ pageId,
+ resetNavigationStack = false,
+ }: CommandMenuNavigationStackItem & {
+ resetNavigationStack?: boolean;
+ }) => {
+ const computedPageId = pageId || v4();
+
+ openCommandMenu();
+ set(commandMenuPageState, page);
+ set(commandMenuPageInfoState, {
+ title: pageTitle,
+ Icon: pageIcon,
+ instanceId: computedPageId,
+ });
+
+ const isCommandMenuClosing = snapshot
+ .getLoadable(isCommandMenuClosingState)
+ .getValue();
+
+ const currentNavigationStack = isCommandMenuClosing
+ ? []
+ : snapshot.getLoadable(commandMenuNavigationStackState).getValue();
+
+ if (resetNavigationStack) {
+ set(commandMenuNavigationStackState, [
+ {
+ page,
+ pageTitle,
+ pageIcon,
+ pageIconColor,
+ pageId: computedPageId,
+ },
+ ]);
+
+ set(commandMenuNavigationRecordsState, []);
+ set(commandMenuNavigationMorphItemByPageState, new Map());
+ } else {
+ set(commandMenuNavigationStackState, [
+ ...currentNavigationStack,
+ {
+ page,
+ pageTitle,
+ pageIcon,
+ pageIconColor,
+ pageId: computedPageId,
+ },
+ ]);
+ }
+ };
+ },
+ [openCommandMenu],
+ );
+
+ return {
+ navigateCommandMenu,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useOpenCalendarEventInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useOpenCalendarEventInCommandMenu.ts
new file mode 100644
index 000000000..3cd7a1aa1
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useOpenCalendarEventInCommandMenu.ts
@@ -0,0 +1,63 @@
+import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { t } from '@lingui/core/macro';
+import { useRecoilCallback } from 'recoil';
+import { IconCalendarEvent } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+export const useOpenCalendarEventInCommandMenu = () => {
+ const { navigateCommandMenu } = useNavigateCommandMenu();
+
+ const openCalendarEventInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (calendarEventId: string) => {
+ const pageComponentInstanceId = v4();
+
+ set(
+ viewableRecordIdComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ calendarEventId,
+ );
+
+ // TODO: Uncomment this once we need to calendar event title in the navigation
+ // const objectMetadataItem = snapshot
+ // .getLoadable(objectMetadataItemsState)
+ // .getValue()
+ // .find(
+ // ({ nameSingular }) =>
+ // nameSingular === CoreObjectNameSingular.CalendarEvent,
+ // );
+
+ // set(
+ // commandMenuNavigationMorphItemsState,
+ // new Map([
+ // ...snapshot
+ // .getLoadable(commandMenuNavigationMorphItemsState)
+ // .getValue(),
+ // [
+ // pageComponentInstanceId,
+ // {
+ // objectMetadataId: objectMetadataItem?.id,
+ // recordId: calendarEventId,
+ // },
+ // ],
+ // ]),
+ // );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.ViewCalendarEvent,
+ pageTitle: t`Calendar Event`,
+ pageIcon: IconCalendarEvent,
+ pageId: pageComponentInstanceId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ return {
+ openCalendarEventInCommandMenu,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useOpenEmailThreadInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useOpenEmailThreadInCommandMenu.ts
new file mode 100644
index 000000000..f55698c39
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useOpenEmailThreadInCommandMenu.ts
@@ -0,0 +1,62 @@
+import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { useRecoilCallback } from 'recoil';
+import { IconMail } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+export const useOpenEmailThreadInCommandMenu = () => {
+ const { navigateCommandMenu } = useNavigateCommandMenu();
+
+ const openEmailThreadInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (emailThreadId: string) => {
+ const pageComponentInstanceId = v4();
+
+ set(
+ viewableRecordIdComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ emailThreadId,
+ );
+
+ // TODO: Uncomment this once we need to show the thread title in the navigation
+ // const objectMetadataItem = snapshot
+ // .getLoadable(objectMetadataItemsState)
+ // .getValue()
+ // .find(
+ // ({ nameSingular }) =>
+ // nameSingular === CoreObjectNameSingular.MessageThread,
+ // );
+
+ // set(
+ // commandMenuNavigationMorphItemsState,
+ // new Map([
+ // ...snapshot
+ // .getLoadable(commandMenuNavigationMorphItemsState)
+ // .getValue(),
+ // [
+ // pageComponentInstanceId,
+ // {
+ // objectMetadataId: objectMetadataItem?.id,
+ // recordId: emailThreadId,
+ // },
+ // ],
+ // ]),
+ // );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.ViewEmailThread,
+ pageTitle: 'Email Thread',
+ pageIcon: IconMail,
+ pageId: pageComponentInstanceId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ return {
+ openEmailThreadInCommandMenu,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts
new file mode 100644
index 000000000..2b8a02d81
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts
@@ -0,0 +1,158 @@
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
+import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
+import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
+import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
+import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
+import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
+import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
+import { useTheme } from '@emotion/react';
+import { t } from '@lingui/core/macro';
+import { useRecoilCallback } from 'recoil';
+import { capitalize } from 'twenty-shared';
+import { useIcons } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+export const useOpenRecordInCommandMenu = () => {
+ const { navigateCommandMenu } = useCommandMenu();
+
+ const theme = useTheme();
+ const { getIcon } = useIcons();
+
+ const openRecordInCommandMenu = useRecoilCallback(
+ ({ set, snapshot }) => {
+ return ({
+ recordId,
+ objectNameSingular,
+ isNewRecord = false,
+ }: {
+ recordId: string;
+ objectNameSingular: string;
+ isNewRecord?: boolean;
+ }) => {
+ const pageComponentInstanceId = v4();
+
+ set(
+ viewableRecordNameSingularComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ objectNameSingular,
+ );
+ set(
+ viewableRecordIdComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ recordId,
+ );
+ set(viewableRecordIdState, recordId);
+
+ const objectMetadataItem = snapshot
+ .getLoadable(
+ objectMetadataItemFamilySelector({
+ objectName: objectNameSingular,
+ objectNameType: 'singular',
+ }),
+ )
+ .getValue();
+
+ if (!objectMetadataItem) {
+ throw new Error(
+ `No object metadata item found for object name ${objectNameSingular}`,
+ );
+ }
+
+ set(
+ contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ objectMetadataItem.id,
+ );
+
+ set(
+ contextStoreTargetedRecordsRuleComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ {
+ mode: 'selection',
+ selectedRecordIds: [recordId],
+ },
+ );
+
+ set(
+ contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ 1,
+ );
+
+ set(
+ contextStoreCurrentViewTypeComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ ContextStoreViewType.ShowPage,
+ );
+
+ set(
+ contextStoreCurrentViewIdComponentState.atomFamily({
+ instanceId: pageComponentInstanceId,
+ }),
+ snapshot
+ .getLoadable(
+ contextStoreCurrentViewIdComponentState.atomFamily({
+ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
+ }),
+ )
+ .getValue(),
+ );
+
+ const currentMorphItems = snapshot
+ .getLoadable(commandMenuNavigationMorphItemByPageState)
+ .getValue();
+
+ const morphItemToAdd = {
+ objectMetadataId: objectMetadataItem.id,
+ recordId,
+ };
+
+ const newMorphItems = new Map(currentMorphItems);
+ newMorphItems.set(pageComponentInstanceId, morphItemToAdd);
+
+ set(commandMenuNavigationMorphItemByPageState, newMorphItems);
+
+ const Icon = objectMetadataItem?.icon
+ ? getIcon(objectMetadataItem.icon)
+ : getIcon('IconList');
+
+ const IconColor = getIconColorForObjectType({
+ objectType: objectMetadataItem.nameSingular,
+ theme,
+ });
+
+ const capitalizedObjectNameSingular = capitalize(objectNameSingular);
+
+ navigateCommandMenu({
+ page: CommandMenuPages.ViewRecord,
+ pageTitle: isNewRecord
+ ? t`New ${capitalizedObjectNameSingular}`
+ : capitalizedObjectNameSingular,
+ pageIcon: Icon,
+ pageIconColor: IconColor,
+ pageId: pageComponentInstanceId,
+ resetNavigationStack: false,
+ });
+ };
+ },
+ [getIcon, navigateCommandMenu, theme],
+ );
+
+ return {
+ openRecordInCommandMenu,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
new file mode 100644
index 000000000..28a74b225
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
@@ -0,0 +1,22 @@
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { t } from '@lingui/core/macro';
+import { IconSearch } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+export const useOpenRecordsSearchPageInCommandMenu = () => {
+ const { navigateCommandMenu } = useCommandMenu();
+
+ const openRecordsSearchPage = () => {
+ navigateCommandMenu({
+ page: CommandMenuPages.SearchRecords,
+ pageTitle: t`Search`,
+ pageIcon: IconSearch,
+ pageId: v4(),
+ });
+ };
+
+ return {
+ openRecordsSearchPage,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useSearchRecords.tsx b/packages/twenty-front/src/modules/command-menu/hooks/useSearchRecords.tsx
index fcd678941..22690b1f1 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useSearchRecords.tsx
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useSearchRecords.tsx
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { t } from '@lingui/core/macro';
@@ -24,7 +24,7 @@ export const useSearchRecords = () => {
},
});
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const commands = useMemo(() => {
return (globalSearchData?.globalSearch ?? []).map((searchRecord) => {
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useSetGlobalCommandMenuContext.ts b/packages/twenty-front/src/modules/command-menu/hooks/useSetGlobalCommandMenuContext.ts
new file mode 100644
index 000000000..26a000592
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useSetGlobalCommandMenuContext.ts
@@ -0,0 +1,70 @@
+import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
+import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
+import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
+import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
+import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
+import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { useRecoilCallback } from 'recoil';
+
+export const useSetGlobalCommandMenuContext = () => {
+ const { copyContextStoreStates } = useCopyContextStoreStates();
+
+ const setGlobalCommandMenuContext = useRecoilCallback(
+ ({ set }) => {
+ return () => {
+ copyContextStoreStates({
+ instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
+ });
+
+ set(
+ contextStoreTargetedRecordsRuleComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ {
+ mode: 'selection',
+ selectedRecordIds: [],
+ },
+ );
+
+ set(
+ contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ 0,
+ );
+
+ set(
+ contextStoreFiltersComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ [],
+ );
+
+ set(
+ contextStoreCurrentViewTypeComponentState.atomFamily({
+ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
+ }),
+ ContextStoreViewType.Table,
+ );
+
+ set(commandMenuPageInfoState, {
+ title: undefined,
+ Icon: undefined,
+ instanceId: '',
+ });
+
+ set(hasUserSelectedCommandState, false);
+ };
+ },
+ [copyContextStoreStates],
+ );
+
+ return {
+ setGlobalCommandMenuContext,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts
new file mode 100644
index 000000000..e964cc14a
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts
@@ -0,0 +1,124 @@
+import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
+import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { t } from '@lingui/core/macro';
+import { useRecoilCallback } from 'recoil';
+import { IconBolt, IconComponent, IconSettingsAutomation } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+export const useWorkflowCommandMenu = () => {
+ const { navigateCommandMenu } = useNavigateCommandMenu();
+
+ const openWorkflowTriggerTypeInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (workflowId: string) => {
+ const pageId = v4();
+
+ set(
+ workflowIdComponentState.atomFamily({ instanceId: pageId }),
+ workflowId,
+ );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.WorkflowStepSelectTriggerType,
+ pageTitle: t`Trigger Type`,
+ pageIcon: IconBolt,
+ pageId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ const openStepSelectInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (workflowId: string) => {
+ const pageId = v4();
+
+ set(
+ workflowIdComponentState.atomFamily({ instanceId: pageId }),
+ workflowId,
+ );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.WorkflowStepSelectAction,
+ pageTitle: t`Select Action`,
+ pageIcon: IconSettingsAutomation,
+ pageId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ const openWorkflowEditStepInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (workflowId: string, title: string, icon: IconComponent) => {
+ const pageId = v4();
+
+ set(
+ workflowIdComponentState.atomFamily({ instanceId: pageId }),
+ workflowId,
+ );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.WorkflowStepEdit,
+ pageTitle: title,
+ pageIcon: icon,
+ pageId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ const openWorkflowViewStepInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (workflowId: string, title: string, icon: IconComponent) => {
+ const pageId = v4();
+
+ set(
+ workflowIdComponentState.atomFamily({ instanceId: pageId }),
+ workflowId,
+ );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.WorkflowStepView,
+ pageTitle: title,
+ pageIcon: icon,
+ pageId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ const openWorkflowRunViewStepInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (workflowId: string, title: string, icon: IconComponent) => {
+ const pageId = v4();
+
+ set(
+ workflowIdComponentState.atomFamily({ instanceId: pageId }),
+ workflowId,
+ );
+
+ navigateCommandMenu({
+ page: CommandMenuPages.WorkflowRunStepView,
+ pageTitle: title,
+ pageIcon: icon,
+ pageId,
+ });
+ };
+ },
+ [navigateCommandMenu],
+ );
+
+ return {
+ openWorkflowTriggerTypeInCommandMenu,
+ openStepSelectInCommandMenu,
+ openWorkflowEditStepInCommandMenu,
+ openWorkflowViewStepInCommandMenu,
+ openWorkflowRunViewStepInCommandMenu,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx
index fa270cd54..c12ae9b7a 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import {
WorkflowTriggerType,
@@ -26,7 +26,7 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
- const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
+ const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({
type,
diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx
index c9a1b81a4..31f7061f8 100644
--- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx
+++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx
@@ -2,7 +2,7 @@ import { useLocation } from 'react-router-dom';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { IconSearch, IconSettings } from 'twenty-ui';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { CurrentWorkspaceMemberFavoritesFolders } from '@/favorites/components/CurrentWorkspaceMemberFavoritesFolders';
import { WorkspaceFavorites } from '@/favorites/components/WorkspaceFavorites';
import { NavigationDrawerOpenedSection } from '@/object-metadata/components/NavigationDrawerOpenedSection';
@@ -41,7 +41,7 @@ export const MainNavigationDrawerItems = () => {
const { t } = useLingui();
- const { openRecordsSearchPage } = useCommandMenu();
+ const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
return (
<>
diff --git a/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx b/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx
index 3759e4a06..ded641d49 100644
--- a/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx
+++ b/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx
@@ -1,4 +1,5 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
@@ -17,7 +18,8 @@ type NavigationBarItemName = 'main' | 'search' | 'tasks' | 'settings';
export const MobileNavigationBar = () => {
const [isCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState);
- const { closeCommandMenu, openRecordsSearchPage } = useCommandMenu();
+ const { closeCommandMenu } = useCommandMenu();
+ const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
const isSettingsPage = useIsSettingsPage();
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useRecoilState(isNavigationDrawerExpandedState);
diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
index f49cd7bcc..6646ace6c 100644
--- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
+++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
@@ -6,7 +6,7 @@ import {
isModifiedEvent,
} from 'twenty-ui';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
@@ -39,7 +39,7 @@ export const RecordChip = ({
record,
});
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx
index 88203a1c5..c5e9e696a 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx
@@ -1,13 +1,13 @@
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
@@ -81,7 +81,7 @@ export const RecordBoardCard = ({
position?: 'first' | 'last';
}) => {
const navigate = useNavigateApp();
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const { recordId } = useContext(RecordBoardCardContext);
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx
index 6f9243f94..1f9887736 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
@@ -38,7 +38,7 @@ export const RecordBoardColumnNewOpportunity = ({
viewableRecordNameSingularState,
);
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const createCompanyOpportunityAndOpenCommandMenu = async (
searchInput?: string,
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts
index 687f1baa3..e134072e9 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts
@@ -1,7 +1,7 @@
import { useSetRecoilState } from 'recoil';
import { v4 } from 'uuid';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
@@ -42,7 +42,7 @@ export const useAddNewRecordAndOpenRightDrawer = ({
.nameSingular ?? 'workspaceMember',
});
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
if (
relationObjectMetadataNameSingular === 'workspaceMember' ||
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts
index 7de49f058..81b8059b6 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
@@ -39,7 +39,7 @@ export const useCreateNewTableRecord = ({
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const { createOneRecord } = useCreateOneRecord({
objectNameSingular: objectMetadataItem.nameSingular,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts
index c25fe586b..b17c5d6fd 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts
@@ -17,7 +17,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDefined } from 'twenty-shared';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
@@ -75,7 +75,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
- const { openRecordInCommandMenu } = useCommandMenu();
+ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const { openFieldInput } = useOpenFieldInputEditMode();
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect.tsx
index 51fc67c47..36ba99515 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect.tsx
@@ -2,9 +2,9 @@ import { useCallback, useContext } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
+import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { EMPTY_TRIGGER_STEP_ID } from '@/workflow/workflow-diagram/constants/EmptyTriggerStepId';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
@@ -27,7 +27,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const {
openWorkflowTriggerTypeInCommandMenu,
openWorkflowEditStepInCommandMenu,
- } = useCommandMenu();
+ } = useWorkflowCommandMenu();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx
index f29a8c9be..1e75b864e 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
@@ -16,7 +16,7 @@ import { useIcons } from 'twenty-ui';
export const WorkflowDiagramCanvasReadonlyEffect = () => {
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
- const { openWorkflowViewStepInCommandMenu } = useCommandMenu();
+ const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx
index a26e5c053..dc4124342 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState';
@@ -20,7 +20,7 @@ import { useIcons } from 'twenty-ui';
export const WorkflowRunDiagramCanvasEffect = () => {
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
- const { openWorkflowViewRunStepInCommandMenu } = useCommandMenu();
+ const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
@@ -68,7 +68,7 @@ export const WorkflowRunDiagramCanvasEffect = () => {
}
if (isDefined(workflowId)) {
- openWorkflowViewRunStepInCommandMenu(
+ openWorkflowRunViewStepInCommandMenu(
workflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
@@ -80,7 +80,7 @@ export const WorkflowRunDiagramCanvasEffect = () => {
workflowId,
getIcon,
goBackToFirstWorkflowRunRightDrawerTabIfNeeded,
- openWorkflowViewRunStepInCommandMenu,
+ openWorkflowRunViewStepInCommandMenu,
],
);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts
index 55e268bf7..2a0078354 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState';
import { isDefined } from 'twenty-shared';
@@ -10,7 +10,7 @@ export const useStartNodeCreation = () => {
const setWorkflowCreateStepFromParentStepId = useSetRecoilState(
workflowCreateStepFromParentStepIdState,
);
- const { openWorkflowActionInCommandMenu } = useCommandMenu();
+ const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
@@ -23,14 +23,14 @@ export const useStartNodeCreation = () => {
setWorkflowCreateStepFromParentStepId(parentNodeId);
if (isDefined(workflowId)) {
- openWorkflowActionInCommandMenu(workflowId);
+ openStepSelectInCommandMenu(workflowId);
return;
}
},
[
setWorkflowCreateStepFromParentStepId,
workflowId,
- openWorkflowActionInCommandMenu,
+ openStepSelectInCommandMenu,
],
);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx
index 6e9978526..6ed810101 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx
@@ -1,4 +1,4 @@
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import {
WorkflowTriggerType,
@@ -26,7 +26,7 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
- const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
+ const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({
type,
diff --git a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx
index 1d20db2e4..029c3711c 100644
--- a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx
+++ b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx
@@ -2,12 +2,14 @@ import { PropsWithChildren, useEffect, useState } from 'react';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import {
ContextStoreTargetedRecordsRule,
contextStoreTargetedRecordsRuleComponentState,
} from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@@ -18,6 +20,7 @@ export type JestContextStoreSetterMocks = {
contextStoreFilters?: RecordFilter[];
contextStoreCurrentObjectMetadataNameSingular?: string;
contextStoreCurrentViewId?: string;
+ contextStoreCurrentViewType?: ContextStoreViewType;
};
type JestContextStoreSetterProps =
@@ -31,6 +34,7 @@ export const JestContextStoreSetter = ({
contextStoreNumberOfSelectedRecords = 0,
contextStoreCurrentObjectMetadataNameSingular = 'company',
contextStoreFilters = [],
+ contextStoreCurrentViewType,
children,
}: JestContextStoreSetterProps) => {
const setContextStoreTargetedRecordsRule = useSetRecoilComponentStateV2(
@@ -54,6 +58,10 @@ export const JestContextStoreSetter = ({
contextStoreCurrentViewIdComponentState,
);
+ const setContextStoreCurrentViewType = useSetRecoilComponentStateV2(
+ contextStoreCurrentViewTypeComponentState,
+ );
+
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: contextStoreCurrentObjectMetadataNameSingular,
});
@@ -67,7 +75,7 @@ export const JestContextStoreSetter = ({
setContextStoreCurrentObjectMetadataItemId(objectMetadataItem.id);
setContextStoreNumberOfSelectedRecords(contextStoreNumberOfSelectedRecords);
setcontextStoreFiltersComponentState(contextStoreFilters);
-
+ setContextStoreCurrentViewType(contextStoreCurrentViewType ?? null);
setIsLoaded(true);
}, [
setContextStoreTargetedRecordsRule,
@@ -81,6 +89,8 @@ export const JestContextStoreSetter = ({
objectMetadataItem,
setContextStoreCurrentViewId,
contextStoreCurrentViewId,
+ setContextStoreCurrentViewType,
+ contextStoreCurrentViewType,
]);
return isLoaded ? <>{children}> : null;
diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper.tsx
index 0169d51c2..a76476afd 100644
--- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper.tsx
+++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper.tsx
@@ -28,6 +28,7 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
onInitializeRecoilSnapshot,
contextStoreTargetedRecordsRule,
contextStoreCurrentViewId,
+ contextStoreCurrentViewType,
contextStoreNumberOfSelectedRecords,
contextStoreCurrentObjectMetadataNameSingular,
contextStoreFilters,
@@ -93,6 +94,7 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
contextStoreCurrentObjectMetadataNameSingular={
contextStoreCurrentObjectMetadataNameSingular
}
+ contextStoreCurrentViewType={contextStoreCurrentViewType}
>
{children}