From cfdb3f57785f4cbaa5d4deef2f75be07ac692570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:53:22 +0100 Subject: [PATCH] 584 Refactor Tabs (#11008) Closes https://github.com/twentyhq/core-team-issues/issues/584 This PR: - Migrates the component state `activeTabIdComponentState` from the deprecated V1 version to V2. - Allows the active tab state to be preserved during navigation inside the side panel and reset when the side panel is closed. - Allows the active tab state to be preserved when we open a record in full page from the side panel https://github.com/user-attachments/assets/f2329d7a-ea15-4bd8-81dc-e98ce11edbd0 https://github.com/user-attachments/assets/474bffd5-29e0-40ba-97f4-fa5e9be34dc2 --- .../RecordShowRightDrawerOpenRecordButton.tsx | 54 ++++- .../tasks/components/TaskGroups.tsx | 6 +- ...ommandMenuCloseAnimationCompleteCleanup.ts | 34 ++- .../hooks/useCommandMenuHistory.ts | 29 +++ .../CommandMenuWorkflowRunViewStep.tsx | 26 ++- ...tingsAccountsCalendarChannelsContainer.tsx | 10 +- ...ttingsAccountsMessageChannelsContainer.tsx | 10 +- .../components/SettingsAdminContent.tsx | 2 +- .../components/SettingsAdminGeneral.tsx | 12 +- .../components/SettingsAdminTabContent.tsx | 8 +- .../constants/SettingsAdminGeneralTabsId.ts | 1 + ...ettingsServerlessFunctionCodeEditorTab.tsx | 8 +- .../components/ShowPageSubContainer.tsx | 31 ++- .../constants/ShowPageTabListComponentId.ts | 1 + .../utils/getShowPageTabListComponentId.ts | 12 + .../ui/layout/tab/components/TabList.tsx | 27 ++- .../TabListFromUrlOptionalEffect.tsx | 11 +- .../__stories__/Tablist.stories.tsx | 13 +- .../tab/hooks/__tests__/useTabList.test.tsx | 31 --- .../tab/hooks/internal/useTabListStates.ts | 20 -- .../modules/ui/layout/tab/hooks/useTabList.ts | 18 -- .../tab/states/activeTabIdComponentState.ts | 6 +- .../TabListComponentInstanceContext.ts | 3 + .../WorkflowRunDiagramCanvasEffect.tsx | 20 +- .../RightDrawerWorkflowEditStep.tsx | 23 -- .../RightDrawerWorkflowEditStepContent.tsx | 30 --- .../RightDrawerWorkflowRunViewStep.tsx | 102 --------- ...RightDrawerWorkflowRunViewStep.stories.tsx | 213 ------------------ .../workflow-steps/types/WorkflowRunTabId.tsx | 8 +- .../RightDrawerWorkflowSelectAction.tsx | 16 -- ...RightDrawerWorkflowSelectActionContent.tsx | 45 ---- .../WorkflowEditActionServerlessFunction.tsx | 28 ++- .../types/WorkflowServerlessFunctionTabId.ts | 6 + .../WorkflowVariablesDropdownFieldItems.tsx | 10 +- .../data-model/SettingsObjectDetailPage.tsx | 11 +- .../pages/settings/roles/SettingsRoleEdit.tsx | 11 +- .../SettingsServerlessFunctionDetail.tsx | 12 +- 37 files changed, 299 insertions(+), 609 deletions(-) create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/constants/SettingsAdminGeneralTabsId.ts create mode 100644 packages/twenty-front/src/modules/ui/layout/show-page/constants/ShowPageTabListComponentId.ts create mode 100644 packages/twenty-front/src/modules/ui/layout/show-page/utils/getShowPageTabListComponentId.ts delete mode 100644 packages/twenty-front/src/modules/ui/layout/tab/hooks/__tests__/useTabList.test.tsx delete mode 100644 packages/twenty-front/src/modules/ui/layout/tab/hooks/internal/useTabListStates.ts delete mode 100644 packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts create mode 100644 packages/twenty-front/src/modules/ui/layout/tab/states/contexts/TabListComponentInstanceContext.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStep.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/RightDrawerWorkflowRunViewStep.stories.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId.ts diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx index cd50e3ef2..367872737 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx @@ -1,15 +1,22 @@ import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; +import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { AppPath } from '@/types/AppPath'; +import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; +import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import styled from '@emotion/styled'; +import { useCallback } from 'react'; import { Link } from 'react-router-dom'; import { Button, IconBrowserMaximize, getOsControlSymbol } from 'twenty-ui'; import { useNavigateApp } from '~/hooks/useNavigateApp'; - const StyledLink = styled(Link)` text-decoration: none; `; @@ -25,17 +32,58 @@ export const RecordShowRightDrawerOpenRecordButton = ({ }: RecordShowRightDrawerOpenRecordButtonProps) => { const { closeCommandMenu } = useCommandMenu(); + const commandMenuPageComponentInstance = useComponentInstanceStateContext( + CommandMenuPageComponentInstanceContext, + ); + + const tabListComponentId = getShowPageTabListComponentId({ + pageId: commandMenuPageComponentInstance?.instanceId, + targetObjectId: record.id, + }); + + const activeTabIdInRightDrawer = useRecoilComponentValueV2( + activeTabIdComponentState, + tabListComponentId, + ); + + const tabListComponentIdInRecordPage = getShowPageTabListComponentId({ + targetObjectId: record.id, + }); + + const setActiveTabIdInRecordPage = useSetRecoilComponentStateV2( + activeTabIdComponentState, + tabListComponentIdInRecordPage, + ); + const to = getLinkToShowPage(objectNameSingular, record); const navigate = useNavigateApp(); - const handleOpenRecord = () => { + const handleOpenRecord = useCallback(() => { + const tabIdToOpen = + activeTabIdInRightDrawer === 'home' + ? objectNameSingular === CoreObjectNameSingular.Note || + objectNameSingular === CoreObjectNameSingular.Task + ? 'richText' + : 'timeline' + : activeTabIdInRightDrawer; + + setActiveTabIdInRecordPage(tabIdToOpen); + navigate(AppPath.RecordShowPage, { objectNameSingular, objectRecordId: record.id, }); + closeCommandMenu(); - }; + }, [ + activeTabIdInRightDrawer, + closeCommandMenu, + navigate, + objectNameSingular, + record.id, + setActiveTabIdInRecordPage, + ]); useScopedHotkeys( ['ctrl+Enter,meta+Enter'], diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx index 1d28d6a16..0c8f1ba70 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx @@ -12,13 +12,13 @@ import { import { SkeletonLoader } from '@/activities/components/SkeletonLoader'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; -import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId'; import { useTasks } from '@/activities/tasks/hooks/useTasks'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { Task } from '@/activities/types/Task'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import groupBy from 'lodash.groupby'; import { AddTaskButton } from './AddTaskButton'; import { TaskList } from './TaskList'; @@ -45,7 +45,7 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => { activityObjectNameSingular: CoreObjectNameSingular.Task, }); - const { activeTabId } = useTabList(TASKS_TAB_LIST_COMPONENT_ID); + const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState); const isLoading = (activeTabId !== 'done' && tasksLoading) || diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts index 2855d7cf2..c8ec230ba 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts @@ -16,7 +16,13 @@ import { viewableRecordIdState } from '@/object-record/record-right-drawer/state import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId'; +import { WorkflowRunTabId } from '@/workflow/workflow-steps/types/WorkflowRunTabId'; +import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId'; +import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId'; import { useRecoilCallback } from 'recoil'; export const useCommandMenuCloseAnimationCompleteCleanup = () => { @@ -29,7 +35,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => { const { closeDropdown } = useDropdownV2(); const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback( - ({ set }) => + ({ snapshot, set }) => () => { closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID); @@ -54,6 +60,32 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => { emitRightDrawerCloseEvent(); set(isCommandMenuClosingState, false); + set( + activeTabIdComponentState.atomFamily({ + instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, + }), + WorkflowRunTabId.NODE, + ); + set( + activeTabIdComponentState.atomFamily({ + instanceId: WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID, + }), + WorkflowServerlessFunctionTabId.CODE, + ); + + for (const [pageId, morphItem] of snapshot + .getLoadable(commandMenuNavigationMorphItemByPageState) + .getValue()) { + set( + activeTabIdComponentState.atomFamily({ + instanceId: getShowPageTabListComponentId({ + pageId, + targetObjectId: morphItem.recordId, + }), + }), + null, + ); + } }, [ closeDropdown, diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts index fd2f7da59..1b61d8a48 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHistory.ts @@ -6,6 +6,8 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState'; +import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { isDefined } from 'twenty-shared'; export const useCommandMenuHistory = () => { @@ -47,6 +49,19 @@ export const useCommandMenuHistory = () => { const newMorphItems = new Map(currentMorphItems); newMorphItems.delete(removedItem.pageId); set(commandMenuNavigationMorphItemByPageState, newMorphItems); + + const morphItem = currentMorphItems.get(removedItem.pageId); + if (isDefined(morphItem)) { + set( + activeTabIdComponentState.atomFamily({ + instanceId: getShowPageTabListComponentId({ + pageId: removedItem.pageId, + targetObjectId: morphItem.recordId, + }), + }), + null, + ); + } } } @@ -84,6 +99,20 @@ export const useCommandMenuHistory = () => { .getLoadable(commandMenuNavigationMorphItemByPageState) .getValue(); + for (const [pageId, morphItem] of currentMorphItems.entries()) { + if (!newNavigationStack.some((item) => item.pageId === pageId)) { + set( + activeTabIdComponentState.atomFamily({ + instanceId: getShowPageTabListComponentId({ + pageId, + targetObjectId: morphItem.recordId, + }), + }), + null, + ); + } + } + const newMorphItems = new Map( Array.from(currentMorphItems.entries()).filter(([pageId]) => newNavigationStack.some((item) => item.pageId === pageId), diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx index 9a47c3062..e0139ef12 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx @@ -1,5 +1,6 @@ import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; @@ -9,6 +10,10 @@ import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail'; import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId'; +import { + WorkflowRunTabId, + WorkflowRunTabIdType, +} from '@/workflow/workflow-steps/types/WorkflowRunTabId'; import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus'; import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import styled from '@emotion/styled'; @@ -20,7 +25,7 @@ const StyledTabList = styled(TabList)` padding-left: ${({ theme }) => theme.spacing(2)}; `; -type TabId = 'node' | 'input' | 'output'; +type TabId = WorkflowRunTabIdType; export const CommandMenuWorkflowRunViewStep = () => { const flow = useFlowOrThrow(); @@ -29,7 +34,8 @@ export const CommandMenuWorkflowRunViewStep = () => { const workflowRun = useWorkflowRun({ workflowRunId }); - const { activeTabId } = useTabList( + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, ); @@ -46,15 +52,15 @@ export const CommandMenuWorkflowRunViewStep = () => { stepExecutionStatus === 'not-executed'; const tabs: SingleTabProps[] = [ - { id: 'node', title: 'Node', Icon: IconStepInto }, + { id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto }, { - id: 'input', + id: WorkflowRunTabId.INPUT, title: 'Input', Icon: IconLogin2, disabled: areInputAndOutputTabsDisabled, }, { - id: 'output', + id: WorkflowRunTabId.OUTPUT, title: 'Output', Icon: IconLogout, disabled: areInputAndOutputTabsDisabled, @@ -70,12 +76,12 @@ export const CommandMenuWorkflowRunViewStep = () => { value={{ workflowVersionId: workflowRun.workflowVersionId }} > - {activeTabId === 'node' ? ( + {activeTabId === WorkflowRunTabId.NODE ? ( { /> ) : null} - {activeTabId === 'input' ? ( + {activeTabId === WorkflowRunTabId.INPUT ? ( ) : null} - {activeTabId === 'output' ? ( + {activeTabId === WorkflowRunTabId.OUTPUT ? ( { - const { activeTabId } = useTabList( + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID, ); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); @@ -63,10 +65,10 @@ export const SettingsAccountsCalendarChannelsContainer = () => { {tabs.length > 1 && ( )} diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx index a72e32d19..276b2f43f 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx @@ -10,7 +10,8 @@ import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/compo import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId'; import { TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import React from 'react'; const StyledMessageContainer = styled.div` @@ -18,7 +19,8 @@ const StyledMessageContainer = styled.div` `; export const SettingsAccountsMessageChannelsContainer = () => { - const { activeTabId } = useTabList( + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID, ); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); @@ -62,10 +64,10 @@ export const SettingsAccountsMessageChannelsContainer = () => { {tabs.length > 1 && ( )} diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx index 1417815cb..05dc3f111 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx @@ -37,8 +37,8 @@ export const SettingsAdminContent = () => { <> diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx index 3b6f98ef9..9942e2c55 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx @@ -1,12 +1,10 @@ import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState'; import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent'; -import { SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminUserLookupWorkspaceTabsId'; import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { TextInput } from '@/ui/input/components/TextInput'; import { TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; @@ -29,6 +27,9 @@ import { useUserLookupAdminPanelMutation } from '~/generated/graphql'; import { currentUserState } from '@/auth/states/currentUserState'; import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard'; import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer'; +import { SETTINGS_ADMIN_GENERAL_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminGeneralTabsId'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; const StyledContainer = styled.div` align-items: center; @@ -49,8 +50,9 @@ export const SettingsAdminGeneral = () => { const [userIdentifier, setUserIdentifier] = useState(''); const { enqueueSnackBar } = useSnackBar(); - const { activeTabId, setActiveTabId } = useTabList( - SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID, + const [activeTabId, setActiveTabId] = useRecoilComponentStateV2( + activeTabIdComponentState, + 'settings-admin-general', ); const [userLookupResult, setUserLookupResult] = useRecoilState( userLookupResultState, @@ -200,8 +202,8 @@ export const SettingsAdminGeneral = () => { diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx index 9fe68ceb4..934b702c9 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx @@ -3,10 +3,14 @@ import { SettingsAdminGeneral } from '@/settings/admin-panel/components/Settings import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs'; import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId'; import { SettingsAdminHealthStatus } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatus'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; export const SettingsAdminTabContent = () => { - const { activeTabId } = useTabList(SETTINGS_ADMIN_TABS_ID); + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, + SETTINGS_ADMIN_TABS_ID, + ); switch (activeTabId) { case SETTINGS_ADMIN_TABS.GENERAL: diff --git a/packages/twenty-front/src/modules/settings/admin-panel/constants/SettingsAdminGeneralTabsId.ts b/packages/twenty-front/src/modules/settings/admin-panel/constants/SettingsAdminGeneralTabsId.ts new file mode 100644 index 000000000..140ad057a --- /dev/null +++ b/packages/twenty-front/src/modules/settings/admin-panel/constants/SettingsAdminGeneralTabsId.ts @@ -0,0 +1 @@ +export const SETTINGS_ADMIN_GENERAL_TABS_ID = 'settings-admin-general-tabs-id'; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx index dd16897b2..a11ac4e18 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx @@ -6,8 +6,9 @@ import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/s import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope'; import { SettingsPath } from '@/types/SettingsPath'; import { TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import styled from '@emotion/styled'; import { Key } from 'ts-key-enum'; import { @@ -45,7 +46,8 @@ export const SettingsServerlessFunctionCodeEditorTab = ({ onChange: (filePath: string, value: string) => void; setIsCodeValid: (isCodeValid: boolean) => void; }) => { - const { activeTabId } = useTabList( + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID, ); const TestButton = ( @@ -81,12 +83,12 @@ export const SettingsServerlessFunctionCodeEditorTab = ({ const HeaderTabList = ( file.path !== '.env') .map((file) => { return { id: file.path, title: file.path.split('/').at(-1) || '' }; })} + componentInstanceId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID} /> ); diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx index cf1fa2965..f51ec738a 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx @@ -1,6 +1,7 @@ import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu'; import { RecordShowRightDrawerOpenRecordButton } from '@/action-menu/components/RecordShowRightDrawerOpenRecordButton'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; +import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { CardComponents } from '@/object-record/record-show/components/CardComponents'; import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; @@ -9,9 +10,13 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter'; import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer'; +import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; @@ -41,8 +46,6 @@ const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>` isInRightDrawer ? theme.spacing(16) : 0}; `; -export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list'; - type ShowPageSubContainerProps = { layout?: RecordLayout; tabs: SingleTabProps[]; @@ -52,7 +55,6 @@ type ShowPageSubContainerProps = { >; isInRightDrawer?: boolean; loading: boolean; - isNewRightDrawerItemLoading?: boolean; }; export const ShowPageSubContainer = ({ @@ -62,9 +64,18 @@ export const ShowPageSubContainer = ({ loading, isInRightDrawer = false, }: ShowPageSubContainerProps) => { - const tabListComponentId = `${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}-${targetableObject.id}`; + const commandMenuPageComponentInstance = useComponentInstanceStateContext( + CommandMenuPageComponentInstanceContext, + ); - const { activeTabId } = useTabList(tabListComponentId); + const tabListComponentId = getShowPageTabListComponentId({ + pageId: commandMenuPageComponentInstance?.instanceId, + targetObjectId: targetableObject.id, + }); + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, + tabListComponentId, + ); const isMobile = useIsMobile(); @@ -109,7 +120,9 @@ export const ShowPageSubContainer = ({ layout && !layout.hideSummaryAndFields && !isMobile && !isInRightDrawer; return ( - <> + {displaySummaryAndFields && ( {summaryCard} @@ -121,9 +134,9 @@ export const ShowPageSubContainer = ({ {(isMobile || isInRightDrawer) && summaryCard} @@ -142,6 +155,6 @@ export const ShowPageSubContainer = ({ /> )} - + ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/constants/ShowPageTabListComponentId.ts b/packages/twenty-front/src/modules/ui/layout/show-page/constants/ShowPageTabListComponentId.ts new file mode 100644 index 000000000..923eb6c75 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/show-page/constants/ShowPageTabListComponentId.ts @@ -0,0 +1 @@ +export const SHOW_PAGE_RIGHT_TAB_LIST = 'show-page-right-tab-list'; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/utils/getShowPageTabListComponentId.ts b/packages/twenty-front/src/modules/ui/layout/show-page/utils/getShowPageTabListComponentId.ts new file mode 100644 index 000000000..d76eec5a3 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/show-page/utils/getShowPageTabListComponentId.ts @@ -0,0 +1,12 @@ +import { SHOW_PAGE_RIGHT_TAB_LIST } from '@/ui/layout/show-page/constants/ShowPageTabListComponentId'; + +export const getShowPageTabListComponentId = ({ + pageId, + targetObjectId, +}: { + pageId?: string; + targetObjectId: string; +}): string => { + const id = pageId || targetObjectId; + return `${SHOW_PAGE_RIGHT_TAB_LIST}-${id}`; +}; diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx index 8378a0400..43351eedf 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx +++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx @@ -1,8 +1,9 @@ import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab/components/TabListFromUrlOptionalEffect'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; -import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext'; import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import styled from '@emotion/styled'; import * as React from 'react'; import { useEffect } from 'react'; @@ -21,12 +22,12 @@ export type SingleTabProps = { }; type TabListProps = { - tabListInstanceId: string; tabs: SingleTabProps[]; loading?: boolean; behaveAsLinks?: boolean; className?: string; isInRightDrawer?: boolean; + componentInstanceId: string; }; const StyledContainer = styled.div` @@ -44,15 +45,18 @@ const StyledOuterContainer = styled.div` export const TabList = ({ tabs, - tabListInstanceId, loading, behaveAsLinks = true, isInRightDrawer, className, + componentInstanceId, }: TabListProps) => { const visibleTabs = tabs.filter((tab) => !tab.hide); - const { activeTabId, setActiveTabId } = useTabList(tabListInstanceId); + const [activeTabId, setActiveTabId] = useRecoilComponentStateV2( + activeTabIdComponentState, + componentInstanceId, + ); const initialActiveTabId = activeTabId || visibleTabs[0]?.id || ''; @@ -65,17 +69,18 @@ export const TabList = ({ } return ( - - + + tab.id)} /> {visibleTabs.map((tab) => ( @@ -98,7 +103,7 @@ export const TabList = ({ ))} - - + + ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx index a0c8b5691..84aa229bf 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx +++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabListFromUrlOptionalEffect.tsx @@ -1,20 +1,23 @@ -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; type TabListFromUrlOptionalEffectProps = { - componentInstanceId: string; tabListIds: string[]; isInRightDrawer: boolean; }; export const TabListFromUrlOptionalEffect = ({ - componentInstanceId, tabListIds, isInRightDrawer, }: TabListFromUrlOptionalEffectProps) => { const location = useLocation(); - const { activeTabId, setActiveTabId } = useTabList(componentInstanceId); + const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState); + const setActiveTabId = useSetRecoilComponentStateV2( + activeTabIdComponentState, + ); const hash = location.hash.replace('#', ''); diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx index d2c2cdf47..3b9d5980f 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/tab/components/__stories__/Tablist.stories.tsx @@ -2,8 +2,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; import { ComponentWithRouterDecorator, IconCheckbox } from 'twenty-ui'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - import { TabList } from '../TabList'; const tabs = [ @@ -39,17 +37,10 @@ const meta: Meta = { title: 'UI/Layout/Tab/TabList', component: TabList, args: { - tabListInstanceId: 'tab-list-id', tabs: tabs, + componentInstanceId: 'tab-list', }, - decorators: [ - (Story) => ( - - - - ), - ComponentWithRouterDecorator, - ], + decorators: [ComponentWithRouterDecorator], }; export default meta; diff --git a/packages/twenty-front/src/modules/ui/layout/tab/hooks/__tests__/useTabList.test.tsx b/packages/twenty-front/src/modules/ui/layout/tab/hooks/__tests__/useTabList.test.tsx deleted file mode 100644 index 360d4846a..000000000 --- a/packages/twenty-front/src/modules/ui/layout/tab/hooks/__tests__/useTabList.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { act } from 'react-dom/test-utils'; -import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; - -import { useTabList } from '../useTabList'; - -describe('useTabList', () => { - it('Should update the activeTabId state', async () => { - const { result } = renderHook( - () => { - const { activeTabId, setActiveTabId } = useTabList('TEST_TAB_LIST_ID'); - - return { - activeTabId, - setActiveTabId: setActiveTabId, - }; - }, - { - wrapper: RecoilRoot, - }, - ); - expect(result.current.setActiveTabId).toBeInstanceOf(Function); - expect(result.current.activeTabId).toBeNull(); - - act(() => { - result.current.setActiveTabId('test-value'); - }); - - expect(result.current.activeTabId).toBe('test-value'); - }); -}); diff --git a/packages/twenty-front/src/modules/ui/layout/tab/hooks/internal/useTabListStates.ts b/packages/twenty-front/src/modules/ui/layout/tab/hooks/internal/useTabListStates.ts deleted file mode 100644 index 2d33932be..000000000 --- a/packages/twenty-front/src/modules/ui/layout/tab/hooks/internal/useTabListStates.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TabListScopeInternalContext } from '@/ui/layout/tab/scopes/scope-internal-context/TabListScopeInternalContext'; -import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; -import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; -import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; - -type useTabListStatesProps = { - tabListScopeId?: string; -}; - -export const useTabListStates = ({ tabListScopeId }: useTabListStatesProps) => { - const scopeId = useAvailableScopeIdOrThrow( - TabListScopeInternalContext, - tabListScopeId, - ); - - return { - scopeId, - activeTabIdState: extractComponentState(activeTabIdComponentState, scopeId), - }; -}; diff --git a/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts b/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts deleted file mode 100644 index f71b0c0d8..000000000 --- a/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { RecoilState, useRecoilState } from 'recoil'; - -import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates'; - -export const useTabList = (tabListId?: string) => { - const { activeTabIdState } = useTabListStates({ - tabListScopeId: tabListId, - }); - - const [activeTabId, setActiveTabId] = useRecoilState( - activeTabIdState as RecoilState, - ); - - return { - activeTabId, - setActiveTabId, - }; -}; diff --git a/packages/twenty-front/src/modules/ui/layout/tab/states/activeTabIdComponentState.ts b/packages/twenty-front/src/modules/ui/layout/tab/states/activeTabIdComponentState.ts index fcb07559b..424c5317e 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/states/activeTabIdComponentState.ts +++ b/packages/twenty-front/src/modules/ui/layout/tab/states/activeTabIdComponentState.ts @@ -1,6 +1,8 @@ -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -export const activeTabIdComponentState = createComponentState({ +export const activeTabIdComponentState = createComponentStateV2({ key: 'activeTabIdComponentState', defaultValue: null, + componentInstanceContext: TabListComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/ui/layout/tab/states/contexts/TabListComponentInstanceContext.ts b/packages/twenty-front/src/modules/ui/layout/tab/states/contexts/TabListComponentInstanceContext.ts new file mode 100644 index 000000000..a13ad6460 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/tab/states/contexts/TabListComponentInstanceContext.ts @@ -0,0 +1,3 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const TabListComponentInstanceContext = createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx index dc4124342..405fe1f24 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx @@ -1,5 +1,5 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { workflowIdState } from '@/workflow/states/workflowIdState'; import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; @@ -24,27 +24,29 @@ export const WorkflowRunDiagramCanvasEffect = () => { const workflowId = useRecoilValue(workflowIdState); - const { activeTabIdState: workflowRunRightDrawerListActiveTabIdState } = - useTabListStates({ - tabListScopeId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, - }); - const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback( ({ snapshot, set }) => () => { const activeWorkflowRunRightDrawerTab = getSnapshotValue( snapshot, - workflowRunRightDrawerListActiveTabIdState, + activeTabIdComponentState.atomFamily({ + instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, + }), ) as WorkflowRunTabId | null; if ( activeWorkflowRunRightDrawerTab === 'input' || activeWorkflowRunRightDrawerTab === 'output' ) { - set(workflowRunRightDrawerListActiveTabIdState, 'node'); + set( + activeTabIdComponentState.atomFamily({ + instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, + }), + 'node', + ); } }, - [workflowRunRightDrawerListActiveTabIdState], + [], ); const handleSelectionChange = useCallback( diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStep.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStep.tsx deleted file mode 100644 index acd9c20f8..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStep.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { RightDrawerWorkflowEditStepContent } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent'; -import { useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-shared'; - -export const RightDrawerWorkflowEditStep = () => { - const workflowId = useRecoilValue(workflowIdState); - const workflow = useWorkflowWithCurrentVersion(workflowId); - - if (!isDefined(workflow)) { - return null; - } - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx deleted file mode 100644 index a04dbeccb..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; -import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; -import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; -import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; -import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep'; -import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; - -export const RightDrawerWorkflowEditStepContent = ({ - workflow, -}: { - workflow: WorkflowWithCurrentVersion; -}) => { - const flow = useFlowOrThrow(); - const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); - - const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow }); - const { updateStep } = useUpdateStep({ - workflow, - }); - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx deleted file mode 100644 index c617295a3..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; -import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; -import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; -import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; -import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; -import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; -import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail'; -import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail'; -import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; -import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId'; -import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; -import styled from '@emotion/styled'; -import { isDefined } from 'twenty-shared'; -import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui'; - -const StyledTabList = styled(TabList)` - background-color: ${({ theme }) => theme.background.secondary}; - padding-left: ${({ theme }) => theme.spacing(2)}; -`; - -type TabId = 'node' | 'input' | 'output'; - -export const RightDrawerWorkflowRunViewStep = () => { - const flow = useFlowOrThrow(); - const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); - const workflowRunId = useWorkflowRunIdOrThrow(); - - const workflowRun = useWorkflowRun({ workflowRunId }); - - const { activeTabId } = useTabList( - WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, - ); - - const stepExecutionStatus = isDefined(workflowRun) - ? getWorkflowRunStepExecutionStatus({ - workflowRunOutput: workflowRun.output, - stepId: workflowSelectedNode, - }) - : undefined; - - const areInputAndOutputTabsDisabled = - workflowSelectedNode === TRIGGER_STEP_ID || - stepExecutionStatus === 'running' || - stepExecutionStatus === 'not-executed'; - - const tabs: SingleTabProps[] = [ - { id: 'node', title: 'Node', Icon: IconStepInto }, - { - id: 'input', - title: 'Input', - Icon: IconLogin2, - disabled: areInputAndOutputTabsDisabled, - }, - { - id: 'output', - title: 'Output', - Icon: IconLogout, - disabled: areInputAndOutputTabsDisabled, - }, - ]; - - if (!isDefined(workflowRun)) { - return null; - } - - return ( - - - - {activeTabId === 'node' ? ( - - ) : null} - - {activeTabId === 'input' ? ( - - ) : null} - - {activeTabId === 'output' ? ( - - ) : null} - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/RightDrawerWorkflowRunViewStep.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/RightDrawerWorkflowRunViewStep.stories.tsx deleted file mode 100644 index c8c0f51be..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/RightDrawerWorkflowRunViewStep.stories.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { flowState } from '@/workflow/states/flowState'; -import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; -import styled from '@emotion/styled'; -import { Meta, StoryObj } from '@storybook/react'; -import { expect, userEvent, waitFor, within } from '@storybook/test'; -import { HttpResponse, graphql } from 'msw'; -import { useSetRecoilState } from 'recoil'; -import { ComponentDecorator, RouterDecorator } from 'twenty-ui'; -import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; -import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; -import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; -import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; -import { graphqlMocks } from '~/testing/graphqlMocks'; -import { oneFailedWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run'; -import { RightDrawerWorkflowRunViewStep } from '../RightDrawerWorkflowRunViewStep'; - -const StyledWrapper = styled.div` - display: flex; - flex-direction: column; - width: 500px; -`; - -const meta: Meta = { - title: 'Modules/Workflow/RightDrawerWorkflowRunViewStep', - component: RightDrawerWorkflowRunViewStep, - decorators: [ - (Story) => ( - - - - ), - I18nFrontDecorator, - ComponentDecorator, - (Story) => { - const setFlow = useSetRecoilState(flowState); - const setWorkflowSelectedNode = useSetRecoilState( - workflowSelectedNodeState, - ); - const setWorkflowRunId = useSetRecoilState(workflowRunIdState); - - setFlow({ - workflowVersionId: - oneFailedWorkflowRunQueryResult.workflowRun.workflowVersionId, - trigger: - oneFailedWorkflowRunQueryResult.workflowRun.output.flow.trigger, - steps: oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps, - }); - setWorkflowSelectedNode( - oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps[0].id, - ); - setWorkflowRunId(oneFailedWorkflowRunQueryResult.workflowRun.id); - - return ; - }, - RouterDecorator, - ObjectMetadataItemsDecorator, - WorkspaceDecorator, - WorkflowStepDecorator, - ], - parameters: { - msw: { - handlers: [ - graphql.query('FindOneWorkflowRun', () => { - const workflowRunContext = - oneFailedWorkflowRunQueryResult.workflowRun.context; - - // Rendering the whole objectMetadata information in the JSON viewer is too long for storybook - // so we remove it for the story - return HttpResponse.json({ - data: { - ...oneFailedWorkflowRunQueryResult, - workflowRun: { - ...oneFailedWorkflowRunQueryResult.workflowRun, - context: { - ...workflowRunContext, - trigger: { - ...workflowRunContext.trigger, - objectMetadata: undefined, - }, - }, - }, - }, - }); - }), - ...graphqlMocks.handlers, - ], - }, - }, -}; - -export default meta; - -type Story = StoryObj; - -export const NodeTab: Story = {}; - -export const InputTab: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - await userEvent.click(await canvas.findByRole('button', { name: 'Input' })); - - expect(await canvas.findByText('Trigger')).toBeVisible(); - }, -}; - -export const InputTabDisabledForTrigger: Story = { - decorators: [ - (Story) => { - const setWorkflowSelectedNode = useSetRecoilState( - workflowSelectedNodeState, - ); - - setWorkflowSelectedNode(TRIGGER_STEP_ID); - - return ; - }, - ], - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const inputTab = await canvas.findByRole('button', { name: 'Input' }); - - expect(inputTab).toBeDisabled(); - }, -}; - -export const InputTabNotExecutedStep: Story = { - decorators: [ - (Story) => { - const setWorkflowSelectedNode = useSetRecoilState( - workflowSelectedNodeState, - ); - - setWorkflowSelectedNode( - oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)! - .id, - ); - - return ; - }, - ], - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const inputTab = await canvas.findByRole('button', { name: 'Input' }); - - expect(inputTab).toBeDisabled(); - }, -}; - -export const OutputTab: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - await userEvent.click( - await canvas.findByRole('button', { name: 'Output' }), - ); - - await waitFor(() => { - expect(canvas.queryByText('Create Record')).not.toBeInTheDocument(); - }); - - expect(await canvas.findByText('result')).toBeVisible(); - }, -}; - -export const OutputTabDisabledForTrigger: Story = { - decorators: [ - (Story) => { - const setWorkflowSelectedNode = useSetRecoilState( - workflowSelectedNodeState, - ); - - setWorkflowSelectedNode(TRIGGER_STEP_ID); - - return ; - }, - ], - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const outputTab = await canvas.findByRole('button', { name: 'Output' }); - - expect(outputTab).toBeDisabled(); - }, -}; - -export const OutputTabNotExecutedStep: Story = { - decorators: [ - (Story) => { - const setWorkflowSelectedNode = useSetRecoilState( - workflowSelectedNodeState, - ); - - setWorkflowSelectedNode( - oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)! - .id, - ); - - return ; - }, - ], - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const outputTab = await canvas.findByRole('button', { name: 'Output' }); - - expect(outputTab).toBeDisabled(); - }, -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/types/WorkflowRunTabId.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/types/WorkflowRunTabId.tsx index f284c1fec..a5c7edf74 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/types/WorkflowRunTabId.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/types/WorkflowRunTabId.tsx @@ -1 +1,7 @@ -export type WorkflowRunTabId = 'node' | 'input' | 'output'; +export type WorkflowRunTabIdType = 'node' | 'input' | 'output'; + +export enum WorkflowRunTabId { + NODE = 'node', + INPUT = 'input', + OUTPUT = 'output', +} diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction.tsx deleted file mode 100644 index fe750afc0..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { RightDrawerWorkflowSelectActionContent } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent'; -import { useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-shared'; - -export const RightDrawerWorkflowSelectAction = () => { - const workflowId = useRecoilValue(workflowIdState); - const workflow = useWorkflowWithCurrentVersion(workflowId); - - if (!isDefined(workflow)) { - return null; - } - - return ; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent.tsx deleted file mode 100644 index 73fd1a4f6..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; -import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep'; -import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions'; -import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions'; -import { MenuItemCommand, useIcons } from 'twenty-ui'; -import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer'; -import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle'; - -export const RightDrawerWorkflowSelectActionContent = ({ - workflow, -}: { - workflow: WorkflowWithCurrentVersion; -}) => { - const { getIcon } = useIcons(); - const { createStep } = useCreateStep({ - workflow, - }); - - return ( - - - Records - - {RECORD_ACTIONS.map((action) => ( - createStep(action.type)} - /> - ))} - - Other - - {OTHER_ACTIONS.map((action) => ( - createStep(action.type)} - /> - ))} - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunction.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunction.tsx index 63fda868f..e011b37f9 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunction.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunction.tsx @@ -19,11 +19,13 @@ import { InputLabel } from '@/ui/input/components/InputLabel'; import { TextArea } from '@/ui/input/components/TextArea'; import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter'; import { TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunctionFields'; import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId'; +import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; @@ -77,8 +79,10 @@ export const WorkflowEditActionServerlessFunction = ({ const theme = useTheme(); const { getIcon } = useIcons(); const serverlessFunctionId = action.settings.input.serverlessFunctionId; - const tabListId = `${WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}_${serverlessFunctionId}`; - const { activeTabId } = useTabList(tabListId); + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, + WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID, + ); const { updateOneServerlessFunction } = useUpdateOneServerlessFunction(serverlessFunctionId); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion(); @@ -267,8 +271,12 @@ export const WorkflowEditActionServerlessFunction = ({ }; const tabs = [ - { id: 'code', title: 'Code', Icon: IconCode }, - { id: 'test', title: 'Test', Icon: IconPlayerPlay }, + { id: WorkflowServerlessFunctionTabId.CODE, title: 'Code', Icon: IconCode }, + { + id: WorkflowServerlessFunctionTabId.TEST, + title: 'Test', + Icon: IconPlayerPlay, + }, ]; useEffect(() => { @@ -284,9 +292,11 @@ export const WorkflowEditActionServerlessFunction = ({ !loading && ( { @@ -299,7 +309,7 @@ export const WorkflowEditActionServerlessFunction = ({ disabled={actionOptions.readonly} /> - {activeTabId === 'code' && ( + {activeTabId === WorkflowServerlessFunctionTabId.CODE && ( <> )} - {activeTabId === 'test' && ( + {activeTabId === WorkflowServerlessFunctionTabId.TEST && ( <> )} - {activeTabId === 'test' && ( + {activeTabId === WorkflowServerlessFunctionTabId.TEST && ( { findActiveObjectMetadataItemByNamePlural(objectNamePlural) ?? findActiveObjectMetadataItemByNamePlural(updatedObjectNamePlural); - const { activeTabId } = useTabList( + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID, ); @@ -169,11 +171,10 @@ export const SettingsObjectDetailPage = () => { > {renderActiveTabContent()} diff --git a/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx b/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx index 522cb2592..92d672157 100644 --- a/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx +++ b/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx @@ -10,7 +10,8 @@ import { RoleSettings } from '@/settings/roles/role-settings/components/RoleSett import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useGetRolesQuery } from '~/generated/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; @@ -33,9 +34,7 @@ export const SettingsRoleEdit = () => { const role = rolesData?.getRoles.find((r) => r.id === roleId); - const { activeTabId } = useTabList( - SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID, - ); + const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState); useEffect(() => { if (!rolesLoading && !role) { @@ -101,9 +100,11 @@ export const SettingsRoleEdit = () => { {!rolesLoading && role ? ( {renderActiveTabContent()} diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx index 69ef70eed..47005c34f 100644 --- a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx +++ b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx @@ -14,7 +14,8 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { TabList } from '@/ui/layout/tab/components/TabList'; -import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useState } from 'react'; import { useParams } from 'react-router-dom'; @@ -26,12 +27,15 @@ import { FeatureFlagKey } from '~/generated/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; -const TAB_LIST_COMPONENT_ID = 'serverless-function-detail'; +const SERVERLESS_FUNCTION_DETAIL_ID = 'serverless-function-detail'; export const SettingsServerlessFunctionDetail = () => { const { serverlessFunctionId = '' } = useParams(); const { enqueueSnackBar } = useSnackBar(); - const { activeTabId, setActiveTabId } = useTabList(TAB_LIST_COMPONENT_ID); + const [activeTabId, setActiveTabId] = useRecoilComponentStateV2( + activeTabIdComponentState, + SERVERLESS_FUNCTION_DETAIL_ID, + ); const [isCodeValid, setIsCodeValid] = useState(true); const { updateOneServerlessFunction } = useUpdateOneServerlessFunction(serverlessFunctionId); @@ -209,9 +213,9 @@ export const SettingsServerlessFunctionDetail = () => { > {renderActiveTabContent()}