diff --git a/.changeset/eighty-weeks-rush.md b/.changeset/eighty-weeks-rush.md new file mode 100644 index 0000000000000..d79aadd40c93a --- /dev/null +++ b/.changeset/eighty-weeks-rush.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/ui-client": patch +"@rocket.chat/ui-voip": patch +--- + +Introduces in the call widget a quick link that redirects to the participant's direct message diff --git a/apps/meteor/client/providers/MediaCallProvider.tsx b/apps/meteor/client/providers/MediaCallProvider.tsx index 68d773d49e776..006b2bb067f22 100644 --- a/apps/meteor/client/providers/MediaCallProvider.tsx +++ b/apps/meteor/client/providers/MediaCallProvider.tsx @@ -17,6 +17,7 @@ const MediaCallProvider = ({ children }: { children: ReactNode }) => { onToggleWidget: undefined, onEndCall: undefined, peerInfo: undefined, + setOpenRoomId: undefined, }), [], ); diff --git a/apps/meteor/client/views/room/Room.tsx b/apps/meteor/client/views/room/Room.tsx index fed2a9da31e58..cc5a119f61c02 100644 --- a/apps/meteor/client/views/room/Room.tsx +++ b/apps/meteor/client/views/room/Room.tsx @@ -1,6 +1,7 @@ import { isInviteSubscription } from '@rocket.chat/core-typings'; import { ContextualbarSkeleton } from '@rocket.chat/ui-client'; import { useSetting, useRoomToolbox, useUserId } from '@rocket.chat/ui-contexts'; +import { useMediaCallOpenRoomTracker } from '@rocket.chat/ui-voip'; import type { ReactElement } from 'react'; import { createElement, lazy, memo, Suspense } from 'react'; import { FocusScope } from 'react-aria'; @@ -34,6 +35,8 @@ const Room = (): ReactElement => { const roomLabel = room.t === 'd' ? t('Conversation_with__roomName__', { roomName: room.name }) : t('Channel__roomName__', { roomName: room.name }); + useMediaCallOpenRoomTracker(room._id); + if (subscription && isInviteSubscription(subscription)) { return ( diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.spec.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.spec.tsx index 68a16d072ee68..0f7f038df95bf 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.spec.tsx +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.spec.tsx @@ -46,6 +46,7 @@ describe('useUserMediaCallAction', () => { onToggleWidget: undefined, onEndCall: undefined, peerInfo: undefined, + setOpenRoomId: undefined, }); const { result } = renderHook(() => useUserMediaCallAction(fakeUser, mockRid), { wrapper: mockAppRoot().build() }); @@ -114,6 +115,7 @@ describe('useUserMediaCallAction', () => { onToggleWidget: mockOnToggleWidget, peerInfo: undefined, onEndCall: () => undefined, + setOpenRoomId: () => undefined, }); const { result } = renderHook(() => useUserMediaCallAction(fakeUser, mockRid)); @@ -133,6 +135,7 @@ describe('useUserMediaCallAction', () => { onToggleWidget: jest.fn(), peerInfo: undefined, onEndCall: () => undefined, + setOpenRoomId: () => undefined, }); const { result } = renderHook(() => useUserMediaCallAction(fakeUser, mockRid)); diff --git a/packages/ui-voip/src/components/Widget/WidgetHeader.tsx b/packages/ui-voip/src/components/Widget/WidgetHeader.tsx index bc76e085a77e9..8d635607be9a9 100644 --- a/packages/ui-voip/src/components/Widget/WidgetHeader.tsx +++ b/packages/ui-voip/src/components/Widget/WidgetHeader.tsx @@ -13,7 +13,9 @@ const WidgetHeader = ({ title, children }: WidgetHeaderProps): ReactElement => { {title} - {children} + + {children} + ); }; diff --git a/packages/ui-voip/src/context/MediaCallContext.ts b/packages/ui-voip/src/context/MediaCallContext.ts index 9c8a9d93db14e..b01b346c95b92 100644 --- a/packages/ui-voip/src/context/MediaCallContext.ts +++ b/packages/ui-voip/src/context/MediaCallContext.ts @@ -38,6 +38,8 @@ type MediaCallContextType = { remoteMuted: boolean; remoteHeld: boolean; + onClickDirectMessage?: () => void; + onMute: () => void; onHold: () => void; @@ -54,6 +56,8 @@ type MediaCallContextType = { onSelectPeer: (peerInfo: PeerInfo) => void; + setOpenRoomId: (roomId: string | undefined) => void; + getAutocompleteOptions: (filter: string) => Promise; // This is used to get the peer info from the server in case it's not available in the autocomplete options. getPeerInfo: (id: string) => Promise; @@ -88,6 +92,8 @@ export const defaultMediaCallContextValue: MediaCallContextType = { onSelectPeer: () => undefined, + setOpenRoomId: () => undefined, + getAutocompleteOptions: () => Promise.resolve([]), getPeerInfo: () => Promise.resolve(undefined), }; @@ -97,6 +103,7 @@ type MediaCallExternalContextType = { onToggleWidget: (peerInfo?: PeerInfo) => void; onEndCall: () => void; peerInfo: PeerInfo | undefined; + setOpenRoomId: (roomId: string | undefined) => void; }; type MediaCallUnauthorizedContextType = { @@ -104,6 +111,7 @@ type MediaCallUnauthorizedContextType = { onToggleWidget: undefined; onEndCall: undefined; peerInfo: undefined; + setOpenRoomId: undefined; }; type MediaCallUnlicensedContextType = { @@ -111,6 +119,7 @@ type MediaCallUnlicensedContextType = { onToggleWidget: (peerInfo?: any) => void; onEndCall: undefined; peerInfo: undefined; + setOpenRoomId: undefined; }; const MediaCallContext = createContext( @@ -146,7 +155,13 @@ export const useMediaCallExternalContext = (): return context; } - return { state: context.state, onToggleWidget: context.onToggleWidget, onEndCall: context.onEndCall, peerInfo: context.peerInfo }; + return { + state: context.state, + onToggleWidget: context.onToggleWidget, + onEndCall: context.onEndCall, + peerInfo: context.peerInfo, + setOpenRoomId: context.setOpenRoomId, + }; }; export default MediaCallContext; diff --git a/packages/ui-voip/src/context/MediaCallProvider.tsx b/packages/ui-voip/src/context/MediaCallProvider.tsx index 2c665a9eb20ae..2ca12897d604a 100644 --- a/packages/ui-voip/src/context/MediaCallProvider.tsx +++ b/packages/ui-voip/src/context/MediaCallProvider.tsx @@ -1,4 +1,4 @@ -import { AnchorPortal } from '@rocket.chat/ui-client'; +import { AnchorPortal, useGoToDirectMessage } from '@rocket.chat/ui-client'; import type { Device } from '@rocket.chat/ui-contexts'; import { useEndpoint, @@ -12,7 +12,7 @@ import { useSetting, } from '@rocket.chat/ui-contexts'; import type { ReactNode } from 'react'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; @@ -36,6 +36,7 @@ const MediaCallProvider = ({ children }: MediaCallProviderProps) => { const user = useUser(); const { t } = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); + const [openRoomId, setOpenRoomId] = useState(undefined); const setModal = useSetModal(); @@ -57,6 +58,11 @@ const MediaCallProvider = ({ children }: MediaCallProviderProps) => { const forceSIPRouting = useSetting('VoIP_TeamCollab_SIP_Integration_For_Internal_Calls'); + const onClickDirectMessage = useGoToDirectMessage( + { username: session.peerInfo && 'username' in session.peerInfo ? session.peerInfo.username : undefined }, + openRoomId, + ); + // For some reason `exhaustive-deps` is complaining that "session" is not in the dependencies // But we're only using the changeDevice method from the session // So I'll just destructure it here @@ -262,6 +268,8 @@ const MediaCallProvider = ({ children }: MediaCallProviderProps) => { hidden: session.hidden, remoteMuted: session.remoteMuted, remoteHeld: session.remoteHeld, + onClickDirectMessage, + setOpenRoomId, onMute, onHold, onDeviceChange, diff --git a/packages/ui-voip/src/context/MockedMediaCallProvider.tsx b/packages/ui-voip/src/context/MockedMediaCallProvider.tsx index f361fdb2c5a19..ed0e429e450d1 100644 --- a/packages/ui-voip/src/context/MockedMediaCallProvider.tsx +++ b/packages/ui-voip/src/context/MockedMediaCallProvider.tsx @@ -16,11 +16,13 @@ type MockedMediaCallProviderProps = { remoteHeld?: boolean; muted?: boolean; held?: boolean; + onClickDirectMessage?: () => void; }; const MockedMediaCallProvider = ({ children, state = 'closed', + onClickDirectMessage = undefined, transferredBy = undefined, remoteMuted = false, remoteHeld = false, @@ -131,6 +133,8 @@ const MockedMediaCallProvider = ({ transferredBy, muted: mutedState, held: heldState, + setOpenRoomId: () => undefined, + onClickDirectMessage, remoteMuted, remoteHeld, onMute, diff --git a/packages/ui-voip/src/hooks/index.ts b/packages/ui-voip/src/hooks/index.ts index 3f9656838500c..7e9aeb8130db0 100644 --- a/packages/ui-voip/src/hooks/index.ts +++ b/packages/ui-voip/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useDevicePermissionPrompt'; export { useDraggable } from './VoipPopupDraggable/DraggableCore'; export { useMediaCallAction } from './useMediaCallAction'; +export { useMediaCallOpenRoomTracker } from './useMediaCallOpenRoomTracker'; diff --git a/packages/ui-voip/src/hooks/useMediaCallOpenRoomTracker.ts b/packages/ui-voip/src/hooks/useMediaCallOpenRoomTracker.ts new file mode 100644 index 0000000000000..0059d158fb045 --- /dev/null +++ b/packages/ui-voip/src/hooks/useMediaCallOpenRoomTracker.ts @@ -0,0 +1,17 @@ +import { useEffect } from 'react'; + +import { useMediaCallExternalContext } from '../context/MediaCallContext'; + +export const useMediaCallOpenRoomTracker = (openRoomId?: string) => { + const { setOpenRoomId } = useMediaCallExternalContext(); + + useEffect(() => { + if (!setOpenRoomId) { + return; + } + setOpenRoomId(openRoomId); + return () => { + setOpenRoomId(undefined); + }; + }, [setOpenRoomId, openRoomId]); +}; diff --git a/packages/ui-voip/src/index.ts b/packages/ui-voip/src/index.ts index a659830740e53..ad103e6939a2f 100644 --- a/packages/ui-voip/src/index.ts +++ b/packages/ui-voip/src/index.ts @@ -2,7 +2,7 @@ export { default as MediaCallProvider } from './context/MediaCallProvider'; export { MediaCallContext, useMediaCallExternalContext as useMediaCallContext, isCallingBlocked } from './context'; export type { PeerInfo, MediaCallState } from './context'; -export { useMediaCallAction } from './hooks'; +export { useMediaCallAction, useMediaCallOpenRoomTracker } from './hooks'; export { CallHistoryContextualBar } from './views'; export type { InternalCallHistoryContact, ExternalCallHistoryContact, CallHistoryData } from './views'; diff --git a/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.stories.tsx b/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.stories.tsx index d97afe302369d..291574bdb1680 100644 --- a/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.stories.tsx +++ b/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.stories.tsx @@ -62,3 +62,11 @@ export const OngoingCallWithSlotsAndRemoteStatus: StoryFn = ); }; + +export const OngoingCallWithDmButton: StoryFn = () => { + return ( + undefined}> + + + ); +}; diff --git a/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx b/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx index f1827c563e69e..b07475b82c308 100644 --- a/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx +++ b/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx @@ -21,8 +21,20 @@ import { useMediaCallContext } from '../../context'; const OngoingCall = () => { const { t } = useTranslation(); - const { muted, held, remoteMuted, remoteHeld, onMute, onHold, onForward, onEndCall, onTone, peerInfo, connectionState } = - useMediaCallContext(); + const { + muted, + held, + remoteMuted, + remoteHeld, + onMute, + onHold, + onForward, + onEndCall, + onTone, + peerInfo, + connectionState, + onClickDirectMessage, + } = useMediaCallContext(); const { element: keypad, buttonProps: keypadButtonProps } = useKeypad(onTone); @@ -41,6 +53,9 @@ const OngoingCall = () => { }> + {onClickDirectMessage && ( + + )} diff --git a/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap b/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap index dee814d8a2f74..d9c2c8430eb0e 100644 --- a/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap +++ b/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap @@ -31,7 +31,7 @@ exports[`renders IncomingCall without crashing 1`] = ` Incoming Call...