Skip to content

Commit

Permalink
feat: NavBar Omnichannel items
Browse files Browse the repository at this point in the history
  • Loading branch information
juliajforesti committed May 7, 2024
1 parent 9a7e4bc commit d9af8c7
Show file tree
Hide file tree
Showing 27 changed files with 351 additions and 47 deletions.
29 changes: 23 additions & 6 deletions apps/meteor/client/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import { NavBar as NavBarComponent, NavBarSection, NavBarGroup, NavBarDivider }
import { useAtLeastOnePermission, usePermission, useTranslation, useUser } from '@rocket.chat/ui-contexts';
import React from 'react';

import { useIsCallEnabled, useIsCallReady } from '../contexts/CallContext';
import { useOmnichannelShowQueueLink } from '../hooks/omnichannel/useOmnichannelShowQueueLink';
import UserMenu from '../sidebar/header/UserMenu';
import NavBarPageAdmin from './pages/Admin';
import NavBarPageDirectory from './pages/Directory';
import NavBarPageHome from './pages/Home';
import NavBarPageMarketPlace from './pages/MarketPlace';
import NavBarPageOmnichannel from './pages/Omnichannel';
import {
NavBarItemOmniChannelCallDialPad,
NavBarItemOmnichannel,
NavBarItemOmnichannelContact,
NavBarItemOmnichannelLivechatToggle,
NavBarItemOmnichannelQueue,
NavBarItemOmnichannelCallToggle,
} from './Omnichannel';
import NavBarAuditMenu from './actions/Audit.tsx';
import { NavBarPageAdmin, NavBarPageDirectory, NavBarPageHome, NavBarPageMarketPlace } from './pages';

const ADMIN_PERMISSIONS = [
'view-statistics',
Expand Down Expand Up @@ -47,18 +54,28 @@ export const NavBar = () => {
const hasAccessMarketplacePermission = usePermission('access-marketplace');
const showMarketplace = hasAccessMarketplacePermission || hasManageAppsPermission;

const showOmnichannelQueueLink = useOmnichannelShowQueueLink();
const isCallEnabled = useIsCallEnabled();
const isCallReady = useIsCallReady();

return (
<NavBarComponent>
<NavBarSection>
<NavBarGroup role='toolbar'>
<NavBarPageHome title={t('Home')} />
<NavBarPageDirectory title={t('Directory')} />
{showMarketplace && <NavBarPageMarketPlace title={t('Marketplace')} />}
<NavBarAuditMenu />
</NavBarGroup>
<NavBarDivider />
{showOmnichannel && (
<NavBarGroup role='toolbar'>
<NavBarPageOmnichannel title={t('Omnichannel')} />
{showOmnichannelQueueLink && <NavBarItemOmnichannelQueue title={t('Queue')} />}
{isCallReady && <NavBarItemOmniChannelCallDialPad />}
<NavBarItemOmnichannel title={t('Omnichannel')} />
<NavBarItemOmnichannelContact title={t('Contacts')} />
{isCallEnabled && <NavBarItemOmnichannelCallToggle />}
<NavBarItemOmnichannelLivechatToggle />
</NavBarGroup>
)}
</NavBarSection>
Expand Down
20 changes: 20 additions & 0 deletions apps/meteor/client/navbar/Omnichannel/Omnichannel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

type OmnichannelProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

export const NavBarItemOmnichannel = (props: OmnichannelProps) => {
const router = useRouter();
const currentRoute = useCurrentRoutePath();

return (
<NavBarItem
{...props}
icon='headset'
onClick={() => router.navigate('/omnichannel/current')}
pressed={currentRoute?.includes('/omnichannel/')}
/>
);
};
26 changes: 26 additions & 0 deletions apps/meteor/client/navbar/Omnichannel/OmnichannelCallDialPad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

import { useVoipOutboundStates } from '../../contexts/CallContext';
import { useDialModal } from '../../hooks/useDialModal';

export const NavBarItemOmniChannelCallDialPad = ({ ...props }): ReactElement => {
const t = useTranslation();

const { openDialModal } = useDialModal();

const { outBoundCallsAllowed, outBoundCallsEnabledForUser } = useVoipOutboundStates();

return (
<NavBarItem
icon='dialpad'
onClick={(): void => openDialModal()}
disabled={!outBoundCallsEnabledForUser}
aria-label={t('Open_Dialpad')}
data-tooltip={outBoundCallsAllowed ? t('New_Call') : t('New_Call_Premium_Only')}
{...props}
/>
);
};
21 changes: 21 additions & 0 deletions apps/meteor/client/navbar/Omnichannel/OmnichannelCallToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ReactElement } from 'react';
import React from 'react';

import { useIsCallReady, useIsCallError } from '../../contexts/CallContext';
import { OmnichannelCallToggleError } from './OmnichannelCallToggleError';
import { OmnichannelCallToggleLoading } from './OmnichannelCallToggleLoading';
import { OmnichannelCallToggleReady } from './OmnichannelCallToggleReady';

export const NavBarItemOmnichannelCallToggle = ({ ...props }): ReactElement => {
const isCallReady = useIsCallReady();
const isCallError = useIsCallError();
if (isCallError) {
return <OmnichannelCallToggleError {...props} />;
}

if (!isCallReady) {
return <OmnichannelCallToggleLoading {...props} />;
}

return <OmnichannelCallToggleReady {...props} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

export const OmnichannelCallToggleError = ({ ...props }): ReactElement => {
const t = useTranslation();
return <NavBarItem icon='phone' danger data-tooltip={t('Error')} disabled {...props} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

export const OmnichannelCallToggleLoading = ({ ...props }): ReactElement => {
const t = useTranslation();
return <NavBarItem icon='phone' data-tooltip={t('Loading')} aria-label={t('VoIP_Toggle')} disabled {...props} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useCallback } from 'react';

import { useCallerInfo, useCallRegisterClient, useCallUnregisterClient, useVoipNetworkStatus } from '../../contexts/CallContext';

export const OmnichannelCallToggleReady = ({ ...props }): ReactElement => {
const t = useTranslation();

const caller = useCallerInfo();
const unregister = useCallUnregisterClient();
const register = useCallRegisterClient();

const networkStatus = useVoipNetworkStatus();
const registered = !['ERROR', 'INITIAL', 'UNREGISTERED'].includes(caller.state);
const inCall = ['IN_CALL'].includes(caller.state);

const onClickVoipButton = useCallback((): void => {
if (registered) {
unregister();
return;
}
register();
}, [registered, register, unregister]);

const getTitle = (): string => {
if (networkStatus === 'offline') {
return t('Waiting_for_server_connection');
}

if (inCall) {
return t('Cannot_disable_while_on_call');
}

if (registered) {
return t('Turn_off_answer_calls');
}

return t('Turn_on_answer_calls');
};

const getIcon = (): 'phone-issue' | 'phone' | 'phone-disabled' => {
if (networkStatus === 'offline') {
return 'phone-issue';
}
return registered ? 'phone' : 'phone-disabled';
};

return (
<NavBarItem
icon={getIcon()}
disabled={inCall}
aria-checked={registered}
aria-label={t('VoIP_Toggle')}
data-tooltip={getTitle()}
{...props}
success={registered}
warning={networkStatus === 'offline'}
onClick={onClickVoipButton}
/>
);
};
20 changes: 20 additions & 0 deletions apps/meteor/client/navbar/Omnichannel/OmnichannelContact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

type OmnichannelProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

export const NavBarItemOmnichannelContact = (props: OmnichannelProps) => {
const router = useRouter();
const currentRoute = useCurrentRoutePath();

return (
<NavBarItem
{...props}
icon='address-book'
onClick={() => router.navigate('/omnichannel-directory')}
pressed={currentRoute?.includes('/omnichannel-directory')}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Sidebar } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, ComponentProps } from 'react';
import React from 'react';

import { useOmnichannelAgentAvailable } from '../../hooks/omnichannel/useOmnichannelAgentAvailable';

export const NavBarItemOmnichannelLivechatToggle = (props: Omit<ComponentProps<typeof Sidebar.TopBar.Action>, 'icon'>): ReactElement => {
const t = useTranslation();
const agentAvailable = useOmnichannelAgentAvailable();
const changeAgentStatus = useEndpoint('POST', '/v1/livechat/agent.status');
const dispatchToastMessage = useToastMessageDispatch();

const handleAvailableStatusChange = useMutableCallback(async () => {
try {
await changeAgentStatus({});
} catch (error: unknown) {
dispatchToastMessage({ type: 'error', message: error });
}
});

return (
<Sidebar.TopBar.Action
{...props}
id='omnichannel-status-toggle'
title={agentAvailable ? t('Turn_off_answer_chats') : t('Turn_on_answer_chats')}
success={agentAvailable}
icon={agentAvailable ? 'message' : 'message-disabled'}
onClick={handleAvailableStatusChange}
/>
);
};
20 changes: 20 additions & 0 deletions apps/meteor/client/navbar/Omnichannel/OmnichannelQueue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

type OmnichannelProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

export const NavBarItemOmnichannelQueue = (props: OmnichannelProps) => {
const router = useRouter();
const currentRoute = useCurrentRoutePath();

return (
<NavBarItem
{...props}
icon='queue'
onClick={() => router.navigate('/livechat-queue')}
pressed={currentRoute?.includes('/livechat-queue')}
/>
);
};
6 changes: 6 additions & 0 deletions apps/meteor/client/navbar/Omnichannel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './Omnichannel';
export * from './OmnichannelCallDialPad';
export * from './OmnichannelCallToggle';
export * from './OmnichannelContact';
export * from './OmnichannelLivechatToggle';
export * from './OmnichannelQueue';
27 changes: 27 additions & 0 deletions apps/meteor/client/navbar/actions/Audit.tsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useCurrentRoutePath, useTranslation } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes, VFC } from 'react';
import React from 'react';

import GenericMenu from '../../components/GenericMenu/GenericMenu';
import { useAuditMenu } from './hooks/useAuditMenu';

const NavBarAuditMenu: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => {
const t = useTranslation();
const sections = useAuditMenu();
const currentRoute = useCurrentRoutePath();

return (
<GenericMenu
items={sections}
title={t('Audit')}
is={NavBarItem}
placement='bottom-start'
icon='document-eye'
pressed={currentRoute?.includes('/home')}
{...props}
/>
);
};

export default NavBarAuditMenu;
33 changes: 33 additions & 0 deletions apps/meteor/client/navbar/actions/hooks/useAuditMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { usePermission, useRouter, useTranslation } from '@rocket.chat/ui-contexts';

import { useHasLicenseModule } from '../../../../ee/client/hooks/useHasLicenseModule';
import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem';

export const useAuditMenu = () => {
const router = useRouter();
const t = useTranslation();

const hasAuditLicense = useHasLicenseModule('auditing') === true;

const hasAuditPermission = usePermission('can-audit') && hasAuditLicense;
const hasAuditLogPermission = usePermission('can-audit-log') && hasAuditLicense;

if (!hasAuditPermission && !hasAuditLogPermission) {
return [];
}

const auditMessageItem: GenericMenuItemProps = {
id: 'messages',
icon: 'document-eye',
content: t('Messages'),
onClick: () => router.navigate('/audit'),
};
const auditLogItem: GenericMenuItemProps = {
id: 'auditLog',
icon: 'document-eye',
content: t('Logs'),
onClick: () => router.navigate('/audit-log'),
};

return [hasAuditPermission && auditMessageItem, hasAuditLogPermission && auditLogItem].filter(Boolean) as GenericMenuItemProps[];
};
2 changes: 1 addition & 1 deletion apps/meteor/client/navbar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './NavBar';
export * from './NavBar';

Check failure on line 1 in apps/meteor/client/navbar/index.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Unable to resolve path to module './NavBar'
4 changes: 1 addition & 3 deletions apps/meteor/client/navbar/pages/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React from 'react';

type AdminProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarPageAdmin = (props: AdminProps) => {
export const NavBarPageAdmin = (props: AdminProps) => {
const router = useRouter();
const { sidebar } = useLayout();
const handleDirectory = useEffectEvent(() => {
Expand All @@ -17,5 +17,3 @@ const NavBarPageAdmin = (props: AdminProps) => {

return <NavBarItem {...props} icon='cog' onClick={handleDirectory} pressed={currentRoute?.includes('/admin')} />;
};

export default NavBarPageAdmin;
4 changes: 1 addition & 3 deletions apps/meteor/client/navbar/pages/Directory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React from 'react';

type DirectoryProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarPageDirectory = (props: DirectoryProps) => {
export const NavBarPageDirectory = (props: DirectoryProps) => {
const router = useRouter();
const { sidebar } = useLayout();
const handleDirectory = useEffectEvent(() => {
Expand All @@ -17,5 +17,3 @@ const NavBarPageDirectory = (props: DirectoryProps) => {

return <NavBarItem {...props} icon='notebook-hashtag' onClick={handleDirectory} pressed={currentRoute?.includes('/directory')} />;
};

export default NavBarPageDirectory;
Loading

0 comments on commit d9af8c7

Please sign in to comment.