From 12544da527e33d0c1e4824da58915fcc7aacc4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 18 Dec 2025 10:09:10 +0100 Subject: [PATCH] feat: move support links to workspace switcher (#16653) ## Summary Move the Support and Documentation links from the bottom left navigation drawer to the workspace switcher dropdown menu. ## Changes - **Workspace Switcher**: Added Support (conditional) and Documentation links before Log out - **Settings Navigation**: Added Support and Documentation links in the Other section - **Support visibility**: Support link only appears when FrontChat is configured (not waiting for script to load) - **FrontChat loading**: Created `SupportChatEffect` component to ensure FrontChat script loads at app startup for popup notifications - **Bug fix**: Fixed settings navigation items without a path appearing highlighted - **Cleanup**: Removed unused `SupportDropdown`, `SupportButton`, and `SupportButtonSkeletonLoader` components - **Hidden**: Temporarily commented out Integrations page ## Screenshots The Support and Documentation links now appear in: 1. Workspace switcher dropdown (top left) 2. Settings navigation (Other section) --- .../app/components/AppRouterProviders.tsx | 2 + .../components/MainNavigationDrawer.tsx | 5 -- .../SettingsNavigationDrawerItem.tsx | 6 +- .../hooks/useSettingsNavigationItems.tsx | 43 +++++++++--- .../SignInAppNavigationDrawerMock.tsx | 5 -- .../support/components/SupportButton.tsx | 41 ------------ .../SupportButtonSkeletonLoader.tsx | 16 ----- .../support/components/SupportChatEffect.tsx | 7 ++ .../support/components/SupportDropdown.tsx | 50 -------------- .../__stories__/SupportDropdown.stories.tsx | 67 ------------------- .../support/utils/getDocumentationUrl.ts | 20 ++++++ ...ultiWorkspaceDropdownDefaultComponents.tsx | 33 +++++++++ 12 files changed, 100 insertions(+), 195 deletions(-) delete mode 100644 packages/twenty-front/src/modules/support/components/SupportButton.tsx delete mode 100644 packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx create mode 100644 packages/twenty-front/src/modules/support/components/SupportChatEffect.tsx delete mode 100644 packages/twenty-front/src/modules/support/components/SupportDropdown.tsx delete mode 100644 packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx create mode 100644 packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 00a5a4cf8f..98e6340582 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -21,6 +21,7 @@ import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider'; import { UserThemeProviderEffect } from '@/ui/theme/components/UserThemeProviderEffect'; import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; +import { SupportChatEffect } from '@/support/components/SupportChatEffect'; import { UserAndViewsProviderEffect } from '@/users/components/UserAndViewsProviderEffect'; import { UserProvider } from '@/users/components/UserProvider'; import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect'; @@ -66,6 +67,7 @@ export const AppRouterProviders = () => { + diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx index 3ec988e5f4..4f35c8c63b 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx @@ -5,7 +5,6 @@ import { FavoritesFolderContent } from '@/favorites/components/FavoritesFolderCo import { useFavoritesByFolder } from '@/favorites/hooks/useFavoritesByFolder'; import { MainNavigationDrawerFixedItems } from '@/navigation/components/MainNavigationDrawerFixedItems'; import { MainNavigationDrawerScrollableItems } from '@/navigation/components/MainNavigationDrawerScrollableItems'; -import { SupportDropdown } from '@/support/components/SupportDropdown'; import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer'; import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent'; import { NavigationDrawerScrollableContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerScrollableContent'; @@ -39,10 +38,6 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => { )} - - - - ); }; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx index d73bbbb632..a21cfebfdd 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx @@ -24,7 +24,7 @@ export const SettingsNavigationDrawerItem = ({ end: item.matchSubPages === false, }); - const isActive = !!matchResult && !hasActiveSubItem; + const isActive = !!item.path && !!matchResult && !hasActiveSubItem; if (isDefined(item.isHidden) && item.isHidden) { return null; @@ -37,7 +37,7 @@ export const SettingsNavigationDrawerItem = ({ indentationLevel={item.indentationLevel} subItemState={subItemState} label={item.label} - to={href} + to={href || undefined} Icon={item.Icon} active={isActive} soon={item.soon} @@ -53,7 +53,7 @@ export const SettingsNavigationDrawerItem = ({ indentationLevel={item.indentationLevel} subItemState={subItemState} label={item.label} - to={href} + to={href || undefined} Icon={item.Icon} active={isActive} soon={item.soon} diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 224edf279f..cb036f4614 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -2,25 +2,32 @@ import { SettingsPath } from 'twenty-shared/types'; import { useAuth } from '@/auth/hooks/useAuth'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { billingState } from '@/client-config/states/billingState'; +import { supportChatState } from '@/client-config/states/supportChatState'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; +import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl'; import { type NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { t } from '@lingui/core/macro'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { IconApi, - IconApps, + // IconApps, // TODO: Re-enable when integrations page is ready IconAt, IconCalendarEvent, IconColorSwatch, type IconComponent, IconCurrencyDollar, IconDoorEnter, + IconHelpCircle, IconHierarchy2, IconKey, IconLock, IconMail, + IconMessage, + IconPlug, IconRocket, IconServer, IconSettings, @@ -28,7 +35,6 @@ import { IconUserCircle, IconUsers, IconWorld, - IconPlug, } from 'twenty-ui/display'; import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql'; @@ -55,6 +61,8 @@ export type SettingsNavigationItem = { const useSettingsNavigationItems = (): SettingsNavigationSection[] => { const billing = useRecoilValue(billingState); const { signOut } = useAuth(); + const supportChat = useRecoilValue(supportChatState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const isBillingEnabled = billing?.isBillingEnabled ?? false; const currentUser = useRecoilValue(currentUserState); @@ -65,6 +73,9 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { const isApplicationEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_APPLICATION_ENABLED, ); + const isSupportChatConfigured = + supportChat?.supportDriver === 'FRONT' && + isNonEmptyString(supportChat.supportFrontChatId); const permissionMap = usePermissionFlagMap(); return [ @@ -148,12 +159,13 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { Icon: IconApi, isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], }, - { - label: t`Integrations`, - path: SettingsPath.Integrations, - Icon: IconApps, - isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], - }, + // TODO: Re-enable when integrations page is ready + // { + // label: t`Integrations`, + // path: SettingsPath.Integrations, + // Icon: IconApps, + // isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], + // }, { label: t`Applications`, path: SettingsPath.Applications, @@ -195,6 +207,21 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { Icon: IconRocket, isHidden: !permissionMap[PermissionFlagType.WORKSPACE], }, + { + label: t`Support`, + onClick: () => window.FrontChat?.('show'), + Icon: IconMessage, + isHidden: !isSupportChatConfigured, + }, + { + label: t`Documentation`, + onClick: () => + window.open( + getDocumentationUrl(currentWorkspaceMember?.locale), + '_blank', + ), + Icon: IconHelpCircle, + }, { label: t`Logout`, onClick: signOut, diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx index 02259de1c8..6ecee70f4f 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx @@ -1,6 +1,4 @@ -import { SupportDropdown } from '@/support/components/SupportDropdown'; import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer'; -import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent'; import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -63,9 +61,6 @@ export const SignInAppNavigationDrawerMock = ({ WORKSPACE_FAVORITES.includes(item.nameSingular), )} /> - - - ); }; diff --git a/packages/twenty-front/src/modules/support/components/SupportButton.tsx b/packages/twenty-front/src/modules/support/components/SupportButton.tsx deleted file mode 100644 index b1a29e6984..0000000000 --- a/packages/twenty-front/src/modules/support/components/SupportButton.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import styled from '@emotion/styled'; - -import { SupportButtonSkeletonLoader } from '@/support/components/SupportButtonSkeletonLoader'; -import { useSupportChat } from '@/support/hooks/useSupportChat'; -import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; -import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; -import { IconHelpCircle } from 'twenty-ui/display'; -import { Button, LightIconButton } from 'twenty-ui/input'; - -const StyledButtonContainer = styled.div` - display: flex; -`; - -export const SupportButton = () => { - const { loading, isFrontChatLoaded } = useSupportChat(); - - const isNavigationDrawerExpanded = useRecoilValue( - isNavigationDrawerExpandedState, - ); - if (loading) { - return ; - } - - if (!isFrontChatLoaded) { - return; - } - - return isNavigationDrawerExpanded ? ( - -