Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Team collab VoIP config modal #34957

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/light-yaks-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Implements a modal to let users know about VoIP calls in direct messages and missing configurations.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const useStartCallRoomAction = () => {
key={id}
title={title}
disabled={disabled}
items={[...voipCall.items, ...videoCall.items]}
items={[...(voipCall.allowed ? voipCall.items : []), ...videoCall.items]}
className={className}
placement='bottom-start'
icon={icon}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useUserId } from '@rocket.chat/ui-contexts';
import { useVoipAPI, useVoipState } from '@rocket.chat/ui-voip';
Expand All @@ -8,12 +9,15 @@ import { useTranslation } from 'react-i18next';
import { useMediaPermissions } from '../../../views/room/composer/messageBox/hooks/useMediaPermissions';
import { useRoom } from '../../../views/room/contexts/RoomContext';
import { useUserInfoQuery } from '../../useUserInfoQuery';
import { useVoipWarningModal } from '../../useVoipWarningModal';

const useVoipMenuOptions = () => {
const { t } = useTranslation();
const { uids = [] } = useRoom();
const ownUserId = useUserId();

const dispatchWarning = useVoipWarningModal();

const [isMicPermissionDenied] = useMediaPermissions('microphone');

const { isEnabled, isRegistered, isInCall } = useVoipState();
Expand All @@ -27,7 +31,9 @@ const useVoipMenuOptions = () => {
const isRemoteRegistered = !!remoteUser?.freeSwitchExtension;
const isDM = members.length === 1;

const disabled = isMicPermissionDenied || !isDM || !isRemoteRegistered || !isRegistered || isInCall || isPending;
const disabled = isMicPermissionDenied || !isDM || isInCall || isPending;
const allowed = isDM && !isInCall && !isPending;
const canMakeVoipCall = allowed && isRemoteRegistered && isRegistered && isEnabled && !isMicPermissionDenied;

const title = useMemo(() => {
if (isMicPermissionDenied) {
Expand All @@ -41,13 +47,20 @@ const useVoipMenuOptions = () => {
return disabled ? t('Voice_calling_disabled') : '';
}, [disabled, isInCall, isMicPermissionDenied, t]);

const handleOnClick = useEffectEvent(() => {
if (canMakeVoipCall) {
return makeCall(remoteUser?.freeSwitchExtension as string);
}
dispatchWarning();
});

return useMemo(() => {
const items: GenericMenuItemProps[] = [
{
id: 'start-voip-call',
icon: 'phone',
disabled,
onClick: () => makeCall(remoteUser?.freeSwitchExtension as string),
onClick: handleOnClick,
content: (
<Box is='span' title={title}>
{t('Voice_call')}
Expand All @@ -57,13 +70,13 @@ const useVoipMenuOptions = () => {
];

return {
items: isEnabled ? items : [],
items,
groups: ['direct'] as const,
disabled,
allowed: isEnabled,
order: 4,
allowed,
};
}, [disabled, title, t, isEnabled, makeCall, remoteUser?.freeSwitchExtension]);
}, [disabled, title, t, handleOnClick, allowed]);
};

export default useVoipMenuOptions;
30 changes: 30 additions & 0 deletions apps/meteor/client/hooks/useVoipWarningModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useRole, useRoute, useSetModal } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';

import { useHasLicenseModule } from './useHasLicenseModule';
import TeamsVoipConfigModal from '../views/room/contextualBar/TeamsVoipConfigModal';

export const useVoipWarningModal = (): (() => void) => {
const setModal = useSetModal();
const isAdmin = useRole('admin');
const hasModule = useHasLicenseModule('teams-voip') === true;
const teamsVoipSettingsRoute = useRoute('admin-settings');

const handleClose = useEffectEvent(() => setModal(null));

const handleRedirectToConfiguration = useEffectEvent(() => {
handleClose();
teamsVoipSettingsRoute.push({
group: 'VoIP_TeamCollab',
});
});

return useMemo(
() => (): void =>
setModal(
<TeamsVoipConfigModal hasModule={hasModule} onClose={handleClose} onConfirm={handleRedirectToConfiguration} isAdmin={isAdmin} />,
),
[handleClose, handleRedirectToConfiguration, isAdmin, setModal, hasModule],
);
};
100 changes: 100 additions & 0 deletions apps/meteor/client/views/room/contextualBar/TeamsVoipConfigModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Modal, Button, Box, Callout, Margins } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import { Trans, useTranslation } from 'react-i18next';

import { useExternalLink } from '../../../hooks/useExternalLink';
import { GET_ADDONS_LINK } from '../../admin/subscription/utils/links';

type TeamsVoipConfigModalProps = {
onClose: () => void;
onConfirm?: () => void;
isAdmin: boolean;
hasModule: boolean;
};

const TeamsVoipConfigModal = ({ onClose, onConfirm, isAdmin, hasModule }: TeamsVoipConfigModalProps): ReactElement => {
const { t } = useTranslation();
const openExternalLink = useExternalLink();

const getCalloutWarning = () => {
if (isAdmin && !hasModule) {
return t('Contact_sales_start_using_VoIP');
}

if (!isAdmin && !hasModule) {
return t('Contact_your_workspace_admin_to_start_using_VoIP');
}

return t('VoIP_available_setup_freeswitch_server_details');
};

return (
<Modal>
<Modal.Header>
<Modal.HeaderText>
<Modal.Tagline>{t('VoIP')}</Modal.Tagline>
<Modal.Title>{t('Team_voice_call')}</Modal.Title>
</Modal.HeaderText>
<Modal.Close title={t('Close')} onClick={onClose} />
</Modal.Header>
<Modal.Content>
<Modal.HeroImage maxHeight='initial' src='/images/teams-voip-config.svg' />
<Box paddingBlock={24}>
{t('Fully_integrated_voip_receive_internal_external_calls_without_switching_between_apps_external_systems')}
</Box>
<Box fontScale='h3'>{t('Features')}</Box>
<Margins block={24}>
<Box withRichContent>
<Box is='ul' pis={24}>
<li>
<Trans i18nKey='VoIP_TeamCollab_Feature1'>
<strong>Direct calling:</strong> Instantly start or receive calls with team members within your Rocket.Chat workspace.
</Trans>
</li>
<li>
<Trans i18nKey='VoIP_TeamCollab_Feature2'>
<strong>Extension management:</strong> Admins can assign unique extensions to users, enabling quick, direct dialing both
from inside and outside your organization.
</Trans>
</li>
<li>
<Trans i18nKey='VoIP_TeamCollab_Feature3'>
<strong>Call transfers:</strong> Seamlessly transfer active calls to ensure users reach the right team member.
</Trans>
</li>
<li>
<Trans i18nKey='VoIP_TeamCollab_Feature4'>
<strong>Availability settings:</strong> Users can control their availability to receive calls, enhancing flexibility.
</Trans>
</li>
</Box>
</Box>
</Margins>
<Box fontScale='h3' mbs={24}>
{t('Required_action')}
</Box>
<Callout mbs={12} mbe={24} title={!hasModule ? t('Subscription_add-on_required') : t('FreeSwitch_setup_required')} type='warning'>
{getCalloutWarning()}
</Callout>
</Modal.Content>
<Modal.Footer justifyContent={!isAdmin && hasModule ? 'space-between' : 'end'}>
{!isAdmin && hasModule && <Modal.FooterAnnotation>{t('Only_admins_can_perform_this_setup')}</Modal.FooterAnnotation>}
<Modal.FooterControllers>
<Button onClick={onClose}>{t('Cancel')}</Button>
{onConfirm && isAdmin && hasModule && (
<Button primary onClick={onConfirm}>
{t('Open_settings')}
</Button>
)}
{isAdmin && !hasModule && (
<Button primary onClick={() => openExternalLink(GET_ADDONS_LINK)}>
{t('Contact_sales')}
</Button>
)}
</Modal.FooterControllers>
</Modal.Footer>
</Modal>
);
};

export default TeamsVoipConfigModal;
Loading
Loading