## Recoil to Jotai Migration — PR 13: Workflow, Page Layout, AI,
Activities, Settings, SSE & Remaining Modules

### Summary

This is the 13th PR in the [14-PR migration
series](packages/twenty-front/src/modules/ui/utilities/state/jotai/MIGRATION_PLAN.md)
to replace Recoil with Jotai as the state management library in
`twenty-front`. This PR migrates the remaining consumer code across all
modules — updating ~1,076 files with ~10k insertions/deletions.

### What changed

**New Jotai infrastructure (13 new files):**
- `createComponentSelectorV2` and `createComponentFamilySelectorV2` —
instance-scoped derived atoms, replacing Recoil's component selectors
- 6 new hooks: `useRecoilComponentSelectorValueV2`,
`useRecoilComponentFamilySelectorValueV2`,
`useRecoilComponentSelectorCallbackStateV2`,
`useRecoilComponentFamilySelectorCallbackStateV2`,
`useRecoilComponentStateCallbackStateV2`,
`useRecoilComponentFamilyStateCallbackStateV2`
- New types: `ComponentSelectorV2`, `ComponentFamilySelectorV2`,
`SelectorCallbacksV2`
- Extended `buildGetHelper` to support the new selector patterns

**State definition migrations:**
- `createState()` → `createStateV2()` (Jotai `atom()`)
- `createFamilyState()` → `createFamilyStateV2()` (Jotai `atom()` with
family cache)
- Recoil `selector`/`selectorFamily` → Jotai derived `atom()` via V2
utilities

**Hook migrations (~2,400 removed, ~1,545 added V2 equivalents):**
- `useRecoilValue` → `useRecoilValueV2`
- `useRecoilState` → `useRecoilStateV2`
- `useSetRecoilState` → `useSetRecoilStateV2`
- `useRecoilCallback` → `useCallback` + `jotaiStore.get/set`
- `useRecoilComponentValue` → `useRecoilComponentValueV2`
- `useSetRecoilComponentState` → `useSetRecoilComponentStateV2`
- `useRecoilComponentFamilyValue` → `useRecoilComponentFamilyValueV2` /
`useFamilySelectorValueV2`
- ~397 direct `import from 'recoil'` removed

**Deleted files (9 files):**
- `dateLocaleState.ts` — consolidated
- `currentAIChatThreadTitleStateV2.ts` — renamed to replace the original
- `isCommandMenuOpenedState.ts` — removed
- `filesFieldUploadState.ts` — replaced by V2
- `recordStoreFamilySelector.ts`, `recordStoreFieldValueSelector.ts`,
`recordStoreRecordsSelector.ts` — replaced by V2 equivalents
- `settingsAllRolesSelector.ts` — replaced by `useSettingsAllRoles` hook
- `settingsRoleIdsStateV2.ts` — consolidated

**Modules migrated:**
- Workflow (diagram, steps, variables, filters)
- Page Layout (widgets, fields, graph, tabs)
- AI (chat, agents, browsing context)
- Activities (rich text editor, targets, timeline)
- Action Menu (single/multiple record actions)
- Command Menu (navigation, history, pages, workflow)
- Context Store
- Record Board, Record Table, Record Calendar, Record Index
- Record Field, Record Filter, Record Sort, Record Group
- Record Drag, Record Picker, Record Store
- Views (view bar, view picker, filters, sorts)
- Settings (roles, permissions, SSO, security, data model, developers,
domains)
- SSE (event streams, subscriptions)
- Spreadsheet Import
- Auth, Favorites, Front Components, Information Banner
This commit is contained in:
Charles Bochet
2026-02-24 17:37:01 +01:00
committed by GitHub
parent c6ec764b23
commit 0f2af6a6cb
1326 changed files with 19248 additions and 19389 deletions
@@ -7,9 +7,9 @@ import { useMatchingCommandMenuActions } from '@/command-menu/hooks/useMatchingC
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export type ActionGroupConfig = {
@@ -20,7 +20,7 @@ export type ActionGroupConfig = {
export const CommandMenu = () => {
const { t } = useLingui();
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const commandMenuSearch = useRecoilValueV2(commandMenuSearchState);
const { objectMetadataItems } = useObjectMetadataItems();
const {
@@ -40,12 +40,12 @@ export const CommandMenu = () => {
});
const previousContextStoreCurrentObjectMetadataItemId =
useRecoilComponentValue(
useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
'command-menu-previous',
);
const objectMetadataItemId = useRecoilComponentValue(
const objectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
);
const currentObjectMetadataItem = objectMetadataItems.find(
@@ -1,4 +1,4 @@
import { currentAIChatThreadTitleStateV2 } from '@/ai/states/currentAIChatThreadTitleStateV2';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
@@ -12,7 +12,7 @@ const StyledPageTitle = styled.div`
export const CommandMenuAskAIInfo = () => {
const currentAIChatThreadTitle = useRecoilValueV2(
currentAIChatThreadTitleStateV2,
currentAIChatThreadTitleState,
);
return (
@@ -8,7 +8,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
const StyledCommandMenuContainer = styled.div<{ isMobile: boolean }>`
@@ -29,7 +29,7 @@ type CommandMenuContainerProps = {
export const CommandMenuContainer = ({
children,
}: CommandMenuContainerProps) => {
const objectMetadataItemId = useRecoilComponentValue(
const objectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
@@ -41,7 +41,7 @@ export const CommandMenuContainer = ({
(objectMetadataItem) => objectMetadataItem.id === objectMetadataItemId,
);
const currentViewId = useRecoilComponentValue(
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
@@ -3,8 +3,8 @@ import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelect
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuDefaultSelectionEffect = ({
@@ -21,7 +21,7 @@ export const CommandMenuDefaultSelectionEffect = ({
COMMAND_MENU_LIST_SELECTABLE_LIST_ID,
);
const hasUserSelectedCommand = useRecoilValue(hasUserSelectedCommandState);
const hasUserSelectedCommand = useRecoilValueV2(hasUserSelectedCommandState);
useEffect(() => {
if (
@@ -1,7 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
@@ -17,7 +16,7 @@ import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValu
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
const StyledClickableIconWrapper = styled.div`
cursor: pointer;
@@ -27,9 +26,9 @@ export const CommandMenuFolderInfo = () => {
const theme = useTheme();
const { t } = useLingui();
const { getIcon } = useIcons();
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const commandMenuPageInfo = useRecoilValueV2(commandMenuPageInfoState);
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
useRecoilComponentState(
useRecoilComponentStateV2(
commandMenuShouldFocusTitleInputComponentState,
commandMenuPageInfo.instanceId,
);
@@ -1,6 +1,5 @@
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconLink } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
@@ -14,14 +13,14 @@ import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-i
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
export const CommandMenuLinkInfo = () => {
const theme = useTheme();
const { t } = useLingui();
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const commandMenuPageInfo = useRecoilValueV2(commandMenuPageInfoState);
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
useRecoilComponentState(
useRecoilComponentStateV2(
commandMenuShouldFocusTitleInputComponentState,
commandMenuPageInfo.instanceId,
);
@@ -8,10 +8,10 @@ import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/Comman
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useSetRecoilState } from 'recoil';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
export type CommandMenuListProps = {
@@ -63,7 +63,7 @@ export const CommandMenuList = ({
noResults = false,
noResultsText,
}: CommandMenuListProps) => {
const setHasUserSelectedCommand = useSetRecoilState(
const setHasUserSelectedCommand = useSetRecoilStateV2(
hasUserSelectedCommandState,
);
@@ -3,11 +3,11 @@ import { CommandMenuRecordPageLayoutInfo } from '@/command-menu/components/Comma
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuPageLayoutInfo = () => {
const objectMetadataId = useRecoilComponentValue(
const objectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
);
@@ -9,12 +9,12 @@ import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDr
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useTheme } from '@emotion/react';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
@@ -27,11 +27,11 @@ export const CommandMenuPageLayoutInfoContent = ({
}) => {
const theme = useTheme();
const { getIcon } = useIcons();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
const commandMenuPageInfo = useRecoilValueV2(commandMenuPageInfoState);
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
useRecoilComponentState(
useRecoilComponentStateV2(
commandMenuShouldFocusTitleInputComponentState,
commandMenuPageInfo.instanceId,
);
@@ -40,17 +40,17 @@ export const CommandMenuPageLayoutInfoContent = ({
setShouldFocusTitleInput(false);
};
const draftPageLayout = useRecoilComponentValue(
const draftPageLayout = useRecoilComponentValueV2(
pageLayoutDraftComponentState,
pageLayoutId,
);
const pageLayoutEditingWidgetId = useRecoilComponentValue(
const pageLayoutEditingWidgetId = useRecoilComponentValueV2(
pageLayoutEditingWidgetIdComponentState,
pageLayoutId,
);
const [openTabId] = useRecoilComponentState(
const [openTabId] = useRecoilComponentStateV2(
pageLayoutTabSettingsOpenTabIdComponentState,
pageLayoutId,
);
@@ -7,23 +7,22 @@ import { useIsRecordFieldReadOnly } from '@/object-record/read-only/hooks/useIsR
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
import { recordStoreFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFamilySelectorV2';
import { recordStoreIdentifierFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreIdentifierFamilySelectorV2';
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { Trans } from '@lingui/react/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { Avatar } from 'twenty-ui/display';
import {
FeatureFlagKey,
FieldMetadataType,
} from '~/generated-metadata/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
import { CommandMenuPageInfoLayout } from './CommandMenuPageInfoLayout';
@@ -32,7 +31,7 @@ export const CommandMenuRecordInfo = ({
}: {
commandMenuPageInstanceId: string;
}) => {
const viewableRecordNameSingular = useRecoilComponentValue(
const viewableRecordNameSingular = useRecoilComponentValueV2(
viewableRecordNameSingularComponentState,
commandMenuPageInstanceId,
);
@@ -40,7 +39,7 @@ export const CommandMenuRecordInfo = ({
allowRequestsToTwentyIconsState,
);
const viewableRecordId = useRecoilComponentValue(
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
commandMenuPageInstanceId,
);
@@ -50,26 +49,25 @@ export const CommandMenuRecordInfo = ({
viewableRecordId!,
);
const recordCreatedAt = useRecoilValue<string | null>(
recordStoreFamilySelector({
recordId: objectRecordId,
fieldName: 'createdAt',
}),
);
const recordCreatedAt = useFamilySelectorValueV2(
recordStoreFamilySelectorV2,
{ recordId: objectRecordId, fieldName: 'createdAt' },
) as string | null;
const isFilesFieldMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_FILES_FIELD_MIGRATED,
);
const recordIdentifier = useRecoilValue(
recordStoreIdentifierFamilySelector({
const recordIdentifier = useFamilySelectorValueV2(
recordStoreIdentifierFamilySelectorV2,
{
recordId: objectRecordId,
allowRequestsToTwentyIcons,
isFilesFieldMigrated,
}),
},
);
const { localeCatalog } = useRecoilValue(dateLocaleState);
const { localeCatalog } = useRecoilValueV2(dateLocaleStateV2);
const beautifiedCreatedAt = isNonEmptyString(recordCreatedAt)
? beautifyPastDateRelativeToNow(recordCreatedAt, localeCatalog)
: '';
@@ -5,10 +5,10 @@ import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuP
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
const StyledCommandMenuContent = styled.div`
@@ -17,8 +17,8 @@ const StyledCommandMenuContent = styled.div`
`;
export const CommandMenuRouter = () => {
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
const commandMenuPageInfo = useRecoilValueV2(commandMenuPageInfoState);
const commandMenuPageComponent = isDefined(commandMenuPage) ? (
COMMAND_MENU_PAGES_CONFIG.get(commandMenuPage)
@@ -12,10 +12,11 @@ import { tableWidthResizeIsActiveState } from '@/object-record/record-table/stat
import { ModalContainerContext } from '@/ui/layout/modal/contexts/ModalContainerContext';
import { ResizablePanelGap } from '@/ui/layout/resizable-panel/components/ResizablePanelGap';
import { COMMAND_MENU_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/CommandMenuConstraints';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import styled from '@emotion/styled';
import { useCallback, useState } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
const StyledSidePanelWrapper = styled.div<{
isOpen: boolean;
@@ -56,8 +57,8 @@ const GAP_WIDTH = 8;
export const CommandMenuSidePanelForDesktop = () => {
const isCommandMenuOpened = useRecoilValueV2(isCommandMenuOpenedStateV2);
const isCommandMenuClosing = useRecoilValue(isCommandMenuClosingState);
const [commandMenuWidth, setCommandMenuWidth] = useRecoilState(
const isCommandMenuClosing = useRecoilValueV2(isCommandMenuClosingState);
const [commandMenuWidth, setCommandMenuWidth] = useRecoilStateV2(
commandMenuWidthState,
);
const { closeCommandMenu } = useCommandMenu();
@@ -71,7 +72,7 @@ export const CommandMenuSidePanelForDesktop = () => {
const [shouldRenderContent, setShouldRenderContent] =
useState(isCommandMenuOpened);
const setTableWidthResizeIsActive = useSetRecoilState(
const setTableWidthResizeIsActive = useSetRecoilStateV2(
tableWidthResizeIsActiveState,
);
@@ -18,8 +18,9 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { AnimatePresence, motion } from 'framer-motion';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useRef } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconX } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
@@ -79,7 +80,7 @@ const StyledContentContainer = styled.div`
`;
export const CommandMenuTopBar = () => {
const [commandMenuSearch, setCommandMenuSearch] = useRecoilState(
const [commandMenuSearch, setCommandMenuSearch] = useRecoilStateV2(
commandMenuSearchState,
);
const inputRef = useRef<HTMLInputElement>(null);
@@ -94,9 +95,9 @@ export const CommandMenuTopBar = () => {
const { closeCommandMenu } = useCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
const commandMenuNavigationStack = useRecoilValue(
const commandMenuNavigationStack = useRecoilValueV2(
commandMenuNavigationStackState,
);
@@ -1,6 +1,5 @@
import { useRecoilValue } from 'recoil';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useEffect } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
@@ -11,7 +10,7 @@ type CommandMenuTopBarInputFocusEffectProps = {
export const CommandMenuTopBarInputFocusEffect = ({
inputRef,
}: CommandMenuTopBarInputFocusEffectProps) => {
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
useEffect(() => {
if (
@@ -1,9 +1,9 @@
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconEdit, IconSparkles } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
@@ -19,7 +19,7 @@ const StyledIconButton = styled(IconButton)`
export const CommandMenuTopBarRightCornerIcon = () => {
const isMobile = useIsMobile();
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
const { openAskAIPage } = useOpenAskAIPageInCommandMenu();
const { createChatThread } = useCreateNewAIChatThread();
@@ -1,13 +1,13 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import {
COMMAND_MENU_WIDTH_VAR,
commandMenuWidthState,
} from '@/command-menu/states/commandMenuWidthState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
export const CommandMenuWidthEffect = () => {
const commandMenuWidth = useRecoilValue(commandMenuWidthState);
const commandMenuWidth = useRecoilValueV2(commandMenuWidthState);
useEffect(() => {
document.documentElement.style.setProperty(
@@ -5,7 +5,7 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { getAgentIdFromStep } from '@/workflow/utils/getAgentIdFromStep';
@@ -19,8 +19,8 @@ import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon
import { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
@@ -35,11 +35,11 @@ export const CommandMenuWorkflowStepInfo = ({
const theme = useTheme();
const { getIcon } = useIcons();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflowStepId = useRecoilComponentValue(
const workflowStepId = useRecoilComponentValueV2(
commandMenuWorkflowStepIdComponentState,
commandMenuPageInstanceId,
);
@@ -7,19 +7,19 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { IconArrowBackUp } from 'twenty-ui/display';
export const ResetContextToSelectionCommandButton = () => {
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
'command-menu-previous',
);
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue(
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
'command-menu-previous',
);
@@ -3,12 +3,11 @@ import {
type Meta,
type StoryObj,
} from '@storybook/react-vite';
import { useSetRecoilState } from 'recoil';
import { expect, userEvent, within } from 'storybook/test';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@@ -28,13 +27,18 @@ import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { HttpResponse, graphql } from 'msw';
import { CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconDotsVertical } from 'twenty-ui/display';
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
@@ -73,26 +77,17 @@ const meta: Meta<typeof CommandMenu> = {
component: CommandMenuRouter,
decorators: [
(Story) => {
const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState);
const setCurrentUserWorkspace = useSetRecoilStateV2(
currentUserWorkspaceState,
jotaiStore.set(currentWorkspaceState.atom, mockCurrentWorkspace);
jotaiStore.set(
currentWorkspaceMemberState.atom,
mockedWorkspaceMemberData,
);
const setCurrentWorkspaceMember = useSetRecoilStateV2(
currentWorkspaceMemberState,
jotaiStore.set(
currentUserWorkspaceState.atom,
mockedUserData.currentUserWorkspace,
);
const setIsCommandMenuOpened = useSetRecoilState(
isCommandMenuOpenedState,
);
const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState,
);
setCurrentWorkspace(mockCurrentWorkspace);
setCurrentWorkspaceMember(mockedWorkspaceMemberData);
setCurrentUserWorkspace(mockedUserData.currentUserWorkspace);
setIsCommandMenuOpened(true);
setCommandMenuNavigationStack([
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(commandMenuNavigationStackState.atom, [
{
page: CommandMenuPages.Root,
pageTitle: 'Command Menu',
@@ -101,6 +96,25 @@ const meta: Meta<typeof CommandMenu> = {
},
]);
const objectMetadataItems = jotaiStore.get(objectMetadataItemsState.atom);
const companyMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === 'company',
);
if (isDefined(companyMetadataItem)) {
jotaiStore.set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
companyMetadataItem.id,
);
jotaiStore.set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
ContextStoreViewType.Table,
);
}
return <Story />;
},
ContextStoreDecorator,
@@ -139,10 +153,8 @@ export const LimitedPermissions: Story = {
},
decorators: [
(Story) => {
const setCurrentUserWorkspace = useSetRecoilStateV2(
currentUserWorkspaceState,
);
setCurrentUserWorkspace(
jotaiStore.set(
currentUserWorkspaceState.atom,
mockedLimitedPermissionsUserData.currentUserWorkspace,
);
@@ -9,6 +9,7 @@ import { CommandMenuContextRecordsChip } from '@/command-menu/components/Command
import { PreComputedChipGeneratorsContext } from '@/object-metadata/contexts/PreComputedChipGeneratorsContext';
import { type RecordChipData } from '@/object-record/record-field/ui/types/RecordChipData';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ComponentDecorator } from 'twenty-ui/testing';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
@@ -190,9 +191,12 @@ const createContextStoreWrapper = ({
selectedRecordIds: companies.map((company) => company.id),
},
contextStoreNumberOfSelectedRecords: companies.length,
onInitializeRecoilSnapshot: (snapshot) => {
onInitializeJotaiStore: () => {
for (const company of companies) {
snapshot.set(recordStoreFamilyState(company.id), company);
jotaiStore.set(
recordStoreFamilyState.atomFamily(company.id),
company as ObjectRecord,
);
}
},
});