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)
This commit is contained in:
@@ -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 <SupportButtonSkeletonLoader />;
|
||||
}
|
||||
|
||||
if (!isFrontChatLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
return isNavigationDrawerExpanded ? (
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
title={t`Support`}
|
||||
Icon={IconHelpCircle}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
) : (
|
||||
<LightIconButton Icon={IconHelpCircle} />
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton width={84} height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} />
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useSupportChat } from '@/support/hooks/useSupportChat';
|
||||
|
||||
export const SupportChatEffect = () => {
|
||||
useSupportChat();
|
||||
|
||||
return 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 (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
dropdownPlacement="top-start"
|
||||
dropdownOffset={{ x: 0, y: -28 }}
|
||||
clickableComponent={<SupportButton />}
|
||||
dropdownComponents={
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={t`Talk to us`}
|
||||
LeftIcon={IconMessage}
|
||||
onClick={handleTalkToUs}
|
||||
/>
|
||||
<MenuItem
|
||||
text={t`Documentation`}
|
||||
LeftIcon={IconHelpCircle}
|
||||
onClick={handleUserGuide}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-67
@@ -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<typeof SupportDropdown> = {
|
||||
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 <Story />;
|
||||
},
|
||||
PrefetchLoadedDecorator,
|
||||
],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SupportDropdown>;
|
||||
|
||||
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();
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user