## 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
@@ -1,17 +0,0 @@
import { type Locale } from 'date-fns';
import { enUS } from 'date-fns/locale';
import { type APP_LOCALES } from 'twenty-shared/translations';
import { createState } from '@/ui/utilities/state/utils/createState';
type DateLocaleState = {
locale?: keyof typeof APP_LOCALES;
localeCatalog: Locale;
};
export const dateLocaleState = createState<DateLocaleState>({
key: 'dateLocaleState',
defaultValue: {
locale: undefined,
localeCatalog: enUS,
},
});
@@ -2,10 +2,10 @@ import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDi
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { type MessageDescriptor } from '@lingui/core';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { type CommandMenuPages } from 'twenty-shared/types';
import { type IconComponent } from 'twenty-ui/display';
@@ -26,7 +26,7 @@ export const ActionOpenSidePanelPage = ({
const { navigateCommandMenu } = useNavigateCommandMenu();
const setCommandMenuSearchState = useSetRecoilState(commandMenuSearchState);
const setCommandMenuSearchState = useSetRecoilStateV2(commandMenuSearchState);
if (!actionConfig) {
return null;
@@ -1,8 +1,8 @@
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
import { useCloseActionMenu } from '@/action-menu/hooks/useCloseActionMenu';
import { isHeadlessFrontComponentMountedFamilySelector } from '@/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector';
import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { ActionDisplay } from './ActionDisplay';
@@ -20,8 +20,9 @@ export const HeadlessFrontComponentAction = ({
closeSidePanelOnCommandMenuListActionExecution: false,
});
const isMounted = useRecoilValue(
isHeadlessFrontComponentMountedFamilySelector(frontComponentId),
const isMounted = useFamilySelectorValueV2(
isHeadlessFrontComponentMountedFamilySelector,
frontComponentId,
);
if (!actionConfig) {
@@ -14,7 +14,7 @@ import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -22,8 +22,9 @@ export const DeleteMultipleRecordsAction = () => {
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
recordIndexId,
);
if (!contextStoreCurrentViewId) {
@@ -32,20 +33,24 @@ export const DeleteMultipleRecordsAction = () => {
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
const contextStoreFilters = useRecoilComponentValue(
const contextStoreFilters = useRecoilComponentValueV2(
contextStoreFiltersComponentState,
recordIndexId,
);
const contextStoreFilterGroups = useRecoilComponentValue(
const contextStoreFilterGroups = useRecoilComponentValueV2(
contextStoreFilterGroupsComponentState,
recordIndexId,
);
const contextStoreAnyFieldFilterValue = useRecoilComponentValue(
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
contextStoreAnyFieldFilterValueComponentState,
recordIndexId,
);
const { removeSelectedRecordsFromRecordBoard } =
@@ -14,7 +14,7 @@ import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
@@ -24,8 +24,9 @@ export const DestroyMultipleRecordsAction = () => {
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
recordIndexId,
);
if (!contextStoreCurrentViewId) {
@@ -36,20 +37,24 @@ export const DestroyMultipleRecordsAction = () => {
const { removeSelectedRecordsFromRecordBoard } =
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
const contextStoreFilters = useRecoilComponentValue(
const contextStoreFilters = useRecoilComponentValueV2(
contextStoreFiltersComponentState,
recordIndexId,
);
const contextStoreFilterGroups = useRecoilComponentValue(
const contextStoreFilterGroups = useRecoilComponentValueV2(
contextStoreFilterGroupsComponentState,
recordIndexId,
);
const contextStoreAnyFieldFilterValue = useRecoilComponentValue(
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
contextStoreAnyFieldFilterValueComponentState,
recordIndexId,
);
const { filterValueDependencies } = useFilterValueDependencies();
@@ -7,14 +7,14 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useRecordIndexExportRecords } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const ExportMultipleRecordsAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
@@ -3,12 +3,12 @@ import { useSelectedRecordIds } from '@/action-menu/actions/record-actions/singl
import { useOpenMergeRecordsPageInCommandMenu } from '@/command-menu/hooks/useOpenMergeRecordsPageInCommandMenu';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const MergeMultipleRecordsAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
@@ -12,7 +12,7 @@ import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro';
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
@@ -20,8 +20,9 @@ export const RestoreMultipleRecordsAction = () => {
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
recordIndexId,
);
if (!contextStoreCurrentViewId) {
@@ -36,20 +37,24 @@ export const RestoreMultipleRecordsAction = () => {
objectNameSingular: objectMetadataItem.nameSingular,
});
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
const contextStoreFilters = useRecoilComponentValue(
const contextStoreFilters = useRecoilComponentValueV2(
contextStoreFiltersComponentState,
recordIndexId,
);
const contextStoreFilterGroups = useRecoilComponentValue(
const contextStoreFilterGroups = useRecoilComponentValueV2(
contextStoreFilterGroupsComponentState,
recordIndexId,
);
const contextStoreAnyFieldFilterValue = useRecoilComponentValue(
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
contextStoreAnyFieldFilterValueComponentState,
recordIndexId,
);
const { filterValueDependencies } = useFilterValueDependencies();
@@ -3,8 +3,8 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
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 { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
@@ -13,7 +13,7 @@ export const CreateNewViewNoSelectionRecord = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { openDropdown } = useOpenDropdown();
const currentViewId = useRecoilComponentValue(
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
@@ -26,7 +26,7 @@ export const CreateNewViewNoSelectionRecord = () => {
currentViewId,
);
const setViewPickerReferenceViewId = useSetRecoilComponentState(
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
viewPickerReferenceViewIdComponentState,
recordIndexId,
);
@@ -6,13 +6,13 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
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 HideDeletedRecordsNoSelectionRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const currentViewId = useRecoilComponentValue(
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
@@ -32,7 +32,7 @@ export const HideDeletedRecordsNoSelectionRecordAction = () => {
const { isRecordFilterAboutSoftDelete } = useCheckIsSoftDeleteFilter();
const currentRecordFilters = useRecoilComponentValue(
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
recordIndexId,
);
@@ -3,12 +3,12 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const SeeDeletedRecordsNoSelectionRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const currentViewId = useRecoilComponentValue(
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
@@ -7,14 +7,16 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
import { type WorkflowVersion } from '@/workflow/types/Workflow';
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
@@ -28,12 +30,16 @@ export const useRunWorkflowRecordActions = ({
}) => {
const { getIcon } = useIcons();
const { enqueueWarningSnackBar } = useSnackBar();
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
const isPageInEditMode = useRecoilComponentValue(
const isPageInEditMode = useRecoilComponentValueV2(
contextStoreIsPageInEditModeComponentState,
recordIndexId,
);
const selectedRecordIds =
@@ -49,69 +55,68 @@ export const useRunWorkflowRecordActions = ({
const { runWorkflowVersion } = useRunWorkflowVersion();
const runWorkflowVersionOnSelectedRecords = useRecoilCallback(
({ snapshot }) =>
async (
selectedRecordIds: string[],
activeWorkflowVersion: Pick<
WorkflowVersion,
'id' | 'workflowId' | 'trigger'
>,
) => {
if (selectedRecordIds.length > QUERY_MAX_RECORDS) {
const selectedCountFormatted =
selectedRecordIds.length.toLocaleString();
const limitFormatted = QUERY_MAX_RECORDS.toLocaleString();
const runWorkflowVersionOnSelectedRecords = useCallback(
async (
selectedRecordIds: string[],
activeWorkflowVersion: Pick<
WorkflowVersion,
'id' | 'workflowId' | 'trigger'
>,
) => {
if (selectedRecordIds.length > QUERY_MAX_RECORDS) {
const selectedCountFormatted =
selectedRecordIds.length.toLocaleString();
const limitFormatted = QUERY_MAX_RECORDS.toLocaleString();
enqueueWarningSnackBar({
message: t`You selected ${selectedCountFormatted} records but manual triggers can run on at most ${limitFormatted} records at once. Only the first ${limitFormatted} records will be processed.`,
options: {
dedupeKey: 'workflow-manual-trigger-selection-limit',
},
});
}
enqueueWarningSnackBar({
message: t`You selected ${selectedCountFormatted} records but manual triggers can run on at most ${limitFormatted} records at once. Only the first ${limitFormatted} records will be processed.`,
options: {
dedupeKey: 'workflow-manual-trigger-selection-limit',
},
});
}
const limitedSelectedRecordIds = selectedRecordIds.slice(
0,
QUERY_MAX_RECORDS,
);
const limitedSelectedRecordIds = selectedRecordIds.slice(
0,
QUERY_MAX_RECORDS,
);
if (
isDefined(activeWorkflowVersion?.trigger) &&
isBulkRecordsManualTrigger(activeWorkflowVersion.trigger)
) {
const objectNamePlural = objectMetadataItem.namePlural;
const selectedRecords = limitedSelectedRecordIds
.map((recordId) =>
snapshot.getLoadable(recordStoreFamilyState(recordId)).getValue(),
)
.filter(isDefined);
if (
isDefined(activeWorkflowVersion?.trigger) &&
isBulkRecordsManualTrigger(activeWorkflowVersion.trigger)
) {
const objectNamePlural = objectMetadataItem.namePlural;
const selectedRecords = limitedSelectedRecordIds
.map((recordId) =>
jotaiStore.get(recordStoreFamilyState.atomFamily(recordId)),
)
.filter(isDefined);
await runWorkflowVersion({
workflowId: activeWorkflowVersion.workflowId,
workflowVersionId: activeWorkflowVersion.id,
payload: {
[objectNamePlural]: selectedRecords,
},
});
} else {
for (const selectedRecordId of limitedSelectedRecordIds) {
const selectedRecord = jotaiStore.get(
recordStoreFamilyState.atomFamily(selectedRecordId),
);
if (!isDefined(selectedRecord)) {
continue;
}
await runWorkflowVersion({
workflowId: activeWorkflowVersion.workflowId,
workflowVersionId: activeWorkflowVersion.id,
payload: {
[objectNamePlural]: selectedRecords,
},
payload: selectedRecord,
});
} else {
for (const selectedRecordId of limitedSelectedRecordIds) {
const selectedRecord = snapshot
.getLoadable(recordStoreFamilyState(selectedRecordId))
.getValue();
if (!isDefined(selectedRecord)) {
continue;
}
await runWorkflowVersion({
workflowId: activeWorkflowVersion.workflowId,
workflowVersionId: activeWorkflowVersion.id,
payload: selectedRecord,
});
}
}
},
}
},
[runWorkflowVersion, objectMetadataItem, enqueueWarningSnackBar],
);
@@ -3,7 +3,7 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { isDefined } from 'twenty-shared/utils';
export const AddToFavoritesSingleRecordAction = () => {
@@ -13,7 +13,10 @@ export const AddToFavoritesSingleRecordAction = () => {
const { createFavorite } = useCreateFavorite();
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const selectedRecord = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const handleClick = () => {
if (!isDefined(selectedRecord)) {
@@ -1,13 +1,16 @@
import { Action } from '@/action-menu/actions/components/Action';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { isDefined } from 'twenty-shared/utils';
export const ExportNoteActionSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const selectedRecord = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const filename = `${(selectedRecord?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`;
@@ -2,14 +2,14 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { Action } from '@/action-menu/actions/components/Action';
export const ExportSingleRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
@@ -4,12 +4,15 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout';
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
export const CancelDashboardSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const selectedRecord = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const pageLayoutId = selectedRecord?.pageLayoutId;
@@ -2,13 +2,16 @@ import { Action } from '@/action-menu/actions/components/Action';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useResetLocationHash } from 'twenty-ui/utilities';
export const EditDashboardSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const selectedRecord = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const pageLayoutId = selectedRecord?.pageLayoutId;
@@ -4,12 +4,15 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout';
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
export const SaveDashboardSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const selectedRecord = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const pageLayoutId = selectedRecord?.pageLayoutId;
@@ -1,9 +1,13 @@
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const useSelectedRecordIdOrThrow = () => {
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
if (
@@ -1,9 +1,13 @@
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const useSelectedRecordIds = () => {
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
if (
@@ -4,7 +4,7 @@ import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUp
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { useTidyUpWorkflowVersion } from '@/workflow/workflow-version/hooks/useTidyUpWorkflowVersion';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { isDefined } from 'twenty-shared/utils';
export const TidyUpWorkflowSingleRecordAction = () => {
@@ -16,26 +16,20 @@ export const TidyUpWorkflowSingleRecordAction = () => {
const { getUpdatableWorkflowVersion } =
useGetUpdatableWorkflowVersionOrThrow(instanceId);
const onClick = useRecoilCallback(
({ snapshot }) =>
async () => {
const workflowDiagramState = workflowDiagramComponentState.atomFamily({
instanceId,
});
const workflowDiagram = snapshot
.getLoadable(workflowDiagramState)
.getValue();
const onClick = async () => {
const workflowDiagramAtom = workflowDiagramComponentState.atomFamily({
instanceId,
});
const workflowDiagram = jotaiStore.get(workflowDiagramAtom);
if (!isDefined(workflowDiagram)) {
return;
}
if (!isDefined(workflowDiagram)) {
return;
}
const workflowVersionId = await getUpdatableWorkflowVersion();
const workflowVersionId = await getUpdatableWorkflowVersion();
await tidyUpWorkflowVersion(workflowVersionId, workflowDiagram);
},
[getUpdatableWorkflowVersion, tidyUpWorkflowVersion, instanceId],
);
await tidyUpWorkflowVersion(workflowVersionId, workflowDiagram);
};
return <Action onClick={onClick} />;
};
@@ -2,13 +2,13 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { AppPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const SeeVersionWorkflowRunSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const workflowRun = useRecoilValue(recordStoreFamilyState(recordId));
const workflowRun = useFamilyRecoilValueV2(recordStoreFamilyState, recordId);
if (!isDefined(workflowRun) || !isDefined(workflowRun?.workflowVersion?.id)) {
return null;
@@ -2,13 +2,13 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { AppPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const SeeWorkflowWorkflowRunSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const workflowRun = useRecoilValue(recordStoreFamilyState(recordId));
const workflowRun = useFamilyRecoilValueV2(recordStoreFamilyState, recordId);
if (!isDefined(workflowRun) || !isDefined(workflowRun?.workflow?.id)) {
return null;
@@ -9,26 +9,31 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useStopWorkflowRun } from '@/workflow/hooks/useStopWorkflowRun';
export const StopWorkflowRunSingleRecordAction = () => {
const { objectMetadataItem } = useRecordIndexIdFromCurrentContextStore();
const { objectMetadataItem, recordIndexId } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
const contextStoreFilters = useRecoilComponentValue(
const contextStoreFilters = useRecoilComponentValueV2(
contextStoreFiltersComponentState,
recordIndexId,
);
const contextStoreFilterGroups = useRecoilComponentValue(
const contextStoreFilterGroups = useRecoilComponentValueV2(
contextStoreFilterGroupsComponentState,
recordIndexId,
);
const contextStoreAnyFieldFilterValue = useRecoilComponentValue(
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
contextStoreAnyFieldFilterValueComponentState,
recordIndexId,
);
const { filterValueDependencies } = useFilterValueDependencies();
@@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { useRecoilValue } from 'recoil';
import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -40,7 +40,10 @@ const SeeRunsWorkflowVersionSingleRecordActionContent = ({
export const SeeRunsWorkflowVersionSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId));
const workflowVersion = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const workflowId = workflowVersion?.workflow?.id;
@@ -3,7 +3,7 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -33,7 +33,10 @@ const SeeVersionsWorkflowVersionSingleRecordActionContent = ({
export const SeeVersionsWorkflowVersionSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId));
const workflowVersion = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
if (!isDefined(workflowVersion) || !isDefined(workflowVersion.workflowId)) {
return null;
@@ -2,12 +2,15 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { AppPath } from 'twenty-shared/types';
export const SeeWorkflowWorkflowVersionSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId));
const workflowVersion = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
return (
<ActionLink
@@ -2,8 +2,9 @@ import { Action } from '@/action-menu/actions/components/Action';
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionType } from '@/action-menu/actions/types/ActionType';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
@@ -14,8 +15,9 @@ import { useIcons } from 'twenty-ui/display';
export const useRunWorkflowRecordAgnosticActions = () => {
const { getIcon } = useIcons();
const isPageInEditMode = useRecoilComponentValue(
const isPageInEditMode = useRecoilComponentValueV2(
contextStoreIsPageInEditModeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const { actionMenuType } = useContext(ActionMenuContext);
@@ -1,6 +1,6 @@
import { createState } from '@/ui/utilities/state/utils/createState';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const forceRegisteredActionsByKeyState = createState<
export const forceRegisteredActionsByKeyState = createStateV2<
Record<string, boolean | undefined>
>({
key: 'forceRegisteredActionsByKeyComponentState',
@@ -1,13 +1,15 @@
import { PageHeaderActionMenuButtons } from '@/action-menu/components/PageHeaderActionMenuButtons';
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
import { ActionMenuContextProvider } from '@/action-menu/contexts/ActionMenuContextProvider';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useIsMobile } from 'twenty-ui/utilities';
export const RecordIndexActionMenu = () => {
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue(
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const isMobile = useIsMobile();
@@ -15,7 +15,6 @@ 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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
@@ -50,7 +49,7 @@ export const RecordIndexActionMenuDropdown = () => {
const dropdownId = getActionMenuDropdownIdFromActionMenuId(actionMenuId);
const { closeDropdown } = useCloseDropdown();
const actionMenuDropdownPosition = useRecoilComponentValue(
const actionMenuDropdownPosition = useRecoilComponentValueV2(
recordIndexActionMenuDropdownPositionComponentState,
dropdownId,
);
@@ -1,17 +1,20 @@
import { PageHeaderActionMenuButtons } from '@/action-menu/components/PageHeaderActionMenuButtons';
import { ActionMenuContextProvider } from '@/action-menu/contexts/ActionMenuContextProvider';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useIsMobile } from 'twenty-ui/utilities';
export const RecordShowActionMenu = () => {
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue(
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const hasSelectedRecord =
@@ -1,11 +1,13 @@
import { CommandMenuActionMenuDropdown } from '@/action-menu/components/CommandMenuActionMenuDropdown';
import { ActionMenuContextProvider } from '@/action-menu/contexts/ActionMenuContextProvider';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const RecordShowRightDrawerActionMenu = () => {
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue(
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
return (
@@ -14,11 +14,13 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentStateCallbackStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2';
import { t } from '@lingui/core/macro';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { AppPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconBrowserMaximize } from 'twenty-ui/display';
@@ -35,9 +37,10 @@ export const RecordShowRightDrawerOpenRecordButton = ({
objectNameSingular,
recordId,
}: RecordShowRightDrawerOpenRecordButtonProps) => {
const record = useRecoilValue<ObjectRecord | null | undefined>(
recordStoreFamilyState(recordId),
);
const record = useFamilyRecoilValueV2(recordStoreFamilyState, recordId) as
| ObjectRecord
| null
| undefined;
const { closeCommandMenu } = useCommandMenu();
const commandMenuPageComponentInstance = useComponentInstanceStateContext(
@@ -63,11 +66,13 @@ export const RecordShowRightDrawerOpenRecordButton = ({
tabListComponentIdInRecordPage,
);
const parentViewState = useRecoilComponentCallbackState(
const parentViewState = useRecoilComponentStateCallbackStateV2(
contextStoreRecordShowParentViewComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const store = useStore();
const navigate = useNavigateApp();
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
@@ -76,48 +81,45 @@ export const RecordShowRightDrawerOpenRecordButton = ({
const { closeDropdown } = useCloseDropdown();
const handleOpenRecord = useRecoilCallback(
({ snapshot, reset }) =>
() => {
const tabIdToOpen =
activeTabIdInRightDrawer === 'home'
? objectNameSingular === CoreObjectNameSingular.Note ||
objectNameSingular === CoreObjectNameSingular.Task
? 'richText'
: 'timeline'
: activeTabIdInRightDrawer;
const handleOpenRecord = useCallback(() => {
const tabIdToOpen =
activeTabIdInRightDrawer === 'home'
? objectNameSingular === CoreObjectNameSingular.Note ||
objectNameSingular === CoreObjectNameSingular.Task
? 'richText'
: 'timeline'
: activeTabIdInRightDrawer;
setActiveTabIdInRecordPage(tabIdToOpen);
setActiveTabIdInRecordPage(tabIdToOpen);
const parentView = snapshot.getLoadable(parentViewState).getValue();
const parentView = store.get(parentViewState);
if (parentView?.parentViewObjectNameSingular !== objectNameSingular) {
reset(parentViewState);
}
if (parentView?.parentViewObjectNameSingular !== objectNameSingular) {
store.set(parentViewState, undefined);
}
navigate(AppPath.RecordShowPage, {
objectNameSingular,
objectRecordId: recordId,
});
closeDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
);
closeCommandMenu();
},
[
actionMenuId,
activeTabIdInRightDrawer,
closeCommandMenu,
closeDropdown,
navigate,
navigate(AppPath.RecordShowPage, {
objectNameSingular,
parentViewState,
recordId,
setActiveTabIdInRecordPage,
],
);
objectRecordId: recordId,
});
closeDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
);
closeCommandMenu();
}, [
actionMenuId,
activeTabIdInRightDrawer,
closeCommandMenu,
closeDropdown,
navigate,
objectNameSingular,
parentViewState,
recordId,
setActiveTabIdInRecordPage,
store,
]);
useHotkeysOnFocusedElement({
keys: ['ctrl+Enter,meta+Enter'],
@@ -1,5 +1,5 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { RecoilRoot } from 'recoil';
import { Provider as JotaiProvider } from 'jotai';
import * as test from 'storybook/test';
import { expect, userEvent, waitFor, within } from 'storybook/test';
@@ -7,10 +7,14 @@ import { CommandMenuActionMenuDropdown } from '@/action-menu/components/CommandM
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
const deleteMock = test.fn();
const addToFavoritesMock = test.fn();
const exportMock = test.fn();
@@ -20,47 +24,43 @@ const meta: Meta<typeof CommandMenuActionMenuDropdown> = {
component: CommandMenuActionMenuDropdown,
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
{
<JotaiProvider store={jotaiStore}>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID }}
>
<JestContextStoreSetter
contextStoreTargetedRecordsRule={{
mode: 'selection',
selectedRecordIds: ['1'],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
1,
);
}}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<ActionMenuContext.Provider
value={{
isInRightDrawer: true,
displayType: 'dropdownItem',
actionMenuType: 'command-menu-show-page-action-menu-dropdown',
actions: createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
exportMock,
}),
}}
contextStoreNumberOfSelectedRecords={1}
>
<Story />
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<ActionMenuContext.Provider
value={{
isInRightDrawer: true,
displayType: 'dropdownItem',
actionMenuType: 'command-menu-show-page-action-menu-dropdown',
actions: createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
exportMock,
}),
}}
>
<Story />
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
</JestContextStoreSetter>
</ContextStoreComponentInstanceContext.Provider>
</JotaiProvider>
),
ComponentDecorator,
ContextStoreDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,
RouterDecorator,
],
args: {
@@ -1,6 +1,5 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot } from 'recoil';
import * as test from 'storybook/test';
import { expect, userEvent, waitFor, within } from 'storybook/test';
@@ -30,38 +29,33 @@ const meta: Meta<typeof RecordIndexActionMenuDropdown> = {
}),
true,
);
jotaiStore.set(
recordIndexActionMenuDropdownPositionComponentState.atomFamily({
instanceId: 'action-menu-dropdown-story',
}),
{ x: 10, y: 10 },
);
return (
<JotaiProvider store={jotaiStore}>
<RecoilRoot
initializeState={({ set }) => {
set(
recordIndexActionMenuDropdownPositionComponentState.atomFamily({
instanceId: 'action-menu-dropdown-story',
}),
{ x: 10, y: 10 },
);
}}
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
<ActionMenuContext.Provider
value={{
isInRightDrawer: true,
displayType: 'dropdownItem',
actionMenuType: 'index-page-action-menu-dropdown',
actions: createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
exportMock,
}),
}}
>
<ActionMenuContext.Provider
value={{
isInRightDrawer: true,
displayType: 'dropdownItem',
actionMenuType: 'index-page-action-menu-dropdown',
actions: createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
exportMock,
}),
}}
>
<Story />
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
<Story />
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
</JotaiProvider>
);
},
@@ -1,11 +1,12 @@
import { type ActionMenuContextType } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuContextProviderDefault } from '@/action-menu/contexts/ActionMenuContextProviderDefault';
import { ActionMenuContextProviderWorkflowObjects } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowObjects';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
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 { isDefined } from 'twenty-shared/utils';
@@ -19,8 +20,9 @@ export const ActionMenuContextProvider = ({
children: React.ReactNode;
objectMetadataItemOverride?: ObjectMetadataItem;
}) => {
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue(
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState);
@@ -7,9 +7,10 @@ import {
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const ActionMenuContextProviderDefault = ({
objectMetadataItem,
@@ -34,8 +35,9 @@ export const ActionMenuContextProviderDefault = ({
const actions = useRegisteredActions(shouldBeRegisteredParams);
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const isRecordSelection =
@@ -6,13 +6,14 @@ import {
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
type ActionMenuContextProviderWorkflowObjectsProps = {
@@ -122,8 +123,9 @@ export const ActionMenuContextProviderWorkflowObjects = ({
actionMenuType,
children,
}: ActionMenuContextProviderWorkflowObjectsProps) => {
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const recordId =
@@ -133,7 +135,7 @@ export const ActionMenuContextProviderWorkflowObjects = ({
: undefined;
const selectedRecord =
useRecoilValue(recordStoreFamilyState(recordId ?? '')) || undefined;
useFamilyRecoilValueV2(recordStoreFamilyState, recordId ?? '') || undefined;
if (isDefined(selectedRecord?.id)) {
return (
@@ -4,11 +4,13 @@ import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { type ShouldBeRegisteredFunctionParams } from '@/action-menu/actions/types/ShouldBeRegisteredFunctionParams';
import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig';
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
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';
import { useIcons } from 'twenty-ui/display';
@@ -20,16 +22,21 @@ export const useRegisteredActions = (
const { getIcon } = useIcons();
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const contextStoreCurrentViewType = useRecoilComponentValue(
const contextStoreCurrentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const isFullTabWidgetInEditMode = useRecoilComponentValue(
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const isFullTabWidgetInEditMode = useRecoilComponentValueV2(
contextStoreIsPageInEditModeComponentState,
recordIndexId,
);
const viewType = getActionViewType(
@@ -4,6 +4,7 @@ import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@@ -12,15 +13,17 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { usePrefetchedNavigationMenuItemsData } from '@/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
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 { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useCallback, useContext, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
@@ -35,8 +38,9 @@ export const useShouldActionBeRegisteredParams = ({
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
);
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const recordId =
@@ -71,7 +75,7 @@ export const useShouldActionBeRegisteredParams = ({
]);
const selectedRecord =
useRecoilValue(recordStoreFamilyState(recordId ?? '')) || undefined;
useFamilyRecoilValueV2(recordStoreFamilyState, recordId ?? '') || undefined;
const objectPermissions = useObjectPermissionsForObject(
objectMetadataItem?.id ?? '',
@@ -83,20 +87,27 @@ export const useShouldActionBeRegisteredParams = ({
const { isInRightDrawer } = useContext(ActionMenuContext);
const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const hasAnySoftDeleteFilterOnView = useRecoilComponentSelectorValueV2(
hasAnySoftDeleteFilterOnViewComponentSelector,
recordIndexId,
);
const isShowPage =
useRecoilComponentValue(contextStoreCurrentViewTypeComponentState) ===
ContextStoreViewType.ShowPage;
useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
) === ContextStoreViewType.ShowPage;
const numberOfSelectedRecords = useRecoilComponentValue(
const numberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const contextStoreCurrentViewType = useRecoilComponentValue(
const contextStoreCurrentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const viewType = getActionViewType(
@@ -128,7 +139,7 @@ export const useShouldActionBeRegisteredParams = ({
[],
);
const forceRegisteredActionsByKey = useRecoilValue(
const forceRegisteredActionsByKey = useRecoilValueV2(
forceRegisteredActionsByKeyState,
);
@@ -1,9 +1,9 @@
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { type PositionType } from '@/action-menu/types/PositionType';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createComponentStateV2 } from '@/ui/utilities/state/jotai/utils/createComponentStateV2';
export const recordIndexActionMenuDropdownPositionComponentState =
createComponentState<PositionType>({
createComponentStateV2<PositionType>({
key: 'recordIndexActionMenuDropdownPositionComponentState',
defaultValue: {
x: null,
@@ -1,7 +1,7 @@
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { format, getYear } from 'date-fns';
import { useRecoilValue } from 'recoil';
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from '@/activities/calendar/constants/Calendar';
@@ -26,7 +26,7 @@ import {
Section,
} from 'twenty-ui/layout';
import { type TimelineCalendarEventsWithTotal } from '~/generated/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
const StyledContainer = styled.div`
box-sizing: border-box;
@@ -49,7 +49,7 @@ const StyledTitleContainer = styled.div`
export const CalendarEventsCard = () => {
const { t } = useLingui();
const targetRecord = useTargetRecord();
const { localeCatalog } = useRecoilValue(dateLocaleState);
const { localeCatalog } = useRecoilValueV2(dateLocaleStateV2);
const [query, queryName] =
targetRecord.targetObjectNameSingular === CoreObjectNameSingular.Person
@@ -1,10 +1,11 @@
import { useCallback, useMemo } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { useAtom, useStore } from 'jotai';
import { v4 } from 'uuid';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@@ -56,7 +57,10 @@ export const ActivityRichTextEditor = ({
activityId,
activityObjectNameSingular,
}: ActivityRichTextEditorProps) => {
const [activityInStore] = useRecoilState(recordStoreFamilyState(activityId));
const store = useStore();
const [activityInStore] = useAtom(
recordStoreFamilyState.atomFamily(activityId),
);
const cache = useApolloCoreClient().cache;
const activity = activityInStore as Task | Note | null;
@@ -122,7 +126,7 @@ export const ActivityRichTextEditor = ({
}
}, 300);
const [canCreateActivity, setCanCreateActivity] = useRecoilState(
const [canCreateActivity, setCanCreateActivity] = useRecoilStateV2(
canCreateActivityState,
);
@@ -145,47 +149,45 @@ export const ActivityRichTextEditor = ({
[canCreateActivity, persistBodyDebounced, setCanCreateActivity],
);
const handleBodyChange = useRecoilCallback(
({ set, snapshot }) =>
async (newStringifiedBody: string) => {
const oldActivity = snapshot
.getLoadable(recordStoreFamilyState(activityId))
.getValue();
const handleBodyChange = useCallback(
async (newStringifiedBody: string) => {
const oldActivity = store.get(
recordStoreFamilyState.atomFamily(activityId),
);
set(recordStoreFamilyState(activityId), (oldActivity) => {
return {
...oldActivity,
id: activityId,
bodyV2: {
store.set(
recordStoreFamilyState.atomFamily(activityId),
(prev: typeof oldActivity) => ({
...prev,
id: activityId,
bodyV2: {
blocknote: newStringifiedBody,
markdown: null,
},
__typename: 'Activity',
}),
);
modifyRecordFromCache({
recordId: activityId,
fieldModifiers: {
bodyV2: () => {
return {
blocknote: newStringifiedBody,
markdown: null,
},
__typename: 'Activity',
};
});
modifyRecordFromCache({
recordId: activityId,
fieldModifiers: {
bodyV2: () => {
return {
blocknote: newStringifiedBody,
markdown: null,
};
},
};
},
cache,
objectMetadataItem: objectMetadataItemActivity,
});
},
cache,
objectMetadataItem: objectMetadataItemActivity,
});
handlePersistBody(newStringifiedBody);
handlePersistBody(newStringifiedBody);
await syncAttachments(
newStringifiedBody,
oldActivity?.bodyV2.blocknote,
);
},
await syncAttachments(newStringifiedBody, oldActivity?.bodyV2.blocknote);
},
[
store,
activityId,
cache,
objectMetadataItemActivity,
@@ -299,59 +301,43 @@ export const ActivityRichTextEditor = ({
// but this leaves the door open for unpredicted behavior with click handlers conflicts,
// we recently had a bug which was deleting what the user typed and closed the right drawer if he used backspace key.
// We could maybe use the types of components in the focus stack.
const handleBlockEditorFocus = useRecoilCallback(
({ snapshot }) =>
() => {
// TODO: Here we want to detect anything that is open to avoid conflicts with the library click event
// that is not prevented and propagate to other click handlers in the app.
// Because that is how we do in the app, for example with stacked dropdowns, we always close what's open before
// letting the click being captured by a button or input that can capture it.
const isRecordTitleCellOpen = snapshot
.getLoadable(
isTitleCellInEditModeComponentState.atomFamily({
instanceId: recordTitleCellId,
}),
)
.getValue();
const handleBlockEditorFocus = useCallback(() => {
const isRecordTitleCellOpen = store.get(
isTitleCellInEditModeComponentState.atomFamily({
instanceId: recordTitleCellId,
}),
);
if (isRecordTitleCellOpen) {
editor.domElement?.blur();
return;
}
if (isRecordTitleCellOpen) {
editor.domElement?.blur();
return;
}
pushFocusItemToFocusStack({
component: {
instanceId: activityId,
type: FocusComponentType.ACTIVITY_RICH_TEXT_EDITOR,
},
focusId: activityId,
globalHotkeysConfig: BLOCK_EDITOR_GLOBAL_HOTKEYS_CONFIG,
});
pushFocusItemToFocusStack({
component: {
instanceId: activityId,
type: FocusComponentType.ACTIVITY_RICH_TEXT_EDITOR,
},
[recordTitleCellId, activityId, editor, pushFocusItemToFocusStack],
);
focusId: activityId,
globalHotkeysConfig: BLOCK_EDITOR_GLOBAL_HOTKEYS_CONFIG,
});
}, [recordTitleCellId, activityId, editor, pushFocusItemToFocusStack, store]);
const handlerBlockEditorBlur = useRecoilCallback(
({ snapshot }) =>
() => {
const isRecordTitleCellOpen = snapshot
.getLoadable(
isTitleCellInEditModeComponentState.atomFamily({
instanceId: recordTitleCellId,
}),
)
.getValue();
const handlerBlockEditorBlur = useCallback(() => {
const isRecordTitleCellOpen = store.get(
isTitleCellInEditModeComponentState.atomFamily({
instanceId: recordTitleCellId,
}),
);
if (isRecordTitleCellOpen) {
return;
}
if (isRecordTitleCellOpen) {
return;
}
removeFocusItemFromFocusStackById({
focusId: activityId,
});
},
[activityId, recordTitleCellId, removeFocusItemFromFocusStackById],
);
removeFocusItemFromFocusStackById({
focusId: activityId,
});
}, [activityId, recordTitleCellId, removeFocusItemFromFocusStackById, store]);
return (
<>
@@ -1,8 +1,8 @@
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
type EmailThreadHeaderProps = {
@@ -43,7 +43,7 @@ export const EmailThreadHeader = ({
subject,
lastMessageSentAt,
}: EmailThreadHeaderProps) => {
const { localeCatalog } = useRecoilValue(dateLocaleState);
const { localeCatalog } = useRecoilValueV2(dateLocaleStateV2);
const lastMessageSentAtFormatted = beautifyPastDateRelativeToNow(
lastMessageSentAt,
localeCatalog,
@@ -1,10 +1,10 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { ParticipantChip } from '@/activities/components/ParticipantChip';
import { type EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { AppTooltip, TooltipPosition } from 'twenty-ui/display';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import {
beautifyPastDateRelativeToNow,
formatToHumanReadableDate,
@@ -31,7 +31,7 @@ export const EmailThreadMessageSender = ({
sender,
sentAt,
}: EmailThreadMessageSenderProps) => {
const { localeCatalog } = useRecoilValue(dateLocaleState);
const { localeCatalog } = useRecoilValueV2(dateLocaleStateV2);
const tooltipId = `date-tooltip-${sentAt.replace(/[^a-zA-Z0-9]/g, '-')}`;
return (
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const emailThreadIdWhenEmailThreadWasClosedState = createState<
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const emailThreadIdWhenEmailThreadWasClosedState = createStateV2<
string | null
>({
key: 'emailThreadIdWhenEmailThreadWasClosedState',
@@ -3,12 +3,13 @@ import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { RecoilRoot } from 'recoil';
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
@@ -139,8 +140,9 @@ describe('useActivityTargetObjectRecords', () => {
const { result } = renderHook(
() => {
const setRecordFromStore = useSetRecoilState(
recordStoreFamilyState(task.id),
const setRecordFromStore = useSetFamilyRecoilStateV2(
recordStoreFamilyState,
task.id,
);
const { activityTargetObjectRecords } = useActivityTargetObjectRecords(
@@ -1,11 +1,11 @@
import { type MockedResponse } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import { useRecoilValue } from 'recoil';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import gql from 'graphql-tag';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
@@ -83,7 +83,7 @@ describe('useOpenCreateActivityDrawer', () => {
const openActivityRightDrawer = useOpenCreateActivityDrawer({
activityObjectNameSingular: CoreObjectNameSingular.Note,
});
const viewableRecordId = useRecoilValue(viewableRecordIdState);
const viewableRecordId = useRecoilValueV2(viewableRecordIdState);
return {
openActivityRightDrawer,
viewableRecordId,
@@ -1,3 +1,6 @@
import { useCallback } from 'react';
import { useStore } from 'jotai';
import { useActivityTargetsForTargetableObjects } from '@/activities/hooks/useActivityTargetsForTargetableObjects';
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { type Note } from '@/activities/types/Note';
@@ -7,7 +10,6 @@ import { type TaskTarget } from '@/activities/types/TaskTarget';
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilCallback } from 'recoil';
import { type RecordGqlOperationOrderBy } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -24,15 +26,15 @@ export const useActivities = <T extends Task | Note>({
skip?: boolean;
limit: number;
}) => {
const updateActivitiesInStore = useRecoilCallback(
({ set }) =>
(activityTargets: (TaskTarget | NoteTarget)[]) => {
for (const activityTarget of activityTargets) {
const activity = activityTarget[objectNameSingular];
set(recordStoreFamilyState(activity.id), activity);
}
},
[objectNameSingular],
const store = useStore();
const updateActivitiesInStore = useCallback(
(activityTargets: (TaskTarget | NoteTarget)[]) => {
for (const activityTarget of activityTargets) {
const activity = activityTarget[objectNameSingular];
store.set(recordStoreFamilyState.atomFamily(activity.id), activity);
}
},
[store, objectNameSingular],
);
const {
@@ -1,5 +1,3 @@
import { useRecoilValue } from 'recoil';
import { type Note } from '@/activities/types/Note';
import { type NoteTarget } from '@/activities/types/NoteTarget';
import { type Task } from '@/activities/types/Task';
@@ -7,6 +5,7 @@ import { type TaskTarget } from '@/activities/types/TaskTarget';
import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTargetObjectRecords';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { type Nullable } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -17,8 +16,9 @@ export const useActivityTargetObjectRecords = (
) => {
const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState);
const activity = useRecoilValue(
recordStoreFamilyState(activityRecordId ?? ''),
const activity = useFamilyRecoilValueV2(
recordStoreFamilyState,
activityRecordId ?? '',
) as Note | Task | null;
if (!isDefined(activity) && !isDefined(activityTargets)) {
@@ -1,3 +1,5 @@
import { useCallback } from 'react';
import { useStore } from 'jotai';
import { isNonEmptyArray } from '@sniptt/guards';
import { type ActivityForEditor } from '@/activities/types/ActivityForEditor';
@@ -15,7 +17,6 @@ import { createOneActivityOperationSignatureFactory } from '@/activities/graphql
import { type NoteTarget } from '@/activities/types/NoteTarget';
import { type TaskTarget } from '@/activities/types/TaskTarget';
import { getJoinObjectNameSingular } from '@/activities/utils/getJoinObjectNameSingular';
import { useRecoilCallback } from 'recoil';
import { capitalize } from 'twenty-shared/utils';
export const useCreateActivityInDB = ({
@@ -56,53 +57,52 @@ export const useCreateActivityInDB = ({
});
const cache = useApolloCoreClient().cache;
const store = useStore();
const createActivityInDB = useRecoilCallback(
({ set }) =>
async (activityToCreate: ActivityForEditor) => {
const createdActivity = await createOneActivity?.({
...activityToCreate,
updatedAt: new Date().toISOString(),
const createActivityInDB = useCallback(
async (activityToCreate: ActivityForEditor) => {
const createdActivity = await createOneActivity?.({
...activityToCreate,
updatedAt: new Date().toISOString(),
});
const activityTargetsToCreate =
activityToCreate.noteTargets ?? activityToCreate.taskTargets ?? [];
if (isNonEmptyArray(activityTargetsToCreate)) {
await createManyActivityTargets({
recordsToCreate: activityTargetsToCreate,
});
}
const activityTargetsToCreate =
activityToCreate.noteTargets ?? activityToCreate.taskTargets ?? [];
const activityTargetsConnection = getRecordConnectionFromRecords({
objectMetadataItems,
objectMetadataItem: objectMetadataItemActivityTarget,
records: activityTargetsToCreate.map((activityTarget) => ({
...activityTarget,
__typename: capitalize(objectMetadataItemActivityTarget.nameSingular),
})),
withPageInfo: false,
computeReferences: true,
isRootLevel: false,
});
if (isNonEmptyArray(activityTargetsToCreate)) {
await createManyActivityTargets({
recordsToCreate: activityTargetsToCreate,
});
}
modifyRecordFromCache({
recordId: createdActivity.id,
cache,
fieldModifiers: {
activityTargets: () => activityTargetsConnection,
},
objectMetadataItem: objectMetadataItemActivity,
});
const activityTargetsConnection = getRecordConnectionFromRecords({
objectMetadataItems,
objectMetadataItem: objectMetadataItemActivityTarget,
records: activityTargetsToCreate.map((activityTarget) => ({
...activityTarget,
__typename: capitalize(
objectMetadataItemActivityTarget.nameSingular,
),
})),
withPageInfo: false,
computeReferences: true,
isRootLevel: false,
});
modifyRecordFromCache({
recordId: createdActivity.id,
cache,
fieldModifiers: {
activityTargets: () => activityTargetsConnection,
},
objectMetadataItem: objectMetadataItemActivity,
});
set(recordStoreFamilyState(createdActivity.id), {
...createdActivity,
activityTargets: activityTargetsToCreate,
});
},
store.set(recordStoreFamilyState.atomFamily(createdActivity.id), {
...createdActivity,
activityTargets: activityTargetsToCreate,
});
},
[
store,
cache,
createManyActivityTargets,
createOneActivity,
@@ -1,5 +1,3 @@
import { useSetRecoilState } from 'recoil';
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
@@ -7,6 +5,7 @@ import { viewableRecordNameSingularState } from '@/object-record/record-right-dr
import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { type Note } from '@/activities/types/Note';
import { type NoteTarget } from '@/activities/types/NoteTarget';
@@ -40,15 +39,15 @@ export const useOpenCreateActivityDrawer = ({
shouldMatchRootQueryFilter: true,
});
const setActivityTargetableEntityArray = useSetRecoilState(
const setActivityTargetableEntityArray = useSetRecoilStateV2(
activityTargetableEntityArrayState,
);
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
const setViewableRecordNameSingular = useSetRecoilState(
const setViewableRecordId = useSetRecoilStateV2(viewableRecordIdState);
const setViewableRecordNameSingular = useSetRecoilStateV2(
viewableRecordNameSingularState,
);
const setIsUpsertingActivityInDB = useSetRecoilState(
const setIsUpsertingActivityInDB = useSetRecoilStateV2(
isUpsertingActivityInDBState,
);
@@ -1,31 +1,33 @@
import { useCallback } from 'react';
import { useStore } from 'jotai';
import { type BLOCK_SCHEMA } from '@/blocknote-editor/blocks/Schema';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useReplaceActivityBlockEditorContent = (
editor: typeof BLOCK_SCHEMA.BlockNoteEditor,
) => {
const replaceBlockEditorContent = useRecoilCallback(
({ snapshot }) =>
(activityId: string) => {
if (isDefined(editor)) {
const activityInStore = snapshot
.getLoadable(recordStoreFamilyState(activityId))
.getValue();
const store = useStore();
const replaceBlockEditorContent = useCallback(
(activityId: string) => {
if (isDefined(editor)) {
const activityInStore = store.get(
recordStoreFamilyState.atomFamily(activityId),
);
const content = isNonEmptyString(activityInStore?.bodyV2.blocknote)
? JSON.parse(activityInStore?.bodyV2.blocknote)
: [{ type: 'paragraph', content: '' }];
const content = isNonEmptyString(activityInStore?.bodyV2.blocknote)
? JSON.parse(activityInStore?.bodyV2.blocknote)
: [{ type: 'paragraph', content: '' }];
if (!isDeeplyEqual(editor.document, content)) {
editor.replaceBlocks(editor.document, content);
}
if (!isDeeplyEqual(editor.document, content)) {
editor.replaceBlocks(editor.document, content);
}
},
[editor],
}
},
[store, editor],
);
return {
@@ -1,5 +1,3 @@
import { useRecoilState } from 'recoil';
import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
import { useRefreshShowPageFindManyActivitiesQueries } from '@/activities/hooks/useRefreshShowPageFindManyActivitiesQueries';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
@@ -30,7 +28,7 @@ export const useUpsertActivity = ({
activityObjectNameSingular,
});
const [, setIsUpsertingActivityInDB] = useRecoilState(
const [, setIsUpsertingActivityInDB] = useRecoilStateV2(
isUpsertingActivityInDBState,
);
@@ -9,7 +9,7 @@ import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
type OpenActivityTargetCellEditModeProps = {
recordPickerInstanceId: string;
@@ -24,81 +24,80 @@ export const useOpenActivityTargetCellEditMode = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openActivityTargetCellEditMode = useRecoilCallback(
({ set }) =>
({
recordPickerInstanceId,
activityTargetObjectRecords,
}: OpenActivityTargetCellEditModeProps) => {
const objectMetadataItems = jotaiStore
.get(objectMetadataItemsState.atom)
.filter(
(objectMetadataItem) =>
objectMetadataItem.isSearchable &&
objectMetadataItem.isActive &&
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Task &&
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Note &&
objectMetadataItem.nameSingular !==
CoreObjectNameSingular.WorkspaceMember,
);
const openActivityTargetCellEditMode = useCallback(
({
recordPickerInstanceId,
activityTargetObjectRecords,
}: OpenActivityTargetCellEditModeProps) => {
const objectMetadataItems = jotaiStore
.get(objectMetadataItemsState.atom)
.filter(
(objectMetadataItem) =>
objectMetadataItem.isSearchable &&
objectMetadataItem.isActive &&
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Task &&
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Note &&
objectMetadataItem.nameSingular !==
CoreObjectNameSingular.WorkspaceMember,
);
set(
multipleRecordPickerPickableMorphItemsComponentState.atomFamily({
jotaiStore.set(
multipleRecordPickerPickableMorphItemsComponentState.atomFamily({
instanceId: recordPickerInstanceId,
}),
activityTargetObjectRecords.map((activityTargetObjectRecord) => ({
recordId: activityTargetObjectRecord.targetObject.id,
objectMetadataId:
activityTargetObjectRecord.targetObjectMetadataItem.id,
isSelected: true,
isMatchingSearchFilter: true,
})),
);
jotaiStore.set(
multipleRecordPickerSearchableObjectMetadataItemsComponentState.atomFamily(
{
instanceId: recordPickerInstanceId,
}),
activityTargetObjectRecords.map((activityTargetObjectRecord) => ({
},
),
objectMetadataItems,
);
jotaiStore.set(
multipleRecordPickerSearchFilterComponentState.atomFamily({
instanceId: recordPickerInstanceId,
}),
'',
);
openMultipleRecordPicker(recordPickerInstanceId);
multipleRecordPickerPerformSearch({
multipleRecordPickerInstanceId: recordPickerInstanceId,
forceSearchFilter: '',
forceSearchableObjectMetadataItems: objectMetadataItems,
forcePickableMorphItems: activityTargetObjectRecords.map(
(activityTargetObjectRecord) => ({
recordId: activityTargetObjectRecord.targetObject.id,
objectMetadataId:
activityTargetObjectRecord.targetObjectMetadataItem.id,
isSelected: true,
isMatchingSearchFilter: true,
})),
);
set(
multipleRecordPickerSearchableObjectMetadataItemsComponentState.atomFamily(
{
instanceId: recordPickerInstanceId,
},
),
objectMetadataItems,
);
set(
multipleRecordPickerSearchFilterComponentState.atomFamily({
instanceId: recordPickerInstanceId,
}),
'',
);
),
});
openMultipleRecordPicker(recordPickerInstanceId);
multipleRecordPickerPerformSearch({
multipleRecordPickerInstanceId: recordPickerInstanceId,
forceSearchFilter: '',
forceSearchableObjectMetadataItems: objectMetadataItems,
forcePickableMorphItems: activityTargetObjectRecords.map(
(activityTargetObjectRecord) => ({
recordId: activityTargetObjectRecord.targetObject.id,
objectMetadataId:
activityTargetObjectRecord.targetObjectMetadataItem.id,
isSelected: true,
isMatchingSearchFilter: true,
}),
),
});
pushFocusItemToFocusStack({
focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
});
},
pushFocusItemToFocusStack({
focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
});
},
[
multipleRecordPickerPerformSearch,
openMultipleRecordPicker,
@@ -10,12 +10,13 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { searchRecordStoreFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState';
import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useCallback } from 'react';
import { useStore } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
type UpdateActivityTargetFromCellProps = {
recordPickerInstanceId: string;
@@ -64,148 +65,148 @@ export const useUpdateActivityTargetFromCell = ({
? isTaskTargetMigrated
: isNoteTargetMigrated;
const setActivityFromStore = useSetRecoilState(
recordStoreFamilyState(activityId),
const store = useStore();
const setActivityFromStore = useSetFamilyRecoilStateV2(
recordStoreFamilyState,
activityId,
);
const updateActivityTargetFromCell = useRecoilCallback(
({ snapshot }) =>
async ({
morphItem,
activityTargetWithTargetRecords,
}: UpdateActivityTargetFromCellProps) => {
const targetObjectName =
activityObjectNameSingular === CoreObjectNameSingular.Task
? 'task'
: 'note';
const updateActivityTargetFromCell = useCallback(
async ({
morphItem,
activityTargetWithTargetRecords,
}: UpdateActivityTargetFromCellProps) => {
const targetObjectName =
activityObjectNameSingular === CoreObjectNameSingular.Task
? 'task'
: 'note';
const objectMetadataItems = jotaiStore.get(
objectMetadataItemsState.atom,
const objectMetadataItems = store.get(objectMetadataItemsState.atom);
const pickedObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === morphItem.objectMetadataId,
);
if (!isDefined(pickedObjectMetadataItem)) {
throw new Error('Could not find object metadata item');
}
const targetFieldName = getActivityTargetFieldNameForObject({
activityObjectNameSingular,
targetObjectMetadataId: morphItem.objectMetadataId,
objectMetadataItems,
isMorphRelation,
});
if (!isDefined(targetFieldName)) {
throw new Error(
`Could not find field on activity target for object ${pickedObjectMetadataItem.nameSingular}`,
);
}
const pickedObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === morphItem.objectMetadataId,
);
let activityTargetsAfterUpdate: (TaskTarget | NoteTarget)[] = [];
if (!isDefined(pickedObjectMetadataItem)) {
throw new Error('Could not find object metadata item');
}
const existingActivityTarget = activityTargetWithTargetRecords.find(
(activityTarget) =>
activityTarget.targetObject.id === morphItem.recordId,
);
const targetFieldName = getActivityTargetFieldNameForObject({
activityObjectNameSingular,
targetObjectMetadataId: morphItem.objectMetadataId,
objectMetadataItems,
isMorphRelation,
});
if (isDefined(existingActivityTarget)) {
activityTargetsAfterUpdate = activityTargetWithTargetRecords
.map((activityTarget) => {
if (
activityTarget.targetObject.id === morphItem.recordId &&
!morphItem.isSelected
) {
return undefined;
}
if (!isDefined(targetFieldName)) {
throw new Error(
`Could not find field on activity target for object ${pickedObjectMetadataItem.nameSingular}`,
return activityTarget.activityTarget;
})
.filter(isDefined);
if (!morphItem.isSelected) {
await deleteOneActivityTarget(
existingActivityTarget.activityTarget.id,
);
}
let activityTargetsAfterUpdate: (TaskTarget | NoteTarget)[] = [];
const existingActivityTarget = activityTargetWithTargetRecords.find(
(activityTarget) =>
activityTarget.targetObject.id === morphItem.recordId,
} else {
const searchRecord = store.get(
searchRecordStoreFamilyState.atomFamily(morphItem.recordId),
);
if (isDefined(existingActivityTarget)) {
activityTargetsAfterUpdate = activityTargetWithTargetRecords
.map((activityTarget) => {
if (
activityTarget.targetObject.id === morphItem.recordId &&
!morphItem.isSelected
) {
return undefined;
}
return activityTarget.activityTarget;
})
.filter(isDefined);
if (!morphItem.isSelected) {
await deleteOneActivityTarget(
existingActivityTarget.activityTarget.id,
);
}
} else {
const searchRecord = snapshot
.getLoadable(searchRecordStoreFamilyState(morphItem.recordId))
.getValue();
if (!isDefined(searchRecord) || !isDefined(searchRecord?.record)) {
return;
}
if (!morphItem.isSelected) {
return;
}
const targetRecord = searchRecord.record;
const activityTarget: NoteTarget | TaskTarget =
activityObjectNameSingular === CoreObjectNameSingular.Task
? {
id: v4(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
__typename: 'TaskTarget',
taskId: activityId,
task: {
id: activityId,
__typename: 'Task',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
[targetFieldName]: targetRecord,
}
: {
id: v4(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
__typename: 'NoteTarget',
noteId: activityId,
note: {
id: activityId,
__typename: 'Note',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
[targetFieldName]: targetRecord,
};
activityTargetsAfterUpdate = [
...activityTargetWithTargetRecords.map((activityTarget) => {
return activityTarget.activityTarget;
}),
activityTarget,
];
const activityTargetInput: Partial<NoteTarget | TaskTarget> = {
...activityTarget,
[targetObjectName]: undefined,
[targetFieldName]: undefined,
[`${targetFieldName}Id`]: morphItem.recordId,
};
await createOneActivityTarget(activityTargetInput);
if (!isDefined(searchRecord) || !isDefined(searchRecord?.record)) {
return;
}
setActivityFromStore((currentActivity) => {
if (!isDefined(currentActivity)) {
return null;
}
if (!morphItem.isSelected) {
return;
}
return {
...currentActivity,
[`${targetObjectName}Targets`]: activityTargetsAfterUpdate,
};
});
},
const targetRecord = searchRecord.record;
const activityTarget: NoteTarget | TaskTarget =
activityObjectNameSingular === CoreObjectNameSingular.Task
? {
id: v4(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
__typename: 'TaskTarget',
taskId: activityId,
task: {
id: activityId,
__typename: 'Task',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
[targetFieldName]: targetRecord,
}
: {
id: v4(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
__typename: 'NoteTarget',
noteId: activityId,
note: {
id: activityId,
__typename: 'Note',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
[targetFieldName]: targetRecord,
};
activityTargetsAfterUpdate = [
...activityTargetWithTargetRecords.map((activityTarget) => {
return activityTarget.activityTarget;
}),
activityTarget,
];
const activityTargetInput: Partial<NoteTarget | TaskTarget> = {
...activityTarget,
[targetObjectName]: undefined,
[targetFieldName]: undefined,
[`${targetFieldName}Id`]: morphItem.recordId,
};
await createOneActivityTarget(activityTargetInput);
}
setActivityFromStore((currentActivity) => {
if (!isDefined(currentActivity)) {
return null;
}
return {
...currentActivity,
[`${targetObjectName}Targets`]: activityTargetsAfterUpdate,
};
});
},
[
store,
activityId,
activityObjectNameSingular,
createOneActivityTarget,
@@ -1,6 +1,6 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2';
export const activityBodyFamilyState = createFamilyState<
export const activityBodyFamilyState = createFamilyStateV2<
string,
{ activityId: string }
>({
@@ -1,7 +1,7 @@
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { createState } from '@/ui/utilities/state/utils/createState';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const activityTargetableEntityArrayState = createState<
export const activityTargetableEntityArrayState = createStateV2<
ActivityTargetableObject[]
>({
key: 'activities/targetable-entity-array',
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const canCreateActivityState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const canCreateActivityState = createStateV2<boolean>({
key: 'canCreateActivityState',
defaultValue: false,
});
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const isUpsertingActivityInDBState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isUpsertingActivityInDBState = createStateV2<boolean>({
key: 'isUpsertingActivityInDBState',
defaultValue: false,
});
@@ -1,7 +1,6 @@
import styled from '@emotion/styled';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
@@ -14,7 +13,8 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons';
@@ -99,11 +99,14 @@ export const EventRow = ({
allowRequestsToTwentyIconsState,
);
const { localeCatalog } = useRecoilValue(dateLocaleState);
const { localeCatalog } = useRecoilValueV2(dateLocaleStateV2);
const { recordId } = useContext(TimelineActivityContext);
const recordFromStore = useRecoilValue(recordStoreFamilyState(recordId));
const recordFromStore = useFamilyRecoilValueV2(
recordStoreFamilyState,
recordId,
);
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
event.createdAt,
@@ -1,9 +1,9 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2';
import { isDefined } from 'twenty-shared/utils';
export const EventFieldDiffValueEffect = ({
@@ -17,8 +17,9 @@ export const EventFieldDiffValueEffect = ({
mainObjectMetadataItem: ObjectMetadataItem;
fieldMetadataItem: FieldMetadataItem;
}) => {
const setEntity = useSetRecoilState(
recordStoreFamilyState(diffArtificialRecordStoreId),
const setEntity = useSetFamilyRecoilStateV2(
recordStoreFamilyState,
diffArtificialRecordStoreId,
);
useEffect(() => {
@@ -1,5 +1,4 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { AgentChatFilePreview } from '@/ai/components/internal/AgentChatFilePreview';
import { AgentMessageRole } from '@/ai/constants/AgentMessageRole';
@@ -7,9 +6,10 @@ import { AgentMessageRole } from '@/ai/constants/AgentMessageRole';
import { AIChatAssistantMessageRenderer } from '@/ai/components/AIChatAssistantMessageRenderer';
import { AIChatErrorRenderer } from '@/ai/components/AIChatErrorRenderer';
import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { type ExtendedUIMessage } from 'twenty-shared/ai';
import { isDefined } from 'twenty-shared/utils';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
const StyledMessageBubble = styled.div<{ isUser?: boolean }>`
@@ -141,7 +141,7 @@ export const AIChatMessage = ({
isLastMessageStreaming: boolean;
error?: Error | null;
}) => {
const { localeCatalog } = useRecoilValue(dateLocaleState);
const { localeCatalog } = useRecoilValueV2(dateLocaleStateV2);
const isUser = message.role === AgentMessageRole.USER;
const showError =
@@ -1,6 +1,6 @@
import { agentChatUsageStateV2 } from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { currentAIChatThreadTitleStateV2 } from '@/ai/states/currentAIChatThreadTitleStateV2';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
@@ -83,7 +83,7 @@ export const AIChatThreadGroup = ({
currentAIChatThreadStateV2,
);
const setCurrentAIChatThreadTitle = useSetRecoilStateV2(
currentAIChatThreadTitleStateV2,
currentAIChatThreadTitleState,
);
const setAgentChatUsage = useSetRecoilStateV2(agentChatUsageStateV2);
const { openAskAIPage } = useOpenAskAIPageInCommandMenu();
@@ -3,7 +3,7 @@ import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFile
import { agentChatUploadedFilesStateV2 } from '@/ai/states/agentChatUploadedFilesStateV2';
import { agentChatUsageStateV2 } from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { currentAIChatThreadTitleStateV2 } from '@/ai/states/currentAIChatThreadTitleStateV2';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { agentChatInputStateV2 } from '@/ai/states/agentChatInputStateV2';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
@@ -26,7 +26,7 @@ export const useAgentChat = (uiMessages: ExtendedUIMessage[]) => {
const { getBrowsingContext } = useGetBrowsingContext();
const setCurrentAIChatThreadTitle = useSetRecoilStateV2(
currentAIChatThreadTitleStateV2,
currentAIChatThreadTitleState,
);
const agentChatSelectedFiles = useRecoilValueV2(
@@ -4,7 +4,7 @@ import {
type AgentChatUsageState,
} from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { currentAIChatThreadTitleStateV2 } from '@/ai/states/currentAIChatThreadTitleStateV2';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { isCreatingChatThreadStateV2 } from '@/ai/states/isCreatingChatThreadStateV2';
import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
@@ -46,7 +46,7 @@ export const useAgentChatData = () => {
);
const setAgentChatUsage = useSetRecoilStateV2(agentChatUsageStateV2);
const setCurrentAIChatThreadTitle = useSetRecoilStateV2(
currentAIChatThreadTitleStateV2,
currentAIChatThreadTitleState,
);
const [isCreatingChatThread, setIsCreatingChatThread] = useRecoilStateV2(
isCreatingChatThreadStateV2,
@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { type BrowsingContext } from '@/ai/types/BrowsingContext';
@@ -11,7 +11,7 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { recordStoreFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFamilySelectorV2';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useStore } from 'jotai';
@@ -20,139 +20,129 @@ import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreV
export const useGetBrowsingContext = () => {
const store = useStore();
const getBrowsingContext = useRecoilCallback(
({ snapshot }) =>
(): BrowsingContext | null => {
const instanceId = MAIN_CONTEXT_STORE_INSTANCE_ID;
const getBrowsingContext = useCallback((): BrowsingContext | null => {
const instanceId = MAIN_CONTEXT_STORE_INSTANCE_ID;
const viewType = snapshot
.getLoadable(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId,
}),
)
.getValue();
const viewType = store.get(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId,
}),
);
const objectMetadataItemId = snapshot
.getLoadable(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId,
}),
)
.getValue();
const objectMetadataItemId = store.get(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId,
}),
);
const objectMetadataItems = store.get(objectMetadataItemsState.atom);
const objectMetadataItems = store.get(objectMetadataItemsState.atom);
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === objectMetadataItemId,
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === objectMetadataItemId,
);
if (!objectMetadataItem) {
return null;
}
if (viewType === ContextStoreViewType.ShowPage) {
const targetedRecordsRule = store.get(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId,
}),
);
if (
targetedRecordsRule.mode !== 'selection' ||
targetedRecordsRule.selectedRecordIds.length !== 1
) {
return null;
}
const recordContext: BrowsingContext = {
type: 'recordPage',
objectNameSingular: objectMetadataItem.nameSingular,
recordId: targetedRecordsRule.selectedRecordIds[0],
};
const pageLayoutId = store.get(
recordStoreFamilySelectorV2.selectorFamily({
recordId: targetedRecordsRule.selectedRecordIds[0],
fieldName: 'pageLayoutId',
}),
) as string | null | undefined;
if (isDefined(pageLayoutId)) {
const tabListInstanceId =
getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const activeTabId = store.get(
activeTabIdComponentState.atomFamily({
instanceId: tabListInstanceId,
}),
);
if (!objectMetadataItem) {
return null;
}
return {
...recordContext,
pageLayoutId,
activeTabId,
};
}
if (viewType === ContextStoreViewType.ShowPage) {
const targetedRecordsRule = snapshot
.getLoadable(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId,
}),
)
.getValue();
return recordContext;
}
if (
targetedRecordsRule.mode !== 'selection' ||
targetedRecordsRule.selectedRecordIds.length !== 1
) {
return null;
}
if (
viewType === ContextStoreViewType.Table ||
viewType === ContextStoreViewType.Kanban
) {
const currentViewId = store.get(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId,
}),
);
const recordContext: BrowsingContext = {
type: 'recordPage',
objectNameSingular: objectMetadataItem.nameSingular,
recordId: targetedRecordsRule.selectedRecordIds[0],
};
const pageLayoutId = snapshot
.getLoadable(
recordStoreFamilySelector<string | null | undefined>({
recordId: targetedRecordsRule.selectedRecordIds[0],
fieldName: 'pageLayoutId',
}),
)
.getValue();
if (isDefined(pageLayoutId)) {
const tabListInstanceId =
getTabListInstanceIdFromPageLayoutId(pageLayoutId);
const activeTabId = store.get(
activeTabIdComponentState.atomFamily({
instanceId: tabListInstanceId,
}),
);
return {
...recordContext,
pageLayoutId,
activeTabId,
};
}
return recordContext;
}
if (
viewType === ContextStoreViewType.Table ||
viewType === ContextStoreViewType.Kanban
) {
const currentViewId = snapshot
.getLoadable(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId,
}),
)
.getValue();
const currentView = store.get(
coreViewFromViewIdFamilySelector.selectorFamily({
viewId: currentViewId ?? '',
}),
);
if (!currentView) {
return null;
}
const contextStoreFilters = snapshot
.getLoadable(
contextStoreFiltersComponentState.atomFamily({
instanceId,
}),
)
.getValue();
const filterDescriptions = contextStoreFilters.map((filter) => {
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === filter.fieldMetadataId,
);
const fieldLabel = fieldMetadataItem?.label ?? t`Unknown field`;
return `${fieldLabel} ${filter.operand} "${filter.displayValue}"`;
});
return {
type: 'listView',
objectNameSingular: objectMetadataItem.nameSingular,
viewId: currentView.id,
viewName: currentView.name,
filterDescriptions,
};
}
const currentView = store.get(
coreViewFromViewIdFamilySelector.selectorFamily({
viewId: currentViewId ?? '',
}),
);
if (!currentView) {
return null;
},
[store],
);
}
const contextStoreFilters = store.get(
contextStoreFiltersComponentState.atomFamily({
instanceId,
}),
);
const filterDescriptions = contextStoreFilters.map(
(filter: {
fieldMetadataId: string;
operand: string;
displayValue: string;
}) => {
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === filter.fieldMetadataId,
);
const fieldLabel = fieldMetadataItem?.label ?? t`Unknown field`;
return `${fieldLabel} ${filter.operand} "${filter.displayValue}"`;
},
);
return {
type: 'listView',
objectNameSingular: objectMetadataItem.nameSingular,
viewId: currentView.id,
viewName: currentView.name,
filterDescriptions,
};
}
return null;
}, [store]);
return { getBrowsingContext };
};
@@ -1,6 +1,6 @@
import { agentChatUsageStateV2 } from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { currentAIChatThreadTitleStateV2 } from '@/ai/states/currentAIChatThreadTitleStateV2';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
@@ -12,7 +12,7 @@ export const useCreateNewAIChatThread = () => {
);
const setAgentChatUsage = useSetRecoilStateV2(agentChatUsageStateV2);
const setCurrentAIChatThreadTitle = useSetRecoilStateV2(
currentAIChatThreadTitleStateV2,
currentAIChatThreadTitleState,
);
const { openAskAIPage } = useOpenAskAIPageInCommandMenu();
@@ -1,11 +1,11 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createComponentStateV2 } from '@/ui/utilities/state/jotai/utils/createComponentStateV2';
import { type AgentMessage } from '~/generated-metadata/graphql';
export const AgentChatMessagesComponentInstanceContext =
createComponentInstanceContext();
export const agentChatMessagesComponentState = createComponentState<
export const agentChatMessagesComponentState = createComponentStateV2<
AgentMessage[]
>({
key: 'agentChatMessagesComponentState',
@@ -1,6 +1,6 @@
import { atom } from 'recoil';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const currentAIChatThreadTitleState = atom<string | null>({
export const currentAIChatThreadTitleState = createStateV2<string | null>({
key: 'ai/currentAIChatThreadTitleState',
default: null,
defaultValue: null,
});
@@ -1,6 +0,0 @@
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const currentAIChatThreadTitleStateV2 = createStateV2<string | null>({
key: 'ai/currentAIChatThreadTitleStateV2',
defaultValue: null,
});
@@ -7,14 +7,13 @@ import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirec
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import {
matchPath,
useLocation,
useNavigate,
useParams,
} from 'react-router-dom';
import { useRecoilCallback } from 'recoil';
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
@@ -35,7 +34,8 @@ import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/u
import { PageFocusId } from '@/types/PageFocusId';
import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { AppBasePath, AppPath, CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { AnalyticsType } from '~/generated-metadata/graphql';
@@ -65,12 +65,12 @@ export const PageChangeEffect = () => {
const objectNamePlural =
useParams().objectNamePlural ?? CoreObjectNamePlural.Person;
const contextStoreCurrentViewId = useRecoilComponentValue(
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const contextStoreCurrentViewType = useRecoilComponentValue(
const contextStoreCurrentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
@@ -98,19 +98,13 @@ export const PageChangeEffect = () => {
const { closeCommandMenu } = useCommandMenu();
const closeCommandMenuUnlessOnEditPage = useRecoilCallback(
({ snapshot }) =>
() => {
const currentPage = snapshot
.getLoadable(commandMenuPageState)
.getValue();
if (currentPage === CommandMenuPages.NavigationMenuItemEdit) {
return;
}
closeCommandMenu();
},
[closeCommandMenu],
);
const closeCommandMenuUnlessOnEditPage = useCallback(() => {
const currentPage = jotaiStore.get(commandMenuPageState.atom);
if (currentPage === CommandMenuPages.NavigationMenuItemEdit) {
return;
}
closeCommandMenu();
}, [closeCommandMenu]);
const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem();
@@ -1,8 +1,7 @@
import { useRecoilCallback } from 'recoil';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { hasInitializedFieldsWidgetGroupsDraftComponentState } from '@/page-layout/states/hasInitializedFieldsWidgetGroupsDraftComponentState';
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
@@ -11,84 +10,76 @@ import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/page
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useExecuteTasksOnAnyLocationChange = () => {
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
const resetPageLayoutEditMode = useRecoilCallback(
({ set, snapshot }) =>
() => {
const pageLayoutId = snapshot
.getLoadable(currentPageLayoutIdState)
.getValue();
const resetPageLayoutEditMode = useCallback(() => {
const pageLayoutId = jotaiStore.get(currentPageLayoutIdState.atom);
if (isDefined(pageLayoutId)) {
const pageLayoutPersisted = snapshot
.getLoadable(
pageLayoutPersistedComponentState.atomFamily({
instanceId: pageLayoutId,
}),
)
.getValue();
if (isDefined(pageLayoutId)) {
const pageLayoutPersisted = jotaiStore.get(
pageLayoutPersistedComponentState.atomFamily({
instanceId: pageLayoutId,
}),
);
if (isDefined(pageLayoutPersisted)) {
set(
pageLayoutDraftComponentState.atomFamily({
instanceId: pageLayoutId,
}),
{
id: pageLayoutPersisted.id,
name: pageLayoutPersisted.name,
type: pageLayoutPersisted.type,
objectMetadataId: pageLayoutPersisted.objectMetadataId,
tabs: pageLayoutPersisted.tabs,
},
);
const tabLayouts =
convertPageLayoutToTabLayouts(pageLayoutPersisted);
set(
pageLayoutCurrentLayoutsComponentState.atomFamily({
instanceId: pageLayoutId,
}),
tabLayouts,
);
}
set(
isPageLayoutInEditModeComponentState.atomFamily({
instanceId: pageLayoutId,
}),
false,
);
set(
pageLayoutIsInitializedComponentState.atomFamily({
instanceId: pageLayoutId,
}),
false,
);
set(
hasInitializedFieldsWidgetGroupsDraftComponentState.atomFamily({
instanceId: pageLayoutId,
}),
{},
);
set(currentPageLayoutIdState, null);
}
set(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
if (isDefined(pageLayoutPersisted)) {
jotaiStore.set(
pageLayoutDraftComponentState.atomFamily({
instanceId: pageLayoutId,
}),
false,
{
id: pageLayoutPersisted.id,
name: pageLayoutPersisted.name,
type: pageLayoutPersisted.type,
objectMetadataId: pageLayoutPersisted.objectMetadataId,
tabs: pageLayoutPersisted.tabs,
},
);
},
[],
);
const tabLayouts = convertPageLayoutToTabLayouts(pageLayoutPersisted);
jotaiStore.set(
pageLayoutCurrentLayoutsComponentState.atomFamily({
instanceId: pageLayoutId,
}),
tabLayouts,
);
}
jotaiStore.set(
isPageLayoutInEditModeComponentState.atomFamily({
instanceId: pageLayoutId,
}),
false,
);
jotaiStore.set(
pageLayoutIsInitializedComponentState.atomFamily({
instanceId: pageLayoutId,
}),
false,
);
jotaiStore.set(
hasInitializedFieldsWidgetGroupsDraftComponentState.atomFamily({
instanceId: pageLayoutId,
}),
{},
);
jotaiStore.set(currentPageLayoutIdState.atom, null);
}
jotaiStore.set(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
false,
);
}, []);
/**
* Be careful to put idempotent tasks here.
@@ -1,10 +1,6 @@
import { ApolloError, useApolloClient } from '@apollo/client';
import { useCallback } from 'react';
import {
snapshot_UNSTABLE,
useGotoRecoilSnapshot,
useRecoilCallback,
} from 'recoil';
import { snapshot_UNSTABLE, useGotoRecoilSnapshot } from 'recoil';
import { AppPath } from 'twenty-shared/types';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
@@ -59,7 +55,6 @@ import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { sseClientState } from '@/sse-db-event/states/sseClientState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
import { i18n } from '@lingui/core';
@@ -123,81 +118,69 @@ export const useAuth = () => {
const navigate = useNavigate();
const clearSession = useRecoilCallback(
({ snapshot }) =>
async () => {
const sseClient = getSnapshotValue(snapshot, sseClientState);
const clearSession = useCallback(async () => {
const sseClient = jotaiStore.get(sseClientState.atom);
sseClient?.dispose();
sseClient?.dispose();
const emptySnapshot = snapshot_UNSTABLE();
const emptySnapshot = snapshot_UNSTABLE();
const authProvidersValue = jotaiStore.get(
workspaceAuthProvidersState.atom,
);
const domainConfigurationValue = jotaiStore.get(
domainConfigurationState.atom,
);
const workspacePublicDataValue = jotaiStore.get(
workspacePublicDataState.atom,
);
const lastAuthenticatedMethod = jotaiStore.get(
lastAuthenticatedMethodState.atom,
);
const isCaptchaScriptLoadedValue = jotaiStore.get(
isCaptchaScriptLoadedState.atom,
);
const authProvidersValue = jotaiStore.get(workspaceAuthProvidersState.atom);
const domainConfigurationValue = jotaiStore.get(
domainConfigurationState.atom,
);
const workspacePublicDataValue = jotaiStore.get(
workspacePublicDataState.atom,
);
const lastAuthenticatedMethod = jotaiStore.get(
lastAuthenticatedMethodState.atom,
);
const isCaptchaScriptLoadedValue = jotaiStore.get(
isCaptchaScriptLoadedState.atom,
);
const initialSnapshot = emptySnapshot.map(() => {
return undefined;
});
const initialSnapshot = emptySnapshot.map(() => {
return undefined;
});
sessionStorage.clear();
localStorage.clear();
sessionStorage.clear();
localStorage.clear();
goToRecoilSnapshot(initialSnapshot);
goToRecoilSnapshot(initialSnapshot);
jotaiStore.set(workspaceAuthProvidersState.atom, authProvidersValue);
jotaiStore.set(workspacePublicDataState.atom, workspacePublicDataValue);
jotaiStore.set(domainConfigurationState.atom, domainConfigurationValue);
jotaiStore.set(
isCaptchaScriptLoadedState.atom,
isCaptchaScriptLoadedValue,
);
jotaiStore.set(
lastAuthenticatedMethodState.atom,
lastAuthenticatedMethod,
);
jotaiStore.set(workspaceAuthProvidersState.atom, authProvidersValue);
jotaiStore.set(workspacePublicDataState.atom, workspacePublicDataValue);
jotaiStore.set(domainConfigurationState.atom, domainConfigurationValue);
jotaiStore.set(isCaptchaScriptLoadedState.atom, isCaptchaScriptLoadedValue);
jotaiStore.set(lastAuthenticatedMethodState.atom, lastAuthenticatedMethod);
// Reset user-data Jotai states that were migrated from Recoil
// (Recoil snapshot reset no longer handles these since they are Jotai V2)
jotaiStore.set(tokenPairState.atom, null);
jotaiStore.set(currentUserState.atom, null);
jotaiStore.set(currentWorkspaceState.atom, null);
jotaiStore.set(currentUserWorkspaceState.atom, null);
jotaiStore.set(currentWorkspaceMemberState.atom, null);
jotaiStore.set(currentWorkspaceMembersState.atom, []);
jotaiStore.set(availableWorkspacesState.atom, {
availableWorkspacesForSignIn: [],
availableWorkspacesForSignUp: [],
});
jotaiStore.set(loginTokenState.atom, null);
jotaiStore.set(signInUpStepState.atom, SignInUpStep.Init);
jotaiStore.set(coreViewsState.atom, []);
// Reset user-data Jotai states that were migrated from Recoil
// (Recoil snapshot reset no longer handles these since they are Jotai V2)
jotaiStore.set(tokenPairState.atom, null);
jotaiStore.set(currentUserState.atom, null);
jotaiStore.set(currentWorkspaceState.atom, null);
jotaiStore.set(currentUserWorkspaceState.atom, null);
jotaiStore.set(currentWorkspaceMemberState.atom, null);
jotaiStore.set(currentWorkspaceMembersState.atom, []);
jotaiStore.set(availableWorkspacesState.atom, {
availableWorkspacesForSignIn: [],
availableWorkspacesForSignUp: [],
});
jotaiStore.set(loginTokenState.atom, null);
jotaiStore.set(signInUpStepState.atom, SignInUpStep.Init);
jotaiStore.set(coreViewsState.atom, []);
await client.clearStore();
setLastAuthenticateWorkspaceDomain(null);
await resetToMockedMetadata();
navigate(AppPath.SignInUp);
},
[
goToRecoilSnapshot,
client,
setLastAuthenticateWorkspaceDomain,
resetToMockedMetadata,
navigate,
],
);
await client.clearStore();
setLastAuthenticateWorkspaceDomain(null);
await resetToMockedMetadata();
navigate(AppPath.SignInUp);
}, [
goToRecoilSnapshot,
client,
setLastAuthenticateWorkspaceDomain,
resetToMockedMetadata,
navigate,
]);
const handleSetAuthTokens = useCallback(
(tokens: AuthTokenPair) => {
@@ -2,8 +2,9 @@ import { type BLOCK_SCHEMA } from '@/blocknote-editor/blocks/Schema';
import { isSlashMenuOpenComponentState } from '@/blocknote-editor/states/isSlashMenuOpenComponentState';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilCallback } from 'recoil';
import { useRecoilComponentStateCallbackStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentStateCallbackStateV2';
import { useStore } from 'jotai';
import { useCallback } from 'react';
export type BlockEditorDropdownFocusEffectProps = {
editor: typeof BLOCK_SCHEMA.BlockNoteEditor;
@@ -12,7 +13,7 @@ export type BlockEditorDropdownFocusEffectProps = {
export const BlockEditorDropdownFocusEffect = ({
editor,
}: BlockEditorDropdownFocusEffectProps) => {
const isSlashMenuOpenState = useRecoilComponentCallbackState(
const isSlashMenuOpenState = useRecoilComponentStateCallbackStateV2(
isSlashMenuOpenComponentState,
);
@@ -22,41 +23,39 @@ export const BlockEditorDropdownFocusEffect = ({
const { goBackToPreviousDropdownFocusId } =
useGoBackToPreviousDropdownFocusId();
const updateCallBack = useRecoilCallback(
({ snapshot, set }) =>
(event: any) => {
// TODO: This triggers before the onClick event of the slash menu item, so the click outside of the editor dropdown is triggered and everything closes.
// This is due to useRecoilCallback being executed before the onClick event of the slash menu item.
const eventWantsToOpen = event.show === true;
const store = useStore();
const isAlreadyOpen = snapshot
.getLoadable(isSlashMenuOpenState)
.getValue();
const updateCallBack = useCallback(
(event: any) => {
const eventWantsToOpen = event.show === true;
const shouldOpen = eventWantsToOpen && !isAlreadyOpen;
const isAlreadyOpen = store.get(isSlashMenuOpenState);
if (shouldOpen) {
setActiveDropdownFocusIdAndMemorizePrevious('custom-slash-menu');
set(isSlashMenuOpenState, true);
return;
}
const shouldOpen = eventWantsToOpen && !isAlreadyOpen;
const eventWantsToClose = event.show === false;
if (shouldOpen) {
setActiveDropdownFocusIdAndMemorizePrevious('custom-slash-menu');
store.set(isSlashMenuOpenState, true);
return;
}
const isAlreadyClosed = !isAlreadyOpen;
const eventWantsToClose = event.show === false;
const shouldClose = eventWantsToClose && !isAlreadyClosed;
const isAlreadyClosed = !isAlreadyOpen;
if (shouldClose) {
goBackToPreviousDropdownFocusId();
set(isSlashMenuOpenState, false);
return;
}
},
const shouldClose = eventWantsToClose && !isAlreadyClosed;
if (shouldClose) {
goBackToPreviousDropdownFocusId();
store.set(isSlashMenuOpenState, false);
return;
}
},
[
isSlashMenuOpenState,
setActiveDropdownFocusIdAndMemorizePrevious,
goBackToPreviousDropdownFocusId,
store,
],
);
@@ -1,7 +1,7 @@
import { BlockEditorComponentInstanceContext } from '@/blocknote-editor/contexts/BlockEditorCompoponeInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createComponentStateV2 } from '@/ui/utilities/state/jotai/utils/createComponentStateV2';
export const isSlashMenuOpenComponentState = createComponentState<boolean>({
export const isSlashMenuOpenComponentState = createComponentStateV2<boolean>({
key: 'isSlashMenuOpenComponentState',
defaultValue: false,
componentInstanceContext: BlockEditorComponentInstanceContext,
@@ -8,7 +8,7 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useMountHeadlessFrontComponent } from '@/front-components/hooks/useMountHeadlessFrontComponent';
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 { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -94,17 +94,17 @@ export const useCommandMenuItemFrontComponentActions = () => {
useOpenFrontComponentInCommandMenu();
const mountHeadlessFrontComponent = useMountHeadlessFrontComponent();
const isPageInEditMode = useRecoilComponentValue(
const isPageInEditMode = useRecoilComponentValueV2(
contextStoreIsPageInEditModeComponentState,
);
const { actionMenuType } = useContext(ActionMenuContext);
const currentObjectMetadataItemId = useRecoilComponentValue(
const currentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
);
const targetedRecordsRule = useRecoilComponentValue(
const targetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
@@ -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,
);
}
},
});
@@ -1,13 +1,14 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
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 { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconDotsVertical } from 'twenty-ui/display';
@@ -26,19 +27,9 @@ const renderHooks = () => {
const { result } = renderHook(
() => {
const commandMenu = useCommandMenu();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
return {
commandMenu,
isCommandMenuOpened,
commandMenuNavigationStack,
commandMenuPage,
commandMenuPageInfo,
};
},
{
@@ -60,31 +51,31 @@ describe('useCommandMenu', () => {
result.current.commandMenu.openCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
act(() => {
result.current.commandMenu.closeCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
});
it('should toggle the command menu', () => {
const { result } = renderHooks();
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
act(() => {
result.current.commandMenu.toggleCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
act(() => {
result.current.commandMenu.toggleCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
});
it('should navigate command menu and reset navigation stack when resetNavigationStack is true', () => {
@@ -99,9 +90,15 @@ describe('useCommandMenu', () => {
});
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo.title).toBe('First Page');
expect(result.current.commandMenuNavigationStack).toHaveLength(1);
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom).title).toBe(
'First Page',
);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toHaveLength(
1,
);
act(() => {
result.current.commandMenu.navigateCommandMenu({
@@ -112,7 +109,9 @@ describe('useCommandMenu', () => {
});
});
expect(result.current.commandMenuNavigationStack).toHaveLength(2);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toHaveLength(
2,
);
act(() => {
result.current.commandMenu.navigateCommandMenu({
@@ -123,8 +122,14 @@ describe('useCommandMenu', () => {
});
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo.title).toBe('Reset Page');
expect(result.current.commandMenuNavigationStack).toHaveLength(1);
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom).title).toBe(
'Reset Page',
);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toHaveLength(
1,
);
});
});
@@ -1,21 +1,23 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
import { RecoilRoot } 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 { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
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 { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconList } from 'twenty-ui/display';
@@ -65,62 +67,13 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const viewableRecordId = useRecoilValueV2(viewableRecordIdState);
const commandMenuNavigationMorphItemsByPage = useRecoilValue(
commandMenuNavigationMorphItemsByPageState,
);
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 setHasUserSelectedCommand = useSetRecoilState(
hasUserSelectedCommandState,
);
const setIsCommandMenuClosing = useSetRecoilState(
isCommandMenuClosingState,
);
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
const setViewableRecordId = useSetRecoilStateV2(viewableRecordIdState);
return {
commandMenuCloseAnimationCompleteCleanup,
commandMenuPage,
commandMenuPageInfo,
isCommandMenuOpened,
commandMenuSearch,
commandMenuNavigationStack,
commandMenuNavigationMorphItemsByPage,
hasUserSelectedCommand,
isCommandMenuClosing,
viewableRecordId,
setCommandMenuPage,
setCommandMenuPageInfo,
setIsCommandMenuOpened,
setCommandMenuSearch,
setCommandMenuNavigationStack,
setHasUserSelectedCommand,
setIsCommandMenuClosing,
setViewableRecordId,
};
},
@@ -135,15 +88,15 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
const { result } = renderHooks();
act(() => {
result.current.setCommandMenuPage(CommandMenuPages.ViewRecord);
result.current.setCommandMenuPageInfo({
jotaiStore.set(commandMenuPageState.atom, CommandMenuPages.ViewRecord);
jotaiStore.set(commandMenuPageInfoState.atom, {
title: 'Test Record',
Icon: IconList,
instanceId: 'test-id',
});
result.current.setIsCommandMenuOpened(true);
result.current.setCommandMenuSearch('test search');
result.current.setCommandMenuNavigationStack([
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(commandMenuSearchState.atom, 'test search');
jotaiStore.set(commandMenuNavigationStackState.atom, [
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
@@ -151,20 +104,22 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
pageId: '1',
},
]);
result.current.setHasUserSelectedCommand(true);
result.current.setIsCommandMenuClosing(true);
jotaiStore.set(hasUserSelectedCommandState.atom, true);
jotaiStore.set(isCommandMenuClosingState.atom, true);
result.current.setViewableRecordId('record-123');
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.ViewRecord);
expect(result.current.commandMenuPageInfo).toEqual({
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.ViewRecord,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).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([
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
expect(jotaiStore.get(commandMenuSearchState.atom)).toBe('test search');
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
@@ -172,25 +127,27 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
pageId: '1',
},
]);
expect(result.current.hasUserSelectedCommand).toBe(true);
expect(result.current.isCommandMenuClosing).toBe(true);
expect(jotaiStore.get(hasUserSelectedCommandState.atom)).toBe(true);
expect(jotaiStore.get(isCommandMenuClosingState.atom)).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({
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).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.hasUserSelectedCommand).toBe(false);
expect(result.current.isCommandMenuClosing).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(commandMenuSearchState.atom)).toBe('');
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([]);
expect(jotaiStore.get(hasUserSelectedCommandState.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuClosingState.atom)).toBe(false);
expect(result.current.viewableRecordId).toBe(null);
});

Some files were not shown because too many files have changed in this diff Show More