diff --git a/app/message-read-receipt/client/index.js b/app/message-read-receipt/client/index.js
deleted file mode 100644
index 537b2f2268a..00000000000
--- a/app/message-read-receipt/client/index.js
+++ /dev/null
@@ -1 +0,0 @@
-import './views/readReceipts';
diff --git a/app/message-read-receipt/client/views/readReceipts.html b/app/message-read-receipt/client/views/readReceipts.html
deleted file mode 100644
index 3a81ee9cdcd..00000000000
--- a/app/message-read-receipt/client/views/readReceipts.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
- {{#if isLoading}}
- {{> loading class="loading-animation--primary"}}
- {{else}}
- {{_ "Read_by"}}:
-
- {{#each receipts}}
- -
- {{> avatar username=user.username}}
-
{{displayName}}
- {{time}}
-
- {{/each}}
-
- {{/if}}
-
diff --git a/app/message-read-receipt/client/views/readReceipts.js b/app/message-read-receipt/client/views/readReceipts.js
deleted file mode 100644
index 7664d860ba1..00000000000
--- a/app/message-read-receipt/client/views/readReceipts.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import moment from 'moment';
-
-import { settings } from '../../../settings';
-
-import './readReceipts.html';
-
-Template.readReceipts.helpers({
- receipts() {
- return Template.instance().readReceipts.get();
- },
- displayName() {
- return (settings.get('UI_Use_Real_Name') && this.user.name) || this.user.username;
- },
- time() {
- return moment(this.ts).format('L LTS');
- },
- isLoading() {
- return Template.instance().loading.get();
- },
-});
-
-Template.readReceipts.onCreated(function readReceiptsOnCreated() {
- this.loading = new ReactiveVar(false);
- this.readReceipts = new ReactiveVar([]);
-});
-
-Template.readReceipts.onRendered(function readReceiptsOnRendered() {
- this.loading.set(true);
- Meteor.call('getReadReceipts', { messageId: this.data.messageId }, (error, result) => {
- if (!error) {
- this.readReceipts.set(result);
- }
-
- this.loading.set(false);
- });
-});
diff --git a/app/ui-utils/client/lib/MessageAction.js b/app/ui-utils/client/lib/MessageAction.js
index b99c7d95d9d..cb047ced6a6 100644
--- a/app/ui-utils/client/lib/MessageAction.js
+++ b/app/ui-utils/client/lib/MessageAction.js
@@ -14,7 +14,7 @@ import { Messages, Rooms, Subscriptions } from '../../../models/client';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/client';
import { modal } from './modal';
import { imperativeModal } from '../../../../client/lib/imperativeModal';
-import ReactionList from '../../../../client/components/modals/ReactionList';
+import ReactionList from '../../../../client/views/room/modals/ReactionListModal';
import { call } from '../../../../client/lib/utils/call';
import { canDeleteMessage } from '../../../../client/lib/utils/canDeleteMessage';
import { dispatchToastMessage } from '../../../../client/lib/toast';
diff --git a/app/ui/client/lib/fileUpload.js b/app/ui/client/lib/fileUpload.js
index ea919ba53cc..ce2fc94fb1c 100644
--- a/app/ui/client/lib/fileUpload.js
+++ b/app/ui/client/lib/fileUpload.js
@@ -6,7 +6,7 @@ import { settings } from '../../../settings/client';
import { UserAction, USER_ACTIVITIES } from '../index';
import { fileUploadIsValidContentType, APIClient } from '../../../utils';
import { imperativeModal } from '../../../../client/lib/imperativeModal';
-import FileUploadModal from '../../../../client/components/modals/FileUploadModal';
+import FileUploadModal from '../../../../client/views/room/modals/FileUploadModal';
import { prependReplies } from '../../../../client/lib/utils/prependReplies';
export const uploadFileWithMessage = async (rid, tmid, { description, fileName, msg, file }) => {
diff --git a/client/components/Omnichannel/modals/ForwardChatModal.js b/client/components/Omnichannel/modals/ForwardChatModal.js
index e6ea39b0dfe..1c8ba7048bf 100644
--- a/client/components/Omnichannel/modals/ForwardChatModal.js
+++ b/client/components/Omnichannel/modals/ForwardChatModal.js
@@ -16,9 +16,9 @@ import { useTranslation } from '../../../contexts/TranslationContext';
import { useRecordList } from '../../../hooks/lists/useRecordList';
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useForm } from '../../../hooks/useForm';
-import ModalSeparator from '../../ModalSeparator';
import UserAutoComplete from '../../UserAutoComplete';
import { useDepartmentsList } from '../hooks/useDepartmentsList';
+import ModalSeparator from './ModalSeparator';
const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => {
const t = useTranslation();
diff --git a/client/components/ModalSeparator/ModalSeparator.tsx b/client/components/Omnichannel/modals/ModalSeparator/ModalSeparator.tsx
similarity index 100%
rename from client/components/ModalSeparator/ModalSeparator.tsx
rename to client/components/Omnichannel/modals/ModalSeparator/ModalSeparator.tsx
diff --git a/client/components/ModalSeparator/index.ts b/client/components/Omnichannel/modals/ModalSeparator/index.ts
similarity index 100%
rename from client/components/ModalSeparator/index.ts
rename to client/components/Omnichannel/modals/ModalSeparator/index.ts
diff --git a/client/components/ModalSeparator/style.css b/client/components/Omnichannel/modals/ModalSeparator/style.css
similarity index 100%
rename from client/components/ModalSeparator/style.css
rename to client/components/Omnichannel/modals/ModalSeparator/style.css
diff --git a/client/components/modals/ReactionList/index.ts b/client/components/modals/ReactionList/index.ts
deleted file mode 100644
index 9903ceae759..00000000000
--- a/client/components/modals/ReactionList/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ReactionList';
diff --git a/client/contexts/ServerContext/methods.ts b/client/contexts/ServerContext/methods.ts
index 9b4396febab..bdf2b83e50c 100644
--- a/client/contexts/ServerContext/methods.ts
+++ b/client/contexts/ServerContext/methods.ts
@@ -1,6 +1,7 @@
import { IRoom } from '../../../definition/IRoom';
import { IUser } from '../../../definition/IUser';
import { FollowMessageMethod } from './methods/followMessage';
+import { GetReadReceiptsMethod } from './methods/getReadReceipts';
import { UnsubscribeMethod as MailerUnsubscribeMethod } from './methods/mailer/unsubscribe';
import { RoomNameExistsMethod } from './methods/roomNameExists';
import { SaveRoomSettingsMethod } from './methods/saveRoomSettings';
@@ -137,6 +138,7 @@ export type ServerMethods = {
'uploadCustomSound': (...args: any[]) => any;
'Mailer:unsubscribe': MailerUnsubscribeMethod;
'getRoomById': (rid: IRoom['_id']) => IRoom;
+ 'getReadReceipts': GetReadReceiptsMethod;
};
export type ServerMethodName = keyof ServerMethods;
diff --git a/client/contexts/ServerContext/methods/getReadReceipts.ts b/client/contexts/ServerContext/methods/getReadReceipts.ts
new file mode 100644
index 00000000000..dda0ea345a2
--- /dev/null
+++ b/client/contexts/ServerContext/methods/getReadReceipts.ts
@@ -0,0 +1,4 @@
+import type { IMessage } from '../../../../definition/IMessage';
+import type { ReadReceipt } from '../../../../definition/ReadReceipt';
+
+export type GetReadReceiptsMethod = (options: { mid: IMessage['_id'] }) => Array;
diff --git a/client/hooks/useUserDisplayName.ts b/client/hooks/useUserDisplayName.ts
new file mode 100644
index 00000000000..1ddf8b405ea
--- /dev/null
+++ b/client/hooks/useUserDisplayName.ts
@@ -0,0 +1,12 @@
+import { IUser } from '../../definition/IUser';
+import { useSetting } from '../contexts/SettingsContext';
+import { getUserDisplayName } from '../lib/getUserDisplayName';
+
+export const useUserDisplayName = ({
+ name,
+ username,
+}: Pick): string | undefined => {
+ const useRealName = useSetting('UI_Use_Real_Name');
+
+ return getUserDisplayName(name, username, !!useRealName);
+};
diff --git a/client/importPackages.ts b/client/importPackages.ts
index 15b23214b37..a23817a2723 100644
--- a/client/importPackages.ts
+++ b/client/importPackages.ts
@@ -92,4 +92,3 @@ import '../app/livechat/client';
import '../app/meteor-autocomplete/client';
import '../app/theme/client';
import '../app/custom/client';
-import '../app/message-read-receipt/client';
diff --git a/client/lib/getUserDisplayName.ts b/client/lib/getUserDisplayName.ts
new file mode 100644
index 00000000000..c696a5bd3fc
--- /dev/null
+++ b/client/lib/getUserDisplayName.ts
@@ -0,0 +1,7 @@
+import { IUser } from '../../definition/IUser';
+
+export const getUserDisplayName = (
+ name: IUser['name'],
+ username: IUser['username'],
+ useRealName: boolean,
+): string | undefined => (useRealName ? name || username : username);
diff --git a/client/startup/readReceipt.ts b/client/startup/readReceipt.ts
index ef1590d7d85..7caf036f1c2 100644
--- a/client/startup/readReceipt.ts
+++ b/client/startup/readReceipt.ts
@@ -2,8 +2,9 @@ import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { settings } from '../../app/settings/client';
-import { modal, MessageAction, messageArgs } from '../../app/ui-utils/client';
-import { t } from '../../app/utils/client';
+import { MessageAction, messageArgs } from '../../app/ui-utils/client';
+import { imperativeModal } from '../lib/imperativeModal';
+import ReadReceiptsModal from '../views/room/modals/ReadReceiptsModal';
Meteor.startup(() => {
Tracker.autorun(() => {
@@ -20,15 +21,9 @@ Meteor.startup(() => {
context: ['starred', 'message', 'message-mobile', 'threads'],
action() {
const { msg: message } = messageArgs(this);
- modal.open({
- title: t('Info'),
- content: 'readReceipts',
- data: {
- messageId: message._id,
- },
- showConfirmButton: true,
- showCancelButton: false,
- confirmButtonText: t('Close'),
+ imperativeModal.open({
+ component: ReadReceiptsModal,
+ props: { messageId: message._id, onClose: imperativeModal.close },
});
},
order: 10,
diff --git a/client/components/modals/FileUploadModal/FilePreview.tsx b/client/views/room/modals/FileUploadModal/FilePreview.tsx
similarity index 95%
rename from client/components/modals/FileUploadModal/FilePreview.tsx
rename to client/views/room/modals/FileUploadModal/FilePreview.tsx
index 24dba78c748..a6f7ce1bb95 100644
--- a/client/components/modals/FileUploadModal/FilePreview.tsx
+++ b/client/views/room/modals/FileUploadModal/FilePreview.tsx
@@ -1,6 +1,6 @@
import React, { ReactElement } from 'react';
-import { isIE11 } from '../../../lib/utils/isIE11';
+import { isIE11 } from '../../../../lib/utils/isIE11';
import GenericPreview from './GenericPreview';
import MediaPreview from './MediaPreview';
diff --git a/client/components/modals/FileUploadModal/FileUploadModal.stories.js b/client/views/room/modals/FileUploadModal/FileUploadModal.stories.js
similarity index 100%
rename from client/components/modals/FileUploadModal/FileUploadModal.stories.js
rename to client/views/room/modals/FileUploadModal/FileUploadModal.stories.js
diff --git a/client/components/modals/FileUploadModal/FileUploadModal.tsx b/client/views/room/modals/FileUploadModal/FileUploadModal.tsx
similarity index 95%
rename from client/components/modals/FileUploadModal/FileUploadModal.tsx
rename to client/views/room/modals/FileUploadModal/FileUploadModal.tsx
index 05fc7d979ba..c465e594b52 100644
--- a/client/components/modals/FileUploadModal/FileUploadModal.tsx
+++ b/client/views/room/modals/FileUploadModal/FileUploadModal.tsx
@@ -17,8 +17,8 @@ import React, {
useEffect,
} from 'react';
-import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext';
-import { useTranslation } from '../../../contexts/TranslationContext';
+import { useToastMessageDispatch } from '../../../../contexts/ToastMessagesContext';
+import { useTranslation } from '../../../../contexts/TranslationContext';
import FilePreview from './FilePreview';
type FilePreviewModalProps = {
diff --git a/client/components/modals/FileUploadModal/GenericPreview.tsx b/client/views/room/modals/FileUploadModal/GenericPreview.tsx
similarity index 85%
rename from client/components/modals/FileUploadModal/GenericPreview.tsx
rename to client/views/room/modals/FileUploadModal/GenericPreview.tsx
index 4f74d98aa8d..cc5dd7fc0a3 100644
--- a/client/components/modals/FileUploadModal/GenericPreview.tsx
+++ b/client/views/room/modals/FileUploadModal/GenericPreview.tsx
@@ -1,7 +1,7 @@
import { Box, Icon } from '@rocket.chat/fuselage';
import React, { ReactElement } from 'react';
-import { formatBytes } from '../../../lib/utils/formatBytes';
+import { formatBytes } from '../../../../lib/utils/formatBytes';
const GenericPreview = ({ file }: { file: File }): ReactElement => (
diff --git a/client/components/modals/FileUploadModal/ImagePreview.tsx b/client/views/room/modals/FileUploadModal/ImagePreview.tsx
similarity index 100%
rename from client/components/modals/FileUploadModal/ImagePreview.tsx
rename to client/views/room/modals/FileUploadModal/ImagePreview.tsx
diff --git a/client/components/modals/FileUploadModal/MediaPreview.tsx b/client/views/room/modals/FileUploadModal/MediaPreview.tsx
similarity index 96%
rename from client/components/modals/FileUploadModal/MediaPreview.tsx
rename to client/views/room/modals/FileUploadModal/MediaPreview.tsx
index 70e476e193a..c92aecab38d 100644
--- a/client/components/modals/FileUploadModal/MediaPreview.tsx
+++ b/client/views/room/modals/FileUploadModal/MediaPreview.tsx
@@ -1,7 +1,7 @@
import { Box, Icon } from '@rocket.chat/fuselage';
import React, { ReactElement, useEffect, useState, memo } from 'react';
-import { useTranslation } from '../../../contexts/TranslationContext';
+import { useTranslation } from '../../../../contexts/TranslationContext';
import { FilePreviewType } from './FilePreview';
import ImagePreview from './ImagePreview';
import PreviewSkeleton from './PreviewSkeleton';
diff --git a/client/components/modals/FileUploadModal/PreviewSkeleton.tsx b/client/views/room/modals/FileUploadModal/PreviewSkeleton.tsx
similarity index 100%
rename from client/components/modals/FileUploadModal/PreviewSkeleton.tsx
rename to client/views/room/modals/FileUploadModal/PreviewSkeleton.tsx
diff --git a/client/components/modals/FileUploadModal/index.ts b/client/views/room/modals/FileUploadModal/index.ts
similarity index 100%
rename from client/components/modals/FileUploadModal/index.ts
rename to client/views/room/modals/FileUploadModal/index.ts
diff --git a/client/components/modals/ReactionList/ReactionList.tsx b/client/views/room/modals/ReactionListModal/ReactionListModal.tsx
similarity index 78%
rename from client/components/modals/ReactionList/ReactionList.tsx
rename to client/views/room/modals/ReactionListModal/ReactionListModal.tsx
index 2c8a8f2cd07..659a8f127ca 100644
--- a/client/components/modals/ReactionList/ReactionList.tsx
+++ b/client/views/room/modals/ReactionListModal/ReactionListModal.tsx
@@ -1,10 +1,10 @@
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { ReactElement } from 'react';
-import { openUserCard } from '../../../../app/ui/client/lib/UserCard';
-import { IUser } from '../../../../definition/IUser';
-import { useTranslation } from '../../../contexts/TranslationContext';
-import GenericModal from '../../GenericModal';
+import { openUserCard } from '../../../../../app/ui/client/lib/UserCard';
+import { IUser } from '../../../../../definition/IUser';
+import GenericModal from '../../../../components/GenericModal';
+import { useTranslation } from '../../../../contexts/TranslationContext';
import Reactions from './Reactions';
type ReactionListProps = {
diff --git a/client/components/modals/ReactionList/ReactionUserTag.tsx b/client/views/room/modals/ReactionListModal/ReactionUserTag.tsx
similarity index 89%
rename from client/components/modals/ReactionList/ReactionUserTag.tsx
rename to client/views/room/modals/ReactionListModal/ReactionUserTag.tsx
index e6256c8e4a2..45492becc6f 100644
--- a/client/components/modals/ReactionList/ReactionUserTag.tsx
+++ b/client/views/room/modals/ReactionListModal/ReactionUserTag.tsx
@@ -1,7 +1,7 @@
import { Box, Tag } from '@rocket.chat/fuselage';
import React, { ReactElement } from 'react';
-import { IUser } from '../../../../definition/IUser';
+import { IUser } from '../../../../../definition/IUser';
type ReactionUserTag = {
username: IUser['username'];
diff --git a/client/components/modals/ReactionList/Reactions.tsx b/client/views/room/modals/ReactionListModal/Reactions.tsx
similarity index 86%
rename from client/components/modals/ReactionList/Reactions.tsx
rename to client/views/room/modals/ReactionListModal/Reactions.tsx
index 7df02bcffe5..2d416c04adf 100644
--- a/client/components/modals/ReactionList/Reactions.tsx
+++ b/client/views/room/modals/ReactionListModal/Reactions.tsx
@@ -1,9 +1,9 @@
import { Box } from '@rocket.chat/fuselage';
import React, { ReactElement } from 'react';
-import { IUser } from '../../../../definition/IUser';
-import { useSetting } from '../../../contexts/SettingsContext';
-import Emoji from '../../Emoji';
+import { IUser } from '../../../../../definition/IUser';
+import Emoji from '../../../../components/Emoji';
+import { useSetting } from '../../../../contexts/SettingsContext';
import ReactionUserTag from './ReactionUserTag';
type ReactionsProps = {
diff --git a/client/views/room/modals/ReactionListModal/index.ts b/client/views/room/modals/ReactionListModal/index.ts
new file mode 100644
index 00000000000..93bba896f3f
--- /dev/null
+++ b/client/views/room/modals/ReactionListModal/index.ts
@@ -0,0 +1 @@
+export { default } from './ReactionListModal';
diff --git a/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx b/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx
new file mode 100644
index 00000000000..0c26e718eb1
--- /dev/null
+++ b/client/views/room/modals/ReadReceiptsModal/ReadReceiptRow.tsx
@@ -0,0 +1,45 @@
+import { css } from '@rocket.chat/css-in-js';
+import { Box } from '@rocket.chat/fuselage';
+import colors from '@rocket.chat/fuselage-tokens/colors';
+import React, { ReactElement } from 'react';
+
+import type { ReadReceipt } from '../../../../../definition/ReadReceipt';
+import UserAvatar from '../../../../components/avatar/UserAvatar';
+import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime';
+import { useUserDisplayName } from '../../../../hooks/useUserDisplayName';
+
+const hoverStyle = css`
+ &:hover {
+ background-color: ${colors.n400};
+ }
+`;
+
+const ReadReceiptRow = ({ user, ts }: ReadReceipt): ReactElement => {
+ const displayName = useUserDisplayName(user);
+ const formatDateAndTime = useFormatDateAndTime();
+
+ return (
+
+
+
+
+ {displayName}
+
+
+
+ {formatDateAndTime(ts)}
+
+
+ );
+};
+
+export default ReadReceiptRow;
diff --git a/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx b/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
new file mode 100644
index 00000000000..8bc6b3d4678
--- /dev/null
+++ b/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
@@ -0,0 +1,52 @@
+import { Skeleton } from '@rocket.chat/fuselage';
+import React, { ReactElement, useMemo, useEffect } from 'react';
+
+import type { IMessage } from '../../../../../definition/IMessage/IMessage';
+import type { ReadReceipt } from '../../../../../definition/ReadReceipt';
+import GenericModal from '../../../../components/GenericModal';
+import { useToastMessageDispatch } from '../../../../contexts/ToastMessagesContext';
+import { useTranslation } from '../../../../contexts/TranslationContext';
+import { useMethodData } from '../../../../hooks/useMethodData';
+import { AsyncStatePhase } from '../../../../lib/asyncState';
+import ReadReceiptRow from './ReadReceiptRow';
+
+type ReadReceiptsModalProps = {
+ messageId: IMessage['_id'];
+ onClose: () => void;
+};
+
+const ReadReceiptsModal = ({ messageId, onClose }: ReadReceiptsModalProps): ReactElement => {
+ const t = useTranslation();
+ const dispatchToastMessage = useToastMessageDispatch();
+
+ const { phase, value, error } = useMethodData>(
+ 'getReadReceipts',
+ useMemo(() => [{ messageId }], [messageId]),
+ );
+
+ useEffect(() => {
+ if (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ onClose();
+ }
+ }, [error, dispatchToastMessage, t, onClose]);
+
+ if (phase === AsyncStatePhase.LOADING || !value || error) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {value.length < 1 && t('No_results_found')}
+ {value.map((receipt) => (
+
+ ))}
+
+ );
+};
+
+export default ReadReceiptsModal;
diff --git a/client/views/room/modals/ReadReceiptsModal/index.ts b/client/views/room/modals/ReadReceiptsModal/index.ts
new file mode 100644
index 00000000000..a8f05d9cc9d
--- /dev/null
+++ b/client/views/room/modals/ReadReceiptsModal/index.ts
@@ -0,0 +1 @@
+export { default } from './ReadReceiptsModal';
diff --git a/definition/ReadReceipt.ts b/definition/ReadReceipt.ts
new file mode 100644
index 00000000000..9617645c987
--- /dev/null
+++ b/definition/ReadReceipt.ts
@@ -0,0 +1,12 @@
+import type { IMessage } from './IMessage/IMessage';
+import type { IRoom } from './IRoom';
+import type { IUser } from './IUser';
+
+export type ReadReceipt = {
+ messageId: IMessage['_id'];
+ roomId: IRoom['_id'];
+ ts: Date;
+ user: Pick;
+ userId: IUser['_id'];
+ _id: string;
+}