Skip to content

Commit

Permalink
[NEW][ENTERPRISE] Introducing dial pad component into sidebar, calls …
Browse files Browse the repository at this point in the history
…table, contextual bar (#26081)

Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com>
Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com>
  • Loading branch information
3 people committed Jul 2, 2022
1 parent 58e32cf commit 0bc25a0
Show file tree
Hide file tree
Showing 37 changed files with 6,021 additions and 5,568 deletions.
15 changes: 9 additions & 6 deletions apps/meteor/client/components/FilterByText.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { Box, Icon, TextInput, Button } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { FC, ChangeEvent, FormEvent, memo, useCallback, useEffect, useState } from 'react';
import React, { ReactNode, ChangeEvent, FormEvent, memo, useCallback, useEffect, useState, ReactElement } from 'react';

type FilterByTextProps = {
type FilterByTextCommonProps = {
children?: ReactNode | undefined;
placeholder?: string;
onChange: (filter: { text: string }) => void;
inputRef?: () => void;
onChange: (filter: { text: string }) => void;
};

type FilterByTextPropsWithButton = FilterByTextProps & {
type FilterByTextPropsWithButton = FilterByTextCommonProps & {
displayButton: true;
textButton: string;
onButtonClick: () => void;
};

type FilterByTextProps = FilterByTextCommonProps | FilterByTextPropsWithButton;

const isFilterByTextPropsWithButton = (props: any): props is FilterByTextPropsWithButton =>
'displayButton' in props && props.displayButton === true;

const FilterByText: FC<FilterByTextProps> = ({ placeholder, onChange: setFilter, inputRef, children, ...props }) => {
const FilterByText = ({ placeholder, onChange: setFilter, inputRef, children, ...props }: FilterByTextProps): ReactElement => {
const t = useTranslation();

const [text, setText] = useState('');
Expand Down Expand Up @@ -58,4 +61,4 @@ const FilterByText: FC<FilterByTextProps> = ({ placeholder, onChange: setFilter,
);
};

export default memo<FC<FilterByTextProps>>(FilterByText);
export default memo<FilterByTextProps>(FilterByText);
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { usePagination } from './hooks/usePagination';
const defaultParamsValue = { text: '', current: 0, itemsPerPage: 25 } as const;
const defaultSetParamsValue = (): void => undefined;

type GenericTableParams = {
export type GenericTableParams = {
text?: string;
current?: number;
itemsPerPage?: 25 | 50 | 100;
Expand Down
91 changes: 76 additions & 15 deletions apps/meteor/client/contexts/CallContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,31 @@ import { Device } from '@rocket.chat/ui-contexts';
import { createContext, useContext, useMemo } from 'react';
import { useSyncExternalStore } from 'use-sync-external-store/shim';

import { useHasLicenseModule } from '../../ee/client/hooks/useHasLicenseModule';
import { VoIPUser } from '../lib/voip/VoIPUser';

export type CallContextValue = CallContextDisabled | CallContextEnabled | CallContextReady | CallContextError;
export type CallContextValue = CallContextDisabled | CallContextReady | CallContextError | CallContextEnabled;

type CallContextDisabled = {
export type CallContextDisabled = {
enabled: false;
ready: false;
outBoundCallsAllowed: undefined;
outBoundCallsEnabled: undefined;
outBoundCallsEnabledForUser: undefined;
};

type CallContextEnabled = {
enabled: true;
ready: unknown;
outBoundCallsAllowed: undefined;
outBoundCallsEnabled: undefined;
outBoundCallsEnabledForUser: undefined;
};

type CallContextReady = {
canMakeCall: boolean;
outBoundCallsEnabled: boolean;
outBoundCallsAllowed: boolean;
outBoundCallsEnabledForUser: boolean;
enabled: true;
ready: true;
voipClient: VoIPUser;
Expand All @@ -28,15 +38,20 @@ type CallContextReady = {
openedRoomInfo: { v: { token?: string }; rid: string };
openWrapUpModal: () => void;
openRoom: (rid: IVoipRoom['_id']) => void;
createRoom: (caller: ICallerInfo) => IVoipRoom['_id'];
createRoom: (caller: ICallerInfo) => Promise<IVoipRoom['_id']>;
closeRoom: (data?: { comment?: string; tags?: string[] }) => void;
changeAudioOutputDevice: (selectedAudioDevices: Device) => void;
changeAudioInputDevice: (selectedAudioDevices: Device) => void;
register: () => void;
unregister: () => void;
};
type CallContextError = {
export type CallContextError = {
enabled: true;
ready: false;
error: Error;
outBoundCallsAllowed: undefined;
outBoundCallsEnabled: undefined;
outBoundCallsEnabledForUser: undefined;
error: Error | unknown;
};

export const isCallContextReady = (context: CallContextValue): context is CallContextReady => (context as CallContextReady).ready;
Expand All @@ -57,10 +72,15 @@ export type CallActionsType = {
const CallContextValueDefault: CallContextValue = {
enabled: false,
ready: false,
outBoundCallsAllowed: undefined,
outBoundCallsEnabled: undefined,
outBoundCallsEnabledForUser: undefined,
};

export const CallContext = createContext<CallContextValue>(CallContextValueDefault);

export const useIsVoipEnterprise = (): boolean => useHasLicenseModule('voip-enterprise') === true;

export const useIsCallEnabled = (): boolean => {
const { enabled } = useContext(CallContext);
return enabled;
Expand All @@ -76,7 +96,13 @@ export const useIsCallError = (): boolean => {
return Boolean(isCallContextError(context));
};

export const useCallContext = (): CallContextValue => useContext(CallContext);
export const useCallContext = (): CallContextValue => {
const context = useContext(CallContext);
if (!isCallContextReady(context)) {
throw new Error('useCallContext only if Calls are enabled and ready');
}
return context;
};

export const useCallActions = (): CallActionsType => {
const context = useContext(CallContext);
Expand Down Expand Up @@ -116,7 +142,7 @@ export const useCallCreateRoom = (): CallContextReady['createRoom'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useCallerInfo only if Calls are enabled and ready');
throw new Error('useCallCreateRoom only if Calls are enabled and ready');
}

return context.createRoom;
Expand All @@ -126,7 +152,7 @@ export const useCallOpenRoom = (): CallContextReady['openRoom'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useCallerInfo only if Calls are enabled and ready');
throw new Error('useCallOpenRoom only if Calls are enabled and ready');
}

return context.openRoom;
Expand All @@ -136,7 +162,7 @@ export const useCallCloseRoom = (): CallContextReady['closeRoom'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useCallerInfo only if Calls are enabled and ready');
throw new Error('useCallCloseRoom only if Calls are enabled and ready');
}

return context.closeRoom;
Expand All @@ -146,7 +172,7 @@ export const useCallClient = (): VoIPUser => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useClient only if Calls are enabled and ready');
throw new Error('useCallClient only if Calls are enabled and ready');
}

return context.voipClient;
Expand All @@ -156,7 +182,7 @@ export const useQueueName = (): CallContextReady['queueName'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useQueueInfo only if Calls are enabled and ready');
throw new Error('useQueueName only if Calls are enabled and ready');
}

return context.queueName;
Expand All @@ -166,7 +192,7 @@ export const useQueueCounter = (): CallContextReady['queueCounter'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useQueueInfo only if Calls are enabled and ready');
throw new Error('useQueueCounter only if Calls are enabled and ready');
}

return context.queueCounter;
Expand All @@ -176,7 +202,7 @@ export const useWrapUpModal = (): CallContextReady['openWrapUpModal'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useClient only if Calls are enabled and ready');
throw new Error('useWrapUpModal only if Calls are enabled and ready');
}

return context.openWrapUpModal;
Expand All @@ -186,7 +212,7 @@ export const useOpenedRoomInfo = (): CallContextReady['openedRoomInfo'] => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useClient only if Calls are enabled and ready');
throw new Error('useOpenedRoomInfo only if Calls are enabled and ready');
}

return context.openedRoomInfo;
Expand All @@ -211,3 +237,38 @@ export const useChangeAudioInputDevice = (): CallContextReady['changeAudioOutput

return context.changeAudioInputDevice;
};

export const useCallRegisterClient = (): (() => void) => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useCallRegisterClient only if Calls are enabled and ready');
}

return context.register;
};

export const useCallUnregisterClient = (): (() => void) => {
const context = useContext(CallContext);

if (!isCallContextReady(context)) {
throw new Error('useCallUnregisterClient only if Calls are enabled and ready');
}

return context.unregister;
};

export const useVoipOutboundStates = (): {
outBoundCallsAllowed: boolean;
outBoundCallsEnabled: boolean;
outBoundCallsEnabledForUser: boolean;
} => {
const isEnterprise = useIsVoipEnterprise();
const callerInfo = useCallerInfo();

return {
outBoundCallsAllowed: isEnterprise,
outBoundCallsEnabled: isEnterprise,
outBoundCallsEnabledForUser: isEnterprise && !['IN_CALL', 'ON_HOLD', 'UNREGISTERED', 'INITIAL'].includes(callerInfo.state),
};
};
23 changes: 0 additions & 23 deletions apps/meteor/client/contexts/VoIPAgentContext.ts

This file was deleted.

44 changes: 44 additions & 0 deletions apps/meteor/client/hooks/useDialModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useCallback, useMemo } from 'react';

import DialPadModal from '../../ee/client/voip/modal/DialPad/DialPadModal';
import { useIsVoipEnterprise } from '../contexts/CallContext';
import { dispatchToastMessage } from '../lib/toast';

type DialModalProps = {
initialValue?: string;
errorMessage?: string;
};

type DialModalControls = {
openDialModal: (props?: DialModalProps) => void;
closeDialModal: () => void;
};

export const useDialModal = (): DialModalControls => {
const setModal = useSetModal();
const isEnterprise = useIsVoipEnterprise();
const t = useTranslation();

const closeDialModal = useCallback(() => setModal(null), [setModal]);

const openDialModal = useCallback(
({ initialValue, errorMessage }: DialModalProps = {}) => {
if (!isEnterprise) {
dispatchToastMessage({ type: 'error', message: t('You_do_not_have_permission_to_do_this') });
return;
}

setModal(<DialPadModal initialValue={initialValue} errorMessage={errorMessage} handleClose={closeDialModal} />);
},
[setModal, isEnterprise, t, closeDialModal],
);

return useMemo(
() => ({
openDialModal,
closeDialModal,
}),
[openDialModal, closeDialModal],
);
};
23 changes: 20 additions & 3 deletions apps/meteor/client/lib/voip/VoIPUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
Registerer,
SessionInviteOptions,
RequestPendingError,
Inviter,
} from 'sip.js';
import { OutgoingByeRequest, OutgoingRequestDelegate, URI } from 'sip.js/lib/core';
import { SessionDescriptionHandler, SessionDescriptionHandlerOptions } from 'sip.js/lib/platform/web';
Expand Down Expand Up @@ -228,6 +229,10 @@ export class VoIPUser extends Emitter<VoipEvents> {
this._connectionState = 'WAITING_FOR_NETWORK';
}

get userConfig(): VoIPUserConfiguration {
return this.config;
}

get callState(): CallStates {
return this._callState;
}
Expand All @@ -237,7 +242,12 @@ export class VoIPUser extends Emitter<VoipEvents> {
}

get callerInfo(): VoIpCallerInfo {
if (this.callState === 'IN_CALL' || this.callState === 'OFFER_RECEIVED' || this.callState === 'ON_HOLD') {
if (
this.callState === 'IN_CALL' ||
this.callState === 'OFFER_RECEIVED' ||
this.callState === 'ON_HOLD' ||
this.callState === 'OFFER_SENT'
) {
if (!this._callerInfo) {
throw new Error('[VoIPUser callerInfo] invalid state');
}
Expand Down Expand Up @@ -661,7 +671,7 @@ export class VoIPUser extends Emitter<VoipEvents> {
}

private canEndOrHoldCall(): boolean {
return ['ANSWER_SENT', 'ANSWER_RECEIVED', 'IN_CALL', 'ON_HOLD'].includes(this._callState);
return ['ANSWER_SENT', 'ANSWER_RECEIVED', 'IN_CALL', 'ON_HOLD', 'OFFER_SENT'].includes(this._callState);
}

/* Helper routines for checking call actions END */
Expand Down Expand Up @@ -707,6 +717,9 @@ export class VoIPUser extends Emitter<VoipEvents> {
if (this.session instanceof Invitation) {
return this.session.reject();
}
if (this.session instanceof Inviter) {
return this.session.cancel();
}
throw new Error('Session not instance of Invitation.');
case SessionState.Established:
return this.session.bye();
Expand Down Expand Up @@ -1045,7 +1058,11 @@ export class VoIPUser extends Emitter<VoipEvents> {
// }
// eslint-disable-next-line @typescript-eslint/no-unused-vars

async makeCall(_callee: string, _mediaRenderer?: IMediaStreamRenderer): Promise<void> {
async makeCallURI(_callee: string, _mediaRenderer?: IMediaStreamRenderer): Promise<void> {
throw new Error('Not implemented');
}

async makeCall(_calleeNumber: string): Promise<void> {
throw new Error('Not implemented');
}
}
Loading

0 comments on commit 0bc25a0

Please sign in to comment.