Migrate more to Jotai (#17968)

As per title

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
This commit is contained in:
Charles Bochet
2026-02-17 19:24:36 +01:00
committed by GitHub
parent 98482f3a01
commit d7f025157b
179 changed files with 1219 additions and 988 deletions
@@ -1,9 +1,12 @@
import { renderHook, act } from '@testing-library/react';
import { type MutableSnapshot, RecoilRoot } from 'recoil';
import { Provider as JotaiProvider } from 'jotai';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { IconSparkles } from 'twenty-ui/display';
const navigateCommandMenuMock = jest.fn();
@@ -17,22 +20,22 @@ jest.mock('@/command-menu/hooks/useCommandMenu', () => ({
}),
}));
const renderWithRecoil = (
initializeState?: (snapshot: MutableSnapshot) => void,
) =>
renderHook(() => useOpenAskAIPageInCommandMenu(), {
wrapper: ({ children }) => (
<RecoilRoot initializeState={initializeState}>{children}</RecoilRoot>
),
});
const Wrapper = ({ children }: { children: ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
);
describe('useOpenAskAIPageInCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
jotaiStore.set(isCommandMenuOpenedStateV2.atom, false);
});
it('should navigate to AskAI page with correct defaults', () => {
const { result } = renderWithRecoil();
const { result } = renderHook(() => useOpenAskAIPageInCommandMenu(), {
wrapper: Wrapper,
});
act(() => {
result.current.openAskAIPage();
@@ -48,8 +51,10 @@ describe('useOpenAskAIPageInCommandMenu', () => {
});
it('should use resetNavigationStack from argument when provided', () => {
const { result } = renderWithRecoil((snapshot) => {
snapshot.set(isCommandMenuOpenedState, true);
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
const { result } = renderHook(() => useOpenAskAIPageInCommandMenu(), {
wrapper: Wrapper,
});
act(() => {
@@ -64,8 +69,10 @@ describe('useOpenAskAIPageInCommandMenu', () => {
});
it('should default resetNavigationStack to isCommandMenuOpened', () => {
const { result } = renderWithRecoil((snapshot) => {
snapshot.set(isCommandMenuOpenedState, true);
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
const { result } = renderHook(() => useOpenAskAIPageInCommandMenu(), {
wrapper: Wrapper,
});
act(() => {
@@ -5,10 +5,12 @@ import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandM
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/states/addToNavPayloadRegistryState';
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
@@ -29,8 +31,9 @@ export const useCommandMenu = () => {
.getValue();
if (isCommandMenuOpened) {
set(addToNavPayloadRegistryState, new Map());
jotaiStore.set(addToNavPayloadRegistryStateV2.atom, new Map());
set(isCommandMenuOpenedState, false);
jotaiStore.set(isCommandMenuOpenedStateV2.atom, false);
set(isCommandMenuClosingState, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStackById({
@@ -11,6 +11,7 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
@@ -25,6 +26,7 @@ import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getSh
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -104,6 +106,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
instanceId: '',
});
set(isCommandMenuOpenedState, false);
jotaiStore.set(isCommandMenuOpenedStateV2.atom, false);
set(commandMenuSearchState, '');
set(commandMenuNavigationMorphItemsByPageState, new Map());
set(commandMenuNavigationStackState, []);
@@ -10,10 +10,12 @@ import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/s
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { type CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useRecoilCallback } from 'recoil';
import { type IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -70,6 +72,7 @@ export const useNavigateCommandMenu = () => {
});
set(isCommandMenuOpenedState, true);
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
set(hasUserSelectedCommandState, false);
},
[
@@ -1,39 +1,36 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { IconSparkles } from 'twenty-ui/display';
import { v4 } from 'uuid';
export const useOpenAskAIPageInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const isCommandMenuOpened = useRecoilValueV2(isCommandMenuOpenedStateV2);
const openAskAIPage = useRecoilCallback(
({ snapshot }) =>
({
resetNavigationStack,
}: {
resetNavigationStack?: boolean;
} = {}) => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
const openAskAIPage = useCallback(
({
resetNavigationStack,
}: {
resetNavigationStack?: boolean;
} = {}) => {
const shouldReset =
resetNavigationStack !== undefined
? resetNavigationStack
: isCommandMenuOpened;
const shouldReset =
resetNavigationStack !== undefined
? resetNavigationStack
: isCommandMenuOpened;
navigateCommandMenu({
page: CommandMenuPages.AskAI,
pageTitle: t`Ask AI`,
pageIcon: IconSparkles,
pageId: v4(),
resetNavigationStack: shouldReset,
});
},
[navigateCommandMenu],
navigateCommandMenu({
page: CommandMenuPages.AskAI,
pageTitle: t`Ask AI`,
pageIcon: IconSparkles,
pageId: v4(),
resetNavigationStack: shouldReset,
});
},
[navigateCommandMenu, isCommandMenuOpened],
);
return {
@@ -1,14 +1,14 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { IconSearch } from 'twenty-ui/display';
export const useOpenRecordsSearchPageInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const isCommandMenuOpened = useRecoilValueV2(isCommandMenuOpenedStateV2);
const openRecordsSearchPage = () => {
navigateCommandMenu({
@@ -1,35 +1,34 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableRichTextComponentState } from '@/command-menu/pages/rich-text-page/states/viewableRichTextComponentState';
import { viewableRichTextComponentStateV2 } from '@/command-menu/pages/rich-text-page/states/viewableRichTextComponentStateV2';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
import { useRecoilCallback } from 'recoil';
import { IconPencil } from 'twenty-ui/display';
export const useRichTextCommandMenu = () => {
const { navigateCommandMenu, openCommandMenu } = useCommandMenu();
const openRichTextInCommandMenu = useRecoilCallback(
({ set }) =>
({
const openRichTextInCommandMenu = useCallback(
({
activityId,
activityObjectNameSingular,
}: {
activityId: string;
activityObjectNameSingular: string;
}) => {
jotaiStore.set(viewableRichTextComponentStateV2.atom, {
activityId,
activityObjectNameSingular,
}: {
activityId: string;
activityObjectNameSingular: string;
}) => {
set(viewableRichTextComponentState, {
activityId,
activityObjectNameSingular,
});
});
openCommandMenu();
navigateCommandMenu({
page: CommandMenuPages.EditRichText,
pageTitle: t`Rich Text`,
pageIcon: IconPencil,
});
},
openCommandMenu();
navigateCommandMenu({
page: CommandMenuPages.EditRichText,
pageTitle: t`Rich Text`,
pageIcon: IconPencil,
});
},
[navigateCommandMenu, openCommandMenu],
);