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 ? (
-
-
-
- ) : (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx b/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx
deleted file mode 100644
index 78440fe021..0000000000
--- a/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
-import { useTheme } from '@emotion/react';
-import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
-
-export const SupportButtonSkeletonLoader = () => {
- const theme = useTheme();
- return (
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/support/components/SupportChatEffect.tsx b/packages/twenty-front/src/modules/support/components/SupportChatEffect.tsx
new file mode 100644
index 0000000000..fcf1be55cf
--- /dev/null
+++ b/packages/twenty-front/src/modules/support/components/SupportChatEffect.tsx
@@ -0,0 +1,7 @@
+import { useSupportChat } from '@/support/hooks/useSupportChat';
+
+export const SupportChatEffect = () => {
+ useSupportChat();
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/support/components/SupportDropdown.tsx b/packages/twenty-front/src/modules/support/components/SupportDropdown.tsx
deleted file mode 100644
index a86cd57116..0000000000
--- a/packages/twenty-front/src/modules/support/components/SupportDropdown.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { SupportButton } from '@/support/components/SupportButton';
-import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
-import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
-import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
-import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
-import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
-import { t } from '@lingui/core/macro';
-import { IconHelpCircle, IconMessage } from 'twenty-ui/display';
-import { MenuItem } from 'twenty-ui/navigation';
-
-export const SupportDropdown = () => {
- const dropdownId = `support-field-active-action-dropdown`;
-
- const { closeDropdown } = useCloseDropdown();
-
- const handleTalkToUs = () => {
- window.FrontChat?.('show');
- closeDropdown(dropdownId);
- };
-
- const handleUserGuide = () => {
- window.open('https://docs.twenty.com/user-guide/introduction', '_blank');
- closeDropdown(dropdownId);
- };
-
- return (
- }
- dropdownComponents={
-
-
-
-
-
-
- }
- />
- );
-};
diff --git a/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx b/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx
deleted file mode 100644
index 24563f2a47..0000000000
--- a/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { type Meta, type StoryObj } from '@storybook/react';
-import { expect, userEvent, within } from '@storybook/test';
-import { useSetRecoilState } from 'recoil';
-
-import { currentUserState } from '@/auth/states/currentUserState';
-import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
-import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
-import { supportChatState } from '@/client-config/states/supportChatState';
-import { graphqlMocks } from '~/testing/graphqlMocks';
-import {
- mockCurrentWorkspace,
- mockedUserData,
- mockedWorkspaceMemberData,
-} from '~/testing/mock-data/users';
-
-import { SupportDropdown } from '@/support/components/SupportDropdown';
-import { SupportDriver } from '~/generated-metadata/graphql';
-import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
-import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator';
-
-const meta: Meta = {
- title: 'Modules/Support/SupportDropdown',
- component: SupportDropdown,
- decorators: [
- I18nFrontDecorator,
- (Story) => {
- const setCurrentUser = useSetRecoilState(currentUserState);
- const setSupportChat = useSetRecoilState(supportChatState);
- const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
- const setCurrentWorkspaceMember = useSetRecoilState(
- currentWorkspaceMemberState,
- );
-
- setCurrentWorkspace(mockCurrentWorkspace);
- setCurrentWorkspaceMember(mockedWorkspaceMemberData);
- setCurrentUser(mockedUserData);
- setSupportChat({
- supportDriver: SupportDriver.FRONT,
- supportFrontChatId: '1234',
- });
-
- return ;
- },
- PrefetchLoadedDecorator,
- ],
- parameters: {
- msw: graphqlMocks,
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- play: async () => {
- const canvas = within(document.body);
-
- expect(await canvas.findByText('Support')).toBeInTheDocument();
- await userEvent.click(canvas.getByText('Support'));
-
- // Add delay to account for the timeout in useSupportChat
- await new Promise((resolve) => setTimeout(resolve, 600));
-
- expect(await canvas.findByText('Documentation')).toBeInTheDocument();
- expect(await canvas.findByText('Talk to us')).toBeInTheDocument();
- },
-};
diff --git a/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts b/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts
new file mode 100644
index 0000000000..98610d44e8
--- /dev/null
+++ b/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts
@@ -0,0 +1,20 @@
+const DOCUMENTATION_BASE_URL = 'https://docs.twenty.com';
+const DOCUMENTATION_PATH = '/user-guide/introduction';
+
+// Locales that have documentation translations available
+const SUPPORTED_DOC_LOCALES = ['fr', 'pt', 'de', 'es', 'it', 'ja', 'ko', 'zh'];
+
+export const getDocumentationUrl = (locale?: string | null): string => {
+ if (!locale) {
+ return `${DOCUMENTATION_BASE_URL}${DOCUMENTATION_PATH}`;
+ }
+
+ // Extract language code from locale (e.g., 'fr' from 'fr-FR')
+ const langCode = locale.split('-')[0].toLowerCase();
+
+ if (SUPPORTED_DOC_LOCALES.includes(langCode)) {
+ return `${DOCUMENTATION_BASE_URL}/l/${langCode}${DOCUMENTATION_PATH}`;
+ }
+
+ return `${DOCUMENTATION_BASE_URL}${DOCUMENTATION_PATH}`;
+};
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 4b6c27174f..e27e0c3563 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
@@ -2,10 +2,13 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta
import { useAuth } from '@/auth/hooks/useAuth';
import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { countAvailableWorkspaces } from '@/auth/utils/availableWorkspacesUtils';
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
+import { supportChatState } from '@/client-config/states/supportChatState';
+import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
@@ -19,6 +22,7 @@ import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/s
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
import { type ApolloError } from '@apollo/client';
import styled from '@emotion/styled';
+import { isNonEmptyString } from '@sniptt/guards';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { AppPath, SettingsPath } from 'twenty-shared/types';
@@ -26,7 +30,9 @@ import { getSettingsPath } from 'twenty-shared/utils';
import {
Avatar,
IconDotsVertical,
+ IconHelpCircle,
IconLogout,
+ IconMessage,
IconPlus,
IconSwitchHorizontal,
IconUserPlus,
@@ -50,6 +56,7 @@ const StyledDescription = styled.div`
export const MultiWorkspaceDropdownDefaultComponents = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
+ const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { t } = useLingui();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const availableWorkspaces = useRecoilValue(availableWorkspacesState);
@@ -60,6 +67,10 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
const { signOut } = useAuth();
const { enqueueErrorSnackBar } = useSnackBar();
const { colorScheme, colorSchemeList } = useColorScheme();
+ const supportChat = useRecoilValue(supportChatState);
+ const isSupportChatConfigured =
+ supportChat?.supportDriver === 'FRONT' &&
+ isNonEmptyString(supportChat.supportFrontChatId);
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
@@ -67,6 +78,16 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
multiWorkspaceDropdownState,
);
+ const handleSupport = () => {
+ window.FrontChat?.('show');
+ closeDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
+ };
+
+ const handleDocumentation = () => {
+ window.open(getDocumentationUrl(currentWorkspaceMember?.locale), '_blank');
+ closeDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
+ };
+
const handleChange = async (availableWorkspace: AvailableWorkspace) => {
redirectToWorkspaceDomain(
getWorkspaceUrl(availableWorkspace.workspaceUrls),
@@ -200,6 +221,18 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
>
+ {isSupportChatConfigured && (
+
+ )}
+