diff --git a/.changeset/brave-shrimps-marry.md b/.changeset/brave-shrimps-marry.md new file mode 100644 index 000000000000..af5b0f2a8c93 --- /dev/null +++ b/.changeset/brave-shrimps-marry.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Improved support for higlighted words in threads (rooms are now marked as unread and notifications are sent) diff --git a/.changeset/bump-patch-1702298298384.md b/.changeset/bump-patch-1702298298384.md deleted file mode 100644 index e1eaa7980afb..000000000000 --- a/.changeset/bump-patch-1702298298384.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Bump @rocket.chat/meteor version. diff --git a/.changeset/chilled-cooks-end.md b/.changeset/chilled-cooks-end.md new file mode 100644 index 000000000000..820a3bf679f1 --- /dev/null +++ b/.changeset/chilled-cooks-end.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed an issue displaying the language selection preference empty when it should display 'Default' on the initial value diff --git a/.changeset/eight-windows-report.md b/.changeset/eight-windows-report.md new file mode 100644 index 000000000000..cce370fee1ad --- /dev/null +++ b/.changeset/eight-windows-report.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed a problem with the Fallback Forward Department functionality when transferring rooms, caused by a missing return. This provoked the system to transfer to fallback department, as expected, but then continue the process and transfer to the department with no agents anyways. Also, a duplicated "user joined" message was removed from "Forward to department" functionality. diff --git a/.changeset/fresh-radios-whisper.md b/.changeset/fresh-radios-whisper.md deleted file mode 100644 index cba234524dce..000000000000 --- a/.changeset/fresh-radios-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed issue with the new `custom-roles` license module not being checked throughout the application diff --git a/.changeset/green-turkeys-fry.md b/.changeset/green-turkeys-fry.md new file mode 100644 index 000000000000..64443653ba8e --- /dev/null +++ b/.changeset/green-turkeys-fry.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed toolbox sub-menu not being displayed when in smaller resolutions diff --git a/.changeset/late-pots-travel.md b/.changeset/late-pots-travel.md new file mode 100644 index 000000000000..cd2131ac65b6 --- /dev/null +++ b/.changeset/late-pots-travel.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": minor +--- + +fix: Loading state for `Marketplace` related lists diff --git a/.changeset/lemon-shrimps-draw.md b/.changeset/lemon-shrimps-draw.md new file mode 100644 index 000000000000..75c2031fbaee --- /dev/null +++ b/.changeset/lemon-shrimps-draw.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed error message when uploading a file that is not allowed diff --git a/.changeset/nasty-islands-trade.md b/.changeset/nasty-islands-trade.md deleted file mode 100644 index b6df94282dd5..000000000000 --- a/.changeset/nasty-islands-trade.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@rocket.chat/rest-typings': minor -'@rocket.chat/meteor': minor ---- - -fix Federation Regression, builds service correctly diff --git a/.changeset/new-avocados-sort.md b/.changeset/new-avocados-sort.md deleted file mode 100644 index 1f8b90ed008d..000000000000 --- a/.changeset/new-avocados-sort.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -fix: Wrong `Message Roundtrip Time` metric - -Removes the wrong metric gauge named `rocketchat_messages_roundtrip_time` and replace it by a new summary metric named `rocketchat_messages_roundtrip_time_summary`. Add new percentiles `0.5, 0.95 and 1` to all summary metrics. diff --git a/.changeset/spicy-kiwis-argue.md b/.changeset/spicy-kiwis-argue.md deleted file mode 100644 index 520fdfe22015..000000000000 --- a/.changeset/spicy-kiwis-argue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fix desktop notification routing for direct rooms diff --git a/.changeset/thin-chairs-clean.md b/.changeset/thin-chairs-clean.md new file mode 100644 index 000000000000..0ad80730f293 --- /dev/null +++ b/.changeset/thin-chairs-clean.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed the problem of displaying the wrong composer for archived room diff --git a/.changeset/thin-socks-brush.md b/.changeset/thin-socks-brush.md new file mode 100644 index 000000000000..a1da5c81e684 --- /dev/null +++ b/.changeset/thin-socks-brush.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Removed an old behavior that allowed visitors to be created with an empty token on `livechat/visitor` endpoint. diff --git a/.changeset/three-moles-look.md b/.changeset/three-moles-look.md new file mode 100644 index 000000000000..a6429fb6197e --- /dev/null +++ b/.changeset/three-moles-look.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed conversations in queue being limited to 50 items diff --git a/.changeset/three-steaks-cry.md b/.changeset/three-steaks-cry.md new file mode 100644 index 000000000000..3de321b2ff3f --- /dev/null +++ b/.changeset/three-steaks-cry.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/livechat": patch +--- + +Fixed a problem that caused Livechat Widget registration page to ignore the `showOnRegistration` flag for departments, showing all items. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6e14d4935eaf..0b2af2ef7881 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -22,15 +22,13 @@ --> ## Proposed changes (including videos or screenshots) - - - ## Issue(s) diff --git a/apps/meteor/CHANGELOG.md b/apps/meteor/CHANGELOG.md index 874fc20d33b6..125ba1541bf2 100644 --- a/apps/meteor/CHANGELOG.md +++ b/apps/meteor/CHANGELOG.md @@ -1,5 +1,52 @@ # @rocket.chat/meteor +## 6.5.1 + +### Patch Changes + +- c2b224fd82: Bump @rocket.chat/meteor version. +- Bump @rocket.chat/meteor version. +- c2b224fd82: Security improvements +- c2b224fd82: Fixed issue with the new `custom-roles` license module not being checked throughout the application +- c2b224fd82: fix: stop refetching banner data each 5 minutes +- c2b224fd82: Fixed an issue allowing admin user cancelling subscription when license's trial param is provided +- c2b224fd82: Fixed Country select component at Organization form from `onboarding-ui` package +- c2b224fd82: fix Federation Regression, builds service correctly +- c2b224fd82: fix: Wrong `Message Roundtrip Time` metric + + Removes the wrong metric gauge named `rocketchat_messages_roundtrip_time` and replace it by a new summary metric named `rocketchat_messages_roundtrip_time_summary`. Add new percentiles `0.5, 0.95 and 1` to all summary metrics. + +- c2b224fd82: Exceeding API calls when sending OTR messages +- c2b224fd82: Fixed a problem with the subscription creation on Omnichannel rooms. + Rooms were being created as seen, causing sound notifications to not work +- c2b224fd82: Fixed a problem where chained callbacks' return value was being overrided by some callbacks returning something different, causing callbacks with lower priority to operate on invalid values +- c2b224fd82: Fix desktop notification routing for direct rooms +- c2b224fd82: Improved the experience of receiving conference calls on the mobile app by disabling the push notification for the "new call" message if a push is already being sent to trigger the phone's ringing tone. +- c2b224fd82: Fixed verify the account through email link +- c2b224fd82: Fixed the filter for file type in the list of room files +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/api-client@0.1.19 + - @rocket.chat/omnichannel-services@0.1.1 + - @rocket.chat/presence@0.1.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/ui-contexts@3.0.1 + - @rocket.chat/license@0.1.1 + - @rocket.chat/pdf-worker@0.0.25 + - @rocket.chat/cron@0.0.21 + - @rocket.chat/gazzodown@3.0.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/ui-theming@0.1.1 + - @rocket.chat/fuselage-ui-kit@3.0.1 + - @rocket.chat/ui-client@3.0.1 + - @rocket.chat/ui-video-conf@3.0.1 + - @rocket.chat/web-ui-registration@3.0.1 + - @rocket.chat/server-cloud-communication@0.0.1 + - @rocket.chat/models@0.0.25 + - @rocket.chat/instance-status@0.0.25 + ## 6.5.0 ### Minor Changes diff --git a/apps/meteor/app/2fa/client/TOTPPassword.js b/apps/meteor/app/2fa/client/TOTPPassword.js index 3fccb646bad1..20269744fa77 100644 --- a/apps/meteor/app/2fa/client/TOTPPassword.js +++ b/apps/meteor/app/2fa/client/TOTPPassword.js @@ -2,7 +2,7 @@ import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; import { process2faReturn } from '../../../client/lib/2fa/process2faReturn'; -import { isTotpInvalidError, reportError } from '../../../client/lib/2fa/utils'; +import { isTotpInvalidError, isTotpMaxAttemptsError, reportError } from '../../../client/lib/2fa/utils'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { t } from '../../utils/lib/i18n'; @@ -47,6 +47,14 @@ Meteor.loginWithPassword = function (email, password, cb) { emailOrUsername: email, onCode: (code) => { Meteor.loginWithPasswordAndTOTP(email, password, code, (error) => { + if (isTotpMaxAttemptsError(error)) { + dispatchToastMessage({ + type: 'error', + message: t('totp-max-attempts'), + }); + cb(); + return; + } if (isTotpInvalidError(error)) { dispatchToastMessage({ type: 'error', @@ -55,7 +63,6 @@ Meteor.loginWithPassword = function (email, password, cb) { cb(); return; } - cb(error); }); }, diff --git a/apps/meteor/app/2fa/server/code/EmailCheck.ts b/apps/meteor/app/2fa/server/code/EmailCheck.ts index bf2e4aa170c2..123df96ee264 100644 --- a/apps/meteor/app/2fa/server/code/EmailCheck.ts +++ b/apps/meteor/app/2fa/server/code/EmailCheck.ts @@ -69,26 +69,26 @@ ${t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email')} return false; } - if (!user.services || !Array.isArray(user.services?.emailCode)) { + if (!user.services?.emailCode) { return false; } // Remove non digits codeFromEmail = codeFromEmail.replace(/([^\d])/g, ''); - await Users.removeExpiredEmailCodesOfUserId(user._id); + const { code, expire } = user.services.emailCode; - for await (const { code, expire } of user.services.emailCode) { - if (expire < new Date()) { - continue; - } + if (expire < new Date()) { + return false; + } - if (await bcrypt.compare(codeFromEmail, code)) { - await Users.removeEmailCodeByUserIdAndCode(user._id, code); - return true; - } + if (await bcrypt.compare(codeFromEmail, code)) { + await Users.removeEmailCodeOfUserId(user._id); + return true; } + await Users.incrementInvalidEmailCodeAttempt(user._id); + return false; } @@ -109,7 +109,7 @@ ${t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email')} } public async processInvalidCode(user: IUser): Promise { - await Users.removeExpiredEmailCodesOfUserId(user._id); + await Users.removeExpiredEmailCodeOfUserId(user._id); // Generate new code if the there isn't any code with more than 5 minutes to expire const expireWithDelta = new Date(); @@ -119,13 +119,15 @@ ${t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email')} const emailOrUsername = user.username || emails[0]; - const hasValidCode = user.services?.emailCode?.filter(({ expire }) => expire > expireWithDelta); - if (hasValidCode?.length) { + const hasValidCode = + user.services?.emailCode?.expire && + user.services?.emailCode?.expire > expireWithDelta && + !(await this.maxFaildedAttemtpsReached(user)); + if (hasValidCode) { return { emailOrUsername, codeGenerated: false, - codeCount: hasValidCode.length, - codeExpires: hasValidCode.map((i) => i.expire), + codeExpires: user.services?.emailCode?.expire, }; } @@ -136,4 +138,9 @@ ${t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email')} emailOrUsername, }; } + + public async maxFaildedAttemtpsReached(user: IUser) { + const maxAttempts = settings.get('Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts'); + return (await Users.maxInvalidEmailCodeAttemptsReached(user._id, maxAttempts)) as boolean; + } } diff --git a/apps/meteor/app/2fa/server/code/ICodeCheck.ts b/apps/meteor/app/2fa/server/code/ICodeCheck.ts index f0e165735ce8..1cd1ba68e0db 100644 --- a/apps/meteor/app/2fa/server/code/ICodeCheck.ts +++ b/apps/meteor/app/2fa/server/code/ICodeCheck.ts @@ -2,8 +2,7 @@ import type { IUser } from '@rocket.chat/core-typings'; export interface IProcessInvalidCodeResult { codeGenerated: boolean; - codeCount?: number; - codeExpires?: Date[]; + codeExpires?: Date; emailOrUsername?: string; } @@ -15,4 +14,6 @@ export interface ICodeCheck { verify(user: IUser, code: string, force?: boolean): Promise; processInvalidCode(user: IUser): Promise; + + maxFaildedAttemtpsReached(user: IUser): Promise; } diff --git a/apps/meteor/app/2fa/server/code/PasswordCheckFallback.ts b/apps/meteor/app/2fa/server/code/PasswordCheckFallback.ts index 10d2f01fadbb..6c441127f79d 100644 --- a/apps/meteor/app/2fa/server/code/PasswordCheckFallback.ts +++ b/apps/meteor/app/2fa/server/code/PasswordCheckFallback.ts @@ -41,4 +41,8 @@ export class PasswordCheckFallback implements ICodeCheck { codeGenerated: false, }; } + + public async maxFaildedAttemtpsReached(_user: IUser): Promise { + return false; + } } diff --git a/apps/meteor/app/2fa/server/code/TOTPCheck.ts b/apps/meteor/app/2fa/server/code/TOTPCheck.ts index 2ed91d7ec2aa..3aa2604c7965 100644 --- a/apps/meteor/app/2fa/server/code/TOTPCheck.ts +++ b/apps/meteor/app/2fa/server/code/TOTPCheck.ts @@ -38,4 +38,8 @@ export class TOTPCheck implements ICodeCheck { codeGenerated: false, }; } + + public async maxFaildedAttemtpsReached(_user: IUser): Promise { + return false; + } } diff --git a/apps/meteor/app/2fa/server/code/index.ts b/apps/meteor/app/2fa/server/code/index.ts index 250fd5b158ca..1fbe658e5682 100644 --- a/apps/meteor/app/2fa/server/code/index.ts +++ b/apps/meteor/app/2fa/server/code/index.ts @@ -217,6 +217,15 @@ export async function checkCodeForUser({ user, code, method, options = {}, conne const valid = await selectedMethod.verify(existingUser, code, options.requireSecondFactor); if (!valid) { + const tooManyFailedAttempts = await selectedMethod.maxFaildedAttemtpsReached(existingUser); + if (tooManyFailedAttempts) { + throw new Meteor.Error('totp-max-attempts', 'TOTP Maximun Failed Attempts Reached', { + method: selectedMethod.name, + ...data, + availableMethods, + }); + } + throw new Meteor.Error('totp-invalid', 'TOTP Invalid', { method: selectedMethod.name, ...data, diff --git a/apps/meteor/app/2fa/server/loginHandler.ts b/apps/meteor/app/2fa/server/loginHandler.ts index feb1923c8799..26c9869a132f 100644 --- a/apps/meteor/app/2fa/server/loginHandler.ts +++ b/apps/meteor/app/2fa/server/loginHandler.ts @@ -26,19 +26,14 @@ Accounts.registerLoginHandler('totp', function (options) { callbacks.add( 'onValidateLogin', async (login) => { - if (login.methodName === 'verifyEmail') { - throw new Meteor.Error('verify-email', 'E-mail verified'); - } - - if (login.type === 'resume' || login.type === 'proxy' || (login.type === 'password' && login.methodName === 'resetPassword')) { - return login; - } - // CAS login doesn't yet support 2FA. - if (login.type === 'cas') { - return login; - } - - if (!login.user) { + if ( + !login.user || + login.type === 'resume' || + login.type === 'proxy' || + login.type === 'cas' || + (login.type === 'password' && login.methodName === 'resetPassword') || + login.methodName === 'verifyEmail' + ) { return login; } diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index ae5a79719cce..7b6c964a50bb 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -538,7 +538,7 @@ API.v1.addRoute( this.token || crypto .createHash('md5') - .update(this.requestIp + this.request.headers['user-agent']) + .update(this.requestIp + this.user._id) .digest('hex'); const rateLimiterInput = { @@ -594,12 +594,7 @@ API.v1.addRoute( const { method, params, id } = data; - const connectionId = - this.token || - crypto - .createHash('md5') - .update(this.requestIp + this.request.headers['user-agent']) - .digest('hex'); + const connectionId = this.token || crypto.createHash('md5').update(this.requestIp).digest('hex'); const rateLimiterInput = { userId: this.userId || undefined, diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index ca6a2c3a56be..034a73f54104 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -27,6 +27,10 @@ API.v1.addRoute( { authRequired: true, validateParams: isOauthAppsGetParams }, { async get() { + if (!(await hasPermissionAsync(this.userId, 'manage-oauth-apps'))) { + return API.v1.unauthorized(); + } + const oauthApp = await OAuthApps.findOneAuthAppByIdOrClientId(this.queryParams); if (!oauthApp) { diff --git a/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts b/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts index 657a64002a5a..da85e9b73296 100644 --- a/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts +++ b/apps/meteor/app/importer-pending-files/server/PendingFileImporter.ts @@ -1,6 +1,7 @@ import http from 'http'; import https from 'https'; +import { api } from '@rocket.chat/core-services'; import type { IImport, MessageAttachment, IUpload } from '@rocket.chat/core-typings'; import { Messages } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; @@ -80,6 +81,7 @@ export class PendingFileImporter extends Importer { try { const pendingFileMessageList = Messages.findAllImportedMessagesWithFilesToDownload(); + const importedRoomIds = new Set(); for await (const message of pendingFileMessageList) { try { const { _importFile } = message; @@ -140,6 +142,7 @@ export class PendingFileImporter extends Importer { await Messages.setImportFileRocketChatAttachment(_importFile.id, url, attachment); await completeFile(details); + importedRoomIds.add(message.rid); } catch (error) { await completeFile(details); logError(error); @@ -150,6 +153,8 @@ export class PendingFileImporter extends Importer { this.logger.error(error); } } + + void api.broadcast('notify.importedMessages', { roomIds: Array.from(importedRoomIds) }); } catch (error) { // If the cursor expired, restart the method if (this.isCursorNotFoundError(error)) { diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts index fff8e8c9efd4..1ce40b9415e5 100644 --- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts @@ -715,7 +715,12 @@ export class ImportDataConverter { return ImportData.getAllMessages().toArray(); } - async convertMessages({ beforeImportFn, afterImportFn, onErrorFn }: IConversionCallbacks = {}): Promise { + async convertMessages({ + beforeImportFn, + afterImportFn, + onErrorFn, + afterImportAllMessagesFn, + }: IConversionCallbacks & { afterImportAllMessagesFn?: (roomIds: string[]) => Promise }): Promise { const rids: Array = []; const messages = await this.getMessagesToImport(); @@ -740,7 +745,6 @@ export class ImportDataConverter { this._logger.warn(`Imported user not found: ${data.u._id}`); throw new Error('importer-message-unknown-user'); } - const rid = await this.findImportedRoomId(data.rid); if (!rid) { throw new Error('importer-message-unknown-room'); @@ -807,12 +811,15 @@ export class ImportDataConverter { for await (const rid of rids) { try { - await Rooms.resetLastMessageById(rid); + await Rooms.resetLastMessageById(rid, null); } catch (e) { this._logger.warn(`Failed to update last message of room ${rid}`); this._logger.error(e); } } + if (afterImportAllMessagesFn) { + await afterImportAllMessagesFn(rids); + } } async updateRoom(room: IRoom, roomData: IImportChannel, startedByUserId: string): Promise { diff --git a/apps/meteor/app/importer/server/classes/Importer.ts b/apps/meteor/app/importer/server/classes/Importer.ts index 92f506b379ad..68a12513a06c 100644 --- a/apps/meteor/app/importer/server/classes/Importer.ts +++ b/apps/meteor/app/importer/server/classes/Importer.ts @@ -1,3 +1,4 @@ +import { api } from '@rocket.chat/core-services'; import type { IImport, IImportRecord, IImportChannel, IImportUser, IImportProgress } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; import { Settings, ImportData, Imports } from '@rocket.chat/models'; @@ -170,6 +171,9 @@ export class Importer { } }; + const afterImportAllMessagesFn = async (importedRoomIds: string[]): Promise => + api.broadcast('notify.importedMessages', { roomIds: importedRoomIds }); + const afterBatchFn = async (successCount: number, errorCount: number) => { if (successCount) { await this.addCountCompleted(successCount); @@ -203,7 +207,7 @@ export class Importer { await this.converter.convertChannels(startedByUserId, { beforeImportFn, afterImportFn, onErrorFn }); await this.updateProgress(ProgressStep.IMPORTING_MESSAGES); - await this.converter.convertMessages({ afterImportFn, onErrorFn }); + await this.converter.convertMessages({ afterImportFn, onErrorFn, afterImportAllMessagesFn }); await this.updateProgress(ProgressStep.FINISHING); diff --git a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts index 64202ab39dab..2b495ec4b0d0 100644 --- a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts +++ b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts @@ -111,7 +111,9 @@ export async function cleanRoomHistory({ if (count) { const lastMessage = await Messages.getLastVisibleMessageSentWithNoTypeByRoomId(rid); - await Rooms.resetLastMessageById(rid, lastMessage); + + await Rooms.resetLastMessageById(rid, lastMessage, -count); + void api.broadcast('notify.deleteMessageBulk', rid, { rid, excludePinned, diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index c197f93518ce..37ae72254418 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -79,16 +79,14 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise { return true; }); -const objectMaybeIncluding = (types) => - Match.Where((value) => { +const objectMaybeIncluding = (types: any) => + Match.Where((value: any) => { Object.keys(types).forEach((field) => { if (value[field] != null) { try { check(value[field], types[field]); - } catch (error) { + } catch (error: any) { error.path = field; throw error; } @@ -66,7 +69,7 @@ const objectMaybeIncluding = (types) => return true; }); -const validateAttachmentsFields = (attachmentField) => { +const validateAttachmentsFields = (attachmentField: any) => { check( attachmentField, objectMaybeIncluding({ @@ -81,7 +84,7 @@ const validateAttachmentsFields = (attachmentField) => { } }; -const validateAttachmentsActions = (attachmentActions) => { +const validateAttachmentsActions = (attachmentActions: any) => { check( attachmentActions, objectMaybeIncluding({ @@ -97,7 +100,7 @@ const validateAttachmentsActions = (attachmentActions) => { ); }; -const validateAttachment = (attachment) => { +const validateAttachment = (attachment: any) => { check( attachment, objectMaybeIncluding({ @@ -139,9 +142,9 @@ const validateAttachment = (attachment) => { } }; -const validateBodyAttachments = (attachments) => attachments.map(validateAttachment); +const validateBodyAttachments = (attachments: any[]) => attachments.map(validateAttachment); -export const validateMessage = async (message, room, user) => { +export const validateMessage = async (message: any, room: any, user: any) => { check( message, objectMaybeIncluding({ @@ -171,7 +174,11 @@ export const validateMessage = async (message, room, user) => { } }; -export const prepareMessageObject = function (message, rid, user) { +export function prepareMessageObject( + message: Partial, + rid: IRoom['_id'], + user: { _id: string; username?: string; name?: string }, +): asserts message is IMessage { if (!message.ts) { message.ts = new Date(); } @@ -183,7 +190,7 @@ export const prepareMessageObject = function (message, rid, user) { const { _id, username, name } = user; message.u = { _id, - username, + username: username as string, // FIXME: this is wrong but I don't want to change it now name, }; message.rid = rid; @@ -195,26 +202,12 @@ export const prepareMessageObject = function (message, rid, user) { if (message.ts == null) { message.ts = new Date(); } -}; - -/** - * Clean up the message object before saving on db - * @param {IMessage} message - */ -function cleanupMessageObject(message) { - ['customClass'].forEach((field) => delete message[field]); } /** * Validates and sends the message object. - * @param {IUser} user - * @param {AtLeast} message - * @param {IRoom} room - * @param {boolean} [upsert=false] - * @param {string[]} [previewUrls] - * @returns {Promise} */ -export const sendMessage = async function (user, message, room, upsert = false, previewUrls = undefined) { +export const sendMessage = async function (user: any, message: any, room: any, upsert = false, previewUrls?: string[]) { if (!user || !message || !room._id) { return false; } @@ -222,20 +215,28 @@ export const sendMessage = async function (user, message, room, upsert = false, await validateMessage(message, room, user); prepareMessageObject(message, room._id, user); + if (message.t === 'otr') { + notifications.streamRoomMessage.emit(message.rid, message, user, room); + return message; + } + if (settings.get('Message_Read_Receipt_Enabled')) { message.unread = true; } // For the Rocket.Chat Apps :) - if (Apps && Apps.isLoaded()) { - const prevent = await Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageSentPrevent', message); + if (Apps?.isLoaded()) { + const listenerBridge = Apps.getBridges()?.getListenerBridge(); + + const prevent = await listenerBridge?.messageEvent('IPreMessageSentPrevent', message); if (prevent) { return; } - let result; - result = await Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageSentExtend', message); - result = await Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageSentModify', result); + const result = await listenerBridge?.messageEvent( + 'IPreMessageSentModify', + await listenerBridge?.messageEvent('IPreMessageSentExtend', message), + ); if (typeof result === 'object') { message = Object.assign(message, result); @@ -245,53 +246,48 @@ export const sendMessage = async function (user, message, room, upsert = false, } } - cleanupMessageObject(message); - parseUrlsInMessage(message, previewUrls); message = await Message.beforeSave({ message, room, user }); - if (message) { - if (message.t === 'otr') { - const otrStreamer = notifications.streamRoomMessage; - otrStreamer.emit(message.rid, message, user, room); - } else if (message._id && upsert) { - const { _id } = message; - delete message._id; - await Messages.updateOne( - { - _id, - 'u._id': message.u._id, - }, - { $set: message }, - { upsert: true }, - ); - message._id = _id; - } else { - const messageAlreadyExists = message._id && (await Messages.findOneById(message._id, { projection: { _id: 1 } })); - if (messageAlreadyExists) { - return; - } - const result = await Messages.insertOne(message); - message._id = result.insertedId; - } + if (!message) { + return; + } - if (Apps && Apps.isLoaded()) { - // This returns a promise, but it won't mutate anything about the message - // so, we don't really care if it is successful or fails - void Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageSent', message); + if (message._id && upsert) { + const { _id } = message; + delete message._id; + await Messages.updateOne( + { + _id, + 'u._id': message.u._id, + }, + { $set: message }, + { upsert: true }, + ); + message._id = _id; + } else { + const messageAlreadyExists = message._id && (await Messages.findOneById(message._id, { projection: { _id: 1 } })); + if (messageAlreadyExists) { + return; } + const { insertedId } = await Messages.insertOne(message); + message._id = insertedId; + } - /* - Defer other updates as their return is not interesting to the user - */ - - // Execute all callbacks - await callbacks.run('afterSaveMessage', message, room); - void broadcastMessageSentEvent({ - id: message._id, - broadcastCallback: (message) => api.broadcast('message.sent', message), - }); - return message; + if (Apps?.isLoaded()) { + // This returns a promise, but it won't mutate anything about the message + // so, we don't really care if it is successful or fails + void Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageSent', message); } + + /* Defer other updates as their return is not interesting to the user */ + + // Execute all callbacks + await callbacks.run('afterSaveMessage', message, room); + void broadcastMessageSentEvent({ + id: message._id, + broadcastCallback: (message) => api.broadcast('message.sent', message), + }); + return message; }; diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js index b55a272de558..ccd06e886b9f 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js @@ -73,7 +73,7 @@ const incUserMentions = async (rid, roomType, uids, unreadCount) => { await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread); }; -const getUserIdsFromHighlights = async (rid, message) => { +export const getUserIdsFromHighlights = async (rid, message) => { const highlightOptions = { projection: { 'userHighlights': 1, 'u._id': 1 } }; const subs = await Subscriptions.findByRoomWithUserHighlights(rid, highlightOptions).toArray(); diff --git a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts index 906ace402bb9..5ba2cf0d9791 100644 --- a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts +++ b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts @@ -2,6 +2,7 @@ import type { ILivechatDepartment, ILivechatInquiryRecord, IOmnichannelAgent } f import { queryClient } from '../../../../../client/lib/queryClient'; import { callWithErrorHandling } from '../../../../../client/lib/utils/callWithErrorHandling'; +import { settings } from '../../../../settings/client'; import { sdk } from '../../../../utils/client/lib/SDKClient'; import { LivechatInquiry } from '../../collections/LivechatInquiry'; @@ -39,7 +40,8 @@ const removeInquiry = async (inquiry: ILivechatInquiryRecord) => { }; const getInquiriesFromAPI = async () => { - const { inquiries } = await sdk.rest.get('/v1/livechat/inquiries.queuedForUser', {}); + const count = settings.get('Livechat_guest_pool_max_number_incoming_livechats_displayed') ?? 0; + const { inquiries } = await sdk.rest.get('/v1/livechat/inquiries.queuedForUser', { count }); return inquiries; }; @@ -96,9 +98,12 @@ const subscribe = async (userId: IOmnichannelAgent['_id']) => { // Register to all depts + public queue always to match the inquiry list returned by backend const cleanDepartmentListeners = addListenerForeachDepartment(agentDepartments); const globalCleanup = addGlobalListener(); - const inquiriesFromAPI = (await getInquiriesFromAPI()) as unknown as ILivechatInquiryRecord[]; - await updateInquiries(inquiriesFromAPI); + const computation = Tracker.autorun(async () => { + const inquiriesFromAPI = (await getInquiriesFromAPI()) as unknown as ILivechatInquiryRecord[]; + + await updateInquiries(inquiriesFromAPI); + }); return () => { LivechatInquiry.remove({}); @@ -106,14 +111,15 @@ const subscribe = async (userId: IOmnichannelAgent['_id']) => { cleanDepartmentListeners?.(); globalCleanup?.(); departments.clear(); + computation.stop(); }; }; export const initializeLivechatInquiryStream = (() => { let cleanUp: (() => void) | undefined; - return async (...args: any[]) => { + return async (...args: Parameters) => { cleanUp?.(); - cleanUp = await subscribe(...(args as [IOmnichannelAgent['_id']])); + cleanUp = await subscribe(...args); }; })(); diff --git a/apps/meteor/app/livechat/client/startup/notifyUnreadRooms.js b/apps/meteor/app/livechat/client/startup/notifyUnreadRooms.js index 3aaace847f69..a758c72cadd8 100644 --- a/apps/meteor/app/livechat/client/startup/notifyUnreadRooms.js +++ b/apps/meteor/app/livechat/client/startup/notifyUnreadRooms.js @@ -1,9 +1,8 @@ -import { Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { CustomSounds } from '../../../custom-sounds/client'; -import { Subscriptions } from '../../../models/client'; +import { Subscriptions, Users } from '../../../models/client'; import { settings } from '../../../settings/client'; import { getUserPreference } from '../../../utils/client'; @@ -16,17 +15,20 @@ Meteor.startup(() => { return; } - const subs = await Subscriptions.find({ t: 'l', ls: { $exists: 0 }, open: true }).count(); + const subs = await Subscriptions.find({ t: 'l', ls: { $exists: false }, open: true }).count(); if (subs === 0) { audio && audio.pause(); return; } - const user = await Users.findOne(Meteor.userId(), { - projection: { - 'settings.preferences.newRoomNotification': 1, + const user = await Users.findOne( + { _id: Meteor.userId() }, + { + projection: { + 'settings.preferences.newRoomNotification': 1, + }, }, - }); + ); const newRoomNotification = getUserPreference(user, 'newRoomNotification'); diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index 59be77d298f1..3d78280c5109 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -30,6 +30,11 @@ API.v1.addRoute('livechat/visitor', { }); const { customFields, id, token, name, email, department, phone, username, connectionData } = this.bodyParams.visitor; + + if (!token?.trim()) { + throw new Meteor.Error('error-invalid-token', 'Token cannot be empty', { method: 'livechat/visitor' }); + } + const guest = { token, ...(id && { id }), diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index f360004fc77e..c1acc87018e8 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -256,10 +256,8 @@ export const createLivechatSubscription = async ( status, }, ts: new Date(), - lr: new Date(), - ls: new Date(), ...(department && { department }), - }; + } as InsertionModel; return Subscriptions.insertOne(subscriptionData); }; @@ -578,6 +576,8 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi logger.debug(`Cannot forward room ${room._id}. Unable to delegate inquiry`); return false; } + + return true; } await LivechatTyped.saveTransferHistory(room, transferData); @@ -586,9 +586,6 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi // point the chat is not assigned to him/her and it is still in the queue await RoutingManager.removeAllRoomSubscriptions(room, !chatQueued ? servedBy : undefined); } - if (!chatQueued && servedBy) { - await Message.saveSystemMessage('uj', rid, servedBy.username || '', servedBy); - } await updateChatDepartment({ rid, newDepartmentId: departmentId, oldDepartmentId }); @@ -607,10 +604,6 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi logger.debug(`Inquiry ${inquiry._id} queued succesfully`); } - const { token } = guest; - await LivechatTyped.setDepartmentForGuest({ token, department: departmentId }); - logger.debug(`Department for visitor with token ${token} was updated to ${departmentId}`); - return true; }; diff --git a/apps/meteor/app/otr/client/OTR.ts b/apps/meteor/app/otr/client/OTR.ts index 55e0a1289a67..a855381bd9c0 100644 --- a/apps/meteor/app/otr/client/OTR.ts +++ b/apps/meteor/app/otr/client/OTR.ts @@ -1,51 +1,28 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; +import type { IRoom, IUser } from '@rocket.chat/core-typings'; -import { Subscriptions } from '../../models/client'; import type { IOTR } from '../lib/IOTR'; import { OTRRoom } from './OTRRoom'; class OTR implements IOTR { - private enabled: ReactiveVar; - private instancesByRoomId: { [rid: string]: OTRRoom }; constructor() { - this.enabled = new ReactiveVar(false); this.instancesByRoomId = {}; } - isEnabled(): boolean { - return this.enabled.get(); - } - - setEnabled(enabled: boolean): void { - this.enabled.set(enabled); - } - - getInstanceByRoomId(roomId: string): OTRRoom | undefined { - const userId = Meteor.userId(); - if (!userId) { - return; - } - if (!this.enabled.get()) { - return; - } - - if (this.instancesByRoomId[roomId]) { - return this.instancesByRoomId[roomId]; + getInstanceByRoomId(uid: IUser['_id'], rid: IRoom['_id']): OTRRoom | undefined { + if (this.instancesByRoomId[rid]) { + return this.instancesByRoomId[rid]; } - const subscription = Subscriptions.findOne({ - rid: roomId, - }); + const otrRoom = OTRRoom.create(uid, rid); - if (!subscription || subscription.t !== 'd') { - return; + if (!otrRoom) { + return undefined; } - this.instancesByRoomId[roomId] = new OTRRoom(userId, roomId); - return this.instancesByRoomId[roomId]; + this.instancesByRoomId[rid] = otrRoom; + return this.instancesByRoomId[rid]; } } diff --git a/apps/meteor/app/otr/client/OTRRoom.ts b/apps/meteor/app/otr/client/OTRRoom.ts index 0fdcecd91a28..8899de13b190 100644 --- a/apps/meteor/app/otr/client/OTRRoom.ts +++ b/apps/meteor/app/otr/client/OTRRoom.ts @@ -1,4 +1,4 @@ -import type { IMessage } from '@rocket.chat/core-typings'; +import type { IRoom, IMessage, IUser } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; import EJSON from 'ejson'; import { Meteor } from 'meteor/meteor'; @@ -11,7 +11,6 @@ import { Presence } from '../../../client/lib/presence'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { getUidDirectMessage } from '../../../client/lib/utils/getUidDirectMessage'; import { goToRoomById } from '../../../client/lib/utils/goToRoomById'; -import { Notifications } from '../../notifications/client'; import { sdk } from '../../utils/client/lib/SDKClient'; import { t } from '../../utils/lib/i18n'; import type { IOnUserStreamData, IOTRAlgorithm, IOTRDecrypt, IOTRRoom } from '../lib/IOTR'; @@ -48,15 +47,25 @@ export class OTRRoom implements IOTRRoom { private isFirstOTR: boolean; - constructor(userId: string, roomId: string) { - this._userId = userId; - this._roomId = roomId; + protected constructor(uid: IUser['_id'], rid: IRoom['_id'], peerId: IUser['_id']) { + this._userId = uid; + this._roomId = rid; this._keyPair = null; this._sessionKey = null; - this.peerId = getUidDirectMessage(roomId) as string; + this.peerId = peerId; this.isFirstOTR = true; } + public static create(uid: IUser['_id'], rid: IRoom['_id']): OTRRoom | undefined { + const peerId = getUidDirectMessage(rid); + + if (!peerId) { + return undefined; + } + + return new OTRRoom(uid, rid, peerId); + } + getPeerId(): string { return this.peerId; } @@ -75,61 +84,71 @@ export class OTRRoom implements IOTRRoom { async handshake(refresh?: boolean): Promise { this.setState(OtrRoomState.ESTABLISHING); - try { - await this.generateKeyPair(); - this.peerId && - Notifications.notifyUser(this.peerId, 'otr', 'handshake', { - roomId: this._roomId, - userId: this._userId, - publicKey: EJSON.stringify(this._exportedPublicKey), - refresh, - }); - if (refresh) { - const user = Meteor.user(); - if (!user) { - return; - } - await sdk.rest.post('/v1/chat.otr', { - roomId: this._roomId, - type: otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH, - }); - this.isFirstOTR = false; + + await this.generateKeyPair(); + sdk.publish('notify-user', [ + `${this.peerId}/otr`, + 'handshake', + { + roomId: this._roomId, + userId: this._userId, + publicKey: EJSON.stringify(this._exportedPublicKey), + refresh, + }, + ]); + + if (refresh) { + const user = Meteor.user(); + if (!user) { + return; } - } catch (e) { - throw e; + await sdk.rest.post('/v1/chat.otr', { + roomId: this._roomId, + type: otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH, + }); + this.isFirstOTR = false; } } acknowledge(): void { void sdk.rest.post('/v1/statistics.telemetry', { params: [{ eventName: 'otrStats', timestamp: Date.now(), rid: this._roomId }] }); - this.peerId && - Notifications.notifyUser(this.peerId, 'otr', 'acknowledge', { + sdk.publish('notify-user', [ + `${this.peerId}/otr`, + 'acknowledge', + { roomId: this._roomId, userId: this._userId, publicKey: EJSON.stringify(this._exportedPublicKey), - }); + }, + ]); } deny(): void { this.reset(); this.setState(OtrRoomState.DECLINED); - this.peerId && - Notifications.notifyUser(this.peerId, 'otr', 'deny', { + sdk.publish('notify-user', [ + `${this.peerId}/otr`, + 'deny', + { roomId: this._roomId, userId: this._userId, - }); + }, + ]); } end(): void { this.isFirstOTR = true; this.reset(); this.setState(OtrRoomState.NOT_STARTED); - this.peerId && - Notifications.notifyUser(this.peerId, 'otr', 'end', { + sdk.publish('notify-user', [ + `${this.peerId}/otr`, + 'end', + { roomId: this._roomId, userId: this._userId, - }); + }, + ]); } reset(): void { @@ -145,14 +164,14 @@ export class OTRRoom implements IOTRRoom { } this._userOnlineComputation = Tracker.autorun(() => { - const $room = $(`#chat-window-${this._roomId}`); - const $title = $('.rc-header__title', $room); + const $room = document.querySelector(`#chat-window-${this._roomId}`); + const $title = $room?.querySelector('.rc-header__title'); if (this.getState() === OtrRoomState.ESTABLISHED) { - if ($room.length && $title.length && !$('.otr-icon', $title).length) { + if ($room && $title && !$title.querySelector('.otr-icon')) { $title.prepend(""); } - } else if ($title.length) { - $('.otr-icon', $title).remove(); + } else if ($title) { + $title.querySelector('.otr-icon')?.remove(); } }); try { diff --git a/apps/meteor/app/otr/lib/IOTR.ts b/apps/meteor/app/otr/lib/IOTR.ts index 051752ab947f..9e7cd4ca6b8e 100644 --- a/apps/meteor/app/otr/lib/IOTR.ts +++ b/apps/meteor/app/otr/lib/IOTR.ts @@ -36,9 +36,7 @@ export interface IOTRRoom { } export interface IOTR { - isEnabled(): boolean; - setEnabled(enabled: boolean): void; - getInstanceByRoomId(roomId: IRoom['_id']): OTRRoom | undefined; + getInstanceByRoomId(userId: IUser['_id'], roomId: IRoom['_id']): OTRRoom | undefined; } export interface IOTRAlgorithm extends EcKeyAlgorithm, EcdhKeyDeriveParams {} diff --git a/apps/meteor/app/otr/server/methods/updateOTRAck.ts b/apps/meteor/app/otr/server/methods/updateOTRAck.ts index eaba2906ed06..745c70aa4538 100644 --- a/apps/meteor/app/otr/server/methods/updateOTRAck.ts +++ b/apps/meteor/app/otr/server/methods/updateOTRAck.ts @@ -1,3 +1,4 @@ +import type { IOTRMessage } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +7,7 @@ import notifications from '../../../notifications/server/lib/Notifications'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { - updateOTRAck({ message, ack }: { message: any; ack: any }): void; + updateOTRAck({ message, ack }: { message: IOTRMessage; ack: string }): void; } } @@ -15,7 +16,7 @@ Meteor.methods({ if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'updateOTRAck' }); } - const otrStreamer = notifications.streamRoomMessage; - otrStreamer.emit(message.rid, { ...message, otr: { ack } }); + const acknowlegedMessage: IOTRMessage = { ...message, otrAck: ack }; + notifications.streamRoomMessage.emit(message.rid, acknowlegedMessage); }, }); diff --git a/apps/meteor/app/slackbridge/server/SlackAdapter.js b/apps/meteor/app/slackbridge/server/SlackAdapter.js index 0fb4ee8a7125..78d48deb4993 100644 --- a/apps/meteor/app/slackbridge/server/SlackAdapter.js +++ b/apps/meteor/app/slackbridge/server/SlackAdapter.js @@ -1169,6 +1169,7 @@ export default class SlackAdapter { async processPinnedItemMessage(rocketChannel, rocketUser, slackMessage, isImporting) { if (slackMessage.attachments && slackMessage.attachments[0] && slackMessage.attachments[0].text) { + // TODO: refactor this logic to use the service to send this system message instead of using sendMessage const rocketMsgObj = { rid: rocketChannel._id, t: 'message_pinned', @@ -1380,6 +1381,7 @@ export default class SlackAdapter { for await (const pin of items) { if (pin.message) { const user = await this.rocket.findUser(pin.message.user); + // TODO: send this system message to the room as well (using the service) const msgObj = { rid, t: 'message_pinned', diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index b146cb7d0041..30daef8b8b93 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -2,7 +2,7 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocket.chat/models'; -import { getMentions } from '../../lib/server/lib/notifyUsersOnMessage'; +import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { const { rid, ts, u } = message; @@ -19,7 +19,9 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare ...(Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]), ]), ]; + const highlightedUserIds = new Set(); + (await getUserIdsFromHighlights(rid, message)).forEach((uid) => highlightedUserIds.add(uid)); await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); await ReadReceipts.setAsThreadById(tmid); @@ -35,9 +37,16 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, {}); } - for await (const userId of mentionIds) { + const mentionedUsers = new Set([...mentionIds, ...highlightedUserIds]); + for await (const userId of mentionedUsers) { await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, [userId], tmid, { userMention: true }); } + + const highlightIds = Array.from(highlightedUserIds); + if (highlightIds.length) { + await Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightIds); + await Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightIds); + } } export async function follow({ tmid, uid }: { tmid: string; uid: string }) { diff --git a/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts b/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts index 85aafa4bb185..23221a49a293 100644 --- a/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts +++ b/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts @@ -171,24 +171,78 @@ const computation = Tracker.autorun(() => { record.streamActive = true; openedRoomsDependency.changed(); }); + + // when we receive a messages imported event we just clear the room history and fetch it again + Notifications.onRoom(record.rid, 'messagesImported', async () => { + await RoomHistoryManager.clear(record.rid); + await RoomHistoryManager.getMore(record.rid); + }); + Notifications.onRoom(record.rid, 'deleteMessage', (msg) => { ChatMessage.remove({ _id: msg._id }); // remove thread refenrece from deleted message ChatMessage.update({ tmid: msg._id }, { $unset: { tmid: 1 } }, { multi: true }); }); - Notifications.onRoom(record.rid, 'deleteMessageBulk', ({ rid, ts, excludePinned, ignoreDiscussion, users }) => { - const query: Mongo.Selector = { rid, ts }; - if (excludePinned) { - query.pinned = { $ne: true }; - } - if (ignoreDiscussion) { - query.drid = { $exists: false }; - } - if (users?.length) { - query['u.username'] = { $in: users }; + Notifications.onRoom( + record.rid, + 'deleteMessageBulk', + ({ rid, ts, excludePinned, ignoreDiscussion, users, ids, showDeletedStatus }) => { + const query: Mongo.Selector = { rid }; + + if (ids) { + query._id = { $in: ids }; + } else { + query.ts = ts; + } + if (excludePinned) { + query.pinned = { $ne: true }; + } + if (ignoreDiscussion) { + query.drid = { $exists: false }; + } + if (users?.length) { + query['u.username'] = { $in: users }; + } + + if (showDeletedStatus) { + return ChatMessage.update( + query, + { $set: { t: 'rm', msg: '', urls: [], mentions: [], attachments: [], reactions: {} } }, + { multi: true }, + ); + } + return ChatMessage.remove(query); + }, + ); + Notifications.onRoom(record.rid, 'messagesRead', ({ tmid, until }) => { + if (tmid) { + return ChatMessage.update( + { + tmid, + unread: true, + }, + { $unset: { unread: 1 } }, + { multi: true }, + ); } - ChatMessage.remove(query); + ChatMessage.update( + { + rid: record.rid, + unread: true, + ts: { $lt: until }, + $or: [ + { + tmid: { $exists: false }, + }, + { + tshow: true, + }, + ], + }, + { $unset: { unread: 1 } }, + { multi: true }, + ); }); } } diff --git a/apps/meteor/client/components/GenericCard/GenericCard.tsx b/apps/meteor/client/components/GenericCard/GenericCard.tsx new file mode 100644 index 000000000000..007c7da9f15e --- /dev/null +++ b/apps/meteor/client/components/GenericCard/GenericCard.tsx @@ -0,0 +1,31 @@ +import { Card, CardTitle, CardBody, CardControls, CardHeader } from '@rocket.chat/fuselage'; +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { FramedIcon } from '@rocket.chat/ui-client'; +import type { ComponentProps, ReactElement } from 'react'; +import React from 'react'; + +import type { GenericCardButton } from './GenericCardButton'; + +type GenericCardProps = { + title: string; + body: string; + buttons?: ReactElement[]; + icon?: ComponentProps['icon']; + type?: ComponentProps['type']; +} & ComponentProps; + +export const GenericCard: React.FC = ({ title, body, buttons, icon, type, ...props }) => { + const cardId = useUniqueId(); + const descriptionId = useUniqueId(); + + return ( + + + {icon && } + {title} + + {body} + {buttons && {buttons}} + + ); +}; diff --git a/apps/meteor/client/components/GenericCard/GenericCardButton.tsx b/apps/meteor/client/components/GenericCard/GenericCardButton.tsx new file mode 100644 index 000000000000..b89ec26b6e9b --- /dev/null +++ b/apps/meteor/client/components/GenericCard/GenericCardButton.tsx @@ -0,0 +1,5 @@ +import { Button } from '@rocket.chat/fuselage'; +import type { ComponentProps } from 'react'; +import React from 'react'; + +export const GenericCardButton = (props: ComponentProps) => - + ); }; diff --git a/apps/meteor/client/views/admin/settings/SettingsPage.tsx b/apps/meteor/client/views/admin/settings/SettingsPage.tsx index 5180b89b25fb..f82e16ce637c 100644 --- a/apps/meteor/client/views/admin/settings/SettingsPage.tsx +++ b/apps/meteor/client/views/admin/settings/SettingsPage.tsx @@ -1,4 +1,4 @@ -import { Icon, SearchInput, Skeleton, Grid } from '@rocket.chat/fuselage'; +import { Icon, SearchInput, Skeleton, CardGrid } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useIsSettingsContextLoading, useTranslation } from '@rocket.chat/ui-contexts'; @@ -25,21 +25,31 @@ const SettingsPage = (): ReactElement => { } /> - + + {isLoadingGroups && } - + {!isLoadingGroups && !!groups.length && groups.map((group) => ( - - - + ))} - + + {!isLoadingGroups && !groups.length && } diff --git a/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx b/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx index 98c5054a8f26..93792b517dcf 100644 --- a/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx +++ b/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx @@ -176,7 +176,7 @@ const SubscriptionPage = () => { )} - {Boolean(licensesData?.trial || licensesData?.license?.information.cancellable) && ( + {Boolean(licensesData?.license?.information.cancellable) && ( diff --git a/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx b/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx index bcde39d701fd..3e736062bd7d 100644 --- a/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/FeatureUsageCard.tsx @@ -1,4 +1,4 @@ -import { Card, CardBody, CardColSection, CardFooter, CardTitle } from '@rocket.chat/ui-client'; +import { Box, Card, CardBody, CardControls, CardTitle } from '@rocket.chat/fuselage'; import type { ReactElement, ReactNode } from 'react'; import React, { memo } from 'react'; @@ -18,16 +18,16 @@ export type CardProps = { const FeatureUsageCard = ({ children, card }: FeatureUsageCardProps): ReactElement => { const { title, infoText, upgradeButton } = card; return ( - + {title} {infoText && } - - + + {children} - + - {upgradeButton && {upgradeButton}} + {upgradeButton && {upgradeButton}} ); }; diff --git a/apps/meteor/client/views/admin/subscription/components/UpgradeToGetMore.tsx b/apps/meteor/client/views/admin/subscription/components/UpgradeToGetMore.tsx index 05f83c787b4e..0040fe464c87 100644 --- a/apps/meteor/client/views/admin/subscription/components/UpgradeToGetMore.tsx +++ b/apps/meteor/client/views/admin/subscription/components/UpgradeToGetMore.tsx @@ -1,8 +1,8 @@ -import { Box, States, StatesIcon, StatesTitle, StatesSubtitle, Grid, Button, ButtonGroup } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardTitle, FramedIcon } from '@rocket.chat/ui-client'; +import { Box, States, StatesIcon, StatesTitle, StatesSubtitle, Button, ButtonGroup, CardGrid } from '@rocket.chat/fuselage'; import React, { memo } from 'react'; import { useTranslation } from 'react-i18next'; +import { GenericCard } from '../../../../components/GenericCard'; import { useExternalLink } from '../../../../hooks/useExternalLink'; import { PRICING_LINK } from '../utils/links'; @@ -43,35 +43,28 @@ const UpgradeToGetMore = ({ activeModules, children }: UpgradeToGetMoreProps) => } return ( - + {t('UpgradeToGetMore_Headline')} {t('UpgradeToGetMore_Subtitle')} - - {upgradeModules.map(({ title, body }, index) => ( - - - - - - - {title} - - - - - - {body} - - - - - ))} - - - {children} diff --git a/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsCard.tsx index fbd850f262ba..e42ae7f6b744 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsCard.tsx @@ -55,13 +55,11 @@ const ActiveSessionsCard = (): ReactElement => { }), }} > - - + + {used} / {total} - - {available} {t('ActiveSessions_available')} - + {available} {t('ActiveSessions_available')} ); diff --git a/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsPeakCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsPeakCard.tsx index f62ec2ad164c..02ac3eeeb536 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsPeakCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/ActiveSessionsPeakCard.tsx @@ -33,20 +33,22 @@ const ActiveSessionsPeakCard = (): ReactElement => { }), }; + if (isLoading || maxMonthlyPeakConnections === undefined) { + return ( + + + + ); + } + return ( - {!isLoading && maxMonthlyPeakConnections !== undefined ? ( - - - {used} / {total} - - - {formatDate(new Date())} - + + + {used} / {total} - ) : ( - - )} + {formatDate(new Date())} + ); }; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx index 0043e12d02d1..dbd402ef2c7f 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/AppsUsageCard.tsx @@ -35,37 +35,36 @@ const AppsUsageCard = ({ privateAppsLimit, marketplaceAppsLimit }: AppsUsageCard }), }; + if (!privateAppsLimit || !marketplaceAppsLimit) { + return ( + + + + ); + } + return ( - {privateAppsLimit && marketplaceAppsLimit ? ( - - - - {t('Marketplace_apps')} - = 80 ? 'font-danger' : 'status-font-on-success'}> - {marketplaceAppsEnabled} / {marketplaceAppsLimitCount} - - - - = 80 ? 'danger' : 'success'} - /> + + +
{t('Marketplace_apps')}
+ = 80 ? 'font-danger' : 'status-font-on-success'}> + {marketplaceAppsEnabled} / {marketplaceAppsLimitCount} - - - {t('Private_apps')} - = 80 ? 'font-danger' : 'status-font-on-success'}> - {privateAppsEnabled} / {privateAppsLimitCount} - - + - = 80 ? 'danger' : 'success'} /> + = 80 ? 'danger' : 'success'} /> +
+ + +
{t('Private_apps')}
+ = 80 ? 'font-danger' : 'status-font-on-success'}> + {privateAppsEnabled} / {privateAppsLimitCount}
- ) : ( - - )} + + = 80 ? 'danger' : 'success'} /> +
); }; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/FeaturesCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/FeaturesCard.tsx index 3a2318b70d6d..fbd70a844bdd 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/FeaturesCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/FeaturesCard.tsx @@ -1,12 +1,11 @@ -import { Box } from '@rocket.chat/fuselage'; +import { Box, Card, CardBody, CardControls, CardTitle } from '@rocket.chat/fuselage'; import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; -import { CardCol, CardColSection, CardFooter, FramedIcon } from '@rocket.chat/ui-client'; +import { FramedIcon } from '@rocket.chat/ui-client'; import type { ReactElement } from 'react'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { PRICING_LINK } from '../../utils/links'; -import FeatureUsageCard from '../FeatureUsageCard'; import InfoTextIconModal from '../InfoTextIconModal'; type FeatureSet = { @@ -75,28 +74,27 @@ const FeaturesCard = ({ activeModules, isEnterprise }: FeaturesCardProps): React }; return ( - - - - - {getFeatureSet(activeModules, isEnterprise).map(({ type, title, infoText }, index) => ( - - - - {t(title)} - - {infoText && } + + {!isEnterprise ? t('Unlock_premium_capabilities') : t('Includes')} + + + {getFeatureSet(activeModules, isEnterprise).map(({ type, title, infoText }, index) => ( + + + + {t(title)} - ))} - - - - - {t('Compare_plans')} - - - - + {infoText && } +
+ ))} +
+ + + + {t('Compare_plans')} + + +
); }; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard.tsx b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard.tsx index 066a4fc1f160..5ccc5aeecd18 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard.tsx @@ -17,12 +17,11 @@ type PlanCardProps = { const PlanCard = ({ licenseInformation, licenseLimits }: PlanCardProps): ReactElement => { const isTrial = licenseInformation.trial; - switch (true) { - case isTrial: - return ; - default: - return ; - } + return isTrial ? ( + + ) : ( + + ); }; export default PlanCard; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardBase.tsx b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardBase.tsx deleted file mode 100644 index 1f6cefdb7646..000000000000 --- a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardBase.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Box, Icon, Palette } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardColSection, CardTitle } from '@rocket.chat/ui-client'; -import type { ReactElement, ReactNode } from 'react'; -import React from 'react'; - -const PlanCardBase = ({ name, children }: { name: string; children: ReactNode }): ReactElement => { - return ( - - - - {name} - - - - {children} - - - - ); -}; - -export default PlanCardBase; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardCommunity.tsx b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardCommunity.tsx index 6a8d00ad8b4d..3ce0ee07cabd 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardCommunity.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardCommunity.tsx @@ -1,22 +1,25 @@ -import { Box, Icon } from '@rocket.chat/fuselage'; +import { Card, CardBody, CardRow, Icon } from '@rocket.chat/fuselage'; import type { ReactElement } from 'react'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import PlanCardBase from './PlanCardBase'; +import PlanCardHeader from './PlanCardHeader'; const PlanCardCommunity = (): ReactElement => { const { t } = useTranslation(); return ( - - - {t('free_per_month_user')} - - - {t('Self_managed_hosting')} - - + + + + + {t('free_per_month_user')} + + + {t('Self_managed_hosting')} + + + ); }; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardHeader.tsx b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardHeader.tsx new file mode 100644 index 000000000000..c550cfd2069f --- /dev/null +++ b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardHeader.tsx @@ -0,0 +1,14 @@ +import { CardTitle, Icon, Palette, CardHeader } from '@rocket.chat/fuselage'; +import type { ReactElement } from 'react'; +import React from 'react'; + +const PlanCardHeader = ({ name }: { name: string }): ReactElement => { + return ( + + + {name} + + ); +}; + +export default PlanCardHeader; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardPremium.tsx b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardPremium.tsx index 056aab081785..ea96adf32a38 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardPremium.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardPremium.tsx @@ -1,5 +1,5 @@ import type { ILicenseV3 } from '@rocket.chat/core-typings'; -import { Box, Icon, Skeleton } from '@rocket.chat/fuselage'; +import { Box, Card, CardBody, Icon, Skeleton } from '@rocket.chat/fuselage'; import { ExternalLink } from '@rocket.chat/ui-client'; import type { ReactElement } from 'react'; import React from 'react'; @@ -9,7 +9,7 @@ import { useFormatDate } from '../../../../../../hooks/useFormatDate'; import { useIsSelfHosted } from '../../../../../../hooks/useIsSelfHosted'; import { useLicenseName } from '../../../../../../hooks/useLicense'; import { CONTACT_SALES_LINK } from '../../../utils/links'; -import PlanCardBase from './PlanCardBase'; +import PlanCardHeader from './PlanCardHeader'; type LicenseLimits = { activeUsers: { max: number; value?: number }; @@ -31,35 +31,38 @@ const PlanCardPremium = ({ licenseInformation, licenseLimits }: PlanCardProps): const { visualExpiration } = licenseInformation; return ( - - {licenseLimits?.activeUsers.max === Infinity && ( - - - {t('Unlimited_seats')} - - )} - {visualExpiration && ( - - - - {isAutoRenew ? ( - t('Renews_DATE', { date: formatDate(visualExpiration) }) - ) : ( - - Contact sales to check plan renew date. - - )} + + + + {licenseLimits?.activeUsers.max === Infinity && ( + + + {t('Unlimited_seats')} - - )} - {!isLoading ? ( - - {isSelfHosted ? t('Self_managed_hosting') : t('Cloud_hosting')} - - ) : ( - - )} - + )} + {visualExpiration && ( + + + + {isAutoRenew ? ( + t('Renews_DATE', { date: formatDate(visualExpiration || '') }) + ) : ( + + Contact sales to check plan renew date. + + )} + + + )} + {!isLoading ? ( + + {isSelfHosted ? t('Self_managed_hosting') : t('Cloud_hosting')} + + ) : ( + + )} + + ); }; diff --git a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardTrial.tsx b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardTrial.tsx index 95ce21e67918..eb6a020894ec 100644 --- a/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardTrial.tsx +++ b/apps/meteor/client/views/admin/subscription/components/cards/PlanCard/PlanCardTrial.tsx @@ -1,5 +1,5 @@ import type { ILicenseV3 } from '@rocket.chat/core-typings'; -import { Box, Tag } from '@rocket.chat/fuselage'; +import { Box, Card, CardBody, CardControls, CardRow, Tag } from '@rocket.chat/fuselage'; import { ExternalLink } from '@rocket.chat/ui-client'; import differenceInDays from 'date-fns/differenceInDays'; import type { ReactElement } from 'react'; @@ -9,7 +9,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { useLicenseName } from '../../../../../../hooks/useLicense'; import { DOWNGRADE_LINK, TRIAL_LINK } from '../../../utils/links'; import UpgradeButton from '../../UpgradeButton'; -import PlanCardBase from './PlanCardBase'; +import PlanCardHeader from './PlanCardHeader'; type PlanCardProps = { licenseInformation: ILicenseV3['information']; @@ -23,37 +23,46 @@ const PlanCardTrial = ({ licenseInformation }: PlanCardProps): ReactElement => { const { visualExpiration } = licenseInformation; return ( - - + + + {visualExpiration && ( - - {t('Trial_active')}{' '} + + + {t('Trial_active')} + {t('n_days_left', { n: differenceInDays(new Date(visualExpiration), new Date()) })} - + )} - - {isSalesAssisted ? ( - - Contact sales to finish your purchase and avoid - downgrade consequences. - - ) : ( - - Finish your purchase to avoid downgrade consequences. - - )} - - + + + + {isSalesAssisted ? ( + + Contact sales to finish your purchase and avoid + downgrade consequences. + + ) : ( + + Finish your purchase to avoid downgrade consequences. + + )} + + + + Why has a trial been applied to this workspace? - + + + {isSalesAssisted ? t('Finish_purchase') : t('Contact_sales')} - - + + ); }; diff --git a/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx index 2201e7c9c34c..0b48d0c8cc88 100644 --- a/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx +++ b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx @@ -1,13 +1,13 @@ import type { IWorkspaceInfo, IStats } from '@rocket.chat/core-typings'; -import { ButtonGroup, Button } from '@rocket.chat/fuselage'; +import { Button, Card, CardControls } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { IInstance } from '@rocket.chat/rest-typings'; -import { Card, CardBody, CardCol, CardColSection, CardColTitle, CardFooter } from '@rocket.chat/ui-client'; import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime'; +import WorkspaceCardSection from '../components/WorkspaceCardSection'; import InstancesModal from './components/InstancesModal'; type DeploymentCardProps = { @@ -28,59 +28,41 @@ const DeploymentCard = ({ serverInfo: { info }, statistics, instances }: Deploym }); return ( - - - - {t('Deployment')} - - {t('Version')} - {statistics.version} - - - {t('Deployment_ID')} - {statistics.uniqueId} - - {appsEngineVersion && ( - - {t('Apps_Engine_Version')} - {appsEngineVersion} - - )} - - {t('Node_version')} - {statistics.process.nodeVersion} - - - {t('DB_Migration')} - {`${statistics.migration.version} (${formatDateAndTime(statistics.migration.lockedAt)})`} - - - {t('MongoDB')} - {`${statistics.mongoVersion} / ${statistics.mongoStorageEngine} ${ - !statistics.msEnabled ? `(oplog ${statistics.oplogEnabled ? t('Enabled') : t('Disabled')})` : '' - }`} - - - {t('Commit_details')} + + + + + + {appsEngineVersion && } + + + + {t('github_HEAD')}: ({commit.hash ? commit.hash.slice(0, 9) : ''})
{t('Branch')}: {commit.branch}
{commit.subject} -
- - {t('PID')} - {statistics.process.pid} - -
-
+ + } + /> + {!!instances.length && ( - - - - - + + + )}
); diff --git a/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx b/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx index 7f6538b07b5b..4b3ffd819f81 100644 --- a/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx +++ b/apps/meteor/client/views/admin/workspace/MessagesRoomsCard/MessagesRoomsCard.tsx @@ -1,9 +1,12 @@ import type { IStats } from '@rocket.chat/core-typings'; -import { TextSeparator, Card, CardBody, CardCol, CardColSection, CardColTitle, CardIcon } from '@rocket.chat/ui-client'; +import { Card } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; +import WorkspaceCardSection from '../components/WorkspaceCardSection'; +import WorkspaceCardTextSeparator from '../components/WorkspaceCardTextSeparator'; + type MessagesRoomsCardProps = { statistics: IStats; }; @@ -12,110 +15,46 @@ const MessagesRoomsCard = ({ statistics }: MessagesRoomsCardProps): ReactElement const t = useTranslation(); return ( - - - - - {t('Total_rooms')} - - - {t('Channels')} - - } - value={statistics.totalChannels} - /> - - - {t('Private_Groups')} - - } - value={statistics.totalPrivateGroups} - /> - - - {t('Direct_Messages')} - - } - value={statistics.totalDirect} - /> - - - {t('Discussions')} - - } - value={statistics.totalDiscussions} - /> - - - {t('Omnichannel')} - - } - value={statistics.totalLivechat} - /> - - + + + + + + + + + + } + /> - - {t('Messages')} - - - {t('Stats_Total_Messages_Channel')} - - } - value={statistics.totalChannelMessages} - /> - - - {t('Stats_Total_Messages_PrivateGroup')} - - } + + + - - - {t('Stats_Total_Messages_Direct')} - - } - value={statistics.totalDirectMessages} - /> - - - {t('Stats_Total_Messages_Discussions')} - - } + + - - - {t('Stats_Total_Messages_Livechat')} - - } + - - - - + + + } + /> ); }; diff --git a/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx b/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx index eaf1079d5d70..c3e239c22b8e 100644 --- a/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx +++ b/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx @@ -1,14 +1,14 @@ import type { IStats } from '@rocket.chat/core-typings'; -import { ButtonGroup, Button } from '@rocket.chat/fuselage'; +import { Button, Card, CardControls } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { TextSeparator, Card, CardBody, CardCol, CardColSection, CardColTitle, CardFooter, CardIcon } from '@rocket.chat/ui-client'; import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; import { useHasLicenseModule } from '../../../../../ee/client/hooks/useHasLicenseModule'; -import { UserStatus } from '../../../../components/UserStatus'; import { useFormatMemorySize } from '../../../../hooks/useFormatMemorySize'; +import WorkspaceCardSection from '../components/WorkspaceCardSection'; +import WorkspaceCardTextSeparator from '../components/WorkspaceCardTextSeparator'; type UsersUploadsCardProps = { statistics: IStats; @@ -27,81 +27,48 @@ const UsersUploadsCard = ({ statistics }: UsersUploadsCardProps): ReactElement = const canViewEngagement = useHasLicenseModule('engagement-dashboard'); return ( - - - - - {t('Users')} - - - - - {t('Online')} - - } - value={statistics.onlineUsers} - /> - - - - - {t('Busy')} - - } - value={statistics.busyUsers} - /> - - - - - {t('Away')} - - } - value={statistics.awayUsers} - /> - - - - - {t('Offline')} - - } - value={statistics.offlineUsers} - /> - - + + + + + + + + + } + /> - - {t('Types')} - - - - - - + + + + + + + + } + /> - - {t('Uploads')} - - - - - - - - - - + + + + + } + /> + + + + ); }; diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx index 68f83f4eb505..8de30058bc3d 100644 --- a/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx +++ b/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx @@ -1,8 +1,8 @@ import type { IWorkspaceInfo } from '@rocket.chat/core-typings'; -import { Box, Icon } from '@rocket.chat/fuselage'; +import { Box, Card, CardBody, CardCol, CardControls, CardHeader, CardTitle, Icon } from '@rocket.chat/fuselage'; import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; import type { SupportedVersions } from '@rocket.chat/server-cloud-communication'; -import { Card, CardBody, CardCol, CardColSection, CardColTitle, CardFooter, ExternalLink } from '@rocket.chat/ui-client'; +import { ExternalLink } from '@rocket.chat/ui-client'; import type { LocationPathname } from '@rocket.chat/ui-contexts'; import { useModal, useMediaUrl } from '@rocket.chat/ui-contexts'; import type { ReactElement, ReactNode } from 'react'; @@ -15,7 +15,7 @@ import { useRegistrationStatus } from '../../../../hooks/useRegistrationStatus'; import { isOverLicenseLimits } from '../../../../lib/utils/isOverLicenseLimits'; import VersionCardActionButton from './components/VersionCardActionButton'; import type { VersionActionItem } from './components/VersionCardActionItem'; -import VersionCardActionItemList from './components/VersionCardActionItemList'; +import VersionCardActionItem from './components/VersionCardActionItem'; import { VersionCardSkeleton } from './components/VersionCardSkeleton'; import { VersionTag } from './components/VersionTag'; import { getVersionStatus } from './getVersionStatus'; @@ -171,41 +171,35 @@ const VersionCard = ({ serverInfo }: VersionCardProps): ReactElement => { ).sort((a) => (a.type === 'danger' ? -1 : 1)); }, [isOverLimits, t, isAirgapped, versions, versionStatus?.label, versionStatus?.expiration, formatDate, isRegistered]); + if (isLoading && !licenseData) { + return ( + + ; + + ); + } + return ( - - {!isLoading && licenseData ? ( - <> - - - - - {t('Version_version', { version: serverVersion })} - - {!isAirgapped && versions && } - - - - - - - {licenseName.data} - - - {actionItems.length > 0 && ( - - - - )} - - - {actionButton && ( - - - - )} - - ) : ( - + + + + {t('Version_version', { version: serverVersion })} + {!isAirgapped && versions && } + + + + {licenseName.data} + + + + + {actionItems.length > 0 && actionItems.map((item, index) => )} + + + {actionButton && ( + + + )} ); diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx index b382aee92c33..fa444f0dac70 100644 --- a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx +++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItem.tsx @@ -16,13 +16,7 @@ type VersionCardActionItemProps = { const VersionCardActionItem = ({ actionItem }: VersionCardActionItemProps): ReactElement => { return ( - + {actionItem.label} diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItemList.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItemList.tsx deleted file mode 100644 index 9fdd1416dadc..000000000000 --- a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionItemList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import type { VersionActionItem } from './VersionCardActionItem'; -import VersionCardActionItem from './VersionCardActionItem'; - -type VersionCardActionItemListProps = { - actionItems: VersionActionItem[]; -}; - -const VersionCardActionItemList = ({ actionItems }: VersionCardActionItemListProps) => { - return ( - <> - {actionItems.map((item, index) => ( - - ))} - - ); -}; - -export default VersionCardActionItemList; diff --git a/apps/meteor/client/views/admin/workspace/WorkspacePage.tsx b/apps/meteor/client/views/admin/workspace/WorkspacePage.tsx index 604aaf2dbb9f..d45c01153318 100644 --- a/apps/meteor/client/views/admin/workspace/WorkspacePage.tsx +++ b/apps/meteor/client/views/admin/workspace/WorkspacePage.tsx @@ -1,5 +1,5 @@ import type { IWorkspaceInfo, IStats } from '@rocket.chat/core-typings'; -import { Box, Button, ButtonGroup, Callout, Grid } from '@rocket.chat/fuselage'; +import { Box, Button, ButtonGroup, Callout, CardGrid } from '@rocket.chat/fuselage'; import type { IInstance } from '@rocket.chat/rest-typings'; import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; @@ -78,21 +78,14 @@ const WorkspacePage = ({ )} - - - - - - - - - - - - - - - + + + + + + + +
diff --git a/apps/meteor/client/views/admin/workspace/components/WorkspaceCardSection.tsx b/apps/meteor/client/views/admin/workspace/components/WorkspaceCardSection.tsx new file mode 100644 index 000000000000..f120daaf0886 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/components/WorkspaceCardSection.tsx @@ -0,0 +1,19 @@ +import { Box } from '@rocket.chat/fuselage'; +import type { ReactNode } from 'react'; +import React from 'react'; + +type WorkspaceCardSectionProps = { + title: string; + body?: ReactNode; +}; + +const WorkspaceCardSection = ({ title, body }: WorkspaceCardSectionProps) => { + return ( + + {title} + {body && body} + + ); +}; + +export default WorkspaceCardSection; diff --git a/apps/meteor/client/views/admin/workspace/components/WorkspaceCardTextSeparator.tsx b/apps/meteor/client/views/admin/workspace/components/WorkspaceCardTextSeparator.tsx new file mode 100644 index 000000000000..607189d28365 --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/components/WorkspaceCardTextSeparator.tsx @@ -0,0 +1,30 @@ +import { Box, Icon, StatusBullet } from '@rocket.chat/fuselage'; +import type { Keys } from '@rocket.chat/icons'; +import { TextSeparator } from '@rocket.chat/ui-client'; +import type { ComponentProps, ReactNode } from 'react'; +import React from 'react'; + +type WorkspaceCardTextSeparatorProps = { + label: ReactNode; + icon?: Keys; + status?: ComponentProps['status']; + value: ReactNode; +}; +const WorkspaceCardTextSeparator = ({ icon, label, value, status }: WorkspaceCardTextSeparatorProps) => ( + + {icon && } + {status && ( + + + + )} + {label && label} + + } + value={value} + /> +); + +export default WorkspaceCardTextSeparator; diff --git a/apps/meteor/client/views/cloud/CloudAnnouncementsRegion.tsx b/apps/meteor/client/views/cloud/CloudAnnouncementsRegion.tsx index 7352b54e757f..2f353a4f9701 100644 --- a/apps/meteor/client/views/cloud/CloudAnnouncementsRegion.tsx +++ b/apps/meteor/client/views/cloud/CloudAnnouncementsRegion.tsx @@ -16,7 +16,7 @@ const CloudAnnouncementsRegion = () => { select: (data) => data.banners, enabled: !!uid, staleTime: 0, - refetchInterval: 1000 * 60 * 5, + refetchInterval: 1000 * 60 * 60 * 24, }); const subscribeToNotifyLoggedIn = useStream('notify-logged'); diff --git a/apps/meteor/client/views/home/DefaultHomePage.tsx b/apps/meteor/client/views/home/DefaultHomePage.tsx index 31aab73411fe..85fd1ace2db9 100644 --- a/apps/meteor/client/views/home/DefaultHomePage.tsx +++ b/apps/meteor/client/views/home/DefaultHomePage.tsx @@ -1,4 +1,4 @@ -import { Box, Grid } from '@rocket.chat/fuselage'; +import { Box, CardGroup } from '@rocket.chat/fuselage'; import { useAtLeastOnePermission, useSetting, useTranslation, useRole, usePermission } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -6,7 +6,6 @@ import React from 'react'; import Page from '../../components/Page/Page'; import PageScrollableContent from '../../components/Page/PageScrollableContent'; import HomePageHeader from './HomePageHeader'; -import HomepageGridItem from './HomepageGridItem'; import AddUsersCard from './cards/AddUsersCard'; import CreateChannelsCard from './cards/CreateChannelsCard'; import CustomContentCard from './cards/CustomContentCard'; @@ -36,35 +35,17 @@ const DefaultHomePage = (): ReactElement => { {t('Some_ideas_to_get_you_started')} - - {canAddUsers && ( - - - - )} - {canCreateChannel && ( - - - - )} - + + + {canAddUsers && } + {canCreateChannel && } - - - - - - - - - {(isAdmin || (isCustomContentVisible && !isCustomContentBodyEmpty)) && ( - - - - )} + {(isAdmin || (isCustomContentVisible && !isCustomContentBodyEmpty)) && } + + ); diff --git a/apps/meteor/client/views/home/cards/AddUsersCard.tsx b/apps/meteor/client/views/home/cards/AddUsersCard.tsx index 551552a182d4..33a096fbfe5d 100644 --- a/apps/meteor/client/views/home/cards/AddUsersCard.tsx +++ b/apps/meteor/client/views/home/cards/AddUsersCard.tsx @@ -1,29 +1,27 @@ -import { Button } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; -import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { Card } from '@rocket.chat/fuselage'; +import { useTranslation, useRouter } from '@rocket.chat/ui-contexts'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; -const AddUsersCard = (): ReactElement => { +import { GenericCard, GenericCardButton } from '../../../components/GenericCard'; + +const AddUsersCard = (props: Omit, 'type'>): ReactElement => { const t = useTranslation(); - const adminUsersRoute = useRoute('admin-users'); + const router = useRouter(); const handleOpenUsersRoute = (): void => { - adminUsersRoute.push({}); + router.navigate('/admin/users'); }; return ( - - {t('Add_users')} - {t('Invite_and_add_members_to_this_workspace_to_start_communicating')} - - - - - - + ]} + data-qa-id='homepage-add-users-card' + width='x340' + {...props} + /> ); }; diff --git a/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx b/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx index 9e84a8ff373d..a0edf15ba3ac 100644 --- a/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx +++ b/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx @@ -1,27 +1,26 @@ -import { Button } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; +import type { Card } from '@rocket.chat/fuselage'; import { useTranslation, useSetModal } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; +import { GenericCard, GenericCardButton } from '../../../components/GenericCard'; import CreateChannelWithData from '../../../sidebar/header/CreateChannel'; -const CreateChannelsCard = (): ReactElement => { +const CreateChannelsCard = (props: Omit, 'type'>): ReactElement => { const t = useTranslation(); const setModal = useSetModal(); const openCreateChannelModal = (): void => setModal( setModal(null)} />); return ( - - {t('Create_channels')} - {t('Create_a_public_channel_that_new_workspace_members_can_join')} - - - - - - + ]} + data-qa-id='homepage-create-channels-card' + width='x340' + {...props} + /> ); }; diff --git a/apps/meteor/client/views/home/cards/CustomContentCard.tsx b/apps/meteor/client/views/home/cards/CustomContentCard.tsx index 7683f1e1c300..af1bf899dd84 100644 --- a/apps/meteor/client/views/home/cards/CustomContentCard.tsx +++ b/apps/meteor/client/views/home/cards/CustomContentCard.tsx @@ -1,15 +1,15 @@ -import { Box, Button, Icon, Tag } from '@rocket.chat/fuselage'; -import { Card, CardFooter, CardFooterWrapper } from '@rocket.chat/ui-client'; -import { useRole, useSettingSetValue, useSetting, useToastMessageDispatch, useTranslation, useRoute } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import { Box, Button, Card, CardBody, CardControls, CardHeader, Icon, Tag } from '@rocket.chat/fuselage'; +import { useRole, useSettingSetValue, useSetting, useToastMessageDispatch, useTranslation, useRouter } from '@rocket.chat/ui-contexts'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; import { useIsEnterprise } from '../../../hooks/useIsEnterprise'; import CustomHomepageContent from '../CustomHomePageContent'; -const CustomContentCard = (): ReactElement | null => { +const CustomContentCard = (props: Omit, 'type'>): ReactElement | null => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); + const router = useRouter(); const { data } = useIsEnterprise(); const isAdmin = useRole('admin'); @@ -18,8 +18,6 @@ const CustomContentCard = (): ReactElement | null => { const isCustomContentVisible = useSetting('Layout_Home_Custom_Block_Visible'); const isCustomContentOnly = useSetting('Layout_Custom_Body_Only'); - const settingsRoute = useRoute('admin-settings'); - const setCustomContentVisible = useSettingSetValue('Layout_Home_Custom_Block_Visible'); const setCustomContentOnly = useSettingSetValue('Layout_Custom_Body_Only'); @@ -53,39 +51,37 @@ const CustomContentCard = (): ReactElement | null => { if (isAdmin) { return ( - - + + {willNotShowCustomContent ? t('Not_Visible_To_Workspace') : t('Visible_To_Workspace')} - - {isCustomContentBodyEmpty ? t('Homepage_Custom_Content_Default_Message') : } - - - - - - - + + {isCustomContentBodyEmpty ? t('Homepage_Custom_Content_Default_Message') : } + + + + + ); } diff --git a/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx b/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx index 5a3c7834b3b4..bd3b46c6adbd 100644 --- a/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx +++ b/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx @@ -1,31 +1,32 @@ -import { Button } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; +import type { Card } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; +import { GenericCard, GenericCardButton } from '../../../components/GenericCard'; import { useExternalLink } from '../../../hooks/useExternalLink'; const WINDOWS_APP_URL = 'https://go.rocket.chat/i/hp-desktop-app-windows'; const LINUX_APP_URL = 'https://go.rocket.chat/i/hp-desktop-app-linux'; const MAC_APP_URL = 'https://go.rocket.chat/i/hp-desktop-app-mac'; -const DesktopAppsCard = (): ReactElement => { +const DesktopAppsCard = (props: Omit, 'type'>): ReactElement => { const t = useTranslation(); const handleOpenLink = useExternalLink(); return ( - - {t('Desktop_apps')} - {t('Install_rocket_chat_on_your_preferred_desktop_platform')} - - - - - - - - + handleOpenLink(WINDOWS_APP_URL)} children={t('Platform_Windows')} role='link' />, + handleOpenLink(LINUX_APP_URL)} children={t('Platform_Linux')} role='link' />, + handleOpenLink(MAC_APP_URL)} children={t('Platform_Mac')} role='link' />, + ]} + width='x340' + data-qa-id='homepage-desktop-apps-card' + {...props} + /> ); }; diff --git a/apps/meteor/client/views/home/cards/DocumentationCard.tsx b/apps/meteor/client/views/home/cards/DocumentationCard.tsx index 2ae8c0618093..fc50e0aefa3e 100644 --- a/apps/meteor/client/views/home/cards/DocumentationCard.tsx +++ b/apps/meteor/client/views/home/cards/DocumentationCard.tsx @@ -1,27 +1,26 @@ -import { Button } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; +import type { Card } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; +import { GenericCard, GenericCardButton } from '../../../components/GenericCard'; import { useExternalLink } from '../../../hooks/useExternalLink'; const DOCS_URL = 'https://go.rocket.chat/i/hp-documentation'; -const DocumentationCard = (): ReactElement => { +const DocumentationCard = (props: Omit, 'type'>): ReactElement => { const t = useTranslation(); const handleOpenLink = useExternalLink(); return ( - - {t('Documentation')} - {t('Learn_how_to_unlock_the_myriad_possibilities_of_rocket_chat')} - - - - - - + handleOpenLink(DOCS_URL)} children={t('See_documentation')} role='link' />]} + data-qa-id='homepage-documentation-card' + width='x340' + {...props} + /> ); }; diff --git a/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx b/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx index c4bfcc36d7e7..de995b8e8100 100644 --- a/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx +++ b/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx @@ -1,10 +1,11 @@ -import { Button } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; +import type { Card } from '@rocket.chat/fuselage'; import { useTranslation, useRouter } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; -const JoinRoomsCard = (): ReactElement => { +import { GenericCard, GenericCardButton } from '../../../components/GenericCard'; + +const JoinRoomsCard = (props: Omit, 'type'>): ReactElement => { const t = useTranslation(); const router = useRouter(); @@ -13,15 +14,14 @@ const JoinRoomsCard = (): ReactElement => { }; return ( - - {t('Join_rooms')} - {t('Discover_public_channels_and_teams_in_the_workspace_directory')} - - - - - - + ]} + data-qa-id='homepage-join-rooms-card' + width='x340' + {...props} + /> ); }; diff --git a/apps/meteor/client/views/home/cards/MobileAppsCard.tsx b/apps/meteor/client/views/home/cards/MobileAppsCard.tsx index 5527e8d3e0f5..fdb3d4044a7d 100644 --- a/apps/meteor/client/views/home/cards/MobileAppsCard.tsx +++ b/apps/meteor/client/views/home/cards/MobileAppsCard.tsx @@ -1,29 +1,30 @@ -import { Button } from '@rocket.chat/fuselage'; -import { Card, CardTitle, CardBody, CardFooterWrapper, CardFooter } from '@rocket.chat/ui-client'; +import type { Card } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; +import { GenericCard, GenericCardButton } from '../../../components/GenericCard'; import { useExternalLink } from '../../../hooks/useExternalLink'; const GOOGLE_PLAY_URL = 'https://go.rocket.chat/i/hp-mobile-app-google'; const APP_STORE_URL = 'https://go.rocket.chat/i/hp-mobile-app-apple'; -const MobileAppsCard = (): ReactElement => { +const MobileAppsCard = (props: Omit, 'type'>): ReactElement => { const t = useTranslation(); const handleOpenLink = useExternalLink(); return ( - - {t('Mobile_apps')} - {t('Take_rocket_chat_with_you_with_mobile_applications')} - - - - - - - + handleOpenLink(GOOGLE_PLAY_URL)} children={t('Google_Play')} role='link' />, + handleOpenLink(APP_STORE_URL)} children={t('App_Store')} role='link' />, + ]} + data-qa-id='homepage-mobile-apps-card' + width='x340' + {...props} + /> ); }; diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx index 6b99cab23283..6c854119028d 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatus.tsx @@ -1,5 +1,5 @@ import type { App } from '@rocket.chat/core-typings'; -import { Box, Button, Throbber, Tag, Margins } from '@rocket.chat/fuselage'; +import { Box, Button, Tag, Margins } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useRouteParameter, usePermission, useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; @@ -128,15 +128,15 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed, ...pro invisible={!showStatus && !loading} > {shouldShowPriceDisplay && !installed && ( @@ -146,7 +146,7 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed, ...pro )} {statuses?.map((status, index) => ( - + {handleAppRequestsNumber(status)} {t(`${status.label}` as TranslationKey)} diff --git a/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx b/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx index 3005b706326b..4fba7de54e0d 100644 --- a/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx +++ b/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx @@ -1,7 +1,5 @@ import type { App } from '@rocket.chat/core-typings'; -import { css } from '@rocket.chat/css-in-js'; -import { Badge, Box, Palette } from '@rocket.chat/fuselage'; -import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; +import { Badge, Card, CardBody, CardCol, CardControls, CardHeader, CardRow, CardTitle } from '@rocket.chat/fuselage'; import { useRouteParameter, useRouter } from '@rocket.chat/ui-contexts'; import type { KeyboardEvent, MouseEvent, ReactElement } from 'react'; import React, { memo } from 'react'; @@ -13,15 +11,12 @@ import AppMenu from '../AppMenu'; import BundleChips from '../BundleChips'; // TODO: org props -const AppRow = (props: App): ReactElement => { +const AppRow = ({ className, ...props }: App & { className?: string }): ReactElement => { const { name, id, shortDescription, iconFileData, marketplaceVersion, iconFileContent, installed, bundledIn, version } = props; - const breakpoints = useBreakpoints(); const router = useRouter(); const context = useRouteParameter('context'); - const isMobile = !breakpoints.includes('md'); - const handleNavigateToAppInfo = () => { if (!context) { return; @@ -50,65 +45,38 @@ const AppRow = (props: App): ReactElement => { e.stopPropagation(); }; - const hoverClass = css` - &:hover, - &:focus { - cursor: pointer; - outline: 0; - background-color: ${Palette.surface['surface-hover']} !important; - } - `; - const canUpdate = installed && version && marketplaceVersion && semver.lt(version, marketplaceVersion); return ( -
- + - - - - {name} - - - {bundledIn && Boolean(bundledIn.length) && ( - - - - )} - {shortDescription && !isMobile && ( - - {shortDescription} - - )} - - - - {canUpdate && ( - - - - )} + + + + + + {name} + + {Boolean(bundledIn.length) && } + + {shortDescription && {shortDescription}} + + + + {canUpdate && } - - - + + +
); }; diff --git a/apps/meteor/client/views/marketplace/AppsList/AppsList.tsx b/apps/meteor/client/views/marketplace/AppsList/AppsList.tsx index 27d25a566f80..2567cb9c920c 100644 --- a/apps/meteor/client/views/marketplace/AppsList/AppsList.tsx +++ b/apps/meteor/client/views/marketplace/AppsList/AppsList.tsx @@ -1,5 +1,5 @@ import type { App } from '@rocket.chat/core-typings'; -import { Box } from '@rocket.chat/fuselage'; +import { Box, CardGroup } from '@rocket.chat/fuselage'; import type { ReactElement } from 'react'; import React from 'react'; @@ -13,18 +13,18 @@ type AppsListProps = { const AppsList = ({ apps, title, appsListId }: AppsListProps): ReactElement => { return ( - <> + {title && ( {title} )} - + {apps.map((app) => ( ))} - - + + ); }; diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx index 43feaa8b2999..e72a30e6a5c9 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx @@ -132,16 +132,16 @@ const AppsPageContent = (): ReactElement => { context, }); - const noInstalledApps = appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.totalAppsLength === 0; + const noInstalledApps = appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value?.totalAppsLength === 0; const noMarketplaceOrInstalledAppMatches = - appsResult.phase === AsyncStatePhase.RESOLVED && (isMarketplace || isPremium) && appsResult.value.count === 0; + appsResult.phase === AsyncStatePhase.RESOLVED && (isMarketplace || isPremium) && appsResult.value?.count === 0; const noInstalledAppMatches = appsResult.phase === AsyncStatePhase.RESOLVED && context === 'installed' && - appsResult.value.totalAppsLength !== 0 && - appsResult.value.count === 0; + appsResult.value?.totalAppsLength !== 0 && + appsResult.value?.count === 0; const noAppRequests = context === 'requested' && appsResult?.value?.count === 0; @@ -194,13 +194,13 @@ const AppsPageContent = (): ReactElement => { } if (noMarketplaceOrInstalledAppMatches) { - return ; + return ; } if (noInstalledAppMatches) { return ( diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContentBody.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContentBody.tsx index e9a76d8a67ba..56c3ca26f9a7 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContentBody.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContentBody.tsx @@ -11,7 +11,7 @@ import FeaturedAppsSections from './FeaturedAppsSections'; type AppsPageContentBodyProps = { isMarketplace: boolean; isFiltered: boolean; - appsResult: { items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number }; + appsResult?: { items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number }; itemsPerPage: 25 | 50 | 100; current: number; onSetItemsPerPage: React.Dispatch>; @@ -43,8 +43,8 @@ const AppsPageContentBody = ({ {noErrorsOcurred && ( - {isMarketplace && !isFiltered && } - + {isMarketplace && !isFiltered && } + )} diff --git a/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts b/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts index 437c8d35207d..c90052fd78e3 100644 --- a/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts +++ b/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts @@ -39,8 +39,9 @@ export const useFilteredApps = ({ sortingMethod: string; status: string; context?: string; -}): AsyncState< - { items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number } +}): Omit< + AsyncState<{ items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number }>, + 'error' > => { const value = useMemo(() => { if (appsData.value === undefined) { diff --git a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursDisabledPage.tsx b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursDisabledPage.tsx new file mode 100644 index 000000000000..fcee31bbc3bb --- /dev/null +++ b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursDisabledPage.tsx @@ -0,0 +1,36 @@ +import { States, StatesIcon, StatesTitle, StatesSubtitle, StatesActions, StatesAction, StatesLink, Box } from '@rocket.chat/fuselage'; +import { useRole, useRouter, useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { Page, PageHeader, PageContent } from '../../../components/Page'; + +const BusinessHoursDisabledPage = () => { + const t = useTranslation(); + const router = useRouter(); + const isAdmin = useRole('admin'); + + return ( + + + + + + + {t('Business_hours_is_disabled')} + {t('Business_hours_is_disabled_description')} + {isAdmin && ( + + router.navigate('/admin/settings/Omnichannel')}>{t('Enable_business_hours')} + + )} + + {t('Learn_more_about_business_hours')} + + + + + + ); +}; + +export default BusinessHoursDisabledPage; diff --git a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursRouter.tsx b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursRouter.tsx index f25b00a87c1a..8dd39aa07d5b 100644 --- a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursRouter.tsx +++ b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursRouter.tsx @@ -1,26 +1,31 @@ import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; -import { useRouteParameter, useRouter } from '@rocket.chat/ui-contexts'; +import { useRouteParameter, useRouter, useSetting } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; +import BusinessHoursDisabledPage from './BusinessHoursDisabledPage'; import BusinessHoursMultiplePage from './BusinessHoursMultiplePage'; import EditBusinessHours from './EditBusinessHours'; import EditBusinessHoursWithData from './EditBusinessHoursWithData'; import { useIsSingleBusinessHours } from './useIsSingleBusinessHours'; const BusinessHoursRouter = () => { + const router = useRouter(); const context = useRouteParameter('context'); const id = useRouteParameter('id'); const type = useRouteParameter('type') as LivechatBusinessHourTypes; + const businessHoursEnabled = useSetting('Livechat_enable_business_hours'); const isSingleBH = useIsSingleBusinessHours(); - const router = useRouter(); - useEffect(() => { if (isSingleBH) { router.navigate('/omnichannel/businessHours/edit/default'); } }, [isSingleBH, router, context, type]); + if (!businessHoursEnabled) { + return ; + } + if (context === 'edit' || isSingleBH) { return type ? : null; } diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index 9453ecde0665..e48908d4df9d 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -172,7 +172,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s const { _id, fname, servedBy, ts, lm, department, open, onHold, priorityWeight } = room; const getStatusText = (open: boolean, onHold: boolean): string => { if (!open) return t('Closed'); - return onHold ? t('On_Hold_Chats') : t('Open'); + return onHold ? t('On_Hold_Chats') : t('Room_Status_Open'); }; return ( diff --git a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx index 41ba0bb6bf18..1830500c81b1 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx @@ -28,7 +28,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, customFields, setCu const statusOptions: [string, string][] = [ ['all', t('All')], ['closed', t('Closed')], - ['opened', t('Open')], + ['opened', t('Room_Status_Open')], ['onhold', t('On_Hold_Chats')], ]; diff --git a/apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx b/apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx index aeff8bd15bec..d9d4541b425c 100644 --- a/apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx +++ b/apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx @@ -47,7 +47,13 @@ const ContactField = ({ contact, room }: ContactFieldProps) => { - } /> + } + /> ); diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx index c06b11db7786..843e5de01dc4 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx @@ -134,7 +134,7 @@ const ContactInfo = ({ id: contactId, rid: roomId = '', route }: ContactInfoProp {email && ( - {email} + {email} )} {phoneNumber && ( diff --git a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx index e17ecd855d80..00c9d9cdeecd 100644 --- a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx +++ b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx @@ -91,7 +91,7 @@ const RoomToolbox = ({ className }: RoomToolboxProps) => { {featuredActions.map(mapToToolboxItem)} {featuredActions.length > 0 && } {visibleActions.map(mapToToolboxItem)} - {(normalActions.length > 6 || roomToolboxExpanded) && ( + {(normalActions.length > 6 || !roomToolboxExpanded) && ( )} diff --git a/apps/meteor/client/views/room/composer/ComposerArchived.tsx b/apps/meteor/client/views/room/composer/ComposerArchived.tsx new file mode 100644 index 000000000000..d8e269f9986c --- /dev/null +++ b/apps/meteor/client/views/room/composer/ComposerArchived.tsx @@ -0,0 +1,16 @@ +import { MessageFooterCallout, MessageFooterCalloutContent } from '@rocket.chat/ui-composer'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React from 'react'; + +const ComposerReadOnly = (): ReactElement => { + const t = useTranslation(); + + return ( + + {t('Room_archived')} + + ); +}; + +export default ComposerReadOnly; diff --git a/apps/meteor/client/views/room/composer/ComposerContainer.tsx b/apps/meteor/client/views/room/composer/ComposerContainer.tsx index 3988c080bff2..225ac00e13c7 100644 --- a/apps/meteor/client/views/room/composer/ComposerContainer.tsx +++ b/apps/meteor/client/views/room/composer/ComposerContainer.tsx @@ -5,6 +5,7 @@ import React, { memo } from 'react'; import { useRoom } from '../contexts/RoomContext'; import ComposerAnonymous from './ComposerAnonymous'; +import ComposerArchived from './ComposerArchived'; import ComposerBlocked from './ComposerBlocked'; import ComposerFederation from './ComposerFederation'; import ComposerJoinWithPassword from './ComposerJoinWithPassword'; @@ -14,6 +15,7 @@ import ComposerOmnichannel from './ComposerOmnichannel'; import ComposerReadOnly from './ComposerReadOnly'; import ComposerVoIP from './ComposerVoIP'; import { useMessageComposerIsAnonymous } from './hooks/useMessageComposerIsAnonymous'; +import { useMessageComposerIsArchived } from './hooks/useMessageComposerIsArchived'; import { useMessageComposerIsBlocked } from './hooks/useMessageComposerIsBlocked'; import { useMessageComposerIsReadOnly } from './hooks/useMessageComposerIsReadOnly'; @@ -24,7 +26,9 @@ const ComposerContainer = ({ children, ...props }: ComposerMessageProps): ReactE const isAnonymous = useMessageComposerIsAnonymous(); const isBlockedOrBlocker = useMessageComposerIsBlocked({ subscription: props.subscription }); - const isReadOnly = useMessageComposerIsReadOnly(room._id, props.subscription); + const isArchived = useMessageComposerIsArchived(room._id, props.subscription); + const isReadOnly = useMessageComposerIsReadOnly(room._id); + const isOmnichannel = isOmnichannelRoom(room); const isFederation = isRoomFederated(room); const isVoip = isVoipRoom(room); @@ -45,14 +49,18 @@ const ComposerContainer = ({ children, ...props }: ComposerMessageProps): ReactE return ; } - if (mustJoinWithCode) { - return ; - } - if (isReadOnly) { return ; } + if (isArchived) { + return ; + } + + if (mustJoinWithCode) { + return ; + } + if (isBlockedOrBlocker) { return ; } diff --git a/apps/meteor/client/views/room/composer/RoomComposer/hooks/useAutoGrow.ts b/apps/meteor/client/views/room/composer/RoomComposer/hooks/useAutoGrow.ts index 4ce30df1bb3f..ab2df1cd4630 100644 --- a/apps/meteor/client/views/room/composer/RoomComposer/hooks/useAutoGrow.ts +++ b/apps/meteor/client/views/room/composer/RoomComposer/hooks/useAutoGrow.ts @@ -14,7 +14,7 @@ const shadowStyleBase: CSSProperties = { export const useAutoGrow = ( ref: RefObject, - shadowRef: RefObject, + shadowRef: RefObject, hideTextArea?: boolean, ): { textAreaStyle: CSSProperties; @@ -37,8 +37,7 @@ export const useAutoGrow = ( if (!textarea) { return; } - - const handleInput = () => { + const updateTextareaSize = () => { const { value } = textarea; const { current: shadow } = shadowRef; if (!shadow) { @@ -51,10 +50,10 @@ export const useAutoGrow = ( .replace(/\n$/, '
 ') .replace(/\n/g, '
'); }; - - textarea.addEventListener('input', handleInput); + updateTextareaSize(); + textarea.addEventListener('input', updateTextareaSize); return () => { - textarea.removeEventListener('input', handleInput); + textarea.removeEventListener('input', updateTextareaSize); }; }, [ref, shadowRef]); diff --git a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts new file mode 100644 index 000000000000..d0214f37d960 --- /dev/null +++ b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts @@ -0,0 +1,16 @@ +import type { ISubscription } from '@rocket.chat/core-typings'; +import { useCallback } from 'react'; + +import { useReactiveValue } from '../../../../hooks/useReactiveValue'; +import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; + +export const useMessageComposerIsArchived = (rid: string, subscription?: ISubscription): boolean => { + const isArchived = useReactiveValue( + useCallback( + () => roomCoordinator.archived(rid) || Boolean(subscription && subscription.t === 'd' && subscription.archived), + [rid, subscription], + ), + ); + + return Boolean(isArchived); +}; diff --git a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts index 23a79b405c31..8f538bf493ad 100644 --- a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts +++ b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts @@ -1,20 +1,17 @@ -import type { ISubscription, IUser } from '@rocket.chat/core-typings'; +import type { IUser } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import { useCallback } from 'react'; import { useReactiveValue } from '../../../../hooks/useReactiveValue'; import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; -export const useMessageComposerIsReadOnly = (rid: string, subscription?: ISubscription): boolean => { - const [isReadOnly, isArchived] = useReactiveValue( +export const useMessageComposerIsReadOnly = (rid: string): boolean => { + const isReadOnly = useReactiveValue( useCallback( - () => [ - roomCoordinator.readOnly(rid, Meteor.users.findOne(Meteor.userId() as string, { fields: { username: 1 } }) as IUser), - roomCoordinator.archived(rid) || Boolean(subscription && subscription.t === 'd' && subscription.archived), - ], - [rid, subscription], + () => roomCoordinator.readOnly(rid, Meteor.users.findOne(Meteor.userId() as string, { fields: { username: 1 } }) as IUser), + [rid], ), ); - return Boolean(isReadOnly || isArchived); + return Boolean(isReadOnly); }; diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index f7d5bfaf58e3..330fdbb8771d 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -126,7 +126,7 @@ const MessageBox = ({ const textareaRef = useRef(null); const messageComposerRef = useRef(null); - const shadowRef = useRef(null); + const shadowRef = useRef(null); const storageID = `messagebox_${room._id}${tmid ? `-${tmid}` : ''}`; diff --git a/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx b/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx index 91257c311733..a4c642bdea64 100644 --- a/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx @@ -1,18 +1,26 @@ +import { useUserId } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react'; -import ORTInstance from '../../../../../app/otr/client/OTR'; +import OTR from '../../../../../app/otr/client/OTR'; import { OtrRoomState } from '../../../../../app/otr/lib/OtrRoomState'; import { usePresence } from '../../../../hooks/usePresence'; import { useReactiveValue } from '../../../../hooks/useReactiveValue'; import { useRoom } from '../../contexts/RoomContext'; import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; -import OTR from './OTR'; +import OTRComponent from './OTR'; const OTRWithData = (): ReactElement => { + const uid = useUserId(); const room = useRoom(); const { closeTab } = useRoomToolbox(); - const otr = useMemo(() => ORTInstance.getInstanceByRoomId(room._id), [room._id]); + const otr = useMemo(() => { + if (!uid) { + return; + } + + return OTR.getInstanceByRoomId(uid, room._id); + }, [uid, room._id]); const otrState = useReactiveValue(useCallback(() => (otr ? otr.getState() : OtrRoomState.ERROR), [otr])); const peerUserPresence = usePresence(otr?.getPeerId()); const userStatus = peerUserPresence?.status; @@ -47,7 +55,7 @@ const OTRWithData = (): ReactElement => { }, [otr, otrState]); return ( - ; users: string[]; + ids?: string[]; // message ids have priority over ts + showDeletedStatus?: boolean; }; const createDeleteCriteria = (params: NotifyRoomRidDeleteMessageBulkEvent): ((message: IMessage) => boolean) => { - const query: Query = { ts: params.ts }; + const query: Query = {}; + + if (params.ids) { + query._id = { $in: params.ids }; + } else { + query.ts = params.ts; + } if (params.excludePinned) { query.pinned = { $ne: true }; diff --git a/apps/meteor/ee/app/authorization/server/callback.ts b/apps/meteor/ee/app/authorization/server/callback.ts index 4da07733ab51..707d45d96e0e 100644 --- a/apps/meteor/ee/app/authorization/server/callback.ts +++ b/apps/meteor/ee/app/authorization/server/callback.ts @@ -12,10 +12,36 @@ License.onInstall(() => { callbacks.priority.HIGH, 'validateUserRoles', ); - callbacks.add('afterSaveUser', () => License.shouldPreventAction('activeUsers'), callbacks.priority.HIGH, 'validateUserRoles'); - callbacks.add('afterDeleteUser', () => License.shouldPreventAction('activeUsers'), callbacks.priority.HIGH, 'validateUserRoles'); + callbacks.add( + 'afterSaveUser', + async (user) => { + await License.shouldPreventAction('activeUsers'); - callbacks.add('afterDeactivateUser', () => License.shouldPreventAction('activeUsers'), callbacks.priority.HIGH, 'validateUserStatus'); + return user; + }, + callbacks.priority.HIGH, + 'validateUserRoles', + ); + callbacks.add( + 'afterDeleteUser', + async (user) => { + await License.shouldPreventAction('activeUsers'); + + return user; + }, + callbacks.priority.HIGH, + 'validateUserRoles', + ); + + callbacks.add( + 'afterDeactivateUser', + async (user) => { + await License.shouldPreventAction('activeUsers'); + return user; + }, + callbacks.priority.HIGH, + 'validateUserStatus', + ); callbacks.add( 'beforeActivateUser', diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onTransferFailure.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onTransferFailure.ts index a667d4524926..f5e88ca813d9 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onTransferFailure.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onTransferFailure.ts @@ -38,35 +38,27 @@ const onTransferFailure = async ( } // TODO: find enabled not archived here - const fallbackDepartment = await LivechatDepartment.findOneById(department.fallbackForwardDepartment, { - projection: { name: 1, _id: 1 }, - }); - - if (!fallbackDepartment) { - return false; - } - - const fallbackDepartmentDb = await LivechatDepartment.findOneById>( + const fallbackDepartment = await LivechatDepartment.findOneById>( department.fallbackForwardDepartment, { projection: { name: 1, _id: 1 }, }, ); + if (!fallbackDepartment) { + return false; + } + const transferDataFallback = { ...transferData, prevDepartment: department.name, departmentId: department.fallbackForwardDepartment, - ...(fallbackDepartmentDb && { - department: fallbackDepartmentDb, - }), + department: fallbackDepartment, }; const forwardSuccess = await forwardRoomToDepartment(room, guest, transferDataFallback); if (forwardSuccess) { const { _id, username } = transferData.transferredBy; - // The property is injected dynamically on ee folder - await Message.saveSystemMessage( 'livechat_transfer_history_fallback', room._id, diff --git a/apps/meteor/ee/client/components/dashboards/DownloadDataButton.tsx b/apps/meteor/ee/client/components/dashboards/DownloadDataButton.tsx index ba36dffa9d9f..fb379021f780 100644 --- a/apps/meteor/ee/client/components/dashboards/DownloadDataButton.tsx +++ b/apps/meteor/ee/client/components/dashboards/DownloadDataButton.tsx @@ -49,7 +49,6 @@ const DownloadDataButton = ({ { @@ -25,19 +24,13 @@ const ReportsPage = () => { {t('Omnichannel_Reports_Summary')} - - - - - - - - - - - - - + + + + + + + ); diff --git a/apps/meteor/ee/client/omnichannel/reports/components/CardErrorState.tsx b/apps/meteor/ee/client/omnichannel/reports/components/CardErrorState.tsx deleted file mode 100644 index ddff31df95f5..000000000000 --- a/apps/meteor/ee/client/omnichannel/reports/components/CardErrorState.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { States, StatesAction, StatesActions, StatesIcon, StatesTitle } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement, ReactNode } from 'react'; -import React from 'react'; - -type CardErrorStateProps = { - children: ReactNode; - isError?: boolean; - onRetry?: () => void; -}; - -export const CardErrorState = ({ children, isError, onRetry }: CardErrorStateProps): ReactElement => { - const t = useTranslation(); - - return ( - <> - {isError ? ( - - - {t('Something_went_wrong')} - - {t('Retry')} - - - ) : ( - children - )} - - ); -}; diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx index d885242f7722..76cf1758495d 100644 --- a/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx +++ b/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx @@ -1,101 +1,62 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { Box, Skeleton, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; -import { Card, CardBody, CardCol, CardTitle } from '@rocket.chat/ui-client'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactNode, ComponentProps, ReactElement } from 'react'; -import React from 'react'; +import { Box, Card, CardTitle, CardBody, CardCol, CardRow } from '@rocket.chat/fuselage'; +import type { ReactElement, ComponentProps } from 'react'; +import React, { forwardRef } from 'react'; import DownloadDataButton from '../../../components/dashboards/DownloadDataButton'; import PeriodSelector from '../../../components/dashboards/PeriodSelector'; -import { useIsResizing } from '../hooks/useIsResizing'; -import { CardErrorState } from './CardErrorState'; +import { ReportCardContent } from './ReportCardContent'; type ReportCardProps = { id: string; title: string; - children: ReactNode; + children: ReactElement; periodSelectorProps: ComponentProps; downloadProps: ComponentProps; isLoading?: boolean; isDataFound?: boolean; minHeight?: number; - loadingSkeleton?: ReactElement; subtitle?: string; emptyStateSubtitle?: string; - full?: boolean; isError?: boolean; onRetry?: () => void; + chartHeight?: number; }; -export const ReportCard = ({ - id, - title, - children, - periodSelectorProps, - downloadProps, - isLoading: isLoadingData, - isDataFound, - minHeight, - subtitle, - emptyStateSubtitle, - full, - isError, - onRetry, - loadingSkeleton: LoadingSkeleton = , -}: ReportCardProps) => { - const t = useTranslation(); - const width = full ? '100%' : '50%'; - const isResizing = useIsResizing(); - const isLoading = isLoadingData || isResizing; - +export const ReportCard = forwardRef(function ReportCard( + { id, title, children, periodSelectorProps, downloadProps, isLoading, isDataFound, subtitle, emptyStateSubtitle, isError, onRetry }, + ref, +) { return ( - - - - - - {title} - + + + + + {title} {subtitle} - - - - - + + + + + + + - - - - - - {isLoading && LoadingSkeleton} - - {!isLoading && !isDataFound && ( - - - {t('No_data_available_for_the_selected_period')} - {emptyStateSubtitle} - - )} - - {!isLoading && isDataFound && children} - + + + + {children} + - - + + ); -}; +}); diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ReportCardContent.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardContent.tsx new file mode 100644 index 000000000000..41de9ffceb1f --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardContent.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import type { ReactElement } from 'react'; + +import { ReportCardEmptyState } from './ReportCardEmptyState'; +import { ReportCardErrorState } from './ReportCardErrorState'; +import { ReportCardLoadingState } from './ReportCardLoadingState'; + +type ReportCardContentProps = { + isLoading?: boolean; + isError?: boolean; + isDataFound?: boolean; + subtitle?: string; + onRetry?: () => void; + children: ReactElement; +}; +export const ReportCardContent = ({ isLoading, isError, isDataFound, subtitle, onRetry, children }: ReportCardContentProps) => { + if (isLoading) { + return ; + } + if (isError) { + return ; + } + if (!isDataFound) { + return ; + } + return children; +}; diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ReportCardEmptyState.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardEmptyState.tsx new file mode 100644 index 000000000000..e0253f11665c --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardEmptyState.tsx @@ -0,0 +1,21 @@ +import { States, StatesIcon, StatesTitle, StatesSubtitle } from '@rocket.chat/fuselage'; +import type { Keys } from '@rocket.chat/icons'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +type CardEmpryStateProps = { + icon?: Keys; + subtitle?: string; +}; + +export const ReportCardEmptyState = ({ icon, subtitle }: CardEmpryStateProps) => { + const t = useTranslation(); + + return ( + + + {t('No_data_available_for_the_selected_period')} + {subtitle && {subtitle}} + + ); +}; diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ReportCardErrorState.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardErrorState.tsx new file mode 100644 index 000000000000..023a4b5c918b --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardErrorState.tsx @@ -0,0 +1,22 @@ +import { States, StatesAction, StatesActions, StatesIcon, StatesTitle } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React from 'react'; + +type ReportCardErrorStateProps = { + onRetry?: () => void; +}; + +export const ReportCardErrorState = ({ onRetry }: ReportCardErrorStateProps): ReactElement => { + const t = useTranslation(); + + return ( + + + {t('Something_went_wrong')} + + {t('Retry')} + + + ); +}; diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ReportCardLoadingState.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardLoadingState.tsx new file mode 100644 index 000000000000..bd5ae24c3a9e --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/reports/components/ReportCardLoadingState.tsx @@ -0,0 +1,17 @@ +import { Box, Skeleton } from '@rocket.chat/fuselage'; +import React from 'react'; + +export const ReportCardLoadingState = () => ( + + + + + + + + + + + + +); diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ResizeObserver.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ResizeObserver.tsx deleted file mode 100644 index 3f6641d79225..000000000000 --- a/apps/meteor/ee/client/omnichannel/reports/components/ResizeObserver.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { ReactElement } from 'react'; -import React, { cloneElement, createContext } from 'react'; - -import { useResizeObserver } from '../hooks/useResizeObserver'; - -export const ResizeContext = createContext(false); - -type ResizeProviderProps = { children: ReactElement }; - -export const ResizeObserver = ({ children }: ResizeProviderProps) => { - const { ref, isResizing } = useResizeObserver(); - - return {cloneElement(children, { ref })}; -}; diff --git a/apps/meteor/ee/client/omnichannel/reports/components/index.ts b/apps/meteor/ee/client/omnichannel/reports/components/index.ts index 6cfffe8f648d..6668b17ae7f6 100644 --- a/apps/meteor/ee/client/omnichannel/reports/components/index.ts +++ b/apps/meteor/ee/client/omnichannel/reports/components/index.ts @@ -2,4 +2,6 @@ export * from './AgentsTable'; export * from './BarChart'; export * from './PieChart'; export * from './ReportCard'; -export * from './CardErrorState'; +export * from './ReportCardErrorState'; +export * from './ReportCardLoadingState'; +export * from './ReportCardEmptyState'; diff --git a/apps/meteor/ee/client/omnichannel/reports/hooks/useIsResizing.tsx b/apps/meteor/ee/client/omnichannel/reports/hooks/useIsResizing.tsx deleted file mode 100644 index 301556f00104..000000000000 --- a/apps/meteor/ee/client/omnichannel/reports/hooks/useIsResizing.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useContext } from 'react'; - -import { ResizeContext } from '../components/ResizeObserver'; - -export const useIsResizing = () => { - return useContext(ResizeContext); -}; diff --git a/apps/meteor/ee/client/omnichannel/reports/hooks/useResizeObserver.tsx b/apps/meteor/ee/client/omnichannel/reports/hooks/useResizeObserver.tsx deleted file mode 100644 index 20f999ea17a7..000000000000 --- a/apps/meteor/ee/client/omnichannel/reports/hooks/useResizeObserver.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { useEffect, useRef, useState } from 'react'; - -export const useResizeObserver = () => { - const [isResizing, setResizing] = useState(false); - const ref = useRef(null); - const timeoutId = useRef(null); - const lastWidth = useRef(null); - - useEffect(() => { - const { current: element } = ref; - if (!element) { - return; - } - - const resizeObserver = new ResizeObserver(([firstEntry]) => { - const currentWidth = firstEntry.borderBoxSize[0].inlineSize; - - if (currentWidth === lastWidth.current) { - return; - } - - if (timeoutId.current) { - clearTimeout(timeoutId.current); - } - - if (!isResizing) { - setResizing(true); - } - - lastWidth.current = currentWidth; - timeoutId.current = setTimeout(() => { - setResizing(false); - timeoutId.current = null; - }, 200); - }); - - resizeObserver.observe(element); - - return () => resizeObserver.unobserve(element); - }, []); - - return { isResizing, ref }; -}; diff --git a/apps/meteor/ee/client/omnichannel/reports/sections/AgentsSection.tsx b/apps/meteor/ee/client/omnichannel/reports/sections/AgentsSection.tsx index b1d59be5cee3..a2b23ea9d089 100644 --- a/apps/meteor/ee/client/omnichannel/reports/sections/AgentsSection.tsx +++ b/apps/meteor/ee/client/omnichannel/reports/sections/AgentsSection.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-multi-comp */ -import { Box, Flex, Skeleton } from '@rocket.chat/fuselage'; -import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; +import { Box } from '@rocket.chat/fuselage'; +import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; @@ -8,61 +8,48 @@ import { AgentsTable, BarChart, ReportCard } from '../components'; import { useAgentsSection } from '../hooks'; import { ellipsis } from '../utils/ellipsis'; -const LoadingSkeleton = () => ( - - - - - - - - - - - - -); +const BREAKPOINT = 768; export const AgentsSection = () => { const { data, sortBy, sortDirection, setSort, ...config } = useAgentsSection(); const t = useTranslation(); - const breakpoints = useBreakpoints(); - const isSmallScreen = !breakpoints.includes('lg'); + + const { ref, contentBoxSize: { inlineSize: cardWidth = 200 } = {} } = useResizeObserver(); + const width = cardWidth * 0.9; + const wrapped = cardWidth ? cardWidth < BREAKPOINT : false; return ( - }> - - - - - {t('Top_5_agents_with_the_most_conversations')} - - ellipsis(v, 10), - }, - axisLeft: { - tickSize: 0, - tickRotation: 0, - tickValues: 4, - }, - }} - /> - - - - - + + + + + {t('Top_5_agents_with_the_most_conversations')} - + ellipsis(v, 10), + }, + axisLeft: { + tickSize: 0, + tickRotation: 0, + tickValues: 4, + }, + }} + /> + + + + + ); diff --git a/apps/meteor/ee/client/omnichannel/reports/sections/ChannelsSection.tsx b/apps/meteor/ee/client/omnichannel/reports/sections/ChannelsSection.tsx index 08bd77e7b146..f76bf7072a9c 100644 --- a/apps/meteor/ee/client/omnichannel/reports/sections/ChannelsSection.tsx +++ b/apps/meteor/ee/client/omnichannel/reports/sections/ChannelsSection.tsx @@ -9,7 +9,7 @@ export const ChannelsSection = () => { const colors = useMemo(() => Object.values(COLORS), []); return ( - + ); diff --git a/apps/meteor/ee/client/omnichannel/reports/sections/DepartmentsSection.tsx b/apps/meteor/ee/client/omnichannel/reports/sections/DepartmentsSection.tsx index 3861a5b64811..01f5fd3df5db 100644 --- a/apps/meteor/ee/client/omnichannel/reports/sections/DepartmentsSection.tsx +++ b/apps/meteor/ee/client/omnichannel/reports/sections/DepartmentsSection.tsx @@ -8,7 +8,7 @@ export const DepartmentsSection = () => { const { data, ...config } = useDepartmentsSection(); return ( - + { const { data, ...config } = useStatusSection(); return ( - + ); diff --git a/apps/meteor/ee/client/omnichannel/reports/sections/TagsSection.tsx b/apps/meteor/ee/client/omnichannel/reports/sections/TagsSection.tsx index c9bb6d3baa1e..1ac5c320f97d 100644 --- a/apps/meteor/ee/client/omnichannel/reports/sections/TagsSection.tsx +++ b/apps/meteor/ee/client/omnichannel/reports/sections/TagsSection.tsx @@ -1,3 +1,4 @@ +import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; import React from 'react'; import { BarChart, ReportCard } from '../components'; @@ -6,14 +7,17 @@ import { ellipsis } from '../utils/ellipsis'; export const TagsSection = () => { const { data, ...config } = useTagsSection(); + const { ref, contentBoxSize: { inlineSize: cardWidth = 200 } = {} } = useResizeObserver(); + const width = cardWidth * 0.9; return ( - + {title && {title}} - - - {children} - - + + {children} + diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx index 77311af255d9..c02f3d219663 100644 --- a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx +++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx @@ -7,7 +7,7 @@ type EngagementDashboardCardFilterProps = { }; const EngagementDashboardCardFilter = ({ children = }: EngagementDashboardCardFilterProps): ReactElement => ( - + {children && {children}} ); diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx index 9f2e9435db0f..f901f11d3954 100644 --- a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx +++ b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx @@ -1,4 +1,4 @@ -import { Box, Icon, Margins, Pagination, Skeleton, Table, TableBody, TableCell, TableHead, TableRow, Tile } from '@rocket.chat/fuselage'; +import { Icon, Margins, Pagination, Skeleton, Table, TableBody, TableCell, TableHead, TableRow, Tile } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import type { ReactElement } from 'react'; @@ -59,7 +59,7 @@ const ChannelsOverview = (): ReactElement => { } /> - +
{channels && !channels.length && ( {t('No_data_found')} @@ -127,7 +127,7 @@ const ChannelsOverview = (): ReactElement => { onSetItemsPerPage={setItemsPerPage} onSetCurrent={setCurrent} /> - +
); }; diff --git a/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js b/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js index bb957b8f2b14..d0d627b74665 100644 --- a/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js +++ b/apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js @@ -1,9 +1,11 @@ +import { api } from '@rocket.chat/core-services'; import { LivechatVisitors, ReadReceipts, Messages, Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { settings } from '../../../../app/settings/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { broadcastMessageSentEvent } from '../../../../server/modules/watchers/lib/messages'; // debounced function by roomId, so multiple calls within 2 seconds to same roomId runs only once const list = {}; @@ -24,7 +26,10 @@ const updateMessages = debounceByRoomId(async ({ _id, lm }) => { return; } - await Messages.setVisibleMessagesAsRead(_id, firstSubscription.ls); + const result = await Messages.setVisibleMessagesAsRead(_id, firstSubscription.ls); + if (result.modifiedCount > 0) { + void api.broadcast('notify.messagesRead', { rid: _id, until: firstSubscription.ls }); + } if (lm <= firstSubscription.ls) { await Rooms.setLastMessageAsRead(_id); @@ -61,7 +66,13 @@ export const ReadReceipt = { // mark message as read if the sender is the only one in the room const isUserAlone = (await Subscriptions.countByRoomIdAndNotUserId(roomId, userId)) === 0; if (isUserAlone) { - await Messages.setAsReadById(message._id); + const result = await Messages.setAsReadById(message._id); + if (result.modifiedCount > 0) { + void broadcastMessageSentEvent({ + id: message._id, + broadcastCallback: (message) => api.broadcast('message.sent', message), + }); + } } const extraData = roomCoordinator.getRoomDirectives(t).getReadReceiptsExtraData(message); diff --git a/apps/meteor/ee/server/local-services/message-reads/service.ts b/apps/meteor/ee/server/local-services/message-reads/service.ts index 0c4c323bd783..8e7a2c093bdc 100644 --- a/apps/meteor/ee/server/local-services/message-reads/service.ts +++ b/apps/meteor/ee/server/local-services/message-reads/service.ts @@ -1,4 +1,4 @@ -import { ServiceClassInternal } from '@rocket.chat/core-services'; +import { ServiceClassInternal, api } from '@rocket.chat/core-services'; import { Messages, MessageReads, Subscriptions } from '@rocket.chat/models'; import { MAX_ROOM_SIZE_CHECK_INDIVIDUAL_READ_RECEIPTS } from '../../lib/constants'; @@ -42,7 +42,10 @@ export class MessageReadsService extends ServiceClassInternal implements IMessag const firstRead = await MessageReads.getMinimumLastSeenByThreadId(tmid); if (firstRead?.ls) { - await Messages.setThreadMessagesAsRead(tmid, firstRead.ls); + const result = await Messages.setThreadMessagesAsRead(tmid, firstRead.ls); + if (result.modifiedCount > 0) { + void api.broadcast('notify.messagesRead', { rid: threadMessage.rid, tmid, until: firstRead.ls }); + } } } } diff --git a/apps/meteor/ee/server/services/CHANGELOG.md b/apps/meteor/ee/server/services/CHANGELOG.md index 07a89f2d7235..32506d52706c 100644 --- a/apps/meteor/ee/server/services/CHANGELOG.md +++ b/apps/meteor/ee/server/services/CHANGELOG.md @@ -1,5 +1,17 @@ # rocketchat-services +## 1.1.19 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 1.1.18 ### Patch Changes diff --git a/apps/meteor/ee/server/services/package.json b/apps/meteor/ee/server/services/package.json index 9c4b41c1e0d3..4e7183283831 100644 --- a/apps/meteor/ee/server/services/package.json +++ b/apps/meteor/ee/server/services/package.json @@ -1,7 +1,7 @@ { "name": "rocketchat-services", "private": true, - "version": "1.1.18", + "version": "1.1.19", "description": "Rocket.Chat Authorization service", "main": "index.js", "scripts": { diff --git a/apps/meteor/package.json b/apps/meteor/package.json index bcb5ea57ad6c..51fb671547de 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -236,7 +236,7 @@ "@rocket.chat/favicon": "workspace:^", "@rocket.chat/forked-matrix-appservice-bridge": "^4.0.2", "@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.3", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-hooks": "~0.32.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", "@rocket.chat/fuselage-toastbar": "~0.31.25", @@ -258,7 +258,7 @@ "@rocket.chat/models": "workspace:^", "@rocket.chat/mp3-encoder": "0.24.0", "@rocket.chat/omnichannel-services": "workspace:^", - "@rocket.chat/onboarding-ui": "~0.33.2", + "@rocket.chat/onboarding-ui": "~0.33.3", "@rocket.chat/password-policies": "workspace:^", "@rocket.chat/pdf-worker": "workspace:^", "@rocket.chat/poplib": "workspace:^", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index de0e3cfed58e..d4877dba242f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -22,6 +22,7 @@ "This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}", "Third_party_login": "Third-party login", "Enabled_E2E_Encryption_for_this_room": "enabled E2E Encryption for this room", + "Enable_business_hours": "Enable business hours", "disabled": "disabled", "Disabled_E2E_Encryption_for_this_room": "disabled E2E Encryption for this room", "@username": "@username", @@ -282,6 +283,8 @@ "Accounts_TwoFactorAuthentication_MaxDelta_Description": "The Maximum Delta determines how many tokens are valid at any given time. Tokens are generated every 30 seconds, and are valid for (30 * Maximum Delta) seconds. \nExample: With a Maximum Delta set to 10, each token can be used up to 300 seconds before or after it's timestamp. This is useful when the client's clock is not properly synced with the server.", "Accounts_TwoFactorAuthentication_RememberFor": "Remember Two Factor for (seconds)", "Accounts_TwoFactorAuthentication_RememberFor_Description": "Do not request two factor authorization code if it was already provided before in the given time.", + "Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts": "Maximun Invalid Email OTP Codes Allowed", + "Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts_Description": "The system allows a maximum number of invalid email OTP codes, after which a new code is automatically generated. We highly recommend using this setting along with 'Block failed login attempts by Username'.", "Accounts_UseDefaultBlockedDomainsList": "Use Default Blocked Domains List", "Accounts_UseDNSDomainCheck": "Use DNS Domain Check", "API_EmbedDisabledFor": "Disable Embed for Users", @@ -839,6 +842,8 @@ "Business_Hour_Removed": "Business Hour Removed", "Business_Hours": "Business Hours", "Business_hours_enabled": "Business hours enabled", + "Business_hours_is_disabled": "Business hours is disabled", + "Business_hours_is_disabled_description": "Enable business hours at the workspace admin panel to let customers know when you're available and when can they expect a response.", "Business_hours_updated": "Business hours updated", "busy": "busy", "Busy": "Busy", @@ -3047,6 +3052,7 @@ "Lead_capture_phone_regex": "Lead capture phone regex", "Learn_more": "Learn more", "Learn_more_about_agents": "Learn more about agents", + "Learn_more_about_business_hours": "Learn more about business hours", "Learn_more_about_canned_responses": "Learn more about canned responses", "Learn_more_about_contacts": "Learn more about contacts", "Learn_more_about_current_chats": "Learn more about current chats", @@ -4455,6 +4461,7 @@ "Room_password_changed_successfully": "Room password changed successfully", "room_removed_read_only": "Room added writing permission by {{user_by}}", "room_set_read_only": "Room set as Read Only by {{user_by}}", + "Room_Status_Open": "Open", "room_removed_read_only_permission": "removed read only permission", "room_set_read_only_permission": "set room to read only", "Room_topic_changed_successfully": "Room topic changed successfully", @@ -5231,6 +5238,7 @@ "totp-disabled": "You do not have 2FA login enabled for your user", "totp-invalid": "Code or password invalid", "totp-required": "TOTP Required", + "totp-max-attempts": "Maximum OTP failed attempts reached. A new code will be generated.", "Transcript": "Transcript", "Transcript_Enabled": "Ask Visitor if They Would Like a Transcript After Chat Closed", "Transcript_message": "Message to Show When Asking About Transcript", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 24aeb2bbb6c3..90ed0d6db384 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -3686,6 +3686,7 @@ "Room_password_changed_successfully": "A senha da sala foi alterada com sucesso", "room_removed_read_only": "Permissão de escrita adicionada à sala por {{user_by}}", "room_set_read_only": "Sala definida como somente leitura por {{user_by}}", + "Room_Status_Open": "Aberto", "Room_topic_changed_successfully": "Tópico da sala alterado com sucesso", "Room_type_changed_successfully": "Tipo de sala alterado com sucesso", "Room_type_of_default_rooms_cant_be_changed": "Esta é uma sala padrão e o tipo não pode ser alterado; consulte o seu administrador.", diff --git a/apps/meteor/packages/rocketchat-livechat/assets/demo.html b/apps/meteor/packages/rocketchat-livechat/assets/demo.html index 49dfd42e71fa..ee5fd6944d4f 100644 --- a/apps/meteor/packages/rocketchat-livechat/assets/demo.html +++ b/apps/meteor/packages/rocketchat-livechat/assets/demo.html @@ -10,6 +10,16 @@

test

Talk to us.

+ + +

changing page title

page 0
page 1
diff --git a/apps/meteor/server/lib/moderation/deleteReportedMessages.ts b/apps/meteor/server/lib/moderation/deleteReportedMessages.ts index f53817353d65..218714d5a8b0 100644 --- a/apps/meteor/server/lib/moderation/deleteReportedMessages.ts +++ b/apps/meteor/server/lib/moderation/deleteReportedMessages.ts @@ -1,3 +1,4 @@ +import { api } from '@rocket.chat/core-services'; import type { IUser, IMessage } from '@rocket.chat/core-typings'; import { Messages, Uploads, ReadReceipts } from '@rocket.chat/models'; @@ -7,8 +8,8 @@ import { settings } from '../../../app/settings/server'; // heavily inspired from message delete taking place in the user deletion process // in this path we don't care about the apps engine events - it's a "raw" bulk action export async function deleteReportedMessages(messages: IMessage[], user: IUser): Promise { - const keepHistory = settings.get('Message_KeepHistory'); - const showDeletedStatus = settings.get('Message_ShowDeletedStatus'); + const keepHistory = settings.get('Message_KeepHistory'); + const showDeletedStatus = settings.get('Message_ShowDeletedStatus'); const files: string[] = []; const messageIds: string[] = []; for (const message of messages) { @@ -47,4 +48,24 @@ export async function deleteReportedMessages(messages: IMessage[], user: IUser): if (showDeletedStatus) { await Messages.setAsDeletedByIdsAndUser(messageIds, user as any); } + + const transformed = messages.reduce((acc, { rid, _id }) => { + if (!acc[rid]) { + acc[rid] = []; + } + acc[rid].push(_id); + return acc; + }, {} as Record); + + Object.entries(transformed).forEach(([rid, messageIds]) => { + void api.broadcast('notify.deleteMessageBulk', rid, { + rid, + excludePinned: true, + ignoreDiscussion: true, + ts: { $gt: new Date() }, + users: [], + ids: messageIds, + showDeletedStatus, + }); + }); } diff --git a/apps/meteor/server/models/raw/Messages.ts b/apps/meteor/server/models/raw/Messages.ts index 16366a189bc5..eb0ef0790ebb 100644 --- a/apps/meteor/server/models/raw/Messages.ts +++ b/apps/meteor/server/models/raw/Messages.ts @@ -9,7 +9,6 @@ import type { IMessageWithPendingFileImport, } from '@rocket.chat/core-typings'; import type { FindPaginated, IMessagesModel } from '@rocket.chat/model-typings'; -import { Rooms } from '@rocket.chat/models'; import type { PaginatedRequest } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { @@ -1340,8 +1339,6 @@ export class MessagesRaw extends BaseRaw implements IMessagesModel { const data = Object.assign(record, extraData); - await Rooms.incMsgCountById(rid, 1); - return this.insertOne(data); } @@ -1464,10 +1461,6 @@ export class MessagesRaw extends BaseRaw implements IMessagesModel { if (!limit) { const count = (await this.deleteMany(query)).deletedCount - notCountedMessages; - if (count) { - // decrease message count - await Rooms.decreaseMessageCountById(rid, count); - } return count; } @@ -1481,11 +1474,6 @@ export class MessagesRaw extends BaseRaw implements IMessagesModel { }) ).deletedCount - notCountedMessages; - if (count) { - // decrease message count - await Rooms.decreaseMessageCountById(rid, count); - } - return count; } diff --git a/apps/meteor/server/models/raw/Rooms.ts b/apps/meteor/server/models/raw/Rooms.ts index 536719c5983a..9c1b14dc3f35 100644 --- a/apps/meteor/server/models/raw/Rooms.ts +++ b/apps/meteor/server/models/raw/Rooms.ts @@ -1566,20 +1566,13 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { return this.updateOne(query, update); } - async resetLastMessageById(_id: IRoom['_id'], lastMessage: IRoom['lastMessage']): Promise { + async resetLastMessageById(_id: IRoom['_id'], lastMessage: IRoom['lastMessage'] | null, msgCountDelta?: number): Promise { const query: Filter = { _id }; - const update: UpdateFilter = lastMessage - ? { - $set: { - lastMessage, - }, - } - : { - $unset: { - lastMessage: 1, - }, - }; + const update = { + ...(lastMessage ? { $set: { lastMessage } } : { $unset: { lastMessage: 1 as const } }), + ...(msgCountDelta ? { $inc: { msgs: msgCountDelta } } : {}), + }; return this.updateOne(query, update); } diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 2e9d38faeb8d..0d56fc76bccf 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -1936,45 +1936,63 @@ export class UsersRaw extends BaseRaw { ); } - removeExpiredEmailCodesOfUserId(userId) { + removeExpiredEmailCodeOfUserId(userId) { return this.updateOne( - { _id: userId }, + { '_id': userId, 'services.emailCode.expire': { $lt: new Date() } }, { - $pull: { - 'services.emailCode': { - expire: { $lt: new Date() }, - }, - }, + $unset: { 'services.emailCode': 1 }, }, ); } - removeEmailCodeByUserIdAndCode(userId, code) { + removeEmailCodeOfUserId(userId) { return this.updateOne( { _id: userId }, { - $pull: { - 'services.emailCode': { - code, - }, + $unset: { 'services.emailCode': 1 }, + }, + ); + } + + incrementInvalidEmailCodeAttempt(userId) { + return this.findOneAndUpdate( + { _id: userId }, + { + $inc: { 'services.emailCode.attempts': 1 }, + }, + { + returnDocument: 'after', + projection: { + 'services.emailCode.attempts': 1, + }, + }, + ); + } + + async maxInvalidEmailCodeAttemptsReached(userId, maxAttempts) { + const result = await this.findOne( + { + '_id': userId, + 'services.emailCode.attempts': { $gte: maxAttempts }, + }, + { + projection: { + _id: 1, }, }, ); + return !!result?._id; } addEmailCodeByUserId(userId, code, expire) { return this.updateOne( { _id: userId }, { - $push: { + $set: { 'services.emailCode': { - $each: [ - { - code, - expire, - }, - ], - $slice: -5, + code, + expire, + attempts: 0, }, }, }, diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index aba351dbc958..ae78f78b0e39 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -182,6 +182,10 @@ export class ListenersModule { notifications.streamRoomMessage.emitWithoutBroadcast(message.rid, message); }); + service.onEvent('notify.messagesRead', ({ rid, until, tmid }): void => { + notifications.notifyRoomInThisInstance(rid, 'messagesRead', { tmid, until }); + }); + service.onEvent('watch.subscriptions', ({ clientAction, subscription }) => { if (!subscription.u?._id) { return; @@ -416,6 +420,13 @@ export class ListenersModule { notifications.notifyUserInThisInstance(uid, 'calendar', data); }); + service.onEvent('notify.importedMessages', ({ roomIds }): void => { + roomIds.forEach((rid) => { + // couldnt get TS happy by providing no data, so had to provide null + notifications.notifyRoomInThisInstance(rid, 'messagesImported', null); + }); + }); + service.onEvent('connector.statuschanged', (enabled): void => { notifications.notifyLoggedInThisInstance('voip.statuschanged', enabled); }); diff --git a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts index c4e9afe898e6..b4b099485de3 100644 --- a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts +++ b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts @@ -1,4 +1,4 @@ -import { isE2EEMessage, isOTRMessage } from '@rocket.chat/core-typings'; +import { isE2EEMessage, isOTRMessage, isOTRAckMessage } from '@rocket.chat/core-typings'; import type { IMessage } from '@rocket.chat/core-typings'; import { parse } from '@rocket.chat/message-parser'; @@ -22,7 +22,7 @@ export class BeforeSaveMarkdownParser { return message; } - if (isE2EEMessage(message) || isOTRMessage(message)) { + if (isE2EEMessage(message) || isOTRMessage(message) || isOTRAckMessage(message)) { return message; } diff --git a/apps/meteor/server/services/messages/service.ts b/apps/meteor/server/services/messages/service.ts index 22b05029a65e..f20c545f6abe 100644 --- a/apps/meteor/server/services/messages/service.ts +++ b/apps/meteor/server/services/messages/service.ts @@ -108,18 +108,24 @@ export class MessageService extends ServiceClassInternal implements IMessageServ if (!username) { throw new Error('The username cannot be empty.'); } - const result = await Messages.createWithTypeRoomIdMessageUserAndUnread( - type, - rid, - message, - { _id: userId, username, name }, - settings.get('Message_Read_Receipt_Enabled'), - extraData, - ); + + const [result] = await Promise.all([ + Messages.createWithTypeRoomIdMessageUserAndUnread( + type, + rid, + message, + { _id: userId, username, name }, + settings.get('Message_Read_Receipt_Enabled'), + extraData, + ), + Rooms.incMsgCountById(rid, 1), + ]); + void broadcastMessageSentEvent({ id: result.insertedId, broadcastCallback: async (message) => this.api?.broadcast('message.sent', message), }); + return result.insertedId; } diff --git a/apps/meteor/server/services/video-conference/service.ts b/apps/meteor/server/services/video-conference/service.ts index e6de3e9e2537..f1cf01e6538d 100644 --- a/apps/meteor/server/services/video-conference/service.ts +++ b/apps/meteor/server/services/video-conference/service.ts @@ -501,13 +501,18 @@ export class VideoConfService extends ServiceClassInternal implements IVideoConf msg: '', groupable: false, blocks: customBlocks || [this.buildVideoConfBlock(call._id)], - }; + } satisfies Partial; const room = await Rooms.findOneById(call.rid); const appId = videoConfProviders.getProviderAppId(call.providerName); const user = createdBy || (appId && (await Users.findOneByAppId(appId))) || (await Users.findOneById('rocket.cat')); const message = await sendMessage(user, record, room, false); + + if (!message) { + throw new Error('failed-to-create-message'); + } + return message._id; } diff --git a/apps/meteor/server/settings/accounts.ts b/apps/meteor/server/settings/accounts.ts index ba031c9210d5..2b1dac892c07 100644 --- a/apps/meteor/server/settings/accounts.ts +++ b/apps/meteor/server/settings/accounts.ts @@ -57,6 +57,19 @@ export const createAccountSettings = () => ], }); + await this.add('Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts', 5, { + type: 'int', + enableQuery: [ + enable2FA, + { + _id: 'Accounts_TwoFactorAuthentication_By_Email_Enabled', + value: true, + }, + ], + i18nLabel: 'Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts', + i18nDescription: 'Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts_Description', + }); + await this.add('Accounts_TwoFactorAuthentication_RememberFor', 1800, { type: 'int', enableQuery: enable2FA, @@ -72,7 +85,7 @@ export const createAccountSettings = () => const enableQueryCollectData = { _id: 'Block_Multiple_Failed_Logins_Enabled', value: true }; await this.section('Login_Attempts', async function () { - await this.add('Block_Multiple_Failed_Logins_Enabled', false, { + await this.add('Block_Multiple_Failed_Logins_Enabled', true, { type: 'boolean', }); diff --git a/apps/meteor/server/settings/file-upload.ts b/apps/meteor/server/settings/file-upload.ts index fce9a0059803..643c46ed8489 100644 --- a/apps/meteor/server/settings/file-upload.ts +++ b/apps/meteor/server/settings/file-upload.ts @@ -31,7 +31,7 @@ export const createFileUploadSettings = () => i18nDescription: 'FileUpload_ProtectFilesDescription', }); - await this.add('FileUpload_Restrict_to_room_members', false, { + await this.add('FileUpload_Restrict_to_room_members', true, { type: 'boolean', enableQuery: { _id: 'FileUpload_ProtectFiles', diff --git a/apps/meteor/tests/data/livechat/rooms.ts b/apps/meteor/tests/data/livechat/rooms.ts index ece369d57ae6..a3981a67c3d3 100644 --- a/apps/meteor/tests/data/livechat/rooms.ts +++ b/apps/meteor/tests/data/livechat/rooms.ts @@ -82,7 +82,7 @@ export const fetchInquiry = (roomId: string): Promise => }); }; -export const createDepartment = (agents?: { agentId: string }[], name?: string, enabled = true): Promise => { +export const createDepartment = (agents?: { agentId: string }[], name?: string, enabled = true, opts: Record = {}): Promise => { return new Promise((resolve, reject) => { request .post(api('livechat/department')) @@ -94,6 +94,7 @@ export const createDepartment = (agents?: { agentId: string }[], name?: string, showOnOfflineForm: true, showOnRegistration: true, email: 'a@b.com', + ...opts, }, agents, }) diff --git a/apps/meteor/tests/e2e/fixtures/collections/users.ts b/apps/meteor/tests/e2e/fixtures/collections/users.ts index 9237fd97926f..5b85974fb57c 100644 --- a/apps/meteor/tests/e2e/fixtures/collections/users.ts +++ b/apps/meteor/tests/e2e/fixtures/collections/users.ts @@ -43,7 +43,7 @@ export function createUserFixture(user: IUserState): UserFixture { }, ], }, - emailCode: [{ code: '', expire: new Date() }], + emailCode: { code: '', attempts: 0, expire: new Date() }, }, createdAt: new Date(), _updatedAt: new Date(), diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts new file mode 100644 index 000000000000..617d5ba268cd --- /dev/null +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts @@ -0,0 +1,721 @@ +import { faker } from '@faker-js/faker'; +import type { Page } from '@playwright/test'; + +import { IS_EE } from '../config/constants'; +import { createAuxContext } from '../fixtures/createAuxContext'; +import { Users } from '../fixtures/userStates'; +import { HomeOmnichannel, OmnichannelLiveChatEmbedded } from '../page-objects'; +import { createAgent } from '../utils/omnichannel/agents'; +import { test, expect } from '../utils/test'; + +// TODO: Use official widget typing once that is merged +declare const window: Window & { + // TODO: Improve tests to no longer use window object properties + onPrechatFormSubmit: boolean; + onAssignAgent: boolean; + onAgentStatusChange: boolean; + onOfflineFormSubmit: boolean; + onChatStarted: boolean; + onChatEnded: boolean; + + RocketChat: { + livechat: { + clearBusinessUnit: () => void; + clearDepartment: () => void; + initialize: () => void; + maximizeWidget: () => void; + minimizeWidget: () => void; + hideWidget: () => void; + showWidget: () => void; + pageVisited: () => void; + registerGuest: (visitor: { name: string; email: string; token: string }) => void; + setAgent: (agent: { username: string; _id: string }) => void; + setBusinessUnit: (businessUnit?: string) => void; + setCustomField: (field: { key: string; value: string }) => void; + setDepartment: (department: { _id: string; name: string }) => void; + setGuestEmail: (email: string) => void; + setGuestName: (name: string) => void; + setGuestToken: (token: string) => void; + setParentUrl: (url: string) => void; + setTheme: (theme: { color?: string; fontColor?: string; iconColor?: string; title?: string; offlineTitle?: string }) => void; + setLanguage: (language: string) => void; + onChatMaximized: (callback: () => void) => void; + onChatMinimized: (callback: () => void) => void; + onChatStarted: (callback: () => void) => void; + onChatEnded: (callback: () => void) => void; + onPrechatFormSubmit: (callback: () => void) => void; + onAssignAgent: (callback: () => void) => void; + onAgentStatusChange: (callback: () => void) => void; + onOfflineFormSubmit: (callback: () => void) => void; + onWidgetHidden: (callback: () => void) => void; + onWidgetShown: (callback: () => void) => void; + onServiceOffline: (callback: () => void) => void; + onQueuePositionChange: (callback: () => void) => void; + }; + }; +}; + +test.describe('OC - Livechat API', () => { + // TODO: Check if there is a way to add livechat to the global window object + + test.describe('Basic Widget Interactions', () => { + // Tests that rely only on the widget itself, without requiring further interaction from the main RC app + let poAuxContext: { page: Page; poHomeOmnichannel: HomeOmnichannel }; + let poLiveChat: OmnichannelLiveChatEmbedded; + let page: Page; + let agent: Awaited>; + + test.beforeAll(async ({ browser, api }) => { + agent = await createAgent(api, 'user1') + + page = await browser.newPage(); + await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200); + + poLiveChat = new OmnichannelLiveChatEmbedded(page, api); + + const { page: pageCtx } = await createAuxContext(browser, Users.user1); + poAuxContext = { page: pageCtx, poHomeOmnichannel: new HomeOmnichannel(pageCtx) }; + + await page.goto('/packages/rocketchat_livechat/assets/demo.html'); + }); + + test.afterAll(async ({ api }) => { + await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200); + await agent.delete(); + await poAuxContext.page.close(); + await page.close(); + }); + + test('OC - Livechat API - Open and Close widget', async () => { + await test.step('Expect widget to be visible after maximizeWidget()', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible(); + }); + + await test.step('Expect widget not be visible after minimizeWidget()', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.minimizeWidget()); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + }); + }); + + test('OC - Livechat API - Show and Hide widget', async () => { + await test.step('Expect livechat button not be visible after minimizeWidget()', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.hideWidget()); + + await expect(page.frameLocator('#rocketchat-iframe').getByRole('button', { name: 'Rocket.Chat' })).not.toBeVisible(); + }); + + await test.step('Expect livechat button to be visible after show()', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.showWidget()); + + await expect(page.frameLocator('#rocketchat-iframe').getByRole('button', { name: 'Rocket.Chat' })).toBeVisible(); + }); + }); + + test.skip('OC - Livechat API - setAgent', async () => { + // Set agent does not actually set the agent, it just sets the default agent on the widget state + // Maybe that is used in an integration? Since as it is now, when the user starts a chat, the agent will be overriden + // TODO: Find the use case of the setAgent method + await test.step('Expect setAgent to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setAgent({ username: 'user1', _id: 'user1' })); + }); + }); + + test('OC - Livechat API - setLanguage', async () => { + await test.step('Expect language to be pt-BR', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setLanguage('pt-BR')); + + await expect( + page.frameLocator('#rocketchat-iframe').getByText('Por favor, nos passe algumas informações antes de iniciar o chat'), + ).toBeVisible(); + }); + + await test.step('Expect language to be en', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setLanguage('en')); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Please, tell us some information to start the chat')).toBeVisible(); + }); + }); + + test('OC - Livechat API - setTheme', async () => { + const registerGuestVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + token: faker.string.uuid(), + }; + + await test.step('Expect setTheme set color', async () => { + await poLiveChat.page.evaluate(() => { + window.RocketChat.livechat.maximizeWidget(); + window.RocketChat.livechat.setTheme({ color: 'rgb(50, 50, 50)' }); + }); + + await expect(page.frameLocator('#rocketchat-iframe').locator('header')).toHaveCSS('background-color', 'rgb(50, 50, 50)'); + }); + + await test.step('Expect setTheme set fontColor', async () => { + await poLiveChat.page.evaluate(() => { + window.RocketChat.livechat.maximizeWidget(); + window.RocketChat.livechat.setTheme({ fontColor: 'rgb(50, 50, 50)' }); + }); + + await expect(page.frameLocator('#rocketchat-iframe').locator('header')).toHaveCSS('color', 'rgb(50, 50, 50)'); + }); + + // TODO: fix iconColor setTheme property + // await test.step('Expect setTheme set iconColor', async () => { + // await poLiveChat.page.evaluate(() => { + // window.RocketChat.livechat.maximizeWidget(); + // window.RocketChat.livechat.setTheme({ iconColor: 'rgb(50, 50, 50)' }); + // }); + + // await expect(page.frameLocator('#rocketchat-iframe').locator('header')).toHaveCSS('color', 'rgb(50, 50, 50)'); + // }); + + await test.step('Expect setTheme set title', async () => { + await poLiveChat.page.evaluate(() => { + window.RocketChat.livechat.maximizeWidget(); + window.RocketChat.livechat.setTheme({ title: 'CustomTitle' }); + }); + + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor), + registerGuestVisitor, + ); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('CustomTitle')).toBeVisible(); + }); + + // await test.step('Expect setTheme set offlineTitle', async () => { + // await poLiveChat.page.evaluate(() => { + // window.RocketChat.livechat.maximizeWidget(); + // window.RocketChat.livechat.setTheme({ offlineTitle: 'CustomOfflineTitle' }); + // }); + + // await expect(page.frameLocator('#rocketchat-iframe').getByText('CustomTitle')).toBeVisible(); + // }); + }); + + test.skip('OC - Livechat API - setParentUrl', async () => { + // TODO: check how to test this, not sure there is a clear indication of parent url changes + await test.step('Expect setParentUrl to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setParentUrl('http://localhost:3000')); + }); + }); + }); + + test.describe('Complex Widget Interactions', () => { + // Needs Departments to test this, so needs an EE license for multiple deps + test.skip(!IS_EE, 'Enterprise Only'); + // Tests that requires interaction from an agent or more + let poAuxContext: { page: Page; poHomeOmnichannel: HomeOmnichannel }; + let poLiveChat: OmnichannelLiveChatEmbedded; + let page: Page; + let depId: string; + let agent: Awaited>; + + test.beforeAll(async ({ api }) => { + agent = await createAgent(api, 'user1') + + const response = await api.post('/livechat/department', {department: { + enabled: true, + email: faker.internet.email(), + showOnRegistration: true, + showOnOfflineForm: true, + name: `new department ${Date.now()}`, + description: 'created from api', + }}); + + expect(response.status()).toBe(200); + + const resBody = await response.json(); + depId = resBody.department._id; + await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200); + await expect((await api.post('/settings/Livechat_offline_email', { value: 'test@testing.com' })).status()).toBe(200); + }); + + test.beforeEach(async ({ browser, api }, testInfo) => { + page = await browser.newPage(); + + poLiveChat = new OmnichannelLiveChatEmbedded(page, api); + + const { page: pageCtx } = await createAuxContext(browser, Users.user1); + poAuxContext = { page: pageCtx, poHomeOmnichannel: new HomeOmnichannel(pageCtx) }; + + // This is needed since the livechat will not react to online/offline status changes if already loaded in a page + if (testInfo.title === 'Expect onOfflineFormSubmit to trigger callback') { + await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('offline'); + } else { + await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('online'); + } + + await page.goto('/packages/rocketchat_livechat/assets/demo.html'); + }); + + test.afterEach(async () => { + await poAuxContext.page.close(); + await page.close(); + }); + + test.afterAll(async ({ api }) => { + await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200); + await agent.delete(); + await expect((await api.post('/settings/Omnichannel_enable_department_removal', { value: true })).status()).toBe(200); + const response = await api.delete(`/livechat/department/${depId}`, { name: 'TestDep', email: 'TestDep@email.com' }); + expect(response.status()).toBe(200); + await expect((await api.post('/settings/Omnichannel_enable_department_removal', { value: false })).status()).toBe(200); + }); + + // clearBusinessUnit + // clearDepartment + // initialize + // maximizeWidget + // minimizeWidget + // pageVisited + // registerGuest + // setAgent + // setBusinessUnit + // setCustomField + // setDepartment + // setGuestEmail + // setGuestName + // setGuestToken + // setParentUrl + // setTheme + + test.skip('OC - Livechat API - clearBusinessUnit', async () => { + // TODO: check how to test this, and if this is working as intended + await test.step('Expect clearBusinessUnit to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.clearBusinessUnit()); + }); + }); + + test.skip('OC - Livechat API - setBusinessUnit', async () => { + // TODO + await test.step('Expect setBusinessUnit to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setBusinessUnit()); + }); + }); + + test.skip('OC - Livechat API - setCustomField', async () => { + // TODO + await test.step('Expect setCustomField to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setCustomField({ key: 'test', value: 'test' })); + }); + }); + + test.skip('OC - Livechat API - clearDepartment', async () => { + // TODO + await test.step('Expect clearDepartment to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.clearDepartment()); + }); + }); + + test('OC - Livechat API - registerGuest', async ({ browser }) => { + const registerGuestVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + token: faker.string.uuid(), + }; + + await test.step('Expect registerGuest to create a valid guest', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible(); + + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor), + registerGuestVisitor, + ); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + }); + + await test.step('Expect registered guest to have valid info', async () => { + await poAuxContext.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name); + + await poAuxContext.poHomeOmnichannel.content.btnGuestInfo.click(); + // For some reason the guest info email information is being set to lowercase + await expect(poAuxContext.poHomeOmnichannel.content.infoContactEmail).toHaveText(registerGuestVisitor.email.toLowerCase()); + }); + + await test.step('Expect registerGuest to log in an existing guest and load chat history', async () => { + const { page: pageCtx } = await createAuxContext(browser, Users.user1); + + await pageCtx.goto('/packages/rocketchat_livechat/assets/demo.html'); + + await pageCtx.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible(); + + await pageCtx.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor), + registerGuestVisitor, + ); + + await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor')).toBeVisible(); + }); + }); + + test('OC - Livechat API - setGuestEmail', async () => { + const registerGuestVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + token: faker.string.uuid(), + }; + // Start Chat + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible(); + + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor), + registerGuestVisitor, + ); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + await test.step('Expect setGuestEmail to change a guest email', async () => { + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.setGuestEmail(`changed${registerGuestVisitor.email}`), + registerGuestVisitor, + ); + }); + + await test.step('Expect registered guest to have valid info', async () => { + await poAuxContext.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name); + + await poAuxContext.poHomeOmnichannel.content.btnGuestInfo.click(); + // For some reason the guest info email information is being set to lowercase + await expect(poAuxContext.poHomeOmnichannel.content.infoContactEmail).toHaveText( + `changed${registerGuestVisitor.email}`.toLowerCase(), + ); + }); + }); + + test('OC - Livechat API - setGuestName', async () => { + const registerGuestVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + token: faker.string.uuid(), + }; + // Start Chat + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible(); + + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor), + registerGuestVisitor, + ); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + await test.step('Expect setGuestEmail to change a guest email', async () => { + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.setGuestName(`changed${registerGuestVisitor.name}`), + registerGuestVisitor, + ); + }); + + await test.step('Expect registered guest to have valid info', async () => { + await poAuxContext.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name); + + await expect(poAuxContext.poHomeOmnichannel.content.infoContactName).toContainText(`changed${registerGuestVisitor.name}`); + }); + }); + + test('OC - Livechat API - setGuestToken', async ({ browser }) => { + const registerGuestVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + token: faker.string.uuid(), + }; + + // Register guest and send a message + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + + await poLiveChat.page.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor), + registerGuestVisitor, + ); + + await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + await test.step('Expect setGuestToken to log in an existing guest and load chat history', async () => { + const { page: pageCtx } = await createAuxContext(browser, Users.user1); + + await pageCtx.goto('/packages/rocketchat_livechat/assets/demo.html'); + + await pageCtx.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible(); + + await pageCtx.evaluate( + (registerGuestVisitor) => window.RocketChat.livechat.setGuestToken(registerGuestVisitor.token), + registerGuestVisitor, + ); + + await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible(); + await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor')).toBeVisible(); + }); + }); + }); + + test.describe('Widget Listeners', () => { + // Tests that listen to events from the widget, and check if they are being triggered + + let poAuxContext: { page: Page; poHomeOmnichannel: HomeOmnichannel }; + let poLiveChat: OmnichannelLiveChatEmbedded; + let page: Page; + let agent: Awaited>; + + test.beforeAll(async ({ api }) => { + agent = await createAgent(api, 'user1') + await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200); + await expect((await api.post('/settings/Livechat_offline_email', { value: 'test@testing.com' })).status()).toBe(200); + }); + + test.beforeEach(async ({ browser, api }, testInfo) => { + page = await browser.newPage(); + + poLiveChat = new OmnichannelLiveChatEmbedded(page, api); + + const { page: pageCtx } = await createAuxContext(browser, Users.user1); + poAuxContext = { page: pageCtx, poHomeOmnichannel: new HomeOmnichannel(pageCtx) }; + + // This is needed since the livechat will not react to online/offline status changes if already loaded in a page + if (testInfo.title === 'Expect onOfflineFormSubmit to trigger callback') { + await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('offline'); + } else { + await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('online'); + } + + await page.goto('/packages/rocketchat_livechat/assets/demo.html'); + }); + + test.afterEach(async () => { + await poAuxContext.page.close(); + await page.close(); + }); + + test.afterAll(async ({ api }) => { + await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200); + await agent.delete(); + }); + + test('OC - Livechat API - onChatMaximized & onChatMinimized', async () => { + await test.step('Expect onChatMaximized to trigger callback', async () => { + await poLiveChat.page.evaluate( + () => + new Promise((resolve: (value?: unknown) => void) => { + window.RocketChat.livechat.onChatMaximized(() => { + resolve(); + }); + + window.RocketChat.livechat.maximizeWidget(); + }), + ); + }); + + await test.step('Expect onChatMinimized to trigger callback', async () => { + await poLiveChat.page.evaluate( + () => + new Promise((resolve: (value?: unknown) => void) => { + window.RocketChat.livechat.onChatMinimized(() => { + resolve(); + }); + + window.RocketChat.livechat.minimizeWidget(); + }), + ); + }); + }); + + test('OC - Livechat API - onChatStarted & onChatEnded', async () => { + const newVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + }; + + await test.step('Expect onChatStarted to trigger callback', async () => { + const watchForTrigger = page.waitForFunction(() => window.onChatStarted === true); + + await poLiveChat.page.evaluate(() => + window.RocketChat.livechat.onChatStarted(() => { + window.onChatStarted = true; + }), + ); + + await poLiveChat.openLiveChat(false); + await poLiveChat.sendMessage(newVisitor, false); + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + await watchForTrigger; + }); + + await test.step('Expect onChatEnded to trigger callback', async () => { + const watchForTrigger = page.waitForFunction(() => window.onChatEnded === true); + + await poLiveChat.page.evaluate(() => + window.RocketChat.livechat.onChatEnded(() => { + window.onChatEnded = true; + }), + ); + + await poAuxContext.poHomeOmnichannel.sidenav.openChat(newVisitor.name); + await poAuxContext.poHomeOmnichannel.content.btnCloseChat.click(); + await poAuxContext.poHomeOmnichannel.content.closeChatModal.inputComment.fill('this_is_a_test_comment'); + await poAuxContext.poHomeOmnichannel.content.closeChatModal.btnConfirm.click(); + await expect(poAuxContext.poHomeOmnichannel.toastSuccess).toBeVisible(); + + await watchForTrigger; + }); + }); + + test('OC - Livechat API - onPrechatFormSubmit & onAssignAgent', async () => { + const newVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + }; + + await test.step('Expect onPrechatFormSubmit to trigger callback', async () => { + const watchForTrigger = page.waitForFunction(() => window.onPrechatFormSubmit === true); + + await poLiveChat.page.evaluate(() => + window.RocketChat.livechat.onPrechatFormSubmit(() => { + window.onPrechatFormSubmit = true; + }), + ); + + await poLiveChat.openLiveChat(false); + await poLiveChat.sendMessage(newVisitor, false); + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + await watchForTrigger; + }); + + await test.step('Expect onAssignAgent to trigger callback', async () => { + const watchForTrigger = page.waitForFunction(() => window.onAssignAgent === true); + + await poLiveChat.page.evaluate(() => + window.RocketChat.livechat.onAssignAgent(() => { + window.onAssignAgent = true; + }), + ); + + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + await watchForTrigger; + }); + }); + + // TODO: Fix this Flaky test + test.skip('onAgentStatusChange', async () => { + const newVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + }; + + await poLiveChat.openLiveChat(false); + await poLiveChat.sendMessage(newVisitor, false); + await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + + + const watchForTrigger = page.waitForFunction(() => window.onAgentStatusChange === true); + + await poLiveChat.page.evaluate(() => + window.RocketChat.livechat.onAgentStatusChange(() => { + window.onAgentStatusChange = true; + }), + ); + + await poAuxContext.poHomeOmnichannel.sidenav.openChat(newVisitor.name); + await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('offline'); + + await watchForTrigger; + }); + + test('OC - Livechat API - onOfflineFormSubmit', async () => { + const newVisitor = { + name: faker.person.firstName(), + email: faker.internet.email(), + }; + + await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('offline'); + + const watchForTrigger = page.waitForFunction(() => window.onOfflineFormSubmit === true); + + await poLiveChat.page.reload(); + + await poLiveChat.page.evaluate(() => + window.RocketChat.livechat.onOfflineFormSubmit(() => { + window.onOfflineFormSubmit = true; + }), + ); + + await poLiveChat.openLiveChat(true); + await poLiveChat.sendMessage(newVisitor, true); + + await watchForTrigger; + }); + + test('OC - Livechat API - onWidgetHidden & onWidgetShown', async () => { + await test.step('Expect onWidgetHidden to trigger callback', async () => { + await poLiveChat.page.evaluate( + () => + new Promise((resolve: (value?: unknown) => void) => { + window.RocketChat.livechat.onWidgetHidden(() => { + resolve(); + }); + + window.RocketChat.livechat.hideWidget(); + }), + ); + }); + + await test.step('Expect onWidgetShown to trigger callback', async () => { + await poLiveChat.page.evaluate( + () => + new Promise((resolve: (value?: unknown) => void) => { + window.RocketChat.livechat.onWidgetShown(() => { + resolve(); + }); + + window.RocketChat.livechat.showWidget(); + }), + ); + }); + }); + + test.skip('OC - Livechat API - onServiceOffline', async () => { + // TODO: Not sure how to test this, need to check if playwright has a way to mock a server disconnect + await test.step('Expect onServiceOffline to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.onServiceOffline(() => console.log('onServiceOffline'))); + }); + }); + + test.skip('OC - Livechat API - onQueuePositionChange', async () => { + // TODO + await test.step('Expect onQueuePositionChange to do something', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.onQueuePositionChange(() => console.log('onQueuePositionChange'))); + }); + }); + }); + +}); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts index 38dcee88cf5f..74bcaff7c919 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts @@ -40,7 +40,7 @@ test.describe('OC - Manager Role', () => { test.beforeAll(async ({ api }) => { agents = await Promise.all([createAgent(api, 'user1'), createAgent(api, 'user2'), createAgent(api, MANAGER)]); - const agentsStatuses = await Promise.all(agents.slice(0, 1).map(({ data: agent }) => makeAgentAvailable(api, agent._id))); + const agentsStatuses = await Promise.all(agents.slice(0, 2).map(({ data: agent }) => makeAgentAvailable(api, agent._id))); agentsStatuses.forEach((res) => expect(res.status()).toBe(200)); }); diff --git a/apps/meteor/tests/e2e/page-objects/admin.ts b/apps/meteor/tests/e2e/page-objects/admin.ts index 1887adac45ed..112d285a205f 100644 --- a/apps/meteor/tests/e2e/page-objects/admin.ts +++ b/apps/meteor/tests/e2e/page-objects/admin.ts @@ -122,7 +122,7 @@ export class Admin { } get btnAssetsSettings(): Locator { - return this.page.locator('[data-qa-id="Assets"] >> role=button[name="Open"]'); + return this.page.locator('[data-qa-id="Assets"] >> role=link[name="Open"]'); } get btnDeleteAssetsLogo(): Locator { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts index dd5246528ed8..7243d613c11f 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts @@ -47,6 +47,18 @@ export class HomeOmnichannelContent extends HomeContent { return this.page.locator('[data-qa-id="ToolBoxAction-balloon-close-top-right"]'); } + get btnGuestInfo(): Locator { + return this.page.locator('[data-qa-id="ToolBoxAction-user"]'); + } + + get infoContactEmail(): Locator { + return this.page.locator('[data-qa-id="contactInfo-email"]'); + } + + get infoContactName(): Locator { + return this.page.locator('[data-qa-id="contactInfo-name"]'); + } + get btnReturn(): Locator { return this.page.locator('[data-qa-id="ToolBoxAction-back"]'); } diff --git a/apps/meteor/tests/e2e/page-objects/index.ts b/apps/meteor/tests/e2e/page-objects/index.ts index dfe90bc641c0..312b133bf93c 100644 --- a/apps/meteor/tests/e2e/page-objects/index.ts +++ b/apps/meteor/tests/e2e/page-objects/index.ts @@ -9,6 +9,7 @@ export * from './omnichannel-agents'; export * from './omnichannel-departments'; export * from './omnichannel-current-chats'; export * from './omnichannel-livechat'; +export * from './omnichannel-livechat-embedded'; export * from './omnichannel-manager'; export * from './omnichannel-custom-fields'; export * from './omnichannel-units'; diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts new file mode 100644 index 000000000000..c5bc515a99e2 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts @@ -0,0 +1,110 @@ +import type { Page, Locator, APIResponse } from '@playwright/test'; + +export class OmnichannelLiveChatEmbedded { + readonly page: Page; + + constructor(page: Page, private readonly api: { get(url: string): Promise }) { + this.page = page; + } + + btnOpenLiveChat(label: string): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`role=button[name="${label}"]`); + } + + btnOpenOfflineLiveChat(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`button[aria-label="Leave a message"]`); + } + + btnFinishOfflineMessage(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`button[aria-label="OK"]`); + } + + get btnOptions(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`button >> text="Options"`); + } + + get btnCloseChat(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`button >> text="Finish this chat"`); + } + + get btnCloseChatConfirm(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`button >> text="Yes"`); + } + + get txtHeaderTitle(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('div >> text="Chat Finished"'); + } + + get btnChatNow(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('[type="button"] >> text="Chat now"'); + } + + txtChatMessage(message: string): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`text="${message}"`); + } + + async closeChat(): Promise { + await this.btnOptions.click(); + await this.btnCloseChat.click(); + await this.btnCloseChatConfirm.click(); + } + + async openLiveChat(offline: boolean): Promise { + const { value: siteName } = await (await this.api.get('/settings/Site_Name')).json(); + if (offline) { + return this.btnOpenOfflineLiveChat().click(); + } + await this.btnOpenLiveChat(siteName).click(); + } + + unreadMessagesBadge(count: number): Locator { + const name = count === 1 ? `${count} unread message` : `${count} unread messages`; + + return this.page.frameLocator('#rocketchat-iframe').locator(`role=status[name="${name}"]`); + } + + get inputName(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('[name="name"]'); + } + + get inputEmail(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('[name="email"]'); + } + + get textAreaMessage(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('[name="message"]'); + } + + btnSendMessage(btnText: string): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator(`role=button[name="${btnText}"]`); + } + + get btnOk(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('role=button[name="OK"]'); + } + + get onlineAgentMessage(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('[contenteditable="true"]'); + } + + get btnSendMessageToOnlineAgent(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('footer div div div:nth-child(3) button'); + } + + get firstAutoMessage(): Locator { + return this.page.frameLocator('#rocketchat-iframe').locator('div.message-text__WwYco p'); + } + + public async sendMessage(liveChatUser: { name: string; email: string }, isOffline = true): Promise { + const buttonLabel = isOffline ? 'Send' : 'Start chat'; + await this.inputName.type(liveChatUser.name); + await this.inputEmail.type(liveChatUser.email); + if (isOffline) { + await this.textAreaMessage.type('any_message'); + await this.btnSendMessage(buttonLabel).click(); + return this.btnFinishOfflineMessage().click(); + } + await this.btnSendMessage(buttonLabel).click(); + await this.page.frameLocator('#rocketchat-iframe').locator('[data-qa="livechat-composer"]').waitFor(); + } +} diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 533c0b63da44..c717e526d20c 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -174,7 +174,8 @@ describe('[Rooms]', function () { await request.get(fileOldUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); }); - it('should be able to get the file when no access to the room', async () => { + it('should be able to get the file when no access to the room if setting allows it', async () => { + await updateSetting('FileUpload_Restrict_to_room_members', false); await request.get(fileNewUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); await request.get(fileOldUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); }); diff --git a/apps/meteor/tests/end-to-end/api/18-oauthapps.js b/apps/meteor/tests/end-to-end/api/18-oauthapps.js index e2100e0c1f75..83017a811047 100644 --- a/apps/meteor/tests/end-to-end/api/18-oauthapps.js +++ b/apps/meteor/tests/end-to-end/api/18-oauthapps.js @@ -63,6 +63,32 @@ describe('[OAuthApps]', function () { }) .end(done); }); + it('should return a 403 Forbidden error when the user does not have the necessary permission by client id', (done) => { + updatePermission('manage-oauth-apps', []).then(() => { + request + .get(api('oauth-apps.get?clientId=zapier')) + .set(credentials) + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('unauthorized'); + }) + .end(done); + }); + }); + it('should return a 403 Forbidden error when the user does not have the necessary permission by app id', (done) => { + updatePermission('manage-oauth-apps', []).then(() => { + request + .get(api('oauth-apps.get?appId=zapier')) + .set(credentials) + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('unauthorized'); + }) + .end(done); + }); + }); }); describe('[/oauth-apps.create]', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 3048f2270cd2..0d9e5fff0a65 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -30,6 +30,8 @@ import { startANewLivechatRoomAndTakeIt, createManager, closeOmnichannelRoom, + createDepartment, + fetchMessages, } from '../../../data/livechat/rooms'; import { saveTags } from '../../../data/livechat/tags'; import type { DummyResponse } from '../../../data/livechat/utils'; @@ -732,6 +734,65 @@ describe('LIVECHAT - rooms', function () { expect((latestRoom.lastMessage as any)?.transferData?.scope).to.be.equal('department'); expect((latestRoom.lastMessage as any)?.transferData?.nextDepartment?._id).to.be.equal(forwardToDepartment._id); }); + let roomId: string; + let visitorToken: string; + (IS_EE ? it : it.skip)('should return a success message when transferring to a fallback department', async () => { + await updateSetting('Livechat_Routing_Method', 'Auto_Selection'); + const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); + const { department: forwardToDepartment } = await createDepartmentWithAnOnlineAgent(); + const forwardToDepartment1 = await createDepartment(undefined, undefined, true, { + fallbackForwardDepartment: forwardToDepartment._id, + }); + + const newVisitor = await createVisitor(initialDepartment._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + await request + .post(api('livechat/room.forward')) + .set(credentials) + .send({ + roomId: newRoom._id, + departmentId: forwardToDepartment1._id, + clientAction: true, + comment: 'test comment', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + console.log({ res: res.body }); + expect(res.body).to.have.property('success', true); + }); + + const latestRoom = await getLivechatRoomInfo(newRoom._id); + + expect(latestRoom).to.have.property('departmentId'); + expect(latestRoom.departmentId).to.be.equal(forwardToDepartment._id); + + expect(latestRoom).to.have.property('lastMessage'); + expect(latestRoom.lastMessage?.t).to.be.equal('livechat_transfer_history'); + expect(latestRoom.lastMessage?.u?.username).to.be.equal(adminUsername); + expect((latestRoom.lastMessage as any)?.transferData?.comment).to.be.equal('test comment'); + expect((latestRoom.lastMessage as any)?.transferData?.scope).to.be.equal('department'); + expect((latestRoom.lastMessage as any)?.transferData?.nextDepartment?._id).to.be.equal(forwardToDepartment._id); + + roomId = newRoom._id; + visitorToken = newVisitor.token; + }); + (IS_EE ? it : it.skip)('system messages sent on transfer should be properly generated', async () => { + const messagesList = await fetchMessages(roomId, visitorToken); + + const fallbackMessages = messagesList.filter((m) => m.t === 'livechat_transfer_history_fallback'); + expect(fallbackMessages.length).to.be.equal(1); + + const userJoinedMessages = messagesList.filter((m) => m.t === 'uj'); + expect(userJoinedMessages.length).to.be.equal(2); + + const transferMessages = messagesList.filter((m) => m.t === 'livechat_transfer_history'); + expect(transferMessages.length).to.be.equal(1); + + const userLeavingMessages = messagesList.filter((m) => m.t === 'ul'); + expect(userLeavingMessages.length).to.be.equal(1); + }); }); describe('livechat/room.survey', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts index 6fa4206b6e58..372f7ddf5d7b 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts @@ -44,6 +44,10 @@ describe('LIVECHAT - visitors', function () { const { body } = await request.post(api('livechat/visitor')).send({ visitor: {} }); expect(body).to.have.property('success', false); }); + it('should fail when token is an empty string', async () => { + const { body } = await request.post(api('livechat/visitor')).send({ visitor: { token: '' } }); + expect(body).to.have.property('success', false); + }); it('should create a visitor', async () => { const { body } = await request.post(api('livechat/visitor')).send({ visitor: { token: 'test' } }); expect(body).to.have.property('success', true); diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 073b031c2590..b6527fbc2bb2 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -850,6 +850,7 @@ describe('LIVECHAT - business hours', function () { }); it('should verify if agent becomes unavailable to take chats when user is deactivated', async () => { + await makeAgentAvailable(await login(agent.username, password)); await setUserActiveStatus(agent._id, false); const latestAgent = await getUserByUsername(agent.username); diff --git a/apps/meteor/tests/mocks/client/ServerProviderMock.tsx b/apps/meteor/tests/mocks/client/ServerProviderMock.tsx index 42f0e34a52fa..3c2dbc26c1be 100644 --- a/apps/meteor/tests/mocks/client/ServerProviderMock.tsx +++ b/apps/meteor/tests/mocks/client/ServerProviderMock.tsx @@ -56,7 +56,6 @@ const uploadToEndpoint = async () => { throw new Error('not implemented'); }; // to be implemented const getStream = () => () => () => undefined; // to be implemented -const getSingleStream = () => () => () => undefined; // to be implemented const callEndpoint = () => { throw new Error('not implemented'); }; // to be implemented @@ -67,7 +66,6 @@ const contextValue = { // callMethod, callEndpoint, uploadToEndpoint, - getSingleStream, getStream, }; diff --git a/ee/apps/account-service/CHANGELOG.md b/ee/apps/account-service/CHANGELOG.md index 0cc9b7bc1486..e85436ebfb4f 100644 --- a/ee/apps/account-service/CHANGELOG.md +++ b/ee/apps/account-service/CHANGELOG.md @@ -1,5 +1,17 @@ # @rocket.chat/account-service +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/ee/apps/account-service/package.json b/ee/apps/account-service/package.json index b7f236ddcda6..c1f2d56bbd91 100644 --- a/ee/apps/account-service/package.json +++ b/ee/apps/account-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/account-service", "private": true, - "version": "0.3.0", + "version": "0.3.1", "description": "Rocket.Chat Account service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/authorization-service/CHANGELOG.md b/ee/apps/authorization-service/CHANGELOG.md index 90cad488c774..c66e47fe7af3 100644 --- a/ee/apps/authorization-service/CHANGELOG.md +++ b/ee/apps/authorization-service/CHANGELOG.md @@ -1,5 +1,17 @@ # @rocket.chat/authorization-service +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/ee/apps/authorization-service/package.json b/ee/apps/authorization-service/package.json index 6fe7208e39a3..46ca1fc5c4a7 100644 --- a/ee/apps/authorization-service/package.json +++ b/ee/apps/authorization-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/authorization-service", "private": true, - "version": "0.3.0", + "version": "0.3.1", "description": "Rocket.Chat Authorization service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/ddp-streamer/CHANGELOG.md b/ee/apps/ddp-streamer/CHANGELOG.md index 2790871dc26b..0aff47ff570e 100644 --- a/ee/apps/ddp-streamer/CHANGELOG.md +++ b/ee/apps/ddp-streamer/CHANGELOG.md @@ -1,5 +1,19 @@ # @rocket.chat/ddp-streamer +## 0.2.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/ui-contexts@3.0.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + - @rocket.chat/instance-status@0.0.25 + ## 0.2.0 ### Minor Changes diff --git a/ee/apps/ddp-streamer/package.json b/ee/apps/ddp-streamer/package.json index b01fd248045e..ef4bbc695f80 100644 --- a/ee/apps/ddp-streamer/package.json +++ b/ee/apps/ddp-streamer/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/ddp-streamer", "private": true, - "version": "0.2.0", + "version": "0.2.1", "description": "Rocket.Chat DDP-Streamer service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/omnichannel-transcript/CHANGELOG.md b/ee/apps/omnichannel-transcript/CHANGELOG.md index d7ad0294e408..731eb6908a83 100644 --- a/ee/apps/omnichannel-transcript/CHANGELOG.md +++ b/ee/apps/omnichannel-transcript/CHANGELOG.md @@ -1,5 +1,17 @@ # @rocket.chat/omnichannel-transcript +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/omnichannel-services@0.1.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/pdf-worker@0.0.25 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json index 873eb47c135b..e21e0bb97d87 100644 --- a/ee/apps/omnichannel-transcript/package.json +++ b/ee/apps/omnichannel-transcript/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/omnichannel-transcript", "private": true, - "version": "0.3.0", + "version": "0.3.1", "description": "Rocket.Chat service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/presence-service/CHANGELOG.md b/ee/apps/presence-service/CHANGELOG.md index d8f7c18a73d9..f233462ab0f7 100644 --- a/ee/apps/presence-service/CHANGELOG.md +++ b/ee/apps/presence-service/CHANGELOG.md @@ -1,5 +1,16 @@ # @rocket.chat/presence-service +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/presence@0.1.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/ee/apps/presence-service/package.json b/ee/apps/presence-service/package.json index dd38fb0ac208..7150bbe3af0f 100644 --- a/ee/apps/presence-service/package.json +++ b/ee/apps/presence-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/presence-service", "private": true, - "version": "0.3.0", + "version": "0.3.1", "description": "Rocket.Chat Presence service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/queue-worker/CHANGELOG.md b/ee/apps/queue-worker/CHANGELOG.md index ae9269affed1..e54cc9fda23c 100644 --- a/ee/apps/queue-worker/CHANGELOG.md +++ b/ee/apps/queue-worker/CHANGELOG.md @@ -1,5 +1,16 @@ # @rocket.chat/queue-worker +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/omnichannel-services@0.1.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json index 094da7ad054d..b35c9bd5a0ac 100644 --- a/ee/apps/queue-worker/package.json +++ b/ee/apps/queue-worker/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/queue-worker", "private": true, - "version": "0.3.0", + "version": "0.3.1", "description": "Rocket.Chat service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/stream-hub-service/CHANGELOG.md b/ee/apps/stream-hub-service/CHANGELOG.md index 6bfff89143be..d4e2438d7ec7 100644 --- a/ee/apps/stream-hub-service/CHANGELOG.md +++ b/ee/apps/stream-hub-service/CHANGELOG.md @@ -1,5 +1,15 @@ # @rocket.chat/stream-hub-service +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/ee/apps/stream-hub-service/package.json b/ee/apps/stream-hub-service/package.json index 365404139756..3085d492cef1 100644 --- a/ee/apps/stream-hub-service/package.json +++ b/ee/apps/stream-hub-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/stream-hub-service", "private": true, - "version": "0.3.0", + "version": "0.3.1", "description": "Rocket.Chat Stream Hub service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/packages/api-client/CHANGELOG.md b/ee/packages/api-client/CHANGELOG.md index 1e42f8d3fbfe..3407d68b664a 100644 --- a/ee/packages/api-client/CHANGELOG.md +++ b/ee/packages/api-client/CHANGELOG.md @@ -1,5 +1,14 @@ # @rocket.chat/api-client +## 0.1.19 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + ## 0.1.18 ### Patch Changes diff --git a/ee/packages/api-client/package.json b/ee/packages/api-client/package.json index 17180baeb2fe..3f7a895409a6 100644 --- a/ee/packages/api-client/package.json +++ b/ee/packages/api-client/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/api-client", - "version": "0.1.18", + "version": "0.1.19", "devDependencies": { "@swc/core": "^1.3.95", "@swc/jest": "^0.2.29", diff --git a/ee/packages/ddp-client/CHANGELOG.md b/ee/packages/ddp-client/CHANGELOG.md index 2f84d3e07644..0015f31f22f8 100644 --- a/ee/packages/ddp-client/CHANGELOG.md +++ b/ee/packages/ddp-client/CHANGELOG.md @@ -1,5 +1,19 @@ # @rocket.chat/ddp-client +## 0.2.10 + +### Patch Changes + +- c2b224fd82: Exceeding API calls when sending OTR messages +- c2b224fd82: SDK login methods not saving token +- c2b224fd82: removed @rocket.chat/license as a dependency of ddp client +- c2b224fd82: fixed an issue with the ddp client reconnection not resuming the login +- c2b224fd82: fixed an issue with the ddp client account not saving credentials correctly +- c2b224fd82: Fixed a problem where chained callbacks' return value was being overrided by some callbacks returning something different, causing callbacks with lower priority to operate on invalid values +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/api-client@0.1.19 + ## 0.2.9 ### Patch Changes diff --git a/ee/packages/ddp-client/__tests__/Account.spec.ts b/ee/packages/ddp-client/__tests__/Account.spec.ts new file mode 100644 index 000000000000..948d041ebf2d --- /dev/null +++ b/ee/packages/ddp-client/__tests__/Account.spec.ts @@ -0,0 +1,83 @@ +import WS from 'jest-websocket-mock'; + +import { DDPSDK } from '../src/DDPSDK'; +import { handleConnection, handleMethod } from './helpers'; + +let server: WS; + +beforeEach(async () => { + server = new WS('ws://localhost:1234/websocket'); +}); + +afterEach(() => { + server.close(); + WS.clean(); +}); + +describe('login', () => { + it('should save credentials to user object - loginWithToken', async () => { + const sdk = DDPSDK.create('ws://localhost:1234'); + + await handleConnection(server, sdk.connection.connect()); + + const messageResult = { + id: 123, + token: 'token', + tokenExpires: { $date: 99999999 }, + }; + + await handleMethod(server, 'login', [{ resume: 'token' }], JSON.stringify(messageResult), sdk.account.loginWithToken('token')); + + const { user } = sdk.account; + expect(user?.token).toBe(messageResult.token); + expect((user?.tokenExpires as Date)?.toISOString()).toBe(new Date(messageResult.tokenExpires.$date).toISOString()); + expect(user?.id).toBe(messageResult.id); + }); + + it('should save credentials to user object - loginWithPassword', async () => { + const sdk = DDPSDK.create('ws://localhost:1234'); + + await handleConnection(server, sdk.connection.connect()); + + const messageResult = { + id: 123, + token: 'token', + tokenExpires: { $date: 99999999 }, + }; + + await handleMethod( + server, + 'login', + [{ user: { username: 'username' }, password: { digest: 'password', algorithm: 'sha-256' } }], + JSON.stringify(messageResult), + sdk.account.loginWithPassword('username', 'password'), + ); + + const { user } = sdk.account; + expect(user?.token).toBe(messageResult.token); + expect((user?.tokenExpires as Date)?.toISOString()).toBe(new Date(messageResult.tokenExpires.$date).toISOString()); + expect(user?.id).toBe(messageResult.id); + }); + + it('should logout', async () => { + const sdk = DDPSDK.create('ws://localhost:1234'); + + await handleConnection(server, sdk.connection.connect()); + + const messageResult = { + id: 123, + token: 'token', + tokenExpires: { $date: 99999999 }, + }; + + const cb = jest.fn(); + sdk.account.once('uid', cb); + + await handleMethod(server, 'logout', [], JSON.stringify(messageResult), sdk.account.logout()); + + expect(cb).toHaveBeenCalledTimes(1); + + const { user } = sdk.account; + expect(user).toBeUndefined(); + }); +}); diff --git a/ee/packages/ddp-client/__tests__/Connection.spec.ts b/ee/packages/ddp-client/__tests__/Connection.spec.ts index 78ac9a2d3954..c535a6c58cc5 100644 --- a/ee/packages/ddp-client/__tests__/Connection.spec.ts +++ b/ee/packages/ddp-client/__tests__/Connection.spec.ts @@ -136,7 +136,7 @@ it('should queue messages if the connection is not ready', async () => { expect(connection.queue.size).toBe(0); - await handleMethod(server, 'method', ['arg1', 'arg2']); + await handleMethod(server, 'method', ['arg1', 'arg2'], '1'); }); it('should throw an error if a reconnect is called while a connection is in progress', async () => { diff --git a/ee/packages/ddp-client/__tests__/DDPSDK.spec.ts b/ee/packages/ddp-client/__tests__/DDPSDK.spec.ts index f24a4500b667..cd5b30113de9 100644 --- a/ee/packages/ddp-client/__tests__/DDPSDK.spec.ts +++ b/ee/packages/ddp-client/__tests__/DDPSDK.spec.ts @@ -205,13 +205,52 @@ it('should create and connect to a stream', async () => { sdk.connection.close(); }); +it.skip('should try to loginWithToken after reconnection', async () => { + const sdk = DDPSDK.create('ws://localhost:1234'); + + await handleConnection(server, sdk.connection.connect()); + + const messageResult = { + id: 123, + token: 'token1234', + tokenExpires: { $date: 99999999 }, + }; + + const { loginWithToken } = sdk.account; + const loginFn = jest.fn((token: string) => loginWithToken.apply(sdk.account, [token])); + sdk.account.loginWithToken = loginFn; + + await handleMethod(server, 'login', [{ resume: 'token' }], JSON.stringify(messageResult), sdk.account.loginWithToken('token')); + + expect(sdk.account.user?.token).toBe(messageResult.token); + expect(loginFn).toHaveBeenCalledTimes(1); + + // Fake timers are used to avoid waiting for the reconnect timeout + jest.useFakeTimers(); + + server.close(); + WS.clean(); + server = new WS('ws://localhost:1234/websocket'); + + const reconnect = new Promise((resolve) => sdk.connection.once('reconnecting', () => resolve(undefined))); + const connecting = new Promise((resolve) => sdk.connection.once('connecting', () => resolve(undefined))); + const connected = new Promise((resolve) => sdk.connection.once('connected', () => resolve(undefined))); + await handleConnection(server, jest.advanceTimersByTimeAsync(1000), reconnect, connecting, connected); + + jest.advanceTimersByTimeAsync(1000); + expect(loginFn).toHaveBeenCalledTimes(2); + + jest.useRealTimers(); + sdk.connection.close(); +}); + describe('Method call and Disconnection cases', () => { it('should handle properly if the message was sent after disconnection', async () => { const sdk = DDPSDK.create('ws://localhost:1234'); await handleConnection(server, sdk.connection.connect()); - const [result] = await handleMethod(server, 'method', ['args1'], sdk.client.callAsync('method', 'args1')); + const [result] = await handleMethod(server, 'method', ['args1'], '1', sdk.client.callAsync('method', 'args1')); expect(result).toBe(1); // Fake timers are used to avoid waiting for the reconnect timeout @@ -231,7 +270,7 @@ describe('Method call and Disconnection cases', () => { await handleConnection(server, jest.advanceTimersByTimeAsync(1000), reconnect, connecting, connected); - const [result2] = await handleMethod(server, 'method', ['args2'], callResult); + const [result2] = await handleMethod(server, 'method', ['args2'], '1', callResult); expect(util.inspect(callResult).includes('pending')).toBe(false); expect(result2).toBe(1); @@ -263,7 +302,7 @@ describe('Method call and Disconnection cases', () => { expect(util.inspect(callResult).includes('pending')).toBe(true); - const [result] = await handleMethod(server, 'method', ['args2'], callResult); + const [result] = await handleMethod(server, 'method', ['args2'], '1', callResult); expect(result).toBe(1); diff --git a/ee/packages/ddp-client/__tests__/helpers/index.ts b/ee/packages/ddp-client/__tests__/helpers/index.ts index f13545c7aeeb..dc5da3cd60ca 100644 --- a/ee/packages/ddp-client/__tests__/helpers/index.ts +++ b/ee/packages/ddp-client/__tests__/helpers/index.ts @@ -35,9 +35,9 @@ const handleConnectionButNoResponse = async (server: WS, method: string, params: }); }; -export const handleMethod = async (server: WS, method: string, params: string[], ...client: Promise[]) => { +export const handleMethod = async (server: WS, method: string, params: any[], responseResult: string, ...client: Promise[]) => { const result = await handleConnectionButNoResponse(server, method, params); - return Promise.all([server.send(`{"msg":"result","id":"${result.id}","result":1}`), ...client]).then((result) => { + return Promise.all([server.send(`{"msg":"result","id":"${result.id}","result":${responseResult}}`), ...client]).then((result) => { result.shift(); return result; }); diff --git a/ee/packages/ddp-client/package.json b/ee/packages/ddp-client/package.json index 95232d20a9b6..4087ed39b95c 100644 --- a/ee/packages/ddp-client/package.json +++ b/ee/packages/ddp-client/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/ddp-client", - "version": "0.2.9", + "version": "0.2.10", "devDependencies": { "@swc/core": "^1.3.95", "@swc/jest": "^0.2.29", diff --git a/ee/packages/ddp-client/src/ClientStream.ts b/ee/packages/ddp-client/src/ClientStream.ts index 032496c256b6..166e59a61f6f 100644 --- a/ee/packages/ddp-client/src/ClientStream.ts +++ b/ee/packages/ddp-client/src/ClientStream.ts @@ -12,6 +12,11 @@ export class ClientStreamImpl extends Emitter implements ClientStream { subscriptions = new Map(); constructor(private ddp: DDPClient, readonly dispatcher: DDPDispatcher = new DDPDispatcher()) { + ddp.onConnection(({ msg }) => { + if (msg === 'connected') { + this.emit('connected'); + } + }); super(); } diff --git a/ee/packages/ddp-client/src/DDPSDK.ts b/ee/packages/ddp-client/src/DDPSDK.ts index bfd80980bb8d..445ce90d7c78 100644 --- a/ee/packages/ddp-client/src/DDPSDK.ts +++ b/ee/packages/ddp-client/src/DDPSDK.ts @@ -105,7 +105,7 @@ export class DDPSDK implements SDK { } return { 'X-User-Id': account.uid, - 'X-Auth-Token': account.user?.token, + 'X-Auth-Token': account.user.token, }; } })({ baseUrl: url }); @@ -113,6 +113,9 @@ export class DDPSDK implements SDK { const sdk = new DDPSDK(connection, stream, account, timeoutControl, rest); connection.on('connected', () => { + if (account.user?.token) { + account.loginWithToken(account.user.token); + } [...stream.subscriptions.entries()].forEach(([, sub]) => { ddp.subscribeWithId(sub.id, sub.name, sub.params); }); diff --git a/ee/packages/ddp-client/src/types/Account.ts b/ee/packages/ddp-client/src/types/Account.ts index b5dde5e5ac3a..c8a51e402786 100644 --- a/ee/packages/ddp-client/src/types/Account.ts +++ b/ee/packages/ddp-client/src/types/Account.ts @@ -2,13 +2,20 @@ import { Emitter } from '@rocket.chat/emitter'; import type { ClientStream } from './ClientStream'; +type User = { + id: string; + username?: string; + token?: string; + tokenExpires?: Date; +} & Record; + export interface Account extends Emitter<{ uid: string | undefined; - user: Record | undefined; + user?: User; }> { uid?: string; - user?: Record; + user?: User; loginWithPassword(username: string, password: string): Promise; loginWithToken(token: string): Promise<{ id: string; @@ -21,26 +28,16 @@ export interface Account export class AccountImpl extends Emitter<{ uid: string | undefined; - user: { - id: string; - username: string; - token?: string; - tokenExpires?: Date; - }; + user: User; }> implements Account { uid?: string; - user?: { id: string; username: string; token?: string; tokenExpires?: Date }; + user?: { id: string; username?: string; token?: string; tokenExpires?: Date }; constructor(private readonly client: ClientStream) { super(); - this.client.on('connected', () => { - if (this.user?.token) { - this.loginWithToken(this.user.token); - } - }); client.onCollection('users', (data) => { if (data.collection !== 'users') { @@ -60,8 +57,24 @@ export class AccountImpl }); } + private saveCredentials(id: string, token: string, tokenExpires: string) { + this.user = { + ...this.user, + token, + tokenExpires: new Date(tokenExpires), + id, + }; + this.uid = id; + this.emit('uid', this.uid); + this.emit('user', this.user); + } + async loginWithPassword(username: string, password: string): Promise { - const { uid } = await this.client.callAsyncWithOptions( + const { + id, + token: resultToken, + tokenExpires: { $date }, + } = await this.client.callAsyncWithOptions( 'login', { wait: true, @@ -71,8 +84,8 @@ export class AccountImpl password: { digest: password, algorithm: 'sha-256' }, }, ); - this.uid = uid; - this.emit('uid', this.uid); + + this.saveCredentials(id, resultToken, $date); } async loginWithToken(token: string) { @@ -86,8 +99,12 @@ export class AccountImpl }, ); - this.uid = result.id; - this.emit('uid', this.uid); + const { + id, + token: resultToken, + tokenExpires: { $date }, + } = result; + this.saveCredentials(id, resultToken, $date); return result; } @@ -97,6 +114,7 @@ export class AccountImpl wait: true, }); this.uid = undefined; + this.user = undefined; this.emit('uid', this.uid); } } diff --git a/ee/packages/ddp-client/src/types/streams.ts b/ee/packages/ddp-client/src/types/streams.ts index d3a59a25c288..da9b913fc6dd 100644 --- a/ee/packages/ddp-client/src/types/streams.ts +++ b/ee/packages/ddp-client/src/types/streams.ts @@ -47,16 +47,28 @@ export interface StreamerEvents { { key: `${string}/typing`; args: [username: string, typing: boolean] }, { key: `${string}/deleteMessageBulk`; - args: [args: { rid: IMessage['rid']; excludePinned: boolean; ignoreDiscussion: boolean; ts: Record; users: string[] }]; + args: [ + args: { + rid: IMessage['rid']; + excludePinned: boolean; + ignoreDiscussion: boolean; + ts: Record; + users: string[]; + ids?: string[]; // message ids have priority over ts + showDeletedStatus?: boolean; + }, + ]; }, { key: `${string}/deleteMessage`; args: [{ _id: IMessage['_id'] }] }, { key: `${string}/e2e.keyRequest`; args: [unknown] }, { key: `${string}/videoconf`; args: [id: string] }, + { key: `${string}/messagesRead`; args: [{ until: Date; tmid?: string }] }, + { key: `${string}/messagesImported`; args: [null] }, /* @deprecated over videoconf*/ // { key: `${string}/${string}`; args: [id: string] }, ]; - 'room-messages': [{ key: '__my_messages__'; args: [IMessage] }, { key: string; args: [IMessage] }]; + 'room-messages': [{ key: '__my_messages__'; args: [IMessage] }, { key: string; args: [message: IMessage, user?: IUser, room?: IRoom] }]; 'notify-all': [ { diff --git a/ee/packages/license/CHANGELOG.md b/ee/packages/license/CHANGELOG.md index bb7835e90d17..cee46b612d9d 100644 --- a/ee/packages/license/CHANGELOG.md +++ b/ee/packages/license/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/license +## 0.1.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + ## 0.1.0 ### Minor Changes diff --git a/ee/packages/license/package.json b/ee/packages/license/package.json index 4d38dfda39ff..238d3574f59d 100644 --- a/ee/packages/license/package.json +++ b/ee/packages/license/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/license", - "version": "0.1.0", + "version": "0.1.1", "private": true, "devDependencies": { "@swc/core": "^1.3.95", diff --git a/ee/packages/omnichannel-services/CHANGELOG.md b/ee/packages/omnichannel-services/CHANGELOG.md index 0e1170004d2a..75ca235fcdd2 100644 --- a/ee/packages/omnichannel-services/CHANGELOG.md +++ b/ee/packages/omnichannel-services/CHANGELOG.md @@ -1,5 +1,18 @@ # @rocket.chat/omnichannel-services +## 0.1.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/pdf-worker@0.0.25 + - @rocket.chat/model-typings@0.2.1 + - @rocket.chat/models@0.0.25 + ## 0.1.0 ### Minor Changes diff --git a/ee/packages/omnichannel-services/package.json b/ee/packages/omnichannel-services/package.json index 983731026081..01e051ed5713 100644 --- a/ee/packages/omnichannel-services/package.json +++ b/ee/packages/omnichannel-services/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/omnichannel-services", - "version": "0.1.0", + "version": "0.1.1", "private": true, "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", diff --git a/ee/packages/pdf-worker/CHANGELOG.md b/ee/packages/pdf-worker/CHANGELOG.md index b64c1ce32bdb..e2e460560821 100644 --- a/ee/packages/pdf-worker/CHANGELOG.md +++ b/ee/packages/pdf-worker/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/pdf-worker +## 0.0.25 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + ## 0.0.24 ### Patch Changes diff --git a/ee/packages/pdf-worker/package.json b/ee/packages/pdf-worker/package.json index 4acfe5028439..cd37d8d7b0ae 100644 --- a/ee/packages/pdf-worker/package.json +++ b/ee/packages/pdf-worker/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/pdf-worker", - "version": "0.0.24", + "version": "0.0.25", "private": true, "devDependencies": { "@storybook/addon-essentials": "~6.5.16", diff --git a/ee/packages/presence/CHANGELOG.md b/ee/packages/presence/CHANGELOG.md index 29759422a237..50d1609e7c96 100644 --- a/ee/packages/presence/CHANGELOG.md +++ b/ee/packages/presence/CHANGELOG.md @@ -1,5 +1,14 @@ # @rocket.chat/presence +## 0.1.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/core-services@0.3.1 + - @rocket.chat/models@0.0.25 + ## 0.1.0 ### Minor Changes diff --git a/ee/packages/presence/package.json b/ee/packages/presence/package.json index 8d96ef1cebfc..708b4b55622d 100644 --- a/ee/packages/presence/package.json +++ b/ee/packages/presence/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/presence", - "version": "0.1.0", + "version": "0.1.1", "private": true, "devDependencies": { "@babel/core": "~7.22.20", diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json index bae5f06f41e5..fe3d1af5c36e 100644 --- a/ee/packages/ui-theming/package.json +++ b/ee/packages/ui-theming/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-hooks": "~0.32.1", "@rocket.chat/icons": "~0.32.0", "@rocket.chat/ui-contexts": "workspace:~", diff --git a/packages/core-services/CHANGELOG.md b/packages/core-services/CHANGELOG.md index fbb68e3b6a07..15f5278724e3 100644 --- a/packages/core-services/CHANGELOG.md +++ b/packages/core-services/CHANGELOG.md @@ -1,5 +1,15 @@ # @rocket.chat/core-services +## 0.3.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/models@0.0.25 + ## 0.3.0 ### Minor Changes diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 7656788e837a..987d9ebd30ee 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/core-services", - "version": "0.3.0", + "version": "0.3.1", "private": true, "devDependencies": { "@babel/core": "~7.22.20", diff --git a/packages/core-services/src/events/Events.ts b/packages/core-services/src/events/Events.ts index 5b27b421ba27..67327c3ea215 100644 --- a/packages/core-services/src/events/Events.ts +++ b/packages/core-services/src/events/Events.ts @@ -88,11 +88,15 @@ export type EventSignatures = { ignoreDiscussion: boolean; ts: Record; users: string[]; + ids?: string[]; // message ids have priority over ts + showDeletedStatus?: boolean; }, ): void; 'notify.deleteCustomSound'(data: { soundData: ICustomSound }): void; 'notify.updateCustomSound'(data: { soundData: ICustomSound }): void; 'notify.calendar'(uid: string, data: ICalendarNotification): void; + 'notify.messagesRead'(data: { rid: string; until: Date; tmid?: string }): void; + 'notify.importedMessages'(data: { roomIds: string[] }): void; 'permission.changed'(data: { clientAction: ClientAction; data: any }): void; 'room'(data: { action: string; room: Partial }): void; 'room.avatarUpdate'(room: Pick): void; diff --git a/packages/core-typings/CHANGELOG.md b/packages/core-typings/CHANGELOG.md index 8229232f7471..ade8d2311ebe 100644 --- a/packages/core-typings/CHANGELOG.md +++ b/packages/core-typings/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/core-typings +## 6.5.1 + +### Patch Changes + +- c2b224fd82: Exceeding API calls when sending OTR messages + ## 6.5.0 ### Minor Changes diff --git a/packages/core-typings/src/IMessage/IMessage.ts b/packages/core-typings/src/IMessage/IMessage.ts index 4d6845735898..190446502f02 100644 --- a/packages/core-typings/src/IMessage/IMessage.ts +++ b/packages/core-typings/src/IMessage/IMessage.ts @@ -357,16 +357,22 @@ export type IE2EEMessage = IMessage & { e2e: 'pending' | 'done'; }; -export type IOTRMessage = IMessage & { - t: 'otr' | 'otr-ack'; -}; +export interface IOTRMessage extends IMessage { + t: 'otr'; + otrAck?: string; +} + +export interface IOTRAckMessage extends IMessage { + t: 'otr-ack'; +} export type IVideoConfMessage = IMessage & { t: 'videoconf'; }; export const isE2EEMessage = (message: IMessage): message is IE2EEMessage => message.t === 'e2e'; -export const isOTRMessage = (message: IMessage): message is IOTRMessage => message.t === 'otr' || message.t === 'otr-ack'; +export const isOTRMessage = (message: IMessage): message is IOTRMessage => message.t === 'otr'; +export const isOTRAckMessage = (message: IMessage): message is IOTRAckMessage => message.t === 'otr-ack'; export const isVideoConfMessage = (message: IMessage): message is IVideoConfMessage => message.t === 'videoconf'; export type IMessageWithPendingFileImport = IMessage & { diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index ce14c4020d6f..98785805714c 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -29,6 +29,7 @@ export interface IUserEmailVerificationToken { export interface IUserEmailCode { code: string; expire: Date; + attempts: number; } type LoginToken = IMeteorLoginToken | IPersonalAccessToken; @@ -75,7 +76,7 @@ export interface IUserServices { enabled: boolean; changedAt: Date; }; - emailCode?: IUserEmailCode[]; + emailCode?: IUserEmailCode; saml?: { inResponseTo?: string; provider?: string; diff --git a/packages/cron/CHANGELOG.md b/packages/cron/CHANGELOG.md index 02a65ed11468..190bc292bc48 100644 --- a/packages/cron/CHANGELOG.md +++ b/packages/cron/CHANGELOG.md @@ -1,5 +1,13 @@ # @rocket.chat/cron +## 0.0.21 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/models@0.0.25 + ## 0.0.20 ### Patch Changes diff --git a/packages/cron/package.json b/packages/cron/package.json index 83b05c273aa6..e66d2cc6aa06 100644 --- a/packages/cron/package.json +++ b/packages/cron/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/cron", - "version": "0.0.20", + "version": "0.0.21", "private": true, "devDependencies": { "@types/jest": "~29.5.7", diff --git a/packages/fuselage-ui-kit/CHANGELOG.md b/packages/fuselage-ui-kit/CHANGELOG.md index 99cdbb4bd792..8dc3c6c9560b 100644 --- a/packages/fuselage-ui-kit/CHANGELOG.md +++ b/packages/fuselage-ui-kit/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 3.0.1 + +### Patch Changes + +- @rocket.chat/ui-contexts@3.0.1 +- @rocket.chat/gazzodown@3.0.1 +- @rocket.chat/ui-video-conf@3.0.1 + ## 3.0.0 ### Patch Changes diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 0849872cc174..a21f77dbf20f 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/fuselage-ui-kit", "private": true, - "version": "3.0.0", + "version": "3.0.1", "description": "UiKit elements for Rocket.Chat Apps built under Fuselage design system", "homepage": "https://rocketchat.github.io/Rocket.Chat.Fuselage/", "author": { @@ -48,9 +48,9 @@ "@rocket.chat/icons": "*", "@rocket.chat/prettier-config": "*", "@rocket.chat/styled": "*", - "@rocket.chat/ui-contexts": "3.0.0", + "@rocket.chat/ui-contexts": "3.0.1", "@rocket.chat/ui-kit": "*", - "@rocket.chat/ui-video-conf": "3.0.0", + "@rocket.chat/ui-video-conf": "3.0.1", "@tanstack/react-query": "*", "react": "*", "react-dom": "*" @@ -62,7 +62,7 @@ "@babel/preset-typescript": "~7.22.15", "@rocket.chat/apps-engine": "1.41.0", "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-hooks": "~0.32.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", "@rocket.chat/icons": "~0.32.0", diff --git a/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/hooks/useVideoConfDataStream.ts b/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/hooks/useVideoConfDataStream.ts index bed07f955a9d..bcab7ee10db9 100644 --- a/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/hooks/useVideoConfDataStream.ts +++ b/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/hooks/useVideoConfDataStream.ts @@ -1,5 +1,5 @@ import type { IRoom } from '@rocket.chat/core-typings'; -import { useSingleStream } from '@rocket.chat/ui-contexts'; +import { useStream } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; @@ -14,7 +14,7 @@ export const useVideoConfDataStream = ({ }) => { const queryClient = useQueryClient(); - const subscribeNotifyRoom = useSingleStream('notify-room'); + const subscribeNotifyRoom = useStream('notify-room'); useEffect(() => { return subscribeNotifyRoom( diff --git a/packages/gazzodown/CHANGELOG.md b/packages/gazzodown/CHANGELOG.md index aa6e24f891db..acd5bdce318d 100644 --- a/packages/gazzodown/CHANGELOG.md +++ b/packages/gazzodown/CHANGELOG.md @@ -1,5 +1,14 @@ # @rocket.chat/gazzodown +## 3.0.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + - @rocket.chat/ui-contexts@3.0.1 + - @rocket.chat/ui-client@3.0.1 + ## 3.0.0 ### Patch Changes diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index f3512e4529f3..7503678d33df 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -1,12 +1,12 @@ { "name": "@rocket.chat/gazzodown", - "version": "3.0.0", + "version": "3.0.1", "private": true, "devDependencies": { "@babel/core": "~7.22.20", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-tokens": "~0.32.0", "@rocket.chat/message-parser": "~0.31.27", "@rocket.chat/styled": "~0.31.25", @@ -71,8 +71,8 @@ "@rocket.chat/fuselage-tokens": "*", "@rocket.chat/message-parser": "*", "@rocket.chat/styled": "*", - "@rocket.chat/ui-client": "3.0.0", - "@rocket.chat/ui-contexts": "3.0.0", + "@rocket.chat/ui-client": "3.0.1", + "@rocket.chat/ui-contexts": "3.0.1", "katex": "*", "react": "*" }, diff --git a/packages/instance-status/CHANGELOG.md b/packages/instance-status/CHANGELOG.md index 3bb16663fe3b..803d8b0ccd90 100644 --- a/packages/instance-status/CHANGELOG.md +++ b/packages/instance-status/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/instance-status +## 0.0.25 + +### Patch Changes + +- @rocket.chat/models@0.0.25 + ## 0.0.24 ### Patch Changes diff --git a/packages/instance-status/package.json b/packages/instance-status/package.json index cd051a7f9224..34687a095576 100644 --- a/packages/instance-status/package.json +++ b/packages/instance-status/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/instance-status", - "version": "0.0.24", + "version": "0.0.25", "private": true, "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", diff --git a/packages/livechat/CHANGELOG.md b/packages/livechat/CHANGELOG.md index fa45e1d0b9d7..dbe5d2dd64a9 100644 --- a/packages/livechat/CHANGELOG.md +++ b/packages/livechat/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/livechat Change Log +## 1.14.10 + +### Patch Changes + +- @rocket.chat/gazzodown@3.0.1 + ## 1.14.9 ### Patch Changes diff --git a/packages/livechat/package.json b/packages/livechat/package.json index 26a5668f60a7..82649dc4ce91 100644 --- a/packages/livechat/package.json +++ b/packages/livechat/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/livechat", - "version": "1.14.9", + "version": "1.14.10", "files": [ "/build" ], diff --git a/packages/livechat/src/routes/Register/index.tsx b/packages/livechat/src/routes/Register/index.tsx index a4c483db1282..d9df7a5a3d4a 100644 --- a/packages/livechat/src/routes/Register/index.tsx +++ b/packages/livechat/src/routes/Register/index.tsx @@ -81,23 +81,25 @@ export const Register = ({ screenProps }: { screenProps: { [key: string]: unknow ...(department && { department }), }; - await dispatch({ loading: true, department }); + dispatch({ loading: true, department }); try { const { visitor: user } = await Livechat.grantVisitor({ visitor: { ...fields, token } }); - await dispatch({ user }); + dispatch({ user }); parentCall('callback', ['pre-chat-form-submit', fields]); registerCustomFields(customFields); } finally { - await dispatch({ loading: false }); + dispatch({ loading: false }); } }; const getDepartmentDefault = () => { - if (departments?.some((dept) => dept._id === guestDepartment)) { + if (departments.some((dept) => dept._id === guestDepartment)) { return guestDepartment; } }; + const availableDepartments = departments.filter((dept) => dept.showOnRegistration); + useEffect(() => { if (user?._id) { route('/'); @@ -157,7 +159,7 @@ export const Register = ({ screenProps }: { screenProps: { [key: string]: unknow ) : null} - {departments?.some((dept) => dept.showOnRegistration) ? ( + {availableDepartments.length ? ( ( ({ + options={sortArrayByColumn(availableDepartments, 'name').map(({ _id, name }: { _id: string; name: string }) => ({ value: _id, label: name, }))} diff --git a/packages/livechat/src/widget.js b/packages/livechat/src/widget.js index fafccb1f9c22..46781644ec36 100644 --- a/packages/livechat/src/widget.js +++ b/packages/livechat/src/widget.js @@ -54,6 +54,10 @@ function emitCallback(eventName, data) { } } +function clearAllCallbacks() { + callbacks.all.clear(); +} + // hooks function callHook(action, params) { if (!ready) { @@ -305,18 +309,22 @@ function setLanguage(language) { function showWidget() { callHook('showWidget'); + emitCallback('show-widget'); } function hideWidget() { callHook('hideWidget'); + emitCallback('hide-widget'); } function maximizeWidget() { callHook('maximizeWidget'); + emitCallback('chat-maximized'); } function minimizeWidget() { callHook('minimizeWidget'); + emitCallback('chat-minimized'); } function setParentUrl(url) { @@ -467,6 +475,7 @@ window.RocketChat.livechat = { setBusinessUnit, clearBusinessUnit, setParentUrl, + clearAllCallbacks, // callbacks onChatMaximized(fn) { diff --git a/packages/mock-providers/src/MockedAppRootBuilder.tsx b/packages/mock-providers/src/MockedAppRootBuilder.tsx index df2c69d99b91..15a4db77eb10 100644 --- a/packages/mock-providers/src/MockedAppRootBuilder.tsx +++ b/packages/mock-providers/src/MockedAppRootBuilder.tsx @@ -48,7 +48,6 @@ export class MockedAppRootBuilder { }): Promise>> => { throw new Error('not implemented'); }, - getSingleStream: () => () => () => undefined, getStream: () => () => () => undefined, uploadToEndpoint: () => Promise.reject(new Error('not implemented')), callMethod: () => Promise.reject(new Error('not implemented')), diff --git a/packages/model-typings/CHANGELOG.md b/packages/model-typings/CHANGELOG.md index 869b9d2d54c1..d3cd53378808 100644 --- a/packages/model-typings/CHANGELOG.md +++ b/packages/model-typings/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/model-typings +## 0.2.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/model-typings/package.json b/packages/model-typings/package.json index 8ea1c7e81c10..e8dbf46d9a6f 100644 --- a/packages/model-typings/package.json +++ b/packages/model-typings/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/model-typings", - "version": "0.2.0", + "version": "0.2.1", "private": true, "devDependencies": { "@types/jest": "~29.5.7", diff --git a/packages/model-typings/src/models/IRoomsModel.ts b/packages/model-typings/src/models/IRoomsModel.ts index 66ffe9232749..215eac8b232d 100644 --- a/packages/model-typings/src/models/IRoomsModel.ts +++ b/packages/model-typings/src/models/IRoomsModel.ts @@ -239,7 +239,7 @@ export interface IRoomsModel extends IBaseModel { incUsersCountById(rid: string, inc: number): Promise; incUsersCountNotDMsByIds(rids: string[], inc: number): Promise; setLastMessageById(rid: string, lastMessage: IRoom['lastMessage']): Promise; - resetLastMessageById(rid: string, lastMessage?: IMessage | null): Promise; + resetLastMessageById(rid: string, lastMessage: IMessage | null, msgCountDelta?: number): Promise; replaceUsername(username: string, newUsername: string): Promise; replaceMutedUsername(username: string, newUsername: string): Promise; replaceUsernameOfUserByUserId(userId: string, newUsername: string): Promise; diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index f14f5bc90d0d..f9a2b1c45a2a 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -9,7 +9,7 @@ import type { AtLeast, ILivechatAgentStatus, } from '@rocket.chat/core-typings'; -import type { Document, UpdateResult, FindCursor, FindOptions, Filter, InsertOneResult, DeleteResult } from 'mongodb'; +import type { Document, UpdateResult, FindCursor, FindOptions, Filter, InsertOneResult, DeleteResult, ModifyResult } from 'mongodb'; import type { FindPaginated, IBaseModel } from './IBaseModel'; @@ -279,8 +279,10 @@ export interface IUsersModel extends IBaseModel { disableEmail2FAByUserId(userId: string): Promise; findByIdsWithPublicE2EKey(userIds: string[], options?: FindOptions): FindCursor; resetE2EKey(userId: string): Promise; - removeExpiredEmailCodesOfUserId(userId: string): Promise; - removeEmailCodeByUserIdAndCode(userId: string, code: string): Promise; + removeExpiredEmailCodeOfUserId(userId: string): Promise; + removeEmailCodeByUserId(userId: string): Promise; + increaseInvalidEmailCodeAttempt(userId: string): Promise; + maxInvalidEmailCodeAttemptsReached(userId: string, maxAttemtps: number): Promise; addEmailCodeByUserId(userId: string, code: string, expire: Date): Promise; findActiveUsersInRoles(roles: string[], options?: FindOptions): FindCursor; countActiveUsersInRoles(roles: string[], options?: FindOptions): Promise; @@ -387,4 +389,6 @@ export interface IUsersModel extends IBaseModel { options: FindOptions, ): Promise<{ sortedResults: (T & { departments: string[] })[]; totalCount: { total: number }[] }[]>; countByRole(roleName: string): Promise; + removeEmailCodeOfUserId(userId: string): Promise; + incrementInvalidEmailCodeAttempt(userId: string): Promise>; } diff --git a/packages/models/CHANGELOG.md b/packages/models/CHANGELOG.md index e5fdb024c333..8861ba27c492 100644 --- a/packages/models/CHANGELOG.md +++ b/packages/models/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/models +## 0.0.25 + +### Patch Changes + +- @rocket.chat/model-typings@0.2.1 + ## 0.0.24 ### Patch Changes diff --git a/packages/models/package.json b/packages/models/package.json index 0eb205179de6..8c752d838109 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/models", - "version": "0.0.24", + "version": "0.0.25", "private": true, "devDependencies": { "@types/jest": "~29.5.7", diff --git a/packages/rest-typings/CHANGELOG.md b/packages/rest-typings/CHANGELOG.md index 3bc136c490b3..0e7df4130b53 100644 --- a/packages/rest-typings/CHANGELOG.md +++ b/packages/rest-typings/CHANGELOG.md @@ -1,5 +1,13 @@ # @rocket.chat/rest-typings +## 6.5.1 + +### Patch Changes + +- c2b224fd82: fix Federation Regression, builds service correctly +- Updated dependencies [c2b224fd82] + - @rocket.chat/core-typings@6.5.1 + ## 6.5.0 ### Minor Changes diff --git a/packages/ui-client/CHANGELOG.md b/packages/ui-client/CHANGELOG.md index 430507f15f54..183a47816e39 100644 --- a/packages/ui-client/CHANGELOG.md +++ b/packages/ui-client/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/ui-client +## 3.0.1 + +### Patch Changes + +- @rocket.chat/ui-contexts@3.0.1 + ## 3.0.0 ### Patch Changes diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json index bf7111ce8a06..ab499cab0955 100644 --- a/packages/ui-client/package.json +++ b/packages/ui-client/package.json @@ -1,11 +1,11 @@ { "name": "@rocket.chat/ui-client", - "version": "3.0.0", + "version": "3.0.1", "private": true, "devDependencies": { "@babel/core": "~7.22.20", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-hooks": "~0.32.1", "@rocket.chat/icons": "~0.32.0", "@rocket.chat/mock-providers": "workspace:^", @@ -61,7 +61,7 @@ "@rocket.chat/fuselage": "*", "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/icons": "*", - "@rocket.chat/ui-contexts": "3.0.0", + "@rocket.chat/ui-contexts": "3.0.1", "react": "~17.0.2" }, "volta": { diff --git a/packages/ui-client/src/components/Card/Card.stories.tsx b/packages/ui-client/src/components/Card/Card.stories.tsx deleted file mode 100644 index 56a2220219ed..000000000000 --- a/packages/ui-client/src/components/Card/Card.stories.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage'; -import type { ComponentMeta, ComponentStory } from '@storybook/react'; - -import { Card, CardBody, CardCol, CardColSection, CardColTitle, CardDivider, CardFooter, CardIcon, CardTitle } from '.'; -import TextSeparator from '../TextSeparator'; -import { UserStatus } from '../UserStatus'; - -export default { - title: 'Components/Card', - component: Card, - subcomponents: { - CardTitle, - CardBody, - CardCol, - CardColSection, - CardColTitle, - CardFooter, - CardDivider, - }, - parameters: { - layout: 'centered', - controls: { hideNoControlsWarning: true }, - }, -} as ComponentMeta; - -export const Example: ComponentStory = () => ( - - Usage - - - Users - - Total - - } - value={123} - /> - - - - {' '} - Online - - } - value={123} - /> - - - - {' '} - Busy - - } - value={123} - /> - - - - {' '} - Away - - } - value={123} - /> - - - - {' '} - Offline - - } - value={123} - /> - - - Types and Distribution - - - - - - - - Uploads - - - - - -); - -export const Single: ComponentStory = () => ( - - A card - - - - A Section -
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
- - Another Section -
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
-
-
- - - - - -
-); - -export const Double: ComponentStory = () => ( - - A card - - - - A Section -
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
- - Another Section -
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
-
- - - - A Section - - A bunch of stuff - - - A bunch of stuff - - - A bunch of stuff - - - A bunch of stuff - - - - Another Section -
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
A bunch of stuff
-
-
-
- - - - - -
-); diff --git a/packages/ui-client/src/components/Card/Card.tsx b/packages/ui-client/src/components/Card/Card.tsx deleted file mode 100644 index 41717574dd77..000000000000 --- a/packages/ui-client/src/components/Card/Card.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { CSSProperties, FC, ReactNode } from 'react'; - -type IBackground = { - backgroundImage?: CSSProperties['backgroundImage']; - backgroundRepeat?: CSSProperties['backgroundRepeat']; - backgroundPosition?: CSSProperties['backgroundPosition']; - backgroundSize?: CSSProperties['backgroundSize']; -}; - -type CardProps = { - background?: IBackground; - children: ReactNode; -}; - -const Card: FC = ({ background, children, ...props }) => ( - - {children} - -); - -export default Card; diff --git a/packages/ui-client/src/components/Card/CardBody.tsx b/packages/ui-client/src/components/Card/CardBody.tsx deleted file mode 100644 index 70a9e127c27d..000000000000 --- a/packages/ui-client/src/components/Card/CardBody.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { FC, CSSProperties, ComponentProps } from 'react'; - -type CardBodyProps = { - flexDirection?: CSSProperties['flexDirection']; - minHeight?: ComponentProps['minHeight']; - height?: ComponentProps['height']; -}; - -const CardBody: FC = ({ children, flexDirection = 'row', ...props }) => ( - - {children} - -); - -export default CardBody; diff --git a/packages/ui-client/src/components/Card/CardCol.tsx b/packages/ui-client/src/components/Card/CardCol.tsx deleted file mode 100644 index dd14d7c93591..000000000000 --- a/packages/ui-client/src/components/Card/CardCol.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; - -const CardCol: FC = ({ children }) => ( - - {children} - -); - -export default CardCol; diff --git a/packages/ui-client/src/components/Card/CardColSection.tsx b/packages/ui-client/src/components/Card/CardColSection.tsx deleted file mode 100644 index 9272d87e2ae5..000000000000 --- a/packages/ui-client/src/components/Card/CardColSection.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { ComponentProps, FC } from 'react'; - -const CardColSection: FC> = (props) => ; - -export default CardColSection; diff --git a/packages/ui-client/src/components/Card/CardColTitle.tsx b/packages/ui-client/src/components/Card/CardColTitle.tsx deleted file mode 100644 index 0d4d0a3d5a27..000000000000 --- a/packages/ui-client/src/components/Card/CardColTitle.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; - -const CardColTitle: FC = ({ children }) => ( - - {children} - -); - -export default CardColTitle; diff --git a/packages/ui-client/src/components/Card/CardDivider.tsx b/packages/ui-client/src/components/Card/CardDivider.tsx deleted file mode 100644 index 31f3952ea28a..000000000000 --- a/packages/ui-client/src/components/Card/CardDivider.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Divider } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; - -const CardDivider: FC = () => ; - -export default CardDivider; diff --git a/packages/ui-client/src/components/Card/CardFooter.tsx b/packages/ui-client/src/components/Card/CardFooter.tsx deleted file mode 100644 index 88ef03fba6c6..000000000000 --- a/packages/ui-client/src/components/Card/CardFooter.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { ButtonGroup } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; - -const CardFooter: FC = ({ children }) => {children}; - -export default CardFooter; diff --git a/packages/ui-client/src/components/Card/CardFooterWrapper.tsx b/packages/ui-client/src/components/Card/CardFooterWrapper.tsx deleted file mode 100644 index fa0cfffc802d..000000000000 --- a/packages/ui-client/src/components/Card/CardFooterWrapper.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { ReactElement, ReactNode } from 'react'; - -const CardFooterWrapper = ({ children }: { children: ReactNode }): ReactElement => {children}; - -export default CardFooterWrapper; diff --git a/packages/ui-client/src/components/Card/CardIcon.tsx b/packages/ui-client/src/components/Card/CardIcon.tsx deleted file mode 100644 index c409cd1d2053..000000000000 --- a/packages/ui-client/src/components/Card/CardIcon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Box, Icon } from '@rocket.chat/fuselage'; -import type { ComponentProps, ReactElement, ReactNode } from 'react'; - -type CardIconProps = { children: ReactNode } | ComponentProps; - -const hasChildrenProp = (props: CardIconProps): props is { children: ReactNode } => 'children' in props; - -const CardIcon = (props: CardIconProps): ReactElement => ( - - {hasChildrenProp(props) ? props.children : } - -); - -export default CardIcon; diff --git a/packages/ui-client/src/components/Card/CardTitle.tsx b/packages/ui-client/src/components/Card/CardTitle.tsx deleted file mode 100644 index ac31848a0689..000000000000 --- a/packages/ui-client/src/components/Card/CardTitle.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; - -const CardTitle: FC = ({ children }) => ( - - {children} - -); - -export default CardTitle; diff --git a/packages/ui-client/src/components/Card/index.ts b/packages/ui-client/src/components/Card/index.ts deleted file mode 100644 index 76df65e5a809..000000000000 --- a/packages/ui-client/src/components/Card/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { default as Card } from './Card'; -export { default as CardBody } from './CardBody'; -export { default as CardCol } from './CardCol'; -export { default as CardColSection } from './CardColSection'; -export { default as CardColTitle } from './CardColTitle'; -export { default as CardDivider } from './CardDivider'; -export { default as CardFooter } from './CardFooter'; -export { default as CardFooterWrapper } from './CardFooterWrapper'; -export { default as CardIcon } from './CardIcon'; -export { default as CardTitle } from './CardTitle'; diff --git a/packages/ui-client/src/components/index.ts b/packages/ui-client/src/components/index.ts index 1b84d799c7b0..f7f77474b256 100644 --- a/packages/ui-client/src/components/index.ts +++ b/packages/ui-client/src/components/index.ts @@ -7,7 +7,6 @@ export * from '../hooks/useValidatePassword'; export { default as TextSeparator } from './TextSeparator'; export * from './TooltipComponent'; export * as UserStatus from './UserStatus'; -export * from './Card'; export * from './Header'; export * from './MultiSelectCustom/MultiSelectCustom'; export * from './FeaturePreview/FeaturePreview'; diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json index a81b64317a95..7ceeee6964e0 100644 --- a/packages/ui-composer/package.json +++ b/packages/ui-composer/package.json @@ -5,7 +5,7 @@ "devDependencies": { "@babel/core": "~7.22.20", "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/icons": "~0.32.0", "@storybook/addon-actions": "~6.5.16", "@storybook/addon-docs": "~6.5.16", diff --git a/packages/ui-contexts/CHANGELOG.md b/packages/ui-contexts/CHANGELOG.md index 6e0f2022b888..ec8ff0ce76ae 100644 --- a/packages/ui-contexts/CHANGELOG.md +++ b/packages/ui-contexts/CHANGELOG.md @@ -1,5 +1,20 @@ # @rocket.chat/ui-contexts +## 3.0.1 + +### Patch Changes + +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] +- Updated dependencies [c2b224fd82] + - @rocket.chat/rest-typings@6.5.1 + - @rocket.chat/ddp-client@0.2.10 + - @rocket.chat/core-typings@6.5.1 + ## 3.0.0 ### Patch Changes diff --git a/packages/ui-contexts/package.json b/packages/ui-contexts/package.json index 7317e882d826..1edaec074409 100644 --- a/packages/ui-contexts/package.json +++ b/packages/ui-contexts/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/ui-contexts", - "version": "3.0.0", + "version": "3.0.1", "private": true, "devDependencies": { "@rocket.chat/core-typings": "workspace:^", diff --git a/packages/ui-contexts/src/ServerContext/ServerContext.ts b/packages/ui-contexts/src/ServerContext/ServerContext.ts index 292b64882545..14a2b0e1a3ea 100644 --- a/packages/ui-contexts/src/ServerContext/ServerContext.ts +++ b/packages/ui-contexts/src/ServerContext/ServerContext.ts @@ -38,13 +38,6 @@ export type ServerContextValue = { retransmitToSelf?: boolean | undefined; }, ) => (eventName: K, callback: (...args: StreamerCallbackArgs) => void) => () => void; - getSingleStream: >( - streamName: N, - _options?: { - retransmit?: boolean | undefined; - retransmitToSelf?: boolean | undefined; - }, - ) => (eventName: K, callback: (...args: StreamerCallbackArgs) => void) => () => void; }; export const ServerContext = createContext({ @@ -57,5 +50,4 @@ export const ServerContext = createContext({ throw new Error('not implemented'); }, getStream: () => () => (): void => undefined, - getSingleStream: () => () => (): void => undefined, }); diff --git a/packages/ui-contexts/src/hooks/useStream.ts b/packages/ui-contexts/src/hooks/useStream.ts index 34c49c5a778c..d6fb5e76be78 100644 --- a/packages/ui-contexts/src/hooks/useStream.ts +++ b/packages/ui-contexts/src/hooks/useStream.ts @@ -28,21 +28,3 @@ export function useStream( const { getStream } = useContext(ServerContext); return useMemo(() => getStream(streamName, options), [getStream, streamName, options]); } - -/* - * @param streamName The name of the stream to subscribe to - * @returns A function that can be used to subscribe to the stream - * the main difference between this and useStream is that this function - * will only subscribe to the `stream + key` only once, but you can still add multiple callbacks - * to the same path - */ -export function useSingleStream( - streamName: N, - options?: { - retransmit?: boolean; - retransmitToSelf?: boolean; - }, -): StreamerCallback { - const { getSingleStream } = useContext(ServerContext); - return useMemo(() => getSingleStream(streamName, options), [getSingleStream, streamName, options]); -} diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts index 4e35d9e02af8..6e7b31d8eaf3 100644 --- a/packages/ui-contexts/src/index.ts +++ b/packages/ui-contexts/src/index.ts @@ -66,7 +66,7 @@ export { useSettings } from './hooks/useSettings'; export { useSettingsDispatch } from './hooks/useSettingsDispatch'; export { useSettingSetValue } from './hooks/useSettingSetValue'; export { useSettingStructure } from './hooks/useSettingStructure'; -export { useStream, useSingleStream } from './hooks/useStream'; +export { useStream } from './hooks/useStream'; export { useToastMessageDispatch } from './hooks/useToastMessageDispatch'; export { useTooltipClose } from './hooks/useTooltipClose'; export { useTooltipOpen } from './hooks/useTooltipOpen'; diff --git a/packages/ui-video-conf/CHANGELOG.md b/packages/ui-video-conf/CHANGELOG.md index 8a18b550da0f..6a6e50ec9c5a 100644 --- a/packages/ui-video-conf/CHANGELOG.md +++ b/packages/ui-video-conf/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/ui-video-conf +## 3.0.1 + +### Patch Changes + +- @rocket.chat/ui-contexts@3.0.1 + ## 3.0.0 ### Patch Changes diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json index 88c675f03b7e..c4ef7294e4ef 100644 --- a/packages/ui-video-conf/package.json +++ b/packages/ui-video-conf/package.json @@ -1,12 +1,12 @@ { "name": "@rocket.chat/ui-video-conf", - "version": "3.0.0", + "version": "3.0.1", "private": true, "devDependencies": { "@babel/core": "~7.22.20", "@rocket.chat/css-in-js": "~0.31.25", "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-hooks": "~0.32.1", "@rocket.chat/icons": "~0.32.0", "@rocket.chat/styled": "~0.31.25", @@ -35,7 +35,7 @@ "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/icons": "*", "@rocket.chat/styled": "*", - "@rocket.chat/ui-contexts": "3.0.0", + "@rocket.chat/ui-contexts": "3.0.1", "react": "^17.0.2", "react-dom": "^17.0.2" }, diff --git a/packages/uikit-playground/CHANGELOG.md b/packages/uikit-playground/CHANGELOG.md index 260935c50f21..19d22833be50 100644 --- a/packages/uikit-playground/CHANGELOG.md +++ b/packages/uikit-playground/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/uikit-playground +## 0.2.10 + +### Patch Changes + +- @rocket.chat/ui-contexts@3.0.1 +- @rocket.chat/fuselage-ui-kit@3.0.1 + ## 0.2.9 ### Patch Changes diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index 7c57cf8ef6b5..b97c7d15b235 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/uikit-playground", "private": true, - "version": "0.2.9", + "version": "0.2.10", "type": "module", "scripts": { "dev": "vite", @@ -15,7 +15,7 @@ "@codemirror/tooltip": "^0.19.16", "@lezer/highlight": "^1.1.6", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "0.39.0", + "@rocket.chat/fuselage": "^0.41.0", "@rocket.chat/fuselage-hooks": "~0.32.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", "@rocket.chat/fuselage-tokens": "~0.32.0", diff --git a/packages/web-ui-registration/CHANGELOG.md b/packages/web-ui-registration/CHANGELOG.md index 76b400173d19..afdf34b9f108 100644 --- a/packages/web-ui-registration/CHANGELOG.md +++ b/packages/web-ui-registration/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/web-ui-registration +## 3.0.1 + +### Patch Changes + +- @rocket.chat/ui-contexts@3.0.1 + ## 3.0.0 ### Patch Changes diff --git a/packages/web-ui-registration/package.json b/packages/web-ui-registration/package.json index fa3d4286926a..77f766fdbed1 100644 --- a/packages/web-ui-registration/package.json +++ b/packages/web-ui-registration/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/web-ui-registration", - "version": "3.0.0", + "version": "3.0.1", "private": true, "homepage": "https://rocket.chat", "main": "./dist/index.js", @@ -51,7 +51,7 @@ "peerDependencies": { "@rocket.chat/layout": "*", "@rocket.chat/tools": "*", - "@rocket.chat/ui-contexts": "3.0.0", + "@rocket.chat/ui-contexts": "3.0.1", "@tanstack/react-query": "*", "react": "*", "react-hook-form": "*", diff --git a/yarn.lock b/yarn.lock index 0ea65363d790..10fa7f032dfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9033,7 +9033,7 @@ __metadata: "@babel/preset-typescript": ~7.22.15 "@rocket.chat/apps-engine": 1.41.0 "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-hooks": ~0.32.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 "@rocket.chat/gazzodown": "workspace:^" @@ -9077,18 +9077,18 @@ __metadata: "@rocket.chat/icons": "*" "@rocket.chat/prettier-config": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 3.0.0 + "@rocket.chat/ui-contexts": 3.0.1 "@rocket.chat/ui-kit": "*" - "@rocket.chat/ui-video-conf": 3.0.0 + "@rocket.chat/ui-video-conf": 3.0.1 "@tanstack/react-query": "*" react: "*" react-dom: "*" languageName: unknown linkType: soft -"@rocket.chat/fuselage@npm:0.39.0": - version: 0.39.0 - resolution: "@rocket.chat/fuselage@npm:0.39.0" +"@rocket.chat/fuselage@npm:^0.41.0": + version: 0.41.0 + resolution: "@rocket.chat/fuselage@npm:0.41.0" dependencies: "@rocket.chat/css-in-js": ^0.31.25 "@rocket.chat/css-supports": ^0.31.25 @@ -9106,7 +9106,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 5e9c182479d5a450068fd682967267cddd533e1101083b5b59764a5f159261fc0cae6134fe934401e02753b40c1fcad84cf0a7a05801cc0f5a31b2a60df0649d + checksum: 8ee33c5626ff7fb8970714696332efc723ca0793470d0b4da9a169aa6b2528ec75068d775e8f149dd3a1ea401ff0240be892022b64bd80e5bd4fc04349e0c0aa languageName: node linkType: hard @@ -9117,7 +9117,7 @@ __metadata: "@babel/core": ~7.22.20 "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-tokens": ~0.32.0 "@rocket.chat/message-parser": ~0.31.27 "@rocket.chat/styled": ~0.31.25 @@ -9167,8 +9167,8 @@ __metadata: "@rocket.chat/fuselage-tokens": "*" "@rocket.chat/message-parser": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-client": 3.0.0 - "@rocket.chat/ui-contexts": 3.0.0 + "@rocket.chat/ui-client": 3.0.1 + "@rocket.chat/ui-contexts": 3.0.1 katex: "*" react: "*" languageName: unknown @@ -9448,7 +9448,7 @@ __metadata: "@rocket.chat/favicon": "workspace:^" "@rocket.chat/forked-matrix-appservice-bridge": ^4.0.2 "@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.3 - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-hooks": ~0.32.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 "@rocket.chat/fuselage-toastbar": ~0.31.25 @@ -9472,7 +9472,7 @@ __metadata: "@rocket.chat/models": "workspace:^" "@rocket.chat/mp3-encoder": 0.24.0 "@rocket.chat/omnichannel-services": "workspace:^" - "@rocket.chat/onboarding-ui": ~0.33.2 + "@rocket.chat/onboarding-ui": ~0.33.3 "@rocket.chat/password-policies": "workspace:^" "@rocket.chat/pdf-worker": "workspace:^" "@rocket.chat/poplib": "workspace:^" @@ -9916,9 +9916,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/onboarding-ui@npm:~0.33.2": - version: 0.33.2 - resolution: "@rocket.chat/onboarding-ui@npm:0.33.2" +"@rocket.chat/onboarding-ui@npm:~0.33.3": + version: 0.33.3 + resolution: "@rocket.chat/onboarding-ui@npm:0.33.3" dependencies: i18next: ~21.6.16 react-hook-form: ~7.27.1 @@ -9933,7 +9933,7 @@ __metadata: react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: abbfde9f8ac5655b9dbd30872aabe585f2814d58c1c66d672b4aaf3ef3f69d0e31e626924bab19b052d242a0abdb9c7a0d3ff907a7f21742ec15a6dc367951af + checksum: 18c7e0a78a171086a22aeccde772357f0c06bf561955f3898be0009f2f609698ae67154b91af9841d9a4ae7eb8b2e7ec4ec3e47e8221b35e57fe4ac3adb0d2b7 languageName: node linkType: hard @@ -10289,7 +10289,7 @@ __metadata: dependencies: "@babel/core": ~7.22.20 "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-hooks": ~0.32.1 "@rocket.chat/icons": ~0.32.0 "@rocket.chat/mock-providers": "workspace:^" @@ -10329,7 +10329,7 @@ __metadata: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" - "@rocket.chat/ui-contexts": 3.0.0 + "@rocket.chat/ui-contexts": 3.0.1 react: ~17.0.2 languageName: unknown linkType: soft @@ -10340,7 +10340,7 @@ __metadata: dependencies: "@babel/core": ~7.22.20 "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/icons": ~0.32.0 "@storybook/addon-actions": ~6.5.16 "@storybook/addon-docs": ~6.5.16 @@ -10429,7 +10429,7 @@ __metadata: resolution: "@rocket.chat/ui-theming@workspace:ee/packages/ui-theming" dependencies: "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-hooks": ~0.32.1 "@rocket.chat/icons": ~0.32.0 "@rocket.chat/ui-contexts": "workspace:~" @@ -10472,7 +10472,7 @@ __metadata: "@rocket.chat/css-in-js": ~0.31.25 "@rocket.chat/emitter": ~0.31.25 "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-hooks": ~0.32.1 "@rocket.chat/icons": ~0.32.0 "@rocket.chat/styled": ~0.31.25 @@ -10500,7 +10500,7 @@ __metadata: "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 3.0.0 + "@rocket.chat/ui-contexts": 3.0.1 react: ^17.0.2 react-dom: ^17.0.2 languageName: unknown @@ -10515,7 +10515,7 @@ __metadata: "@codemirror/tooltip": ^0.19.16 "@lezer/highlight": ^1.1.6 "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": 0.39.0 + "@rocket.chat/fuselage": ^0.41.0 "@rocket.chat/fuselage-hooks": ~0.32.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 "@rocket.chat/fuselage-tokens": ~0.32.0 @@ -10586,7 +10586,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": "*" - "@rocket.chat/ui-contexts": 3.0.0 + "@rocket.chat/ui-contexts": 3.0.1 "@tanstack/react-query": "*" react: "*" react-hook-form: "*"