From 955aa9191f5c5c9f8dd53c05efa496bc4e40823a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 7 Apr 2026 09:50:31 +0200 Subject: [PATCH] fix: unify settings layout prep when entering settings from outside (#19373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Clicking **Compose** in the emails tab without a connected account redirects to the New Account settings page, but the settings nav drawer was left in its previous (collapsed / "main") state — producing a visibly half-broken transition. ### Root cause The "enter settings" preparation (memorize previous URL + drawer state, expand the desktop drawer, switch the mobile drawer to `'settings'`) was duplicated **inline in three different places**: - `NavigationDrawerOtherSection.handleSettingsClick` - `MultiWorkspaceDropdownDefaultComponents` Settings link - Implicitly expected (but missing) from every `useNavigateSettings` caller Every other entry point — `ComposeEmailButton`, `ComposeEmailCommand`, `AIChatCreditsExhaustedMessage`, several workflow/role components — just called `navigateSettings(...)` and skipped the prep entirely, reproducing the bug. ### Fix - Move the full prep into `useOpenSettingsMenu`, with a `useIsSettingsPage()` short-circuit so internal navigation doesn't clobber the memorized return target. - `useNavigateSettings` delegates to `openSettingsMenu()` before navigating — fixing every caller in one place. - Collapse the duplicated inline logic in `NavigationDrawerOtherSection` and `MultiWorkspaceDropdownDefaultComponents` to a single call. Net **−9 lines**, single source of truth, no behavior change for the existing happy paths. ## Test plan - [x] \`nx typecheck twenty-front\` passes - [x] \`oxlint\` + \`prettier\` clean on all 4 changed files - [x] Existing \`useNavigateSettings\` tests pass (4/4) - [ ] Manual: Compose button on a Person/Company/Opportunity emails tab with no connected account → settings drawer renders fully expanded, "Exit Settings" returns to the record - [ ] Manual: "Settings" entry in the main nav drawer still works (return path memorized) - [ ] Manual: "Settings" entry in the multi-workspace dropdown still works, and right-click → open in new tab still works (kept \`UndecoratedLink\`) - [ ] Manual: Navigating between settings pages does not overwrite the memorized return URL 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 --- .../src/hooks/useNavigateSettings.ts | 4 +++ .../NavigationDrawerOtherSection.tsx | 28 ++----------------- .../navigation/hooks/useOpenSettings.ts | 22 +++++++++++++-- ...ultiWorkspaceDropdownDefaultComponents.tsx | 20 ++----------- 4 files changed, 30 insertions(+), 44 deletions(-) diff --git a/packages/twenty-front/src/hooks/useNavigateSettings.ts b/packages/twenty-front/src/hooks/useNavigateSettings.ts index 3fdb987f2d..291524bf63 100644 --- a/packages/twenty-front/src/hooks/useNavigateSettings.ts +++ b/packages/twenty-front/src/hooks/useNavigateSettings.ts @@ -1,9 +1,11 @@ +import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings'; import { useNavigate } from 'react-router-dom'; import { type SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; export const useNavigateSettings = () => { const navigate = useNavigate(); + const { openSettingsMenu } = useOpenSettingsMenu(); return ( to: T, @@ -14,6 +16,8 @@ export const useNavigateSettings = () => { state?: any; }, ) => { + openSettingsMenu(); + const path = getSettingsPath(to, params, queryParams); return navigate(path, options); }; diff --git a/packages/twenty-front/src/modules/navigation/components/NavigationDrawerOtherSection.tsx b/packages/twenty-front/src/modules/navigation/components/NavigationDrawerOtherSection.tsx index 6ed5ca9402..640ad9bb0e 100644 --- a/packages/twenty-front/src/modules/navigation/components/NavigationDrawerOtherSection.tsx +++ b/packages/twenty-front/src/modules/navigation/components/NavigationDrawerOtherSection.tsx @@ -1,19 +1,11 @@ import { useLingui } from '@lingui/react/macro'; -import { useLocation, useNavigate } from 'react-router-dom'; import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconHelpCircle, IconSettings } from 'twenty-ui/display'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings'; import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl'; -import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; -import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; -import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; -import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; @@ -22,22 +14,12 @@ import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/ import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; +import { useNavigateSettings } from '~/hooks/useNavigateSettings'; export const NavigationDrawerOtherSection = () => { const { t } = useLingui(); - const location = useLocation(); - const navigate = useNavigate(); + const navigateSettings = useNavigateSettings(); const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); - const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = - useAtomState(isNavigationDrawerExpandedState); - const setNavigationDrawerExpandedMemorized = useSetAtomState( - navigationDrawerExpandedMemorizedState, - ); - const setNavigationMemorizedUrl = useSetAtomState( - navigationMemorizedUrlState, - ); - - const { openSettingsMenu } = useOpenSettingsMenu(); const { toggleNavigationSection } = useNavigationSection('Other'); const isNavigationSectionOpen = useAtomFamilyStateValue( @@ -46,11 +28,7 @@ export const NavigationDrawerOtherSection = () => { ); const handleSettingsClick = () => { - setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded); - setIsNavigationDrawerExpanded(true); - setNavigationMemorizedUrl(location.pathname + location.search); - openSettingsMenu(); - navigate(getSettingsPath(SettingsPath.ProfilePage)); + navigateSettings(SettingsPath.ProfilePage); }; return ( diff --git a/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts b/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts index 92ee5c1de9..ebbc6c7a68 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts @@ -1,17 +1,35 @@ +import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; +import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; +import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { useLocation } from 'react-router-dom'; export const useOpenSettingsMenu = () => { - const setIsNavigationDrawerExpanded = useSetAtomState( - isNavigationDrawerExpandedState, + const location = useLocation(); + const isSettingsPage = useIsSettingsPage(); + const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = + useAtomState(isNavigationDrawerExpandedState); + const setNavigationDrawerExpandedMemorized = useSetAtomState( + navigationDrawerExpandedMemorizedState, + ); + const setNavigationMemorizedUrl = useSetAtomState( + navigationMemorizedUrlState, ); const setCurrentMobileNavigationDrawer = useSetAtomState( currentMobileNavigationDrawerState, ); const openSettingsMenu = () => { + if (isSettingsPage) { + return; + } + + setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded); setIsNavigationDrawerExpanded(true); + setNavigationMemorizedUrl(location.pathname + location.search); setCurrentMobileNavigationDrawer('settings'); }; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx index 9f4f259e27..90f70827a2 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx @@ -15,13 +15,10 @@ import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings'; import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId'; import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState'; -import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; -import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; -import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useColorScheme } from '@/ui/theme/hooks/useColorScheme'; -import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; @@ -29,7 +26,6 @@ import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; import { themeCssVariables } from 'twenty-ui/theme-constants'; import { isNonEmptyString } from '@sniptt/guards'; -import { useLocation } from 'react-router-dom'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { @@ -85,15 +81,7 @@ export const MultiWorkspaceDropdownDefaultComponents = () => { multiWorkspaceDropdownState, ); - const location = useLocation(); - const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = - useAtomState(isNavigationDrawerExpandedState); - const setNavigationDrawerExpandedMemorized = useSetAtomState( - navigationDrawerExpandedMemorizedState, - ); - const setNavigationMemorizedUrl = useSetAtomState( - navigationMemorizedUrlState, - ); + const { openSettingsMenu } = useOpenSettingsMenu(); const handleSupport = () => { window.FrontChat?.('show'); @@ -239,9 +227,7 @@ export const MultiWorkspaceDropdownDefaultComponents = () => { { - setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded); - setIsNavigationDrawerExpanded(true); - setNavigationMemorizedUrl(location.pathname + location.search); + openSettingsMenu(); closeDropdown(MULTI_WORKSPACE_DROPDOWN_ID); }} >