[FIX] Opening a new DM from user card (#24623)

* Change `useRoomIcon` signature

* Fix `IRoom[t]` and `ISubscription[t]`

* Ensure `IRoom['uids']` is not mandatory

* Fix some typings
pull/24752/head^2
Tasso Evangelista 4 years ago committed by GitHub
parent d3b1b162fa
commit c04d544ff8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      client/hooks/usePresence.ts
  2. 23
      client/hooks/useRoomIcon.tsx
  3. 8
      client/lib/RoomManager.ts
  4. 7
      client/views/omnichannel/directory/calls/contextualBar/VoipInfo.tsx
  5. 15
      client/views/room/Header/DirectRoomHeader.js
  6. 32
      client/views/room/Header/DirectRoomHeader.tsx
  7. 11
      client/views/room/Header/Header.tsx
  8. 11
      client/views/room/Header/HeaderIconWithRoom.tsx
  9. 19
      client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx
  10. 11
      client/views/room/Header/ParentRoom.tsx
  11. 17
      client/views/room/Header/ParentRoomWithData.js
  12. 28
      client/views/room/Header/ParentRoomWithData.tsx
  13. 34
      client/views/room/Header/ParentRoomWithEndpointData.js
  14. 27
      client/views/room/Header/ParentRoomWithEndpointData.tsx
  15. 34
      client/views/room/Header/ParentTeam.tsx
  16. 6
      client/views/room/Header/RoomTitle.tsx
  17. 2
      client/views/room/providers/ToolboxProvider.tsx
  18. 5
      client/views/room/providers/VirtualAction.tsx
  19. 9
      definition/IRoom.ts
  20. 5
      definition/ISubscription.ts
  21. 1
      definition/RoomType.ts

@ -12,7 +12,7 @@ type Presence = 'online' | 'offline' | 'busy' | 'away' | 'loading';
* @returns UserPresence
* @public
*/
export const usePresence = (uid: string): UserPresence | undefined => {
export const usePresence = (uid: string | undefined): UserPresence | undefined => {
const subscription = useMemo(
() => ({
getCurrentValue: (): UserPresence | undefined => (uid ? Presence.store.get(uid) : undefined),

@ -1,6 +1,7 @@
import React, { ReactNode } from 'react';
import { Icon } from '@rocket.chat/fuselage';
import React, { ComponentProps, ReactElement } from 'react';
import { IRoom, isDirectMessageRoom } from '../../definition/IRoom';
import { IRoom } from '../../definition/IRoom';
import { ReactiveUserStatus } from '../components/UserStatus';
export const colors = {
@ -10,7 +11,9 @@ export const colors = {
offline: 'neutral-600',
};
export const useRoomIcon = (room: IRoom): ReactNode | { name: string; color?: string } | null => {
export const useRoomIcon = (
room: Pick<IRoom, 't' | 'prid' | 'teamMain' | 'uids' | 'u'>,
): ReactElement | ComponentProps<typeof Icon> | null => {
if (room.prid) {
return { name: 'baloons' };
}
@ -19,13 +22,21 @@ export const useRoomIcon = (room: IRoom): ReactNode | { name: string; color?: st
return { name: room.t === 'p' ? 'team-lock' : 'team' };
}
if (isDirectMessageRoom(room)) {
if (room.t === 'd') {
if (room.uids && room.uids.length > 2) {
return { name: 'balloon' };
}
if (room.u && room.uids && room.uids.length > 0) {
return <ReactiveUserStatus uid={room.uids.find((uid) => uid !== room.u._id) || room.u._id} />;
if (room.uids && room.uids.length > 0) {
const uid = room.uids.find((uid) => uid !== room?.u?._id) || room?.u?._id;
if (!uid) {
return null;
}
return <ReactiveUserStatus uid={uid} />;
}
return { name: 'at' };
}

@ -149,10 +149,12 @@ export const useHandleRoom = <T extends IRoom>(rid: IRoom['_id']): AsyncState<T>
const room = uid ? subscription || _room : _room;
useEffect(() => {
if (room) {
update();
resolve(room);
if (!room) {
return;
}
update();
resolve(room);
}, [resolve, update, room]);
return state;

@ -30,6 +30,7 @@ export const VoipInfo = ({ room, onClickClose /* , onClickReport, onClickCall */
const phoneNumber = Array.isArray(v?.phone) ? v?.phone[0]?.phoneNumber : v?.phone;
const shouldShowWrapup = useMemo(() => lastMessage?.t === 'voip-call-wrapup' && lastMessage?.msg, [lastMessage]);
const shouldShowTags = useMemo(() => tags && tags.length > 0, [tags]);
const _name = name || fname;
return (
<>
@ -48,12 +49,12 @@ export const VoipInfo = ({ room, onClickClose /* , onClickReport, onClickCall */
</Box>
</InfoPanel.Field>
{servedBy && <AgentField isSmall agent={servedBy} />}
{v && (name || fname) && (
{v && _name && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Contact')}</InfoPanel.Label>
<Box display='flex'>
<UserAvatar size='x28' username={name || fname} />
<UserCard.Username mis='x8' title={name || fname} name={name || fname} status={<UserStatus status={v?.status} />} />
<UserAvatar size='x28' username={_name} />
<UserCard.Username mis='x8' title={_name} name={_name} status={<UserStatus status={v?.status} />} />
</Box>
</InfoPanel.Field>
)}

@ -1,15 +0,0 @@
import React from 'react';
import { useUserId } from '../../../contexts/UserContext';
import { usePresence } from '../../../hooks/usePresence';
import RoomHeader from './RoomHeader';
const DirectRoomHeader = ({ room, slots }) => {
const userId = useUserId();
const directUserId = room.uids.filter((uid) => uid !== userId).shift();
const directUserData = usePresence(directUserId);
return <RoomHeader slots={slots} room={room} topic={directUserData?.statusText} />;
};
export default DirectRoomHeader;

@ -0,0 +1,32 @@
import React, { ReactElement } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import { useUserId } from '../../../contexts/UserContext';
import { usePresence } from '../../../hooks/usePresence';
import RoomHeader from './RoomHeader';
type DirectRoomHeaderProps = {
room: IRoom;
slots: {
start?: unknown;
preContent?: unknown;
insideContent?: unknown;
posContent?: unknown;
end?: unknown;
toolbox?: {
pre?: unknown;
content?: unknown;
pos?: unknown;
};
};
};
const DirectRoomHeader = ({ room, slots }: DirectRoomHeaderProps): ReactElement => {
const userId = useUserId();
const directUserId = room.uids?.filter((uid) => uid !== userId).shift();
const directUserData = usePresence(directUserId);
return <RoomHeader slots={slots} room={room} topic={directUserData?.statusText} />;
};
export default DirectRoomHeader;

@ -1,5 +1,6 @@
import React, { memo, useMemo } from 'react';
import React, { memo, ReactElement, useMemo } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import BurgerMenu from '../../../components/BurgerMenu';
import TemplateHeader from '../../../components/Header';
import { useLayout } from '../../../contexts/LayoutContext';
@ -8,7 +9,11 @@ import OmnichannelRoomHeader from './Omnichannel/OmnichannelRoomHeader';
import VoipRoomHeader from './Omnichannel/VoipRoomHeader';
import RoomHeader from './RoomHeader';
const Header = ({ room }) => {
type HeaderProps = {
room: IRoom;
};
const Header = ({ room }: HeaderProps): ReactElement | null => {
const { isMobile, isEmbedded, showTopNavbarEmbeddedLayout } = useLayout();
const slots = useMemo(
@ -26,7 +31,7 @@ const Header = ({ room }) => {
return null;
}
if (room.t === 'd' && room.uids?.length < 3) {
if (room.t === 'd' && (room.uids?.length ?? 0) < 3) {
return <DirectRoomHeader slots={slots} room={room} />;
}

@ -1,15 +1,20 @@
import React from 'react';
import React, { ReactElement } from 'react';
import { isOmnichannelRoom } from '../../../../definition/IRoom';
import { IRoom, isOmnichannelRoom } from '../../../../definition/IRoom';
import Header from '../../../components/Header';
import { OmnichannelRoomIcon } from '../../../components/RoomIcon/OmnichannelRoomIcon';
import { useRoomIcon } from '../../../hooks/useRoomIcon';
const HeaderIconWithRoom = ({ room }) => {
type HeaderIconWithRoomProps = {
room: IRoom;
};
const HeaderIconWithRoom = ({ room }: HeaderIconWithRoomProps): ReactElement => {
const icon = useRoomIcon(room);
if (isOmnichannelRoom(room)) {
return <OmnichannelRoomIcon room={room} size='x20' placement='default' />;
}
return <Header.Icon icon={icon} />;
};
export default HeaderIconWithRoom;

@ -7,12 +7,27 @@ import { useCurrentRoute } from '../../../../contexts/RouterContext';
import { useOmnichannelRoom } from '../../contexts/RoomContext';
import { ToolboxActionConfig } from '../../lib/Toolbox';
import { ToolboxContext, useToolboxContext } from '../../lib/Toolbox/ToolboxContext';
import RoomHeader, { RoomHeaderProps } from '../RoomHeader';
import RoomHeader from '../RoomHeader';
import { BackButton } from './BackButton';
import QuickActions from './QuickActions';
import { useQuickActions } from './QuickActions/hooks/useQuickActions';
const OmnichannelRoomHeader: FC<RoomHeaderProps> = ({ slots: parentSlot }) => {
type OmnichannelRoomHeaderProps = {
slots: {
start?: unknown;
preContent?: unknown;
insideContent?: unknown;
posContent?: unknown;
end?: unknown;
toolbox?: {
pre?: unknown;
content?: unknown;
pos?: unknown;
};
};
};
const OmnichannelRoomHeader: FC<OmnichannelRoomHeaderProps> = ({ slots: parentSlot }) => {
const [name] = useCurrentRoute();
const { isMobile } = useLayout();
const room = useOmnichannelRoom();

@ -1,11 +1,16 @@
import React from 'react';
import React, { ReactElement } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import Header from '../../../components/Header';
import { useRoomIcon } from '../../../hooks/useRoomIcon';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
const ParentRoom = ({ room }) => {
const href = roomCoordinator.getRouteLink(room.t, room);
type ParentRoomProps = {
room: Pick<IRoom, '_id' | 't' | 'name' | 'fname' | 'prid' | 'u'>;
};
const ParentRoom = ({ room }: ParentRoomProps): ReactElement => {
const href = roomCoordinator.getRouteLink(room.t, room) || undefined;
const icon = useRoomIcon(room);
return (

@ -1,17 +0,0 @@
import React from 'react';
import { useUserSubscription } from '../../../contexts/UserContext';
import ParentRoom from './ParentRoom';
import ParentRoomWithEndpointData from './ParentRoomWithEndpointData';
const ParentRoomWithData = ({ room }) => {
const subscription = useUserSubscription(room.prid);
if (subscription) {
return <ParentRoom room={subscription} />;
}
return <ParentRoomWithEndpointData rid={room.prid} />;
};
export default ParentRoomWithData;

@ -0,0 +1,28 @@
import React, { ReactElement } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import { useUserSubscription } from '../../../contexts/UserContext';
import ParentRoom from './ParentRoom';
import ParentRoomWithEndpointData from './ParentRoomWithEndpointData';
type ParentRoomWithDataProps = {
room: IRoom;
};
const ParentRoomWithData = ({ room }: ParentRoomWithDataProps): ReactElement => {
const { prid } = room;
if (!prid) {
throw new Error('Parent room ID is missing');
}
const subscription = useUserSubscription(prid);
if (subscription) {
return <ParentRoom room={subscription} />;
}
return <ParentRoomWithEndpointData rid={prid} />;
};
export default ParentRoomWithData;

@ -1,34 +0,0 @@
import React, { useEffect } from 'react';
import Header from '../../../components/Header';
import { useEndpoint } from '../../../contexts/ServerContext';
import { AsyncStatePhase, useAsyncState } from '../../../hooks/useAsyncState';
import ParentRoom from './ParentRoom';
const ParentRoomWithEndpointData = ({ rid }) => {
const { resolve, reject, reset, phase, value } = useAsyncState();
const getData = useEndpoint('GET', 'rooms.info');
useEffect(() => {
(async () => {
reset();
getData({ roomId: rid })
.then(resolve)
.catch((error) => {
reject(error);
});
})();
}, [reset, getData, rid, resolve, reject]);
if (AsyncStatePhase.LOADING === phase) {
return <Header.Tag.Skeleton />;
}
if (AsyncStatePhase.ERROR === phase || !value?.room) {
return null;
}
return <ParentRoom room={value.room} />;
};
export default ParentRoomWithEndpointData;

@ -0,0 +1,27 @@
import React, { ReactElement } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import Header from '../../../components/Header';
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import ParentRoom from './ParentRoom';
type ParentRoomWithEndpointDataProps = {
rid: IRoom['_id'];
};
const ParentRoomWithEndpointData = ({ rid }: ParentRoomWithEndpointDataProps): ReactElement | null => {
const { phase, value } = useEndpointData('rooms.info', { roomId: rid });
if (AsyncStatePhase.LOADING === phase) {
return <Header.Tag.Skeleton />;
}
if (AsyncStatePhase.REJECTED === phase || !value?.room) {
return null;
}
return <ParentRoom room={value.room} />;
};
export default ParentRoomWithEndpointData;

@ -1,5 +1,6 @@
import React, { useMemo } from 'react';
import React, { ReactElement, useMemo } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import { TEAM_TYPE } from '../../../../definition/ITeam';
import Header from '../../../components/Header';
import { useUserId } from '../../../contexts/UserContext';
@ -7,12 +8,25 @@ import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import { goToRoomById } from '../../../lib/utils/goToRoomById';
const ParentTeam = ({ room }) => {
type ParentTeamProps = {
room: IRoom;
};
const ParentTeam = ({ room }: ParentTeamProps): ReactElement | null => {
const { teamId } = room;
const userId = useUserId();
if (!teamId) {
throw new Error('invalid rid');
}
if (!userId) {
throw new Error('invalid uid');
}
const { value, phase } = useEndpointData(
'teams.info',
useMemo(() => ({ teamId: room.teamId }), [room.teamId]),
useMemo(() => ({ teamId }), [teamId]),
);
const { value: userTeams, phase: userTeamsPhase } = useEndpointData(
@ -20,15 +34,23 @@ const ParentTeam = ({ room }) => {
useMemo(() => ({ userId }), [userId]),
);
const belongsToTeam = userTeams?.teams?.find((team) => team._id === room.teamId) || false;
const belongsToTeam = userTeams?.teams?.find((team) => team._id === teamId) || false;
const isTeamPublic = value?.teamInfo.type === TEAM_TYPE.PUBLIC;
const teamMainRoomHref = () => goToRoomById(value?.teamInfo.roomId);
const teamMainRoomHref = (): void => {
const rid = value?.teamInfo.roomId;
if (!rid) {
return;
}
goToRoomById(rid);
};
if (phase === AsyncStatePhase.LOADING || userTeamsPhase === AsyncStatePhase.LOADING) {
return <Header.Tag.Skeleton />;
}
if (phase === AsyncStatePhase.REJECTED || !value.teamInfo) {
if (phase === AsyncStatePhase.REJECTED || !value?.teamInfo) {
return null;
}

@ -4,7 +4,11 @@ import { IRoom } from '../../../../definition/IRoom';
import Header from '../../../components/Header';
import HeaderIconWithRoom from './HeaderIconWithRoom';
const RoomTitle = ({ room }: { room: IRoom }): ReactElement => (
type RoomTitleProps = {
room: IRoom;
};
const RoomTitle = ({ room }: RoomTitleProps): ReactElement => (
<>
<HeaderIconWithRoom room={room} />
<Header.Title>{room.name}</Header.Title>

@ -71,7 +71,7 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom
open('room-info', username);
break;
case 'd':
room.uids?.length > 2 ? open('user-info-group', username) : open('user-info', username);
(room.uids?.length ?? 0) > 2 ? open('user-info-group', username) : open('user-info', username);
break;
default:
open('members-list', username);

@ -1,10 +1,11 @@
import { useLayoutEffect, memo } from 'react';
import { IRoom } from '../../../../definition/IRoom';
import { RoomType } from '../../../../definition/RoomType';
import { Store } from '../lib/Toolbox/generator';
import { ToolboxAction } from '../lib/Toolbox/index';
const groupsDict = {
const groupsDict: Record<RoomType, string> = {
l: 'live',
v: 'voip',
d: 'direct',
@ -18,7 +19,7 @@ const getGroup = (room: IRoom): string => {
return 'team';
}
if (group === groupsDict.d && room.uids?.length > 2) {
if (group === groupsDict.d && (room.uids?.length ?? 0) > 2) {
return 'direct_multiple';
}

@ -1,8 +1,8 @@
import { IRocketChatRecord } from './IRocketChatRecord';
import { IMessage } from './IMessage';
import { IUser, Username } from './IUser';
import { RoomType } from './RoomType';
export type RoomType = 'c' | 'd' | 'p' | 'l' | 'v';
type CallStatus = 'ringing' | 'ended' | 'declined' | 'ongoing';
export type RoomID = string;
@ -18,7 +18,7 @@ export interface IRoom extends IRocketChatRecord {
_id: RoomID;
t: RoomType;
name?: string;
fname: string;
fname?: string;
msgs: number;
default?: true;
broadcast?: true;
@ -27,7 +27,7 @@ export interface IRoom extends IRocketChatRecord {
topic?: any;
u: Pick<IUser, '_id' | 'username' | 'name'>;
uids: Array<string>;
uids?: Array<string>;
lastMessage?: IMessage;
lm?: Date;
@ -229,3 +229,6 @@ export const isOmnichannelRoomFromAppSource = (room: IRoom): room is IOmnichanne
return room.source?.type === OmnichannelSourceType.APP;
};
/** @deprecated */
export { RoomType };

@ -1,7 +1,6 @@
import { IRocketChatRecord } from './IRocketChatRecord';
import { IUser } from './IUser';
type RoomType = 'c' | 'd' | 'p' | 'l';
import { RoomType } from './RoomType';
type RoomID = string;
@ -40,7 +39,7 @@ export interface ISubscription extends IRocketChatRecord {
E2EKey?: string;
unreadAlert?: 'default' | 'all' | 'mentions' | 'nothing';
fname?: unknown;
fname?: string;
code?: unknown;
archived?: unknown;

@ -0,0 +1 @@
export type RoomType = 'c' | 'd' | 'p' | 'l' | 'v';
Loading…
Cancel
Save