Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GenericMenu } from '@rocket.chat/ui-client';
import { CallHistoryTableRow, useMediaCallContext } from '@rocket.chat/ui-voip';
import type { CallHistoryTableExternalContact, CallHistoryTableRowProps } from '@rocket.chat/ui-voip';
import { CallHistoryTableRow, useMediaCallContext, isCallingBlocked } from '@rocket.chat/ui-voip';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -11,25 +11,27 @@ type CallHistoryRowExternalUserProps = Omit<CallHistoryTableRowProps<CallHistory
const CallHistoryRowExternalUser = ({ _id, contact, type, status, duration, timestamp, onClick }: CallHistoryRowExternalUserProps) => {
const { t } = useTranslation();

const { onToggleWidget } = useMediaCallContext();
const { onToggleWidget, state } = useMediaCallContext();

const handleClick = useCallback(() => {
onClick(_id);
}, [onClick, _id]);

const actions = useMemo(() => {
if (!onToggleWidget) {
if (state === 'unauthorized' || state === 'unlicensed' || !onToggleWidget) {
return [];
}
return [
{
id: 'voiceCall',
icon: 'phone',
content: t('Voice_call'),
disabled: isCallingBlocked(state),
tooltip: isCallingBlocked(state) ? t('Call_in_progress') : undefined,
onClick: () => onToggleWidget({ number: contact.number }),
} as const,
];
}, [contact, onToggleWidget, t]);
}, [contact, onToggleWidget, t, state]);

return (
<CallHistoryTableRow
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Keys as IconName } from '@rocket.chat/icons';
import { GenericMenu } from '@rocket.chat/ui-client';
import { CallHistoryTableRow } from '@rocket.chat/ui-voip';
import type { CallHistoryTableRowProps, CallHistoryTableInternalContact } from '@rocket.chat/ui-voip';
import type { CallHistoryTableRowProps, CallHistoryTableInternalContact, MediaCallState } from '@rocket.chat/ui-voip';
import { CallHistoryTableRow, useMediaCallContext, isCallingBlocked } from '@rocket.chat/ui-voip';
import type { TFunction } from 'i18next';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -37,15 +37,20 @@ const i18nDictionary: Record<HistoryActions, string> = {
userInfo: 'User_info',
} as const;

const getItems = (actions: HistoryActionCallbacks, t: TFunction) => {
const getItems = (actions: HistoryActionCallbacks, t: TFunction, state: MediaCallState) => {
return (Object.entries(actions) as [HistoryActions, () => void][])
.filter(([_, callback]) => callback)
.map(([action, callback]) => ({
id: action,
icon: iconDictionary[action],
content: t(i18nDictionary[action]),
onClick: callback,
}));
.map(([action, callback]) => {
const disabled = action === 'voiceCall' && isCallingBlocked(state);
return {
id: action,
icon: iconDictionary[action],
content: t(i18nDictionary[action]),
disabled,
tooltip: disabled ? t('Call_in_progress') : undefined,
onClick: callback,
};
});
};

const CallHistoryRowInternalUser = ({
Expand All @@ -61,6 +66,7 @@ const CallHistoryRowInternalUser = ({
onClick,
}: CallHistoryRowInternalUserProps) => {
const { t } = useTranslation();
const { state } = useMediaCallContext();
const actions = useMediaCallInternalHistoryActions({
contact: {
_id: contact._id,
Expand All @@ -73,7 +79,7 @@ const CallHistoryRowInternalUser = ({
openUserInfo: onClickUserInfo ? (userId) => onClickUserInfo(userId, rid) : undefined,
});

const items = getItems(actions, t);
const items = getItems(actions, t, state);

const handleClick = useCallback(() => {
onClick(_id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export const useMediaCallInternalHistoryActions = ({
messageRoomId,
openUserInfo,
}: UseMediaCallInternalHistoryActionsBaseOptions) => {
const { onToggleWidget } = useMediaCallContext();
const { onToggleWidget, state } = useMediaCallContext();
const router = useRouter();

const getAvatarUrl = useUserAvatarPath();

const voiceCall = useEffectEvent(() => {
if (!onToggleWidget) {
if (state === 'unauthorized' || state === 'unlicensed' || !onToggleWidget) {
return;
}

Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,7 @@
"Call": "Call",
"Call_Already_Ended": "Call Already Ended",
"Call_ID": "Call ID",
"Call_in_progress": "Call in progress",
"Call_info": "Call info",
"Call_info_could_not_be_loaded": "Call info could not be loaded",
"Call_Information": "Call Information",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui-voip/src/context/MediaCallContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ const MediaCallContext = createContext<MediaCallContextType | MediaCallUnauthori
defaultMediaCallContextValue,
);

export type MediaCallExternalState = State | 'unauthorized' | 'unlicensed';

export const isCallingBlocked = (state: MediaCallExternalState) => {
return state !== 'new' && state !== 'closed';
};

// This hook is for internal use only. It will only be available if the user has the necessary permissions and the workspace has the necessary modules.
export const useMediaCallContext = (): MediaCallContextType => {
const context = useContext(MediaCallContext);
Expand Down
4 changes: 2 additions & 2 deletions packages/ui-voip/src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { useMediaCallContext, useMediaCallExternalContext, default as MediaCallContext, usePeerAutocomplete } from './MediaCallContext';
export type { PeerInfo, ConnectionState } from './MediaCallContext';
export { isFirstPeerAutocompleteOption } from './MediaCallContext';
export type { PeerInfo, ConnectionState, MediaCallExternalState as MediaCallState } from './MediaCallContext';
export { isFirstPeerAutocompleteOption, isCallingBlocked } from './MediaCallContext';
export { default as MockedMediaCallProvider } from './MockedMediaCallProvider';
4 changes: 2 additions & 2 deletions packages/ui-voip/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { default as MediaCallProvider } from './context/MediaCallProvider';

export { MediaCallContext, useMediaCallExternalContext as useMediaCallContext, type PeerInfo } from './context';

export { MediaCallContext, useMediaCallExternalContext as useMediaCallContext, isCallingBlocked } from './context';
export type { PeerInfo, MediaCallState } from './context';
export { useMediaCallAction } from './hooks';

export { CallHistoryContextualBar } from './views';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import type { ReactElement } from 'react';

import type { HistoryActionCallbacks } from './CallHistoryActions';
import CallHistoryActions from './CallHistoryActions';
import { MockedMediaCallProvider } from '../../context';
import type { State } from '../../context/MediaCallContext';

const noop = () => undefined;

Expand Down Expand Up @@ -36,23 +38,42 @@ const getArgs = (index: number) => {
return Object.fromEntries(actionList.slice(0, index).map((action) => [action, noop])) as HistoryActionCallbacks;
};

const getDecorator = (state: State) => {
return (Story: StoryFn): ReactElement => (
<MockedMediaCallProvider state={state}>
<Story />
</MockedMediaCallProvider>
);
};

export const Default: Story = {
args: {
onClose: noop,
actions: getArgs(5),
},
decorators: [getDecorator('closed')],
};

export const WithLessActions: Story = {
args: {
onClose: noop,
actions: getArgs(3),
},
decorators: [getDecorator('closed')],
};

export const WithSingleAction: Story = {
args: {
onClose: noop,
actions: getArgs(1),
},
decorators: [getDecorator('closed')],
};

export const WithDisabledVoiceCall: Story = {
args: {
onClose: noop,
actions: getArgs(1),
},
decorators: [getDecorator('ongoing')],
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { ContextualbarActions, ContextualbarClose, GenericMenu } from '@rocket.c
import type { TFunction } from 'i18next';
import { useTranslation } from 'react-i18next';

import type { MediaCallState } from '../../context';
import { isCallingBlocked, useMediaCallExternalContext } from '../../context/MediaCallContext';

type HistoryActions = 'voiceCall' | 'videoCall' | 'jumpToMessage' | 'directMessage' | 'userInfo';

export type HistoryActionCallbacks = {
Expand Down Expand Up @@ -30,21 +33,27 @@ const i18nDictionary: Record<HistoryActions, string> = {
userInfo: 'User_info',
} as const;

const getItems = (actions: HistoryActionCallbacks, t: TFunction) => {
const getItems = (actions: HistoryActionCallbacks, t: TFunction, state: MediaCallState) => {
return (Object.entries(actions) as [HistoryActions, () => void][])
.filter(([_, callback]) => callback)
.map(([action, callback]) => ({
id: action,
icon: iconDictionary[action],
content: t(i18nDictionary[action]),
onClick: callback,
}));
.map(([action, callback]) => {
const disabled = action === 'voiceCall' && isCallingBlocked(state);
return {
id: action,
icon: iconDictionary[action],
content: t(i18nDictionary[action]),
disabled,
onClick: callback,
tooltip: disabled ? t('Call_in_progress') : undefined,
};
});
};

const CallHistoryActions = ({ onClose, actions }: CallHistoryActionsProps) => {
const { t } = useTranslation();

const items = getItems(actions, t);
const { state } = useMediaCallExternalContext();
const items = getItems(actions, t, state);
return (
<ContextualbarActions>
{items.length > 0 && <GenericMenu title={t('Options')} items={items} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type { HistoryActionCallbacks } from './CallHistoryActions';
import CallHistoryActions from './CallHistoryActions';
import { useFullStartDate } from './useFullStartDate';
import { CallHistoryExternalUser, CallHistoryInternalUser } from '../../components';
import { useMediaCallExternalContext } from '../../context';
import { isCallingBlocked } from '../../context/MediaCallContext';
import { getHistoryMessagePayload } from '../../ui-kit/getHistoryMessagePayload';

export type InternalCallHistoryContact = {
Expand Down Expand Up @@ -64,6 +66,7 @@ const CallHistoryContextualBar = ({ onClose, actions, contact, data }: CallHisto

const { voiceCall, directMessage } = actions;
const { duration, callId, direction, startedAt } = data;
const { state } = useMediaCallExternalContext();

const date = useFullStartDate(startedAt);
return (
Expand Down Expand Up @@ -117,7 +120,12 @@ const CallHistoryContextualBar = ({ onClose, actions, contact, data }: CallHisto
</Button>
)}
{voiceCall && (
<Button success onClick={voiceCall}>
<Button
success
onClick={voiceCall}
disabled={isCallingBlocked(state)}
title={isCallingBlocked(state) ? t('Call_in_progress') : undefined}
>
<Icon name='phone' size='x20' mie='x4' />
{t('Call')}
</Button>
Expand Down
Loading