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 @@ - 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; +}