## 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
@@ -10,9 +10,9 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useSetRecoilState } from 'recoil';
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
import { IconChevronLeft, IconSettings, useIcons } from 'twenty-ui/display';
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
@@ -29,7 +29,7 @@ export const ObjectOptionsDropdownCalendarFieldsContent = () => {
const { updateCurrentView } = useUpdateCurrentView();
const { navigateToDateFieldSettings } = useGetAvailableFieldsForCalendar();
const setRecordIndexCalendarFieldMetadataId = useSetRecoilState(
const setRecordIndexCalendarFieldMetadataId = useSetRecoilStateV2(
recordIndexCalendarFieldMetadataIdState,
);
const availableFieldsForCalendar = objectMetadataItem.fields.filter((field) =>
@@ -9,9 +9,10 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
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 { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
import { t } from '@lingui/core/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Pill } from 'twenty-ui/components';
import {
IconCalendarMonth,
@@ -24,10 +25,10 @@ import { ViewCalendarLayout } from '~/generated-metadata/graphql';
export const ObjectOptionsDropdownCalendarViewContent = () => {
const { resetContent } = useObjectOptionsDropdown();
const recordIndexCalendarLayout = useRecoilValue(
const recordIndexCalendarLayout = useRecoilValueV2(
recordIndexCalendarLayoutState,
);
const setRecordIndexCalendarLayout = useSetRecoilState(
const setRecordIndexCalendarLayout = useSetRecoilStateV2(
recordIndexCalendarLayoutState,
);
const { updateCurrentView } = useUpdateCurrentView();
@@ -11,16 +11,15 @@ import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/Gene
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewKey } from '@/views/types/ViewKey';
import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
import { useDestroyViewFromCurrentState } from '@/views/view-picker/hooks/useDestroyViewFromCurrentState';
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { capitalize, isDefined } from 'twenty-shared/utils';
import {
AppTooltip,
@@ -55,7 +54,7 @@ export const ObjectOptionsDropdownCustomView = ({
}
: null;
const recordGroupFieldMetadata = useRecoilComponentValue(
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordIndexGroupFieldMetadataItemComponentState,
);
@@ -67,7 +66,7 @@ export const ObjectOptionsDropdownCustomView = ({
const isDefaultView = currentView?.key === ViewKey.Index;
const recordIndexCalendarLayout = useRecoilValue(
const recordIndexCalendarLayout = useRecoilValueV2(
recordIndexCalendarLayoutState,
);
@@ -80,8 +79,9 @@ export const ObjectOptionsDropdownCustomView = ({
const visibleFieldsCount = visibleBoardFields.length;
const { destroyViewFromCurrentState } = useDestroyViewFromCurrentState();
const setViewPickerReferenceViewId = useSetRecoilComponentState(
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
viewPickerReferenceViewIdComponentState,
recordIndexId,
);
const handleDelete = () => {
@@ -8,7 +8,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentSelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentSelectorValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useOpenCreateViewDropdown } from '@/views/hooks/useOpenCreateViewDropown';
@@ -29,7 +29,7 @@ export const ObjectOptionsDropdownDefaultView = () => {
const { currentView } = useGetCurrentViewOnly();
const visibleRecordFields = useRecoilComponentValue(
const visibleRecordFields = useRecoilComponentSelectorValueV2(
visibleRecordFieldsComponentSelector,
recordIndexId,
);
@@ -14,9 +14,10 @@ import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilComponentFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentFamilySelectorValueV2';
import { useRecoilComponentSelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentSelectorValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useLingui } from '@lingui/react/macro';
import { useLocation } from 'react-router-dom';
import { VIEW_GROUP_VISIBLE_OPTIONS_MAX } from 'twenty-shared/constants';
@@ -35,15 +36,15 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
viewType,
} = useObjectOptionsDropdown();
const recordGroupFieldMetadata = useRecoilComponentValue(
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordIndexGroupFieldMetadataItemComponentState,
);
const hiddenRecordGroupIds = useRecoilComponentValue(
const hiddenRecordGroupIds = useRecoilComponentSelectorValueV2(
hiddenRecordGroupIdsComponentSelector,
);
const visibleRecordGroupIds = useRecoilComponentFamilyValue(
const visibleRecordGroupIds = useRecoilComponentFamilySelectorValueV2(
visibleRecordGroupIdsComponentFamilySelector,
viewType,
);
@@ -13,7 +13,6 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
@@ -22,9 +21,9 @@ import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useLingui } from '@lingui/react/macro';
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import {
IconBaselineDensitySmall,
@@ -61,11 +60,13 @@ export const ObjectOptionsDropdownLayoutContent = () => {
[updateCurrentView],
);
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const recordIndexCalendarLayout = useRecoilValue(
const recordIndexOpenRecordIn = useRecoilValueV2(
recordIndexOpenRecordInState,
);
const recordIndexCalendarLayout = useRecoilValueV2(
recordIndexCalendarLayoutState,
);
const recordGroupFieldMetadata = useRecoilComponentValue(
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordIndexGroupFieldMetadataItemComponentState,
);
@@ -12,10 +12,10 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
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 { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import {
IconChevronLeft,
IconLayoutNavbar,
@@ -25,7 +25,9 @@ import { MenuItemSelect } from 'twenty-ui/navigation';
export const ObjectOptionsDropdownLayoutOpenInContent = () => {
const { onContentChange } = useObjectOptionsDropdown();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const recordIndexOpenRecordIn = useRecoilValueV2(
recordIndexOpenRecordInState,
);
const { currentView } = useGetCurrentViewOnly();
const { setAndPersistOpenRecordIn } = useUpdateObjectViewOptions();
const { objectMetadataItem } = useRecordIndexContextOrThrow();
@@ -3,9 +3,9 @@ import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextInput } from '@/ui/input/components/TextInput';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2';
import { type View } from '@/views/types/View';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
@@ -60,12 +60,12 @@ export const ObjectOptionsDropdownMenuViewName = ({
currentView,
}: ObjectOptionsDropdownMenuViewNameProps) => {
const [viewPickerSelectedIcon, setViewPickerSelectedIcon] =
useRecoilComponentState(viewPickerSelectedIconComponentState);
useRecoilComponentStateV2(viewPickerSelectedIconComponentState);
const viewPickerIsPersisting = useRecoilComponentValue(
const viewPickerIsPersisting = useRecoilComponentValueV2(
viewPickerIsPersistingComponentState,
);
const setViewPickerIsDirty = useSetRecoilComponentState(
const setViewPickerIsDirty = useSetRecoilComponentStateV2(
viewPickerIsDirtyComponentState,
);
@@ -15,7 +15,8 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilComponentSelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentSelectorValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { ViewType } from '@/views/types/ViewType';
import { useLingui } from '@lingui/react/macro';
@@ -47,11 +48,11 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
objectNameSingular: objectMetadataItem.nameSingular,
});
const hiddenRecordGroupIds = useRecoilComponentValue(
const hiddenRecordGroupIds = useRecoilComponentSelectorValueV2(
hiddenRecordGroupIdsComponentSelector,
);
const recordGroupFieldMetadata = useRecoilComponentValue(
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordIndexGroupFieldMetadataItemComponentState,
);
@@ -12,8 +12,8 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentSelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentSelectorValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro';
import {
@@ -27,11 +27,11 @@ import { MenuItemSelect } from 'twenty-ui/navigation';
export const ObjectOptionsDropdownRecordGroupSortContent = () => {
const { currentContentId, onContentChange } = useObjectOptionsDropdown();
const hiddenRecordGroupIds = useRecoilComponentValue(
const hiddenRecordGroupIds = useRecoilComponentSelectorValueV2(
hiddenRecordGroupIdsComponentSelector,
);
const [recordGroupSort, setRecordGroupSort] = useRecoilComponentState(
const [recordGroupSort, setRecordGroupSort] = useRecoilComponentStateV2(
recordIndexRecordGroupSortComponentState,
);
@@ -17,9 +17,9 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilComponentFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentFamilySelectorValueV2';
import { useRecoilComponentSelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentSelectorValueV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy';
import { useLingui } from '@lingui/react/macro';
@@ -48,27 +48,27 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
const { currentView } = useGetCurrentViewOnly();
const recordGroupFieldMetadata = useRecoilComponentValue(
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordIndexGroupFieldMetadataItemComponentState,
);
const visibleRecordGroupIds = useRecoilComponentFamilyValue(
const visibleRecordGroupIds = useRecoilComponentFamilySelectorValueV2(
visibleRecordGroupIdsComponentFamilySelector,
viewType,
);
const hiddenRecordGroupIds = useRecoilComponentValue(
const hiddenRecordGroupIds = useRecoilComponentSelectorValueV2(
hiddenRecordGroupIdsComponentSelector,
);
const hideEmptyRecordGroup = useRecoilComponentValue(
const hideEmptyRecordGroup = useRecoilComponentValueV2(
recordIndexShouldHideEmptyRecordGroupsComponentState,
);
const shouldHideEmptyGroups =
hideEmptyRecordGroup ?? currentView?.shouldHideEmptyGroups ?? false;
const recordGroupSort = useRecoilComponentValue(
const recordGroupSort = useRecoilComponentValueV2(
recordIndexRecordGroupSortComponentState,
);
@@ -1,6 +1,5 @@
import { type OnDragEndResponder } from '@hello-pangea/dnd';
import { useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@@ -12,7 +11,8 @@ import { type RecordField } from '@/object-record/record-field/types/RecordField
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
import { produce } from 'immer';
@@ -32,7 +32,7 @@ export const useObjectOptionsForBoard = ({
recordBoardId,
}: useObjectOptionsForBoardParams) => {
const [recordIndexFieldDefinitions, setRecordIndexFieldDefinitions] =
useRecoilState(recordIndexFieldDefinitionsState);
useRecoilStateV2(recordIndexFieldDefinitionsState);
const { saveViewFields } = useSaveCurrentViewFields();
@@ -130,7 +130,7 @@ export const useObjectOptionsForBoard = ({
],
);
const currentRecordFields = useRecoilComponentValue(
const currentRecordFields = useRecoilComponentValueV2(
currentRecordFieldsComponentState,
recordBoardId,
);
@@ -4,7 +4,7 @@ import { useReorderVisibleRecordFields } from '@/object-record/record-field/hook
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
export const useProcessOptionDropdownDragEnd = (recordTableId: string) => {
const { reorderVisibleRecordFields } =
@@ -12,8 +12,8 @@ export const useProcessOptionDropdownDragEnd = (recordTableId: string) => {
const { saveViewFields } = useSaveCurrentViewFields();
const processOptionDropdownDragEnd: OnDragEndResponder = useRecoilCallback(
() => async (result) => {
const processOptionDropdownDragEnd: OnDragEndResponder = useCallback(
async (result) => {
if (
!result.destination ||
result.destination.index === 1 ||
@@ -1,6 +1,6 @@
import { objectOptionsDropdownSearchInputComponentState } from '@/object-record/object-options-dropdown/states/objectOptionsDropdownSearchInputComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateV2';
import { useMemo } from 'react';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -8,7 +8,7 @@ export const useSearchRecordGroupField = () => {
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const [recordGroupFieldSearchInput, setRecordGroupFieldSearchInput] =
useRecoilComponentState(objectOptionsDropdownSearchInputComponentState);
useRecoilComponentStateV2(objectOptionsDropdownSearchInputComponentState);
const filteredRecordGroupFieldMetadataItems = useMemo(() => {
const searchInputLowerCase =
@@ -13,13 +13,15 @@ import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
import { convertViewTypeToCore } from '@/views/utils/convertViewTypeToCore';
import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
import { ViewCalendarLayout } from '~/generated-metadata/graphql';
export const useSetViewTypeFromLayoutOptionsMenu = () => {
const { updateCurrentView } = useUpdateCurrentView();
const setRecordIndexViewType = useSetRecoilState(recordIndexViewTypeState);
const setRecordIndexViewType = useSetRecoilStateV2(recordIndexViewTypeState);
const { availableFieldsForGrouping } =
useGetAvailableFieldsToGroupRecordsBy();
const { objectMetadataItem } = useRecordIndexContextOrThrow();
@@ -28,137 +30,137 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
const setAndPersistViewType = useRecoilCallback(
({ snapshot }) =>
async (viewType: ViewType) => {
const currentViewId = snapshot
.getLoadable(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
)
.getValue();
const store = useStore();
const existingCoreViews = jotaiStore.get(coreViewsState.atom);
const setAndPersistViewType = useCallback(
async (viewType: ViewType) => {
const currentViewId = store.get(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
);
if (!isDefined(currentViewId)) {
throw new Error('No view id found');
const existingCoreViews = jotaiStore.get(coreViewsState.atom);
if (!isDefined(currentViewId)) {
throw new Error('No view id found');
}
const currentCoreView = existingCoreViews.find(
(coreView) => coreView.id === currentViewId,
);
if (!isDefined(currentCoreView)) {
throw new Error('No current view found');
}
const currentView = convertCoreViewToView(currentCoreView);
const updateCurrentViewParams: Partial<GraphQLView> = {};
updateCurrentViewParams.type = viewType;
switch (viewType) {
case ViewType.Kanban: {
if (availableFieldsForGrouping.length === 0) {
throw new Error('No fields for kanban - should not happen');
}
const mainGroupByFieldMetadataId = availableFieldsForGrouping[0].id;
updateCurrentViewParams.mainGroupByFieldMetadataId =
mainGroupByFieldMetadataId;
if (shouldChangeIcon(currentView.icon, currentView.type)) {
updateCurrentViewParams.icon =
viewTypeIconMapping(viewType).displayName;
}
setRecordIndexViewType(viewType);
jotaiStore.set(coreViewsState.atom, [
...existingCoreViews.filter(
(coreView) => coreView.id !== currentView.id,
),
{
...currentCoreView,
type: convertViewTypeToCore(viewType),
mainGroupByFieldMetadataId,
},
]);
await updateCurrentView(updateCurrentViewParams);
return;
}
const currentCoreView = existingCoreViews.find(
(coreView) => coreView.id === currentViewId,
);
if (!isDefined(currentCoreView)) {
throw new Error('No current view found');
case ViewType.Table: {
if (shouldChangeIcon(currentView.icon, currentView.type)) {
updateCurrentViewParams.icon =
viewTypeIconMapping(viewType).displayName;
}
updateCurrentViewParams.mainGroupByFieldMetadataId = null;
await updateCurrentView(updateCurrentViewParams);
setRecordIndexViewType(viewType);
jotaiStore.set(coreViewsState.atom, [
...existingCoreViews.filter(
(coreView) => coreView.id !== currentView.id,
),
{
...currentCoreView,
mainGroupByFieldMetadataId: null,
type: convertViewTypeToCore(viewType),
},
]);
return;
}
const currentView = convertCoreViewToView(currentCoreView);
const updateCurrentViewParams: Partial<GraphQLView> = {};
updateCurrentViewParams.type = viewType;
switch (viewType) {
case ViewType.Kanban: {
if (availableFieldsForGrouping.length === 0) {
throw new Error('No fields for kanban - should not happen');
}
const mainGroupByFieldMetadataId = availableFieldsForGrouping[0].id;
updateCurrentViewParams.mainGroupByFieldMetadataId =
mainGroupByFieldMetadataId;
if (shouldChangeIcon(currentView.icon, currentView.type)) {
updateCurrentViewParams.icon =
viewTypeIconMapping(viewType).displayName;
}
setRecordIndexViewType(viewType);
jotaiStore.set(coreViewsState.atom, [
...existingCoreViews.filter(
(coreView) => coreView.id !== currentView.id,
),
{
...currentCoreView,
type: convertViewTypeToCore(viewType),
mainGroupByFieldMetadataId,
},
]);
await updateCurrentView(updateCurrentViewParams);
return;
case ViewType.Calendar: {
if (availableFieldsForCalendar.length === 0) {
throw new Error('No date fields for calendar');
}
case ViewType.Table: {
if (shouldChangeIcon(currentView.icon, currentView.type)) {
updateCurrentViewParams.icon =
viewTypeIconMapping(viewType).displayName;
}
updateCurrentViewParams.mainGroupByFieldMetadataId = null;
await updateCurrentView(updateCurrentViewParams);
setRecordIndexViewType(viewType);
jotaiStore.set(coreViewsState.atom, [
...existingCoreViews.filter(
(coreView) => coreView.id !== currentView.id,
),
{
...currentCoreView,
mainGroupByFieldMetadataId: null,
type: convertViewTypeToCore(viewType),
},
]);
return;
}
case ViewType.Calendar: {
if (availableFieldsForCalendar.length === 0) {
throw new Error('No date fields for calendar');
}
const calendarFieldMetadataId = availableFieldsForCalendar[0].id;
const calendarFieldMetadataId = availableFieldsForCalendar[0].id;
setRecordIndexViewType(viewType);
jotaiStore.set(coreViewsState.atom, [
...existingCoreViews.filter(
(coreView) => coreView.id !== currentView.id,
),
{
...currentCoreView,
mainGroupByFieldMetadataId: null,
type: convertViewTypeToCore(viewType),
calendarLayout: ViewCalendarLayout.MONTH,
calendarFieldMetadataId,
},
]);
setRecordIndexViewType(viewType);
jotaiStore.set(coreViewsState.atom, [
...existingCoreViews.filter(
(coreView) => coreView.id !== currentView.id,
),
{
...currentCoreView,
mainGroupByFieldMetadataId: null,
type: convertViewTypeToCore(viewType),
calendarLayout: ViewCalendarLayout.MONTH,
calendarFieldMetadataId,
},
]);
loadRecordIndexStates(
{
...currentView,
type: viewType,
calendarFieldMetadataId,
calendarLayout: ViewCalendarLayout.MONTH,
},
objectMetadataItem,
);
loadRecordIndexStates(
{
...currentView,
type: viewType,
calendarFieldMetadataId,
calendarLayout: ViewCalendarLayout.MONTH,
},
objectMetadataItem,
);
if (shouldChangeIcon(currentView.icon, currentView.type)) {
updateCurrentViewParams.icon =
viewTypeIconMapping(viewType).displayName;
}
updateCurrentViewParams.calendarLayout = ViewCalendarLayout.MONTH;
updateCurrentViewParams.calendarFieldMetadataId =
calendarFieldMetadataId;
updateCurrentViewParams.mainGroupByFieldMetadataId = null;
return await updateCurrentView(updateCurrentViewParams);
}
case ViewType.FieldsWidget: {
return;
}
default: {
return assertUnreachable(viewType);
if (shouldChangeIcon(currentView.icon, currentView.type)) {
updateCurrentViewParams.icon =
viewTypeIconMapping(viewType).displayName;
}
updateCurrentViewParams.calendarLayout = ViewCalendarLayout.MONTH;
updateCurrentViewParams.calendarFieldMetadataId =
calendarFieldMetadataId;
updateCurrentViewParams.mainGroupByFieldMetadataId = null;
return await updateCurrentView(updateCurrentViewParams);
}
},
case ViewType.FieldsWidget: {
return;
}
default: {
return assertUnreachable(viewType);
}
}
},
[
availableFieldsForGrouping,
setRecordIndexViewType,
store,
updateCurrentView,
availableFieldsForCalendar,
loadRecordIndexStates,
@@ -1,6 +1,7 @@
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useStore } from 'jotai';
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
import { type GraphQLView } from '@/views/types/GraphQLView';
@@ -8,20 +9,19 @@ import { type ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
export const useUpdateObjectViewOptions = () => {
const store = useStore();
const setRecordIndexOpenRecordIn = useSetRecoilState(
const setRecordIndexOpenRecordIn = useSetRecoilStateV2(
recordIndexOpenRecordInState,
);
const setRecordIndexViewName = useSetRecoilComponentState(
const setRecordIndexViewName = useSetRecoilComponentStateV2(
viewPickerInputNameComponentState,
);
const setRecordIndexViewIcon = useSetRecoilComponentState(
const setRecordIndexViewIcon = useSetRecoilComponentStateV2(
viewPickerSelectedIconComponentState,
);
@@ -1,8 +1,8 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createComponentStateV2 } from '@/ui/utilities/state/jotai/utils/createComponentStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const objectOptionsDropdownSearchInputComponentState =
createComponentState<string>({
createComponentStateV2<string>({
key: 'objectOptionsDropdownSearchInputComponentState',
defaultValue: '',
componentInstanceContext: ViewComponentInstanceContext,