Jotai 13 (#18178)
## 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:
@@ -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,
|
||||
);
|
||||
|
||||
+2
-2
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
+2
-2
@@ -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
-9
@@ -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,
|
||||
);
|
||||
|
||||
+16
-18
@@ -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)
|
||||
|
||||
+5
-4
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
+2
-3
@@ -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 (
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
|
||||
+2
-2
@@ -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(
|
||||
|
||||
+4
-4
@@ -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,
|
||||
);
|
||||
|
||||
+3
-3
@@ -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',
|
||||
);
|
||||
|
||||
+37
-25
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
+6
-2
@@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user