diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cc271aee793a..534f23be0100 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,11 @@ /packages/* @RocketChat/chat-engine /packages/core-typings/ @RocketChat/chat-engine /packages/rest-typings/ @RocketChat/chat-engine +/packages/ui-contexts/ @RocketChat/frontend /packages/eslint-config/ @RocketChat/chat-engine +/packages/livechat/ @RocketChat/frontend @RocketChat/chat-engine /.vscode/ @RocketChat/chat-engine /.github/ @RocketChat/chat-engine /_templates/ @RocketChat/chat-engine +/apps/meteor/client/ @RocketChat/frontend +/apps/meteor/tests/ @RocketChat/chat-engine diff --git a/.github/no-js-action-config.json b/.github/no-js-action-config.json index 5e76f81ed62b..5dd97fccaf67 100644 --- a/.github/no-js-action-config.json +++ b/.github/no-js-action-config.json @@ -1,5 +1,5 @@ { "added": { - "ignore": ["packages/accounts-linkedin/**/*", "packages/linkedin-oauth/**/*", "tests/cypress/integration/08-resolutions.spec.js", "**/.eslintrc.js", "packages/eslint-config/**"] + "ignore": ["packages/accounts-linkedin/**/*", "packages/linkedin-oauth/**/*", "tests/cypress/integration/08-resolutions.spec.js", "**/.eslintrc.js", "packages/eslint-config/**", "**/babel.config.js"] } } diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 18bc38f0ef3f..ac59246f76db 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -368,7 +368,7 @@ jobs: run: yarn - name: Build micro services - run: yarn build:services + run: yarn build - name: E2E Test API env: @@ -617,14 +617,14 @@ jobs: IMAGE_TAG: check run: | yarn - yarn build:services + yarn build echo "Building Docker image for service: ${{ matrix.service }}:${IMAGE_TAG}" docker build \ --build-arg SERVICE=${{ matrix.service }} \ -t rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} \ - -f ./apps/meteor/ee/server/services/Dockerfile \ + -f ./ee/apps/ddp-streamer/Dockerfile \ . release-versions: @@ -872,14 +872,20 @@ jobs: # first install repo dependencies yarn - yarn build:services + yarn build echo "Building Docker image for service: ${{ matrix.service }}:${IMAGE_TAG}" + if [[ "${{ matrix.service }}" == "ddp-streamer" ]]; then + DOCKERFILE_PATH="./ee/apps/ddp-streamer/Dockerfile" + else + DOCKERFILE_PATH="./apps/meteor/ee/server/services/Dockerfile" + fi + docker build \ --build-arg SERVICE=${{ matrix.service }} \ -t rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} \ - -f ./apps/meteor/ee/server/services/Dockerfile \ + -f ${DOCKERFILE_PATH} \ . docker push rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} diff --git a/LICENSE b/LICENSE index ded5c91ab199..8cce70381c66 100644 --- a/LICENSE +++ b/LICENSE @@ -2,8 +2,9 @@ Copyright (c) 2015-2022 Rocket.Chat Technologies Corp. Portions of this software are licensed as follows: -* All content that resides under the "apps/meteor/ee/" directory of this repository, if - that directory exists, is licensed under the license defined in "apps/meteor/ee/LICENSE". +* All content that resides under the "apps/meteor/ee/" and "ee/" directories + of this repository, if that directory exists, is licensed under the license + defined in "apps/meteor/ee/LICENSE". * All third-party components incorporated into the Rocket.Chat Software are licensed under the original license provided by the owner of the applicable component. diff --git a/_templates/package/new/package.json.ejs.t b/_templates/package/new/package.json.ejs.t index 3a6f8f9411b5..51f678564c51 100644 --- a/_templates/package/new/package.json.ejs.t +++ b/_templates/package/new/package.json.ejs.t @@ -17,7 +17,7 @@ to: packages/<%= name %>/package.json "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", "jest": "jest", - "build": "tsc -p tsconfig.json" + "build": "rm -rf dist && tsc -p tsconfig.json" }, "main": "./dist/index.js", "typings": "./dist/index.d.ts", diff --git a/apps/meteor/.gitignore b/apps/meteor/.gitignore index 43461008a985..f99fb3d2ccd9 100644 --- a/apps/meteor/.gitignore +++ b/apps/meteor/.gitignore @@ -85,3 +85,4 @@ coverage .nyc_output /data tests/e2e/test-failures/ +out.txt diff --git a/apps/meteor/.scripts/start.js b/apps/meteor/.scripts/start.js index b5001b8b7102..a29e7bb1dbf1 100644 --- a/apps/meteor/.scripts/start.js +++ b/apps/meteor/.scripts/start.js @@ -69,6 +69,7 @@ function killAllProcesses(mainExitCode) { } function startProcess(opts) { + console.log('Starting process', opts.name, opts.command, opts.params, opts.options.cwd); const proc = spawn(opts.command, opts.params, opts.options); processes.push(proc); @@ -125,20 +126,25 @@ function startRocketChat() { } async function startMicroservices() { + const waitStart = (resolve) => (message) => { + if (message.toString().match('NetworkBroker started successfully')) { + return resolve(); + } + }; const startService = (name) => { return new Promise((resolve) => { - const waitStart = (message) => { - if (message.toString().match('NetworkBroker started successfully')) { - return resolve(); - } - }; + const cwd = + name === 'ddp-streamer' + ? path.resolve(srcDir, '..', '..', 'ee', 'apps', name, 'dist', 'ee', 'apps', name) + : path.resolve(srcDir, 'ee', 'server', 'services', 'dist', 'ee', 'server', 'services', name); + startProcess({ name: `${name} service`, command: 'node', - params: ['service.js'], - onData: waitStart, + params: [name === 'ddp-streamer' ? 'src/service.js' : 'service.js'], + onData: waitStart(resolve), options: { - cwd: path.resolve(srcDir, 'ee', 'server', 'services', 'dist', 'ee', 'server', 'services', name), + cwd, env: { ...appOptions.env, ...process.env, diff --git a/apps/meteor/app/2fa/server/code/TOTPCheck.ts b/apps/meteor/app/2fa/server/code/TOTPCheck.ts index 8e0a66f6e34c..5f9c85676d4b 100644 --- a/apps/meteor/app/2fa/server/code/TOTPCheck.ts +++ b/apps/meteor/app/2fa/server/code/TOTPCheck.ts @@ -20,6 +20,10 @@ export class TOTPCheck implements ICodeCheck { return false; } + if (!user.services?.totp?.secret) { + return false; + } + return TOTP.verify({ secret: user.services?.totp?.secret, token: code, diff --git a/apps/meteor/app/2fa/server/index.js b/apps/meteor/app/2fa/server/index.ts similarity index 100% rename from apps/meteor/app/2fa/server/index.js rename to apps/meteor/app/2fa/server/index.ts diff --git a/apps/meteor/app/2fa/server/lib/totp.js b/apps/meteor/app/2fa/server/lib/totp.ts similarity index 74% rename from apps/meteor/app/2fa/server/lib/totp.js rename to apps/meteor/app/2fa/server/lib/totp.ts index e662a5fde9f8..ad831ce37029 100644 --- a/apps/meteor/app/2fa/server/lib/totp.js +++ b/apps/meteor/app/2fa/server/lib/totp.ts @@ -2,22 +2,23 @@ import { SHA256 } from 'meteor/sha'; import { Random } from 'meteor/random'; import speakeasy from 'speakeasy'; +// @ts-expect-error import { Users } from '../../../models'; import { settings } from '../../../settings/server'; export const TOTP = { - generateSecret() { + generateSecret(): speakeasy.GeneratedSecret { return speakeasy.generateSecret(); }, - generateOtpauthURL(secret, username) { + generateOtpauthURL(secret: speakeasy.GeneratedSecret, username: string): string { return speakeasy.otpauthURL({ secret: secret.ascii, label: `Rocket.Chat:${username}`, }); }, - verify({ secret, token, backupTokens, userId }) { + verify({ secret, token, backupTokens, userId }: { secret: string; token: string; backupTokens?: string[]; userId?: string }): boolean { // validates a backup code if (token.length === 8 && backupTokens) { const hashedCode = SHA256(token); @@ -34,7 +35,7 @@ export const TOTP = { return false; } - const maxDelta = settings.get('Accounts_TwoFactorAuthentication_MaxDelta'); + const maxDelta = settings.get('Accounts_TwoFactorAuthentication_MaxDelta'); if (maxDelta) { const verifiedDelta = speakeasy.totp.verifyDelta({ secret, @@ -53,7 +54,7 @@ export const TOTP = { }); }, - generateCodes() { + generateCodes(): { codes: string[]; hashedCodes: string[] } { // generate 12 backup codes const codes = []; const hashedCodes = []; diff --git a/apps/meteor/app/2fa/server/loginHandler.js b/apps/meteor/app/2fa/server/loginHandler.ts similarity index 62% rename from apps/meteor/app/2fa/server/loginHandler.js rename to apps/meteor/app/2fa/server/loginHandler.ts index 942abbf143e7..cf17953cd94a 100644 --- a/apps/meteor/app/2fa/server/loginHandler.js +++ b/apps/meteor/app/2fa/server/loginHandler.ts @@ -6,11 +6,20 @@ import { check } from 'meteor/check'; import { callbacks } from '../../../lib/callbacks'; import { checkCodeForUser } from './code/index'; +const isMeteorError = (error: any): error is Meteor.Error => { + return error?.meteorError !== undefined; +}; + +const isCredentialWithError = (credential: any): credential is { error: Error } => { + return credential?.error !== undefined; +}; + Accounts.registerLoginHandler('totp', function (options) { if (!options.totp || !options.totp.code) { return; } + // @ts-expect-error - not sure how to type this yet return Accounts._runLoginHandlers(this, options.totp.login); }); @@ -27,11 +36,15 @@ callbacks.add( return login; } + if (!login.user) { + return login; + } + const { totp } = loginArgs; checkCodeForUser({ user: login.user, - code: totp && totp.code, + code: totp?.code, options: { disablePasswordFallback: true }, }); @@ -41,24 +54,27 @@ callbacks.add( '2fa', ); -const recreateError = (errorDoc) => { - let error; +const copyTo = (from: T, to: T): T => { + Object.getOwnPropertyNames(to).forEach((key) => { + const idx: keyof T = key as keyof T; + to[idx] = from[idx]; + }); - if (errorDoc.meteorError) { - error = new Meteor.Error(); - delete errorDoc.meteorError; - } else { - error = new Error(); + return to; +}; + +const recreateError = (errorDoc: Error | Meteor.Error): Error | Meteor.Error => { + if (isMeteorError(errorDoc)) { + const error = new Meteor.Error(''); + return copyTo(errorDoc, error); } - Object.getOwnPropertyNames(errorDoc).forEach((key) => { - error[key] = errorDoc[key]; - }); - return error; + const error = new Error(); + return copyTo(errorDoc, error); }; -OAuth._retrievePendingCredential = function (key, ...args) { - const credentialSecret = args.length > 0 && args[0] !== undefined ? args[0] : null; +OAuth._retrievePendingCredential = function (key, ...args): string | Error | void { + const credentialSecret = args.length > 0 && args[0] !== undefined ? args[0] : undefined; check(key, String); const pendingCredential = OAuth._pendingCredentials.findOne({ @@ -70,7 +86,7 @@ OAuth._retrievePendingCredential = function (key, ...args) { return; } - if (pendingCredential.credential.error) { + if (isCredentialWithError(pendingCredential.credential)) { OAuth._pendingCredentials.remove({ _id: pendingCredential._id, }); diff --git a/apps/meteor/app/2fa/server/methods/checkCodesRemaining.js b/apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts similarity index 75% rename from apps/meteor/app/2fa/server/methods/checkCodesRemaining.js rename to apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts index 63222c87da75..b320b51751a1 100644 --- a/apps/meteor/app/2fa/server/methods/checkCodesRemaining.js +++ b/apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts @@ -8,6 +8,12 @@ Meteor.methods({ const user = Meteor.user(); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: '2fa:checkCodesRemaining', + }); + } + if (!user.services || !user.services.totp || !user.services.totp.enabled) { throw new Meteor.Error('invalid-totp'); } diff --git a/apps/meteor/app/2fa/server/methods/disable.js b/apps/meteor/app/2fa/server/methods/disable.ts similarity index 58% rename from apps/meteor/app/2fa/server/methods/disable.js rename to apps/meteor/app/2fa/server/methods/disable.ts index fe6e554305dd..ab0f39753b4b 100644 --- a/apps/meteor/app/2fa/server/methods/disable.js +++ b/apps/meteor/app/2fa/server/methods/disable.ts @@ -1,20 +1,27 @@ import { Meteor } from 'meteor/meteor'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { TOTP } from '../lib/totp'; Meteor.methods({ '2fa:disable'(code) { - if (!Meteor.userId()) { + const userId = Meteor.userId(); + if (!userId) { throw new Meteor.Error('not-authorized'); } const user = Meteor.user(); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: '2fa:disable', + }); + } + const verified = TOTP.verify({ secret: user.services.totp.secret, token: code, - userId: Meteor.userId(), + userId, backupTokens: user.services.totp.hashedBackup, }); @@ -22,6 +29,6 @@ Meteor.methods({ return false; } - return Users.disable2FAByUserId(Meteor.userId()); + return Users.disable2FAByUserId(userId); }, }); diff --git a/apps/meteor/app/2fa/server/methods/enable.js b/apps/meteor/app/2fa/server/methods/enable.ts similarity index 53% rename from apps/meteor/app/2fa/server/methods/enable.js rename to apps/meteor/app/2fa/server/methods/enable.ts index ef34662436e6..3c26effb3805 100644 --- a/apps/meteor/app/2fa/server/methods/enable.js +++ b/apps/meteor/app/2fa/server/methods/enable.ts @@ -1,19 +1,26 @@ import { Meteor } from 'meteor/meteor'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { TOTP } from '../lib/totp'; Meteor.methods({ '2fa:enable'() { - if (!Meteor.userId()) { + const userId = Meteor.userId(); + if (!userId) { throw new Meteor.Error('not-authorized'); } const user = Meteor.user(); + if (!user || !user.username) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: '2fa:enable', + }); + } + const secret = TOTP.generateSecret(); - Users.disable2FAAndSetTempSecretByUserId(Meteor.userId(), secret.base32); + Users.disable2FAAndSetTempSecretByUserId(userId, secret.base32); return { secret: secret.base32, diff --git a/apps/meteor/app/2fa/server/methods/regenerateCodes.js b/apps/meteor/app/2fa/server/methods/regenerateCodes.ts similarity index 73% rename from apps/meteor/app/2fa/server/methods/regenerateCodes.js rename to apps/meteor/app/2fa/server/methods/regenerateCodes.ts index bfdc8d955d78..c3a376575294 100644 --- a/apps/meteor/app/2fa/server/methods/regenerateCodes.js +++ b/apps/meteor/app/2fa/server/methods/regenerateCodes.ts @@ -1,15 +1,21 @@ import { Meteor } from 'meteor/meteor'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { TOTP } from '../lib/totp'; Meteor.methods({ '2fa:regenerateCodes'(userToken) { - if (!Meteor.userId()) { + const userId = Meteor.userId(); + if (!userId) { throw new Meteor.Error('not-authorized'); } const user = Meteor.user(); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: '2fa:regenerateCodes', + }); + } if (!user.services || !user.services.totp || !user.services.totp.enabled) { throw new Meteor.Error('invalid-totp'); @@ -18,7 +24,7 @@ Meteor.methods({ const verified = TOTP.verify({ secret: user.services.totp.secret, token: userToken, - userId: Meteor.userId(), + userId, backupTokens: user.services.totp.hashedBackup, }); diff --git a/apps/meteor/app/2fa/server/methods/validateTempToken.js b/apps/meteor/app/2fa/server/methods/validateTempToken.ts similarity index 74% rename from apps/meteor/app/2fa/server/methods/validateTempToken.js rename to apps/meteor/app/2fa/server/methods/validateTempToken.ts index 71565b0d42e3..13429ca5d5b4 100644 --- a/apps/meteor/app/2fa/server/methods/validateTempToken.js +++ b/apps/meteor/app/2fa/server/methods/validateTempToken.ts @@ -1,15 +1,21 @@ import { Meteor } from 'meteor/meteor'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { TOTP } from '../lib/totp'; Meteor.methods({ '2fa:validateTempToken'(userToken) { - if (!Meteor.userId()) { + const userId = Meteor.userId(); + if (!userId) { throw new Meteor.Error('not-authorized'); } const user = Meteor.user(); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: '2fa:validateTempToken', + }); + } if (!user.services || !user.services.totp || !user.services.totp.tempSecret) { throw new Meteor.Error('invalid-totp'); diff --git a/apps/meteor/app/action-links/client/index.js b/apps/meteor/app/action-links/client/index.ts similarity index 100% rename from apps/meteor/app/action-links/client/index.js rename to apps/meteor/app/action-links/client/index.ts diff --git a/apps/meteor/app/action-links/server/actionLinkHandler.js b/apps/meteor/app/action-links/server/actionLinkHandler.ts similarity index 52% rename from apps/meteor/app/action-links/server/actionLinkHandler.js rename to apps/meteor/app/action-links/server/actionLinkHandler.ts index 7ccd1b05775b..331756a8aafe 100644 --- a/apps/meteor/app/action-links/server/actionLinkHandler.js +++ b/apps/meteor/app/action-links/server/actionLinkHandler.ts @@ -11,7 +11,16 @@ Meteor.methods({ const message = actionLinks.getMessage(name, messageId); - const actionLink = message.actionLinks[name]; + if (!message) { + throw new Meteor.Error('error-invalid-message', 'Invalid message', { method: 'actionLinkHandler' }); + } + + // NOTE: based on types (and how FE uses it) this should be the way of doing it + const actionLink = message.actionLinks?.find((action) => action.method_id === name); + + if (!actionLink) { + throw new Meteor.Error('error-invalid-actionlink', 'Invalid action link', { method: 'actionLinkHandler' }); + } actionLinks.actions[actionLink.method_id](message, actionLink.params); }, diff --git a/apps/meteor/app/action-links/server/index.js b/apps/meteor/app/action-links/server/index.ts similarity index 100% rename from apps/meteor/app/action-links/server/index.js rename to apps/meteor/app/action-links/server/index.ts diff --git a/apps/meteor/app/action-links/server/lib/actionLinks.js b/apps/meteor/app/action-links/server/lib/actionLinks.ts similarity index 53% rename from apps/meteor/app/action-links/server/lib/actionLinks.js rename to apps/meteor/app/action-links/server/lib/actionLinks.ts index f04553d6656c..634e5a54de9f 100644 --- a/apps/meteor/app/action-links/server/lib/actionLinks.js +++ b/apps/meteor/app/action-links/server/lib/actionLinks.ts @@ -1,10 +1,15 @@ +import { IMessage } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import { getMessageForUser } from '../../../../server/lib/messages/getMessageForUser'; -function getMessageById(messageId) { +function getMessageById(messageId: IMessage['_id']): IMessage | undefined { try { - return Promise.await(getMessageForUser(messageId, Meteor.userId())); + const user = Meteor.userId(); + if (!user) { + return; + } + return Promise.await(getMessageForUser(messageId, user)); } catch (e) { throw new Meteor.Error(e.message, 'Invalid message', { function: 'actionLinks.getMessage', @@ -12,13 +17,15 @@ function getMessageById(messageId) { } } +type ActionLinkHandler = (message: IMessage, params?: string, instance?: undefined) => void; + // Action Links namespace creation. export const actionLinks = { - actions: {}, - register(name, funct) { + actions: {} as { [key: string]: ActionLinkHandler }, + register(name: string, funct: ActionLinkHandler): void { actionLinks.actions[name] = funct; }, - getMessage(name, messageId) { + getMessage(name: string, messageId: IMessage['_id']): IMessage | undefined { const message = getMessageById(messageId); if (!message) { @@ -27,7 +34,7 @@ export const actionLinks = { }); } - if (!message.actionLinks || !message.actionLinks[name]) { + if (!message.actionLinks?.some((action) => action.method_id === name)) { throw new Meteor.Error('error-invalid-actionlink', 'Invalid action link', { function: 'actionLinks.getMessage', }); diff --git a/apps/meteor/app/api/server/api.d.ts b/apps/meteor/app/api/server/api.d.ts index 80f718a5da09..a868663634e8 100644 --- a/apps/meteor/app/api/server/api.d.ts +++ b/apps/meteor/app/api/server/api.d.ts @@ -8,6 +8,7 @@ import type { UrlParams, } from '@rocket.chat/rest-typings'; import type { IUser, IMethodConnection } from '@rocket.chat/core-typings'; +import type { ValidateFunction } from 'ajv'; import { ITwoFactorOptions } from '../../2fa/server/code'; @@ -54,7 +55,7 @@ export type NonEnterpriseTwoFactorOptions = { twoFactorOptions: ITwoFactorOptions; }; -type Options = +type Options = ( | { permissionsRequired?: string[]; authRequired?: boolean; @@ -64,7 +65,10 @@ type Options = authRequired: true; twoFactorRequired: true; twoFactorOptions?: ITwoFactorOptions; - }; + } +) & { + validateParams?: ValidateFunction; +}; type Request = { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; @@ -80,11 +84,20 @@ type PartialThis = { type ActionThis = { urlParams: UrlParams; // TODO make it unsafe - readonly queryParams: TMethod extends 'GET' ? Partial> : Record; + readonly queryParams: TMethod extends 'GET' + ? TOptions extends { validateParams: ValidateFunction } + ? T + : Partial> + : Record; // TODO make it unsafe - readonly bodyParams: TMethod extends 'GET' ? Record : Partial>; + readonly bodyParams: TMethod extends 'GET' + ? Record + : TOptions extends { validateParams: ValidateFunction } + ? T + : Partial>; readonly request: Request; requestParams(): OperationParams; + getLoggedInUser(): IUser | undefined; getPaginationItems(): { readonly offset: number; readonly count: number; @@ -94,6 +107,7 @@ type ActionThis; query: Record; }; + /* @deprecated */ getUserFromParams(): IUser; } & (TOptions extends { authRequired: true } ? { diff --git a/apps/meteor/app/api/server/api.js b/apps/meteor/app/api/server/api.js index 9d6ddfb11718..e92d061ae338 100644 --- a/apps/meteor/app/api/server/api.js +++ b/apps/meteor/app/api/server/api.js @@ -397,6 +397,9 @@ export class APIClass extends Restivus { try { api.enforceRateLimit(objectForRateLimitMatch, this.request, this.response, this.userId); + if (_options.validateParams && !_options.validateParams(this.request.method === 'GET' ? this.queryParams : this.bodyParams)) { + throw new Meteor.Error('invalid-params', _options.validateParams.errors?.map((error) => error.message).join('\n ')); + } if (shouldVerifyPermissions && (!this.userId || !hasAllPermission(this.userId, _options.permissionsRequired))) { throw new Meteor.Error('error-unauthorized', 'User does not have the permissions required for this action', { permissions: _options.permissionsRequired, diff --git a/apps/meteor/app/api/server/default/info.js b/apps/meteor/app/api/server/default/info.ts similarity index 100% rename from apps/meteor/app/api/server/default/info.js rename to apps/meteor/app/api/server/default/info.ts diff --git a/apps/meteor/app/api/server/index.ts b/apps/meteor/app/api/server/index.ts index ba35c423903c..3e2a60d115cb 100644 --- a/apps/meteor/app/api/server/index.ts +++ b/apps/meteor/app/api/server/index.ts @@ -12,7 +12,8 @@ import './helpers/requestParams'; import './helpers/isWidget'; import './default/info'; import './v1/assets'; -import './v1/channels'; +import './v1/channels.js'; +import './v1/channels.ts'; import './v1/chat'; import './v1/cloud'; import './v1/commands'; diff --git a/apps/meteor/app/api/server/lib/emailInbox.js b/apps/meteor/app/api/server/lib/emailInbox.js deleted file mode 100644 index a874598197e2..000000000000 --- a/apps/meteor/app/api/server/lib/emailInbox.js +++ /dev/null @@ -1,79 +0,0 @@ -import { EmailInbox } from '../../../models/server/raw'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { Users } from '../../../models'; - -export async function findEmailInboxes({ userId, query = {}, pagination: { offset, count, sort } }) { - if (!(await hasPermissionAsync(userId, 'manage-email-inbox'))) { - throw new Error('error-not-allowed'); - } - const cursor = EmailInbox.find(query, { - sort: sort || { name: 1 }, - skip: offset, - limit: count, - }); - - const total = await cursor.count(); - - const emailInboxes = await cursor.toArray(); - - return { - emailInboxes, - count: emailInboxes.length, - offset, - total, - }; -} - -export async function findOneEmailInbox({ userId, _id }) { - if (!(await hasPermissionAsync(userId, 'manage-email-inbox'))) { - throw new Error('error-not-allowed'); - } - return EmailInbox.findOneById(_id); -} - -export async function insertOneOrUpdateEmailInbox(userId, emailInboxParams) { - const { _id, active, name, email, description, senderInfo, department, smtp, imap } = emailInboxParams; - - if (!_id) { - emailInboxParams._createdAt = new Date(); - emailInboxParams._updatedAt = new Date(); - emailInboxParams._createdBy = Users.findOne(userId, { fields: { username: 1 } }); - return EmailInbox.insertOne(emailInboxParams); - } - - const emailInbox = await findOneEmailInbox({ userId, id: _id }); - - if (!emailInbox) { - throw new Error('error-invalid-email-inbox'); - } - - const updateEmailInbox = { - $set: { - active, - name, - email, - description, - senderInfo, - smtp, - imap, - _updatedAt: new Date(), - }, - }; - - if (department === 'All') { - updateEmailInbox.$unset = { - department: 1, - }; - } else { - updateEmailInbox.$set.department = department; - } - - return EmailInbox.updateOne({ _id }, updateEmailInbox); -} - -export async function findOneEmailInboxByEmail({ userId, email }) { - if (!(await hasPermissionAsync(userId, 'manage-email-inbox'))) { - throw new Error('error-not-allowed'); - } - return EmailInbox.findOne({ email }); -} diff --git a/apps/meteor/app/api/server/lib/emailInbox.ts b/apps/meteor/app/api/server/lib/emailInbox.ts new file mode 100644 index 000000000000..c679c3d714ee --- /dev/null +++ b/apps/meteor/app/api/server/lib/emailInbox.ts @@ -0,0 +1,101 @@ +import { IEmailInbox } from '@rocket.chat/core-typings'; +import { InsertOneWriteOpResult, UpdateWriteOpResult, WithId } from 'mongodb'; + +import { EmailInbox } from '../../../models/server/raw'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { Users } from '../../../models/server'; + +export const findEmailInboxes = async ({ + userId, + query = {}, + pagination: { offset, count, sort }, +}: { + userId: string; + query?: {}; + pagination: { + offset: number; + count: number; + sort?: {}; + }; +}): Promise<{ + emailInboxes: IEmailInbox[]; + total: number; + count: number; + offset: number; +}> => { + if (!(await hasPermissionAsync(userId, 'manage-email-inbox'))) { + throw new Error('error-not-allowed'); + } + const cursor = EmailInbox.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const emailInboxes = await cursor.toArray(); + + return { + emailInboxes, + count: emailInboxes.length, + offset, + total, + }; +}; + +export const findOneEmailInbox = async ({ userId, _id }: { userId: string; _id: string }): Promise => { + if (!(await hasPermissionAsync(userId, 'manage-email-inbox'))) { + throw new Error('error-not-allowed'); + } + return EmailInbox.findOneById(_id); +}; +export const insertOneEmailInbox = async ( + userId: string, + emailInboxParams: Pick, +): Promise>> => { + const obj = { + ...emailInboxParams, + _createdAt: new Date(), + _updatedAt: new Date(), + _createdBy: Users.findOne(userId, { fields: { username: 1 } }), + }; + return EmailInbox.insertOne(obj); +}; + +export const updateEmailInbox = async ( + userId: string, + emailInboxParams: Pick, +): Promise> | UpdateWriteOpResult> => { + const { _id, active, name, email, description, senderInfo, department, smtp, imap } = emailInboxParams; + + const emailInbox = await findOneEmailInbox({ userId, _id }); + + if (!emailInbox) { + throw new Error('error-invalid-email-inbox'); + } + + const updateEmailInbox = { + $set: { + active, + name, + email, + description, + senderInfo, + smtp, + imap, + _updatedAt: new Date(), + ...(department !== 'All' && { department }), + }, + ...(department === 'All' && { $unset: { department: 1 as const } }), + }; + + return EmailInbox.updateOne({ _id }, updateEmailInbox); +}; + +export const findOneEmailInboxByEmail = async ({ userId, email }: { userId: string; email: string }): Promise => { + if (!(await hasPermissionAsync(userId, 'manage-email-inbox'))) { + throw new Error('error-not-allowed'); + } + return EmailInbox.findOne({ email }); +}; diff --git a/apps/meteor/app/api/server/v1/channels.js b/apps/meteor/app/api/server/v1/channels.js index 371a2de3d1c2..ce2590fcce3e 100644 --- a/apps/meteor/app/api/server/v1/channels.js +++ b/apps/meteor/app/api/server/v1/channels.js @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Rooms, Subscriptions, Messages, Users } from '../../../models/server'; import { Integrations, Uploads } from '../../../models/server/raw'; -import { canAccessRoom, hasPermission, hasAtLeastOnePermission, hasAllPermission } from '../../../authorization/server'; +import { canAccessRoom, hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { API } from '../api'; @@ -42,24 +42,6 @@ function findChannelByIdOrName({ params, checkedArchived = true, userId }) { return room; } -API.v1.addRoute( - 'channels.addAll', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('addAllUserToRoom', findResult._id, this.bodyParams.activeUsersOnly); - }); - - return API.v1.success({ - channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }), - }); - }, - }, -); - API.v1.addRoute( 'channels.addModerator', { authRequired: true }, @@ -96,22 +78,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.archive', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('archiveRoom', findResult._id); - }); - - return API.v1.success(); - }, - }, -); - API.v1.addRoute( 'channels.close', { authRequired: true }, @@ -301,25 +267,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.delete', - { authRequired: true }, - { - post() { - const room = findChannelByIdOrName({ - params: this.requestParams(), - checkedArchived: false, - }); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('eraseRoom', room._id); - }); - - return API.v1.success(); - }, - }, -); - API.v1.addRoute( 'channels.files', { authRequired: true }, @@ -424,62 +371,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.history', - { authRequired: true }, - { - get() { - const findResult = findChannelByIdOrName({ - params: this.requestParams(), - checkedArchived: false, - }); - - let latestDate = new Date(); - if (this.queryParams.latest) { - latestDate = new Date(this.queryParams.latest); - } - - let oldestDate = undefined; - if (this.queryParams.oldest) { - oldestDate = new Date(this.queryParams.oldest); - } - - const inclusive = this.queryParams.inclusive || false; - - let count = 20; - if (this.queryParams.count) { - count = parseInt(this.queryParams.count); - } - - let offset = 0; - if (this.queryParams.offset) { - offset = parseInt(this.queryParams.offset); - } - - const unreads = this.queryParams.unreads || false; - - const showThreadMessages = this.queryParams.showThreadMessages !== 'false'; - - const result = Meteor.call('getChannelHistory', { - rid: findResult._id, - latest: latestDate, - oldest: oldestDate, - inclusive, - offset, - count, - unreads, - showThreadMessages, - }); - - if (!result) { - return API.v1.unauthorized(); - } - - return API.v1.success(result); - }, - }, -); - API.v1.addRoute( 'channels.info', { authRequired: true }, @@ -509,65 +400,7 @@ API.v1.addRoute( return API.v1.failure('invalid-user-invite-list', 'Cannot invite if no users are provided'); } - Meteor.runAsUser(this.userId, () => { - Meteor.call('addUsersToRoom', { rid: findResult._id, users: users.map((u) => u.username) }); - }); - - return API.v1.success({ - channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }), - }); - }, - }, -); - -API.v1.addRoute( - 'channels.join', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('joinRoom', findResult._id, this.bodyParams.joinCode); - }); - - return API.v1.success({ - channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }), - }); - }, - }, -); - -API.v1.addRoute( - 'channels.kick', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - const user = this.getUserFromParams(); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('removeUserFromRoom', { rid: findResult._id, username: user.username }); - }); - - return API.v1.success({ - channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }), - }); - }, - }, -); - -API.v1.addRoute( - 'channels.leave', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('leaveRoom', findResult._id); - }); + Meteor.call('addUsersToRoom', { rid: findResult._id, users: users.map((u) => u.username) }); return API.v1.success({ channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }), @@ -721,50 +554,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.messages', - { authRequired: true }, - { - get() { - const findResult = findChannelByIdOrName({ - params: this.requestParams(), - checkedArchived: false, - }); - const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); - - const ourQuery = Object.assign({}, query, { rid: findResult._id }); - - // Special check for the permissions - if ( - hasPermission(this.userId, 'view-joined-room') && - !Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId, { fields: { _id: 1 } }) - ) { - return API.v1.unauthorized(); - } - if (!hasPermission(this.userId, 'view-c-room')) { - return API.v1.unauthorized(); - } - - const cursor = Messages.find(ourQuery, { - sort: sort || { ts: -1 }, - skip: offset, - limit: count, - fields, - }); - - const total = cursor.count(); - const messages = cursor.fetch(); - - return API.v1.success({ - messages: normalizeMessagesForUser(messages, this.userId), - count: messages.length, - offset, - total, - }); - }, - }, -); // TODO: CACHE: I dont like this method( functionality and how we implemented ) its very expensive // TODO check if this code is better or not // RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, { @@ -841,35 +630,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.open', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ - params: this.requestParams(), - checkedArchived: false, - }); - - const sub = Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId); - - if (!sub) { - return API.v1.failure(`The user/callee is not in the channel "${findResult.name}".`); - } - - if (sub.open) { - return API.v1.failure(`The channel, ${findResult.name}, is already open to the sender`); - } - - Meteor.runAsUser(this.userId, () => { - Meteor.call('openRoom', findResult._id); - }); - - return API.v1.success(); - }, - }, -); - API.v1.addRoute( 'channels.removeModerator', { authRequired: true }, @@ -1065,32 +825,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.setReadOnly', - { authRequired: true }, - { - post() { - if (typeof this.bodyParams.readOnly === 'undefined') { - return API.v1.failure('The bodyParam "readOnly" is required'); - } - - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - if (findResult.ro === this.bodyParams.readOnly) { - return API.v1.failure('The channel read only setting is the same as what it would be changed to.'); - } - - Meteor.runAsUser(this.userId, () => { - Meteor.call('saveRoomSettings', findResult._id, 'readOnly', this.bodyParams.readOnly); - }); - - return API.v1.success({ - channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }), - }); - }, - }, -); - API.v1.addRoute( 'channels.setTopic', { authRequired: true }, @@ -1117,28 +851,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.setAnnouncement', - { authRequired: true }, - { - post() { - if (!this.bodyParams.hasOwnProperty('announcement')) { - return API.v1.failure('The bodyParam "announcement" is required'); - } - - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - Meteor.runAsUser(this.userId, () => { - Meteor.call('saveRoomSettings', findResult._id, 'roomAnnouncement', this.bodyParams.announcement); - }); - - return API.v1.success({ - announcement: this.bodyParams.announcement, - }); - }, - }, -); - API.v1.addRoute( 'channels.setType', { authRequired: true }, @@ -1165,106 +877,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.unarchive', - { authRequired: true }, - { - post() { - const findResult = findChannelByIdOrName({ - params: this.requestParams(), - checkedArchived: false, - }); - - if (!findResult.archived) { - return API.v1.failure(`The channel, ${findResult.name}, is not archived`); - } - - Meteor.runAsUser(this.userId, () => { - Meteor.call('unarchiveRoom', findResult._id); - }); - - return API.v1.success(); - }, - }, -); - -API.v1.addRoute( - 'channels.getAllUserMentionsByChannel', - { authRequired: true }, - { - get() { - const { roomId } = this.requestParams(); - const { offset, count } = this.getPaginationItems(); - const { sort } = this.parseJsonQuery(); - - if (!roomId) { - return API.v1.failure('The request param "roomId" is required'); - } - - const mentions = Meteor.runAsUser(this.userId, () => - Meteor.call('getUserMentionsByChannel', { - roomId, - options: { - sort: sort || { ts: 1 }, - skip: offset, - limit: count, - }, - }), - ); - - const allMentions = Meteor.runAsUser(this.userId, () => - Meteor.call('getUserMentionsByChannel', { - roomId, - options: {}, - }), - ); - - return API.v1.success({ - mentions, - count: mentions.length, - offset, - total: allMentions.length, - }); - }, - }, -); - -API.v1.addRoute( - 'channels.roles', - { authRequired: true }, - { - get() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - const roles = Meteor.runAsUser(this.userId, () => Meteor.call('getRoomRoles', findResult._id)); - - return API.v1.success({ - roles, - }); - }, - }, -); - -API.v1.addRoute( - 'channels.moderators', - { authRequired: true }, - { - get() { - const findResult = findChannelByIdOrName({ params: this.requestParams() }); - - const moderators = Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], { - fields: { u: 1 }, - }) - .fetch() - .map((sub) => sub.u); - - return API.v1.success({ - moderators, - }); - }, - }, -); - API.v1.addRoute( 'channels.addLeader', { authRequired: true }, @@ -1340,55 +952,3 @@ API.v1.addRoute( }, }, ); - -API.v1.addRoute( - 'channels.convertToTeam', - { authRequired: true }, - { - post() { - if (!hasAllPermission(this.userId, ['create-team', 'edit-room'])) { - return API.v1.unauthorized(); - } - - const { channelId, channelName } = this.bodyParams; - - if (!channelId && !channelName) { - return API.v1.failure('The parameter "channelId" or "channelName" is required'); - } - - const room = findChannelByIdOrName({ - params: { - roomId: channelId, - roomName: channelName, - }, - userId: this.userId, - }); - - if (!room) { - return API.v1.failure('Channel not found'); - } - - const subscriptions = Subscriptions.findByRoomId(room._id, { - fields: { 'u._id': 1 }, - }); - - const members = subscriptions.fetch().map((s) => s.u && s.u._id); - - const teamData = { - team: { - name: room.name, - type: room.t === 'c' ? 0 : 1, - }, - members, - room: { - name: room.name, - id: room._id, - }, - }; - - const team = Promise.await(Team.create(this.userId, teamData)); - - return API.v1.success({ team }); - }, - }, -); diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts new file mode 100644 index 000000000000..e1ab4da8e8d9 --- /dev/null +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -0,0 +1,508 @@ +import { Meteor } from 'meteor/meteor'; +import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; +import { + isChannelsAddAllProps, + isChannelsArchiveProps, + isChannelsHistoryProps, + isChannelsUnarchiveProps, + isChannelsRolesProps, + isChannelsJoinProps, + isChannelsKickProps, + isChannelsLeaveProps, + isChannelsMessagesProps, + isChannelsOpenProps, + isChannelsSetAnnouncementProps, + isChannelsGetAllUserMentionsByChannelProps, + isChannelsModeratorsProps, + isChannelsConvertToTeamProps, + isChannelsSetReadOnlyProps, + isChannelsDeleteProps, +} from '@rocket.chat/rest-typings'; + +import { Rooms, Subscriptions, Messages } from '../../../models/server'; +import { hasPermission, hasAllPermission } from '../../../authorization/server'; +import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; +import { API } from '../api'; +import { Team } from '../../../../server/sdk'; + +// Returns the channel IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property +function findChannelByIdOrName({ + params, + checkedArchived = true, + userId, +}: { + params: + | { + roomId: string; + } + | { + roomName: string; + }; + userId?: string; + checkedArchived?: boolean; +}): IRoom { + const fields = { ...API.v1.defaultFieldsToExclude }; + + const room: IRoom = 'roomId' in params ? Rooms.findOneById(params.roomId, { fields }) : Rooms.findOneByName(params.roomName, { fields }); + + if (!room || (room.t !== 'c' && room.t !== 'l')) { + throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any channel'); + } + + if (checkedArchived && room.archived) { + throw new Meteor.Error('error-room-archived', `The channel, ${room.name}, is archived`); + } + if (userId && room.lastMessage) { + const [lastMessage] = normalizeMessagesForUser([room.lastMessage], userId); + room.lastMessage = lastMessage; + } + + return room; +} + +API.v1.addRoute( + 'channels.addAll', + { + authRequired: true, + validateParams: isChannelsAddAllProps, + }, + { + post() { + const { activeUsersOnly, ...params } = this.bodyParams; + const findResult = findChannelByIdOrName({ params, userId: this.userId }); + + Meteor.call('addAllUserToRoom', findResult._id, activeUsersOnly); + + return API.v1.success({ + channel: findChannelByIdOrName({ params, userId: this.userId }), + }); + }, + }, +); + +API.v1.addRoute( + 'channels.archive', + { + authRequired: true, + validateParams: isChannelsArchiveProps, + }, + { + post() { + const findResult = findChannelByIdOrName({ params: this.bodyParams }); + + Meteor.call('archiveRoom', findResult._id); + + return API.v1.success(); + }, + }, +); + +API.v1.addRoute( + 'channels.unarchive', + { + authRequired: true, + validateParams: isChannelsUnarchiveProps, + }, + { + post() { + const findResult = findChannelByIdOrName({ + params: this.bodyParams, + checkedArchived: false, + }); + + if (!findResult.archived) { + return API.v1.failure(`The channel, ${findResult.name}, is not archived`); + } + + Meteor.call('unarchiveRoom', findResult._id); + + return API.v1.success(); + }, + }, +); + +API.v1.addRoute( + 'channels.history', + { + authRequired: true, + validateParams: isChannelsHistoryProps, + }, + { + get() { + const { roomId, unreads, oldest, latest, showThreadMessages, inclusive } = this.queryParams; + const findResult = findChannelByIdOrName({ + params: { roomId }, + checkedArchived: false, + }); + + const { count = 20, offset = 0 } = this.getPaginationItems(); + + const result = Meteor.call('getChannelHistory', { + rid: findResult._id, + latest: latest ? new Date(latest) : new Date(), + oldest: oldest && new Date(oldest), + inclusive: inclusive === 'true', + offset, + count, + unreads: unreads === 'true', + showThreadMessages: showThreadMessages === 'true', + }); + + if (!result) { + return API.v1.unauthorized(); + } + + return API.v1.success(result); + }, + }, +); + +API.v1.addRoute( + 'channels.roles', + { + authRequired: true, + validateParams: isChannelsRolesProps, + }, + { + get() { + const findResult = findChannelByIdOrName({ params: this.queryParams }); + + const roles = Meteor.call('getRoomRoles', findResult._id); + + return API.v1.success({ + roles, + }); + }, + }, +); + +API.v1.addRoute( + 'channels.join', + { + authRequired: true, + validateParams: isChannelsJoinProps, + }, + { + post() { + const { roomId, joinCode } = this.bodyParams; + const findResult = findChannelByIdOrName({ params: { roomId } }); + + Meteor.call('joinRoom', findResult._id, joinCode); + + return API.v1.success({ + channel: findChannelByIdOrName({ params: { roomId }, userId: this.userId }), + }); + }, + }, +); + +API.v1.addRoute( + 'channels.kick', + { + authRequired: true, + validateParams: isChannelsKickProps, + }, + { + post() { + const { roomId /* userId */ } = this.bodyParams; + const findResult = findChannelByIdOrName({ params: { roomId } }); + + const user = this.getUserFromParams(); + + Meteor.call('removeUserFromRoom', { rid: findResult._id, username: user.username }); + + return API.v1.success({ + channel: findChannelByIdOrName({ params: { roomId }, userId: this.userId }), + }); + }, + }, +); + +API.v1.addRoute( + 'channels.leave', + { + authRequired: true, + validateParams: isChannelsLeaveProps, + }, + { + post() { + const { roomId } = this.bodyParams; + const findResult = findChannelByIdOrName({ params: { roomId } }); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('leaveRoom', findResult._id); + }); + + return API.v1.success({ + channel: findChannelByIdOrName({ params: { roomId }, userId: this.userId }), + }); + }, + }, +); + +API.v1.addRoute( + 'channels.messages', + { + authRequired: true, + validateParams: isChannelsMessagesProps, + }, + { + get() { + const { roomId } = this.queryParams; + const findResult = findChannelByIdOrName({ + params: { roomId }, + checkedArchived: false, + }); + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + const ourQuery = { ...query, rid: findResult._id }; + + // Special check for the permissions + if ( + hasPermission(this.userId, 'view-joined-room') && + !Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId, { fields: { _id: 1 } }) + ) { + return API.v1.unauthorized(); + } + if (!hasPermission(this.userId, 'view-c-room')) { + return API.v1.unauthorized(); + } + + const cursor = Messages.find(ourQuery, { + sort: sort || { ts: -1 }, + skip: offset, + limit: count, + fields, + }); + + const total = cursor.count(); + const messages = cursor.fetch(); + + return API.v1.success({ + messages: normalizeMessagesForUser(messages, this.userId), + count: messages.length, + offset, + total, + }); + }, + }, +); + +API.v1.addRoute( + 'channels.open', + { + authRequired: true, + validateParams: isChannelsOpenProps, + }, + { + post() { + const { roomId } = this.bodyParams; + + const findResult = findChannelByIdOrName({ + params: { roomId }, + checkedArchived: false, + }); + + const sub = Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId); + + if (!sub) { + return API.v1.failure(`The user/callee is not in the channel "${findResult.name}".`); + } + + if (sub.open) { + return API.v1.failure(`The channel, ${findResult.name}, is already open to the sender`); + } + + Meteor.call('openRoom', findResult._id); + + return API.v1.success(); + }, + }, +); + +API.v1.addRoute( + 'channels.setReadOnly', + { + authRequired: true, + validateParams: isChannelsSetReadOnlyProps, + }, + { + post() { + const { roomId } = this.bodyParams; + + const findResult = findChannelByIdOrName({ params: { roomId } }); + + if (findResult.ro === this.bodyParams.readOnly) { + return API.v1.failure('The channel read only setting is the same as what it would be changed to.'); + } + + Meteor.call('saveRoomSettings', findResult._id, 'readOnly', this.bodyParams.readOnly); + + return API.v1.success({ + channel: findChannelByIdOrName({ params: { roomId }, userId: this.userId }), + }); + }, + }, +); + +API.v1.addRoute( + 'channels.setAnnouncement', + { + authRequired: true, + validateParams: isChannelsSetAnnouncementProps, + }, + { + post() { + const { roomId, announcement } = this.bodyParams; + + const findResult = findChannelByIdOrName({ params: { roomId } }); + + Meteor.call('saveRoomSettings', findResult._id, 'roomAnnouncement', announcement); + + return API.v1.success({ + announcement: this.bodyParams.announcement, + }); + }, + }, +); + +API.v1.addRoute( + 'channels.getAllUserMentionsByChannel', + { + authRequired: true, + validateParams: isChannelsGetAllUserMentionsByChannelProps, + }, + { + get() { + const { roomId } = this.queryParams; + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + + const mentions = Meteor.runAsUser(this.userId, () => + Meteor.call('getUserMentionsByChannel', { + roomId, + options: { + sort: sort || { ts: 1 }, + skip: offset, + limit: count, + }, + }), + ); + + const allMentions = Meteor.runAsUser(this.userId, () => + Meteor.call('getUserMentionsByChannel', { + roomId, + options: {}, + }), + ); + + return API.v1.success({ + mentions, + count: mentions.length, + offset, + total: allMentions.length, + }); + }, + }, +); + +API.v1.addRoute( + 'channels.moderators', + { + authRequired: true, + validateParams: isChannelsModeratorsProps, + }, + { + get() { + const { roomId } = this.queryParams; + + const findResult = findChannelByIdOrName({ params: { roomId } }); + + const moderators = Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], { + fields: { u: 1 }, + }) + .fetch() + .map((sub: ISubscription) => sub.u); + + return API.v1.success({ + moderators, + }); + }, + }, +); + +API.v1.addRoute( + 'channels.delete', + { + authRequired: true, + validateParams: isChannelsDeleteProps, + }, + { + post() { + const room = findChannelByIdOrName({ + params: this.bodyParams, + checkedArchived: false, + }); + + Meteor.call('eraseRoom', room._id); + + return API.v1.success(); + }, + }, +); + +API.v1.addRoute( + 'channels.convertToTeam', + { + authRequired: true, + validateParams: isChannelsConvertToTeamProps, + }, + { + async post() { + if (!hasAllPermission(this.userId, ['create-team', 'edit-room'])) { + return API.v1.unauthorized(); + } + + const { channelId, channelName } = this.bodyParams; + + if (!channelId && !channelName) { + return API.v1.failure('The parameter "channelId" or "channelName" is required'); + } + + const room = findChannelByIdOrName({ + params: { + roomId: channelId, + roomName: channelName, + }, + userId: this.userId, + }); + + if (!room) { + return API.v1.failure('Channel not found'); + } + + const subscriptions = Subscriptions.findByRoomId(room._id, { + fields: { 'u._id': 1 }, + }); + + const members = subscriptions.fetch().map((s: ISubscription) => s.u && s.u._id); + + const teamData = { + team: { + name: room.name ?? '', + type: room.t === 'c' ? 0 : 1, + }, + members, + room: { + name: room.name, + id: room._id, + }, + }; + + const team = await Team.create(this.userId, teamData); + + return API.v1.success({ team }); + }, + }, +); diff --git a/apps/meteor/app/api/server/v1/email-inbox.js b/apps/meteor/app/api/server/v1/email-inbox.ts similarity index 68% rename from apps/meteor/app/api/server/v1/email-inbox.js rename to apps/meteor/app/api/server/v1/email-inbox.ts index e1ad7560d401..1e6392a8aad0 100644 --- a/apps/meteor/app/api/server/v1/email-inbox.js +++ b/apps/meteor/app/api/server/v1/email-inbox.ts @@ -1,7 +1,7 @@ import { check, Match } from 'meteor/check'; import { API } from '../api'; -import { findEmailInboxes, findOneEmailInbox, insertOneOrUpdateEmailInbox } from '../lib/emailInbox'; +import { insertOneEmailInbox, findEmailInboxes, findOneEmailInbox, updateEmailInbox } from '../lib/emailInbox'; import { hasPermission } from '../../../authorization/server/functions/hasPermission'; import { EmailInbox } from '../../../models/server/raw'; import Users from '../../../models/server/models/Users'; @@ -11,10 +11,10 @@ API.v1.addRoute( 'email-inbox.list', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { sort, query } = this.parseJsonQuery(); - const emailInboxes = Promise.await(findEmailInboxes({ userId: this.userId, query, pagination: { offset, count, sort } })); + const emailInboxes = await findEmailInboxes({ userId: this.userId, query, pagination: { offset, count, sort } }); return API.v1.success(emailInboxes); }, @@ -25,40 +25,45 @@ API.v1.addRoute( 'email-inbox', { authRequired: true }, { - post() { + async post() { if (!hasPermission(this.userId, 'manage-email-inbox')) { throw new Error('error-not-allowed'); } check(this.bodyParams, { _id: Match.Maybe(String), + active: Boolean, name: String, email: String, - active: Boolean, - description: Match.Maybe(String), - senderInfo: Match.Maybe(String), - department: Match.Maybe(String), + description: String, + senderInfo: String, + department: String, smtp: Match.ObjectIncluding({ - password: String, - port: Number, - secure: Boolean, server: String, + port: Number, username: String, - }), - imap: Match.ObjectIncluding({ password: String, - port: Number, secure: Boolean, + }), + imap: Match.ObjectIncluding({ server: String, + port: Number, username: String, + password: String, + secure: Boolean, }), }); const emailInboxParams = this.bodyParams; - const { _id } = emailInboxParams; - - Promise.await(insertOneOrUpdateEmailInbox(this.userId, emailInboxParams)); + let _id: string; + if (!emailInboxParams?._id) { + const emailInbox = await insertOneEmailInbox(this.userId, emailInboxParams); + _id = emailInbox.insertedId.toString(); + } else { + _id = emailInboxParams._id; + await updateEmailInbox(this.userId, { ...emailInboxParams, _id }); + } return API.v1.success({ _id }); }, }, @@ -68,7 +73,7 @@ API.v1.addRoute( 'email-inbox/:_id', { authRequired: true }, { - get() { + async get() { check(this.urlParams, { _id: String, }); @@ -77,11 +82,12 @@ API.v1.addRoute( if (!_id) { throw new Error('error-invalid-param'); } - const emailInboxes = Promise.await(findOneEmailInbox({ userId: this.userId, _id })); + // TODO: Chapter day backend - check if user has permission to view this email inbox instead of null values + const emailInboxes = await findOneEmailInbox({ userId: this.userId, _id }); return API.v1.success(emailInboxes); }, - delete() { + async delete() { if (!hasPermission(this.userId, 'manage-email-inbox')) { throw new Error('error-not-allowed'); } @@ -94,12 +100,12 @@ API.v1.addRoute( throw new Error('error-invalid-param'); } - const emailInboxes = Promise.await(EmailInbox.findOneById(_id)); + const emailInboxes = await EmailInbox.findOneById(_id); if (!emailInboxes) { return API.v1.notFound(); } - Promise.await(EmailInbox.removeById(_id)); + await EmailInbox.removeById(_id); return API.v1.success({ _id }); }, }, @@ -109,7 +115,7 @@ API.v1.addRoute( 'email-inbox.search', { authRequired: true }, { - get() { + async get() { if (!hasPermission(this.userId, 'manage-email-inbox')) { throw new Error('error-not-allowed'); } @@ -118,7 +124,9 @@ API.v1.addRoute( }); const { email } = this.queryParams; - const emailInbox = Promise.await(EmailInbox.findOne({ email })); + + // TODO: Chapter day backend - check if user has permission to view this email inbox instead of null values + const emailInbox = await EmailInbox.findOne({ email }); return API.v1.success({ emailInbox }); }, @@ -129,7 +137,7 @@ API.v1.addRoute( 'email-inbox.send-test/:_id', { authRequired: true }, { - post() { + async post() { if (!hasPermission(this.userId, 'manage-email-inbox')) { throw new Error('error-not-allowed'); } @@ -141,7 +149,7 @@ API.v1.addRoute( if (!_id) { throw new Error('error-invalid-param'); } - const emailInbox = Promise.await(findOneEmailInbox({ userId: this.userId, _id })); + const emailInbox = await findOneEmailInbox({ userId: this.userId, _id }); if (!emailInbox) { return API.v1.notFound(); @@ -149,7 +157,7 @@ API.v1.addRoute( const user = Users.findOneById(this.userId); - Promise.await(sendTestEmailToInbox(emailInbox, user)); + await sendTestEmailToInbox(emailInbox, user); return API.v1.success({ _id }); }, diff --git a/apps/meteor/app/api/server/v1/push.js b/apps/meteor/app/api/server/v1/push.ts similarity index 82% rename from apps/meteor/app/api/server/v1/push.js rename to apps/meteor/app/api/server/v1/push.ts index 293fd96c5811..1dc649cb5adf 100644 --- a/apps/meteor/app/api/server/v1/push.js +++ b/apps/meteor/app/api/server/v1/push.ts @@ -6,22 +6,22 @@ import { appTokensCollection } from '../../../push/server'; import { API } from '../api'; import PushNotification from '../../../push-notifications/server/lib/PushNotification'; import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom'; -import { Users, Messages, Rooms } from '../../../models/server'; +import { Users, Rooms } from '../../../models/server'; +import { Messages } from '../../../models/server/raw'; API.v1.addRoute( 'push.token', { authRequired: true }, { post() { - const { type, value, appName } = this.bodyParams; - let { id } = this.bodyParams; + const { id, type, value, appName } = this.bodyParams; if (id && typeof id !== 'string') { throw new Meteor.Error('error-id-param-not-valid', 'The required "id" body param is invalid.'); - } else { - id = Random.id(); } + const deviceId = id || Random.id(); + if (!type || (type !== 'apn' && type !== 'gcm')) { throw new Meteor.Error('error-type-param-not-valid', 'The required "type" body param is missing or invalid.'); } @@ -34,15 +34,14 @@ API.v1.addRoute( throw new Meteor.Error('error-appName-param-not-valid', 'The required "appName" body param is missing or invalid.'); } - let result; - Meteor.runAsUser(this.userId, () => { - result = Meteor.call('raix:push-update', { - id, + const result = Meteor.runAsUser(this.userId, () => + Meteor.call('raix:push-update', { + id: deviceId, token: { [type]: value }, appName, userId: this.userId, - }); - }); + }), + ); return API.v1.success({ result }); }, @@ -78,7 +77,7 @@ API.v1.addRoute( 'push.get', { authRequired: true }, { - get() { + async get() { const params = this.requestParams(); check( params, @@ -92,7 +91,7 @@ API.v1.addRoute( throw new Error('error-user-not-found'); } - const message = Messages.findOneById(params.id); + const message = await Messages.findOneById(params.id); if (!message) { throw new Error('error-message-not-found'); } @@ -106,7 +105,7 @@ API.v1.addRoute( throw new Error('error-not-allowed'); } - const data = PushNotification.getNotificationForMessageId({ receiver, room, message }); + const data = await PushNotification.getNotificationForMessageId({ receiver, room, message }); return API.v1.success({ data }); }, diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts index 112cbe2f263d..157f1ac6d894 100644 --- a/apps/meteor/app/api/server/v1/teams.ts +++ b/apps/meteor/app/api/server/v1/teams.ts @@ -1,4 +1,4 @@ -import { FilterQuery } from 'mongodb'; +import type { FilterQuery } from 'mongodb'; import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { escapeRegExp } from '@rocket.chat/string-helpers'; @@ -115,13 +115,12 @@ const getTeamByIdOrName = async (params: { teamId: string } | { teamName: string API.v1.addRoute( 'teams.convertToChannel', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsConvertToChannelProps, + }, { async post() { - if (!isTeamsConvertToChannelProps(this.bodyParams)) { - return API.v1.failure('invalid-body-params', isTeamsConvertToChannelProps.errors?.map((e) => e.message).join('\n ')); - } - const { roomsToRemove = [] } = this.bodyParams; const team = await getTeamByIdOrName(this.bodyParams); @@ -197,13 +196,12 @@ API.v1.addRoute( API.v1.addRoute( 'teams.removeRoom', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsRemoveRoomProps, + }, { async post() { - if (!isTeamsRemoveRoomProps(this.bodyParams)) { - return API.v1.failure('body-params-invalid', isTeamsRemoveRoomProps.errors?.map((error) => error.message).join('\n ')); - } - const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); @@ -431,13 +429,12 @@ API.v1.addRoute( API.v1.addRoute( 'teams.addMembers', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsAddMembersProps, + }, { async post() { - if (!isTeamsAddMembersProps(this.bodyParams)) { - return API.v1.failure('invalid-params'); - } - const { bodyParams } = this; const { members } = bodyParams; @@ -459,13 +456,12 @@ API.v1.addRoute( API.v1.addRoute( 'teams.updateMember', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsUpdateMemberProps, + }, { async post() { - if (!isTeamsUpdateMemberProps(this.bodyParams)) { - return API.v1.failure('invalid-params', isTeamsUpdateMemberProps.errors?.map((e) => e.message).join('\n ')); - } - const { bodyParams } = this; const { member } = bodyParams; @@ -487,13 +483,12 @@ API.v1.addRoute( API.v1.addRoute( 'teams.removeMember', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsRemoveMemberProps, + }, { async post() { - if (!isTeamsRemoveMemberProps(this.bodyParams)) { - return API.v1.failure('invalid-params', isTeamsRemoveMemberProps.errors?.map((e) => e.message).join('\n ')); - } - const { bodyParams } = this; const { userId, rooms } = bodyParams; @@ -533,13 +528,12 @@ API.v1.addRoute( API.v1.addRoute( 'teams.leave', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsLeaveProps, + }, { async post() { - if (!isTeamsLeaveProps(this.bodyParams)) { - return API.v1.failure('invalid-params', isTeamsLeaveProps.errors?.map((e) => e.message).join('\n ')); - } - const { rooms = [] } = this.bodyParams; const team = await getTeamByIdOrName(this.bodyParams); @@ -592,15 +586,14 @@ API.v1.addRoute( API.v1.addRoute( 'teams.delete', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsDeleteProps, + }, { async post() { const { roomsToRemove = [] } = this.bodyParams; - if (!isTeamsDeleteProps(this.bodyParams)) { - return API.v1.failure('invalid-params', isTeamsDeleteProps.errors?.map((e) => e.message).join('\n ')); - } - const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); @@ -659,13 +652,12 @@ API.v1.addRoute( API.v1.addRoute( 'teams.update', - { authRequired: true }, + { + authRequired: true, + validateParams: isTeamsUpdateProps, + }, { async post() { - if (!isTeamsUpdateProps(this.bodyParams)) { - return API.v1.failure('invalid-params', isTeamsUpdateProps.errors?.map((e) => e.message).join('\n ')); - } - const { data } = this.bodyParams; const team = await getTeamByIdOrName(this.bodyParams); diff --git a/apps/meteor/app/apps/client/orchestrator.js b/apps/meteor/app/apps/client/orchestrator.js index a340fd2a3063..b9194bf5d83e 100644 --- a/apps/meteor/app/apps/client/orchestrator.js +++ b/apps/meteor/app/apps/client/orchestrator.js @@ -160,6 +160,8 @@ class AppClientOrchestrator { return effectiveStatus; }; + screenshots = (appId) => APIClient.get(`apps/${appId}/screenshots`); + enableApp = (appId) => this.setAppStatus(appId, 'manually_enabled'); disableApp = (appId) => this.setAppStatus(appId, 'manually_disabled'); diff --git a/apps/meteor/app/apps/server/communication/rest.js b/apps/meteor/app/apps/server/communication/rest.js index 6dbd2c96ab1f..2bc64e1f8be5 100644 --- a/apps/meteor/app/apps/server/communication/rest.js +++ b/apps/meteor/app/apps/server/communication/rest.js @@ -590,6 +590,29 @@ export class AppsRestApi { }, ); + this.api.addRoute( + ':id/screenshots', + { authRequired: false }, + { + get() { + const baseUrl = orchestrator.getMarketplaceUrl(); + const appId = this.urlParams.id; + const headers = getDefaultHeaders(); + + try { + const { data } = HTTP.get(`${baseUrl}/v1/apps/${appId}/screenshots`, { headers }); + + return API.v1.success({ + screenshots: data, + }); + } catch (e) { + orchestrator.getRocketChatLogger().error('Error getting the screenshots from the Marketplace:', e.message); + return API.v1.failure(e.message); + } + }, + }, + ); + this.api.addRoute( ':id/languages', { authRequired: false }, diff --git a/apps/meteor/app/authentication/server/ILoginAttempt.ts b/apps/meteor/app/authentication/server/ILoginAttempt.ts index 4fc5498dd7ba..f48aeba7d073 100644 --- a/apps/meteor/app/authentication/server/ILoginAttempt.ts +++ b/apps/meteor/app/authentication/server/ILoginAttempt.ts @@ -7,6 +7,12 @@ interface IMethodArgument { algorithm: string; }; resume?: string; + + cas?: boolean; + + totp?: { + code: string; + }; } export interface ILoginAttempt { diff --git a/apps/meteor/app/authorization/client/startup.js b/apps/meteor/app/authorization/client/startup.js index 8bdc2c958e27..7b1643224acd 100644 --- a/apps/meteor/app/authorization/client/startup.js +++ b/apps/meteor/app/authorization/client/startup.js @@ -1,12 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { hasAtLeastOnePermission } from './hasPermission'; import { CachedCollectionManager } from '../../ui-cached-collection'; import { APIClient } from '../../utils/client'; import { Roles } from '../../models/client'; import { rolesStreamer } from './lib/streamer'; -import { registerAdminSidebarItem } from '../../../client/views/admin'; Meteor.startup(() => { CachedCollectionManager.onLogin(async () => { @@ -18,14 +16,6 @@ Meteor.startup(() => { Roles.ready.set(true); }); - registerAdminSidebarItem({ - href: 'admin-permissions', - i18nLabel: 'Permissions', - icon: 'lock', - permissionGranted() { - return hasAtLeastOnePermission(['access-permissions', 'access-setting-permissions']); - }, - }); const events = { changed: (role) => { delete role.type; diff --git a/apps/meteor/app/autotranslate/client/lib/tabBar.ts b/apps/meteor/app/autotranslate/client/lib/tabBar.ts index 2690cdfe67a3..fe564c7bd6b0 100644 --- a/apps/meteor/app/autotranslate/client/lib/tabBar.ts +++ b/apps/meteor/app/autotranslate/client/lib/tabBar.ts @@ -1,8 +1,7 @@ import { lazy, useMemo } from 'react'; +import { useSetting, usePermission } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../../client/views/room/lib/Toolbox'; -import { usePermission } from '../../../../client/contexts/AuthorizationContext'; -import { useSetting } from '../../../../client/contexts/SettingsContext'; addAction('autotranslate', () => { const hasPermission = usePermission('auto-translate'); diff --git a/apps/meteor/app/cors/server/cors.js b/apps/meteor/app/cors/server/cors.js index 5f4e9db2a34b..c4f3c9e7693c 100644 --- a/apps/meteor/app/cors/server/cors.js +++ b/apps/meteor/app/cors/server/cors.js @@ -5,7 +5,7 @@ import { WebApp, WebAppInternals } from 'meteor/webapp'; import _ from 'underscore'; import { settings } from '../../settings/server'; -import { Logger } from '../../logger'; +import { Logger } from '../../logger/server'; const logger = new Logger('CORS'); diff --git a/apps/meteor/app/discussion/client/tabBar.ts b/apps/meteor/app/discussion/client/tabBar.ts index 2c225cca4862..fc5f0934cfd5 100644 --- a/apps/meteor/app/discussion/client/tabBar.ts +++ b/apps/meteor/app/discussion/client/tabBar.ts @@ -1,7 +1,7 @@ import { useMemo, lazy } from 'react'; +import { useSetting } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../client/views/room/lib/Toolbox'; -import { useSetting } from '../../../client/contexts/SettingsContext'; const template = lazy(() => import('../../../client/views/room/contextualBar/Discussions')); diff --git a/apps/meteor/app/e2e/client/tabbar.ts b/apps/meteor/app/e2e/client/tabbar.ts index 6793405b98e8..0d9b5e435c55 100644 --- a/apps/meteor/app/e2e/client/tabbar.ts +++ b/apps/meteor/app/e2e/client/tabbar.ts @@ -1,10 +1,8 @@ import { useMemo, useCallback } from 'react'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useSetting, usePermission, useMethod } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../client/views/room/lib/Toolbox'; -import { useSetting } from '../../../client/contexts/SettingsContext'; -import { usePermission } from '../../../client/contexts/AuthorizationContext'; -import { useMethod } from '../../../client/contexts/ServerContext'; import { useReactiveValue } from '../../../client/hooks/useReactiveValue'; import { e2e } from './rocketchat.e2e'; diff --git a/apps/meteor/app/emoji-custom/client/admin/startup.js b/apps/meteor/app/emoji-custom/client/admin/startup.js deleted file mode 100644 index 4c3054862df6..000000000000 --- a/apps/meteor/app/emoji-custom/client/admin/startup.js +++ /dev/null @@ -1,11 +0,0 @@ -import { registerAdminSidebarItem } from '../../../../client/views/admin'; -import { hasPermission } from '../../../authorization'; - -registerAdminSidebarItem({ - href: 'emoji-custom', - i18nLabel: 'Custom_Emoji', - icon: 'emoji', - permissionGranted() { - return hasPermission('manage-emoji'); - }, -}); diff --git a/apps/meteor/app/emoji-custom/client/index.js b/apps/meteor/app/emoji-custom/client/index.js index 8432b7675b3b..d8a1b75e275d 100644 --- a/apps/meteor/app/emoji-custom/client/index.js +++ b/apps/meteor/app/emoji-custom/client/index.js @@ -1,4 +1,3 @@ import './lib/emojiCustom'; import './notifications/deleteEmojiCustom'; import './notifications/updateEmojiCustom'; -import './admin/startup'; diff --git a/apps/meteor/app/federation-v2/server/bridge.ts b/apps/meteor/app/federation-v2/server/bridge.ts index d02c2b674adc..21457fc56101 100644 --- a/apps/meteor/app/federation-v2/server/bridge.ts +++ b/apps/meteor/app/federation-v2/server/bridge.ts @@ -1,4 +1,4 @@ -import { Bridge, AppServiceRegistration } from '@rocket.chat/forked-matrix-appservice-bridge'; +import { Bridge as MatrixBridge, AppServiceRegistration } from '@rocket.chat/forked-matrix-appservice-bridge'; import { settings } from '../../settings/server'; import { IMatrixEvent } from './definitions/IMatrixEvent'; @@ -6,23 +6,64 @@ import { MatrixEventType } from './definitions/MatrixEventType'; import { addToQueue } from './queue'; import { getRegistrationInfo } from './config'; -export const matrixBridge = new Bridge({ - homeserverUrl: settings.get('Federation_Matrix_homeserver_url'), - domain: settings.get('Federation_Matrix_homeserver_domain'), - registration: AppServiceRegistration.fromObject(getRegistrationInfo()), - disableStores: true, - controller: { - onAliasQuery: (alias, matrixRoomId): void => { - console.log('onAliasQuery', alias, matrixRoomId); - }, - onEvent: async (request /* , context*/): Promise => { - // Get the event - const event = request.getData() as unknown as IMatrixEvent; - - addToQueue(event); - }, - onLog: async (line, isError): Promise => { - console.log(line, isError); - }, - }, -}); +class Bridge { + private bridgeInstance: MatrixBridge; + + private isRunning = false; + + public async start(): Promise { + try { + await this.stop(); + } finally { + this.createInstance(); + if (!this.isRunning) { + await this.bridgeInstance.run(this.getBridgePort()); + this.isRunning = true; + } + } + } + + public async stop(): Promise { + if (!this.isRunning) { + return; + } + // the http server can take some minutes to shutdown and this promise to be resolved + await this.bridgeInstance.close(); + this.isRunning = false; + } + + public getInstance(): MatrixBridge { + return this.bridgeInstance; + } + + private createInstance(): void { + this.bridgeInstance = new MatrixBridge({ + homeserverUrl: settings.get('Federation_Matrix_homeserver_url'), + domain: settings.get('Federation_Matrix_homeserver_domain'), + registration: AppServiceRegistration.fromObject(getRegistrationInfo()), + disableStores: true, + controller: { + onAliasQuery: (alias, matrixRoomId): void => { + console.log('onAliasQuery', alias, matrixRoomId); + }, + onEvent: async (request /* , context*/): Promise => { + // Get the event + const event = request.getData() as unknown as IMatrixEvent; + + addToQueue(event); + }, + onLog: async (line, isError): Promise => { + console.log(line, isError); + }, + }, + }); + } + + private getBridgePort(): number { + const [, , port] = settings.get('Federation_Matrix_bridge_url').split(':'); + + return parseInt(port); + } +} + +export const matrixBridge = new Bridge(); diff --git a/apps/meteor/app/federation-v2/server/index.ts b/apps/meteor/app/federation-v2/server/index.ts index 8577336ac9c6..e331badfd006 100644 --- a/apps/meteor/app/federation-v2/server/index.ts +++ b/apps/meteor/app/federation-v2/server/index.ts @@ -1,2 +1,4 @@ import './settings'; -import './startup'; +import { startBridge } from './startup'; + +startBridge(); diff --git a/apps/meteor/app/federation-v2/server/matrix-client/message.ts b/apps/meteor/app/federation-v2/server/matrix-client/message.ts index ef48ba607ecd..a6a9d8626632 100644 --- a/apps/meteor/app/federation-v2/server/matrix-client/message.ts +++ b/apps/meteor/app/federation-v2/server/matrix-client/message.ts @@ -18,7 +18,7 @@ export const send = async (message: IMessage): Promise => { throw new Error(`Could not find room matrix id for ${message.rid}`); } - const intent = matrixBridge.getIntent(userMatrixId); + const intent = matrixBridge.getInstance().getIntent(userMatrixId); await intent.sendText(roomMatrixId, message.msg || '...not-supported...'); return message; diff --git a/apps/meteor/app/federation-v2/server/matrix-client/room.ts b/apps/meteor/app/federation-v2/server/matrix-client/room.ts index 820e1e77b6cd..21c7a2b00f84 100644 --- a/apps/meteor/app/federation-v2/server/matrix-client/room.ts +++ b/apps/meteor/app/federation-v2/server/matrix-client/room.ts @@ -23,7 +23,7 @@ export const create = async (user: IUser, room: IRoom): Promise matrixUserId.replace('@', ''); +const formatUserIdAsRCUsername = (userId = ''): string => removeUselessCharsFromMatrixId(userId.split(':')[0]); + export const invite = async (inviterId: string, roomId: string, invitedId: string): Promise => { console.log(`[${inviterId}-${invitedId}-${roomId}] Inviting user ${invitedId} to ${roomId}...`); @@ -55,11 +58,13 @@ export const invite = async (inviterId: string, roomId: string, invitedId: strin const invitedUserIsRemote = invitedUserDomain && invitedUserDomain !== settings.get('Federation_Matrix_homeserver_domain'); // Find the invited user in Rocket.Chats users - let invitedUser = Users.findOneByUsername(invitedId.replace('@', '')); + // TODO: this should be refactored asap, since these variable value changes lead us to confusion + let invitedUser = Users.findOneByUsername(removeUselessCharsFromMatrixId(invitedId)); if (!invitedUser) { // Create the invited user - invitedUser = await matrixClient.user.createLocal(invitedUserMatrixId); + const { uid } = await matrixClient.user.createLocal(invitedUserMatrixId); + invitedUser = Users.findOneById(uid); } // If the invited user is not remote, let's ensure it exists remotely @@ -85,14 +90,14 @@ export const invite = async (inviterId: string, roomId: string, invitedId: strin // Invite && Auto-join if the user is Rocket.Chat controlled if (!invitedUserIsRemote) { // Invite the user to the room - await matrixBridge.getIntent(inviterUser.mui).invite(matrixRoomId, invitedUserMatrixId); + await matrixBridge.getInstance().getIntent(inviterUser.mui).invite(matrixRoomId, invitedUserMatrixId); console.log(`[${inviterId}-${invitedId}-${roomId}] Auto-join room...`); - await matrixBridge.getIntent(invitedUserMatrixId).join(matrixRoomId); + await matrixBridge.getInstance().getIntent(invitedUserMatrixId).join(matrixRoomId); } else { // Invite the user to the room but don't wait as this is dependent on the user accepting the invite because we don't control this user - matrixBridge.getIntent(inviterUser.mui).invite(matrixRoomId, invitedUserMatrixId); + matrixBridge.getInstance().getIntent(inviterUser.mui).invite(matrixRoomId, invitedUserMatrixId); } // Add the matrix user to the invited room @@ -104,7 +109,7 @@ export const createRemote = async (u: IUser): Promise => { console.log(`Creating remote user ${matrixUserId}...`); - const intent = matrixBridge.getIntent(matrixUserId); + const intent = matrixBridge.getInstance().getIntent(matrixUserId); await intent.ensureProfile(u.name); @@ -117,10 +122,28 @@ export const createRemote = async (u: IUser): Promise => { return payload; }; +const createLocalUserIfNotExists = async (userId = '', profileInfo: MatrixProfileInfo = {}): Promise => { + const existingUser = await Users.findOneByUsername(formatUserIdAsRCUsername(userId)); + + if (existingUser) { + return existingUser._id; + } + + return Users.create({ + username: removeUselessCharsFromMatrixId(userId), + type: 'user', + status: 'online', + active: true, + roles: ['user'], + name: profileInfo.displayname, + requirePasswordChange: false, + }); +}; + export const createLocal = async (matrixUserId: string): Promise => { console.log(`Creating local user ${matrixUserId}...`); - const intent = matrixBridge.getIntent(matrixUserId); + const intent = matrixBridge.getInstance().getIntent(matrixUserId); let currentProfile: MatrixProfileInfo = {}; @@ -130,16 +153,7 @@ export const createLocal = async (matrixUserId: string): Promise { - if (!isFederationMatrixEnabled()) return; +const watchChanges = (): void => { + settings.watchMultiple( + [ + 'Federation_Matrix_enabled', + 'Federation_Matrix_id', + 'Federation_Matrix_hs_token', + 'Federation_Matrix_as_token', + 'Federation_Matrix_homeserver_url', + 'Federation_Matrix_homeserver_domain', + 'Federation_Matrix_bridge_url', + 'Federation_Matrix_bridge_localpart', + ], + async ([enabled]) => { + setupLogger.info(`Federation Matrix is ${enabled ? 'enabled' : 'disabled'}`); + if (!enabled) { + await matrixBridge.stop(); + return; + } + await matrixBridge.start(); + }, + ); +}; + +export const startBridge = (): void => { + watchChanges(); bridgeLogger.info(`Running Federation V2: - id: ${settings.get('Federation_Matrix_id')} + id: ${settings.get('Federation_Matrix_id')} bridgeUrl: ${settings.get('Federation_Matrix_bridge_url')} - homeserverURL: ${settings.get('Federation_Matrix_homeserver_url')} - homeserverDomain: ${settings.get('Federation_Matrix_homeserver_domain')} + homeserverURL: ${settings.get('Federation_Matrix_homeserver_url')} + homeserverDomain: ${settings.get('Federation_Matrix_homeserver_domain')} `); - - const [, , port] = settings.get('Federation_Matrix_bridge_url').split(':') as bridgeUrlTuple; - - matrixBridge.run(port); - - // TODO: Changes here should re-initialize the bridge instead of needing a restart - // Add settings listeners - settings.watch('Federation_Matrix_enabled', (value) => { - setupLogger.info(`Federation Matrix is ${value ? 'enabled' : 'disabled'}`); - }); -})(); +}; diff --git a/apps/meteor/app/federation-v2/server/tools.ts b/apps/meteor/app/federation-v2/server/tools.ts deleted file mode 100644 index 8e475df2fe73..000000000000 --- a/apps/meteor/app/federation-v2/server/tools.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { settings } from '../../settings/server'; -import { matrixBridge } from './bridge'; - -export const isFederationMatrixEnabled = (): boolean => !!(settings.get('Federation_Matrix_enabled') && matrixBridge); diff --git a/apps/meteor/app/federation/server/startup/settings.ts b/apps/meteor/app/federation/server/startup/settings.ts index 746a2b5e694b..04a9c6a49add 100644 --- a/apps/meteor/app/federation/server/startup/settings.ts +++ b/apps/meteor/app/federation/server/startup/settings.ts @@ -69,7 +69,7 @@ settingsRegistry.addGroup('Federation', function () { const updateSettings = async function (): Promise { // Get the key pair - if (getFederationDiscoveryMethod() === 'hub' && !Promise.await(isRegisteringOrEnabled())) { + if (getFederationDiscoveryMethod() === 'hub' && !(await isRegisteringOrEnabled())) { // Register with hub try { await updateStatus(STATUS_REGISTERING); @@ -89,20 +89,18 @@ const updateSettings = async function (): Promise { }; // Add settings listeners -settings.watch('FEDERATION_Enabled', function enableOrDisable(value) { +settings.watch('FEDERATION_Enabled', async function enableOrDisable(value) { setupLogger.info(`Federation is ${value ? 'enabled' : 'disabled'}`); if (value) { - Promise.await(updateSettings()); + await updateSettings(); enableCallbacks(); } else { - Promise.await(updateStatus(STATUS_DISABLED)); + await updateStatus(STATUS_DISABLED); disableCallbacks(); } - - value && updateSettings(); }); settings.watchMultiple(['FEDERATION_Discovery_Method', 'FEDERATION_Domain'], updateSettings); diff --git a/apps/meteor/app/integrations/client/startup.js b/apps/meteor/app/integrations/client/startup.js deleted file mode 100644 index 58b94ea49df6..000000000000 --- a/apps/meteor/app/integrations/client/startup.js +++ /dev/null @@ -1,15 +0,0 @@ -import { hasAtLeastOnePermission } from '../../authorization'; -import { registerAdminSidebarItem } from '../../../client/views/admin'; - -registerAdminSidebarItem({ - href: 'admin-integrations', - i18nLabel: 'Integrations', - icon: 'code', - permissionGranted: () => - hasAtLeastOnePermission([ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - 'manage-incoming-integrations', - 'manage-own-incoming-integrations', - ]), -}); diff --git a/apps/meteor/app/lib/server/functions/sendMessage.js b/apps/meteor/app/lib/server/functions/sendMessage.js index 37b7f332e896..cb389e13a66c 100644 --- a/apps/meteor/app/lib/server/functions/sendMessage.js +++ b/apps/meteor/app/lib/server/functions/sendMessage.js @@ -1,5 +1,4 @@ import { Match, check } from 'meteor/check'; -import { parser } from '@rocket.chat/message-parser'; import { settings } from '../../../settings'; import { callbacks } from '../../../../lib/callbacks'; @@ -10,9 +9,6 @@ import { FileUpload } from '../../../file-upload/server'; import { hasPermission } from '../../../authorization/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { parseUrlsInMessage } from './parseUrlsInMessage'; -import { isE2EEMessage } from '../../../../lib/isE2EEMessage'; - -const { DISABLE_MESSAGE_PARSER = 'false' } = process.env; /** * IMPORTANT @@ -246,13 +242,6 @@ export const sendMessage = function (user, message, room, upsert = false) { parseUrlsInMessage(message); message = callbacks.run('beforeSaveMessage', message, room); - try { - if (message.msg && DISABLE_MESSAGE_PARSER !== 'true' && !isE2EEMessage(message)) { - message.md = parser(message.msg); - } - } catch (e) { - SystemLogger.error(e); // errors logged while the parser is at experimental stage - } if (message) { if (message._id && upsert) { const { _id } = message; diff --git a/apps/meteor/app/lib/server/functions/updateMessage.ts b/apps/meteor/app/lib/server/functions/updateMessage.ts index ef0602995747..410c7f276f5d 100644 --- a/apps/meteor/app/lib/server/functions/updateMessage.ts +++ b/apps/meteor/app/lib/server/functions/updateMessage.ts @@ -1,16 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { parser } from '@rocket.chat/message-parser'; import type { IMessage, IMessageEdited, IUser } from '@rocket.chat/core-typings'; import { Messages, Rooms } from '../../../models/server'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; -import { SystemLogger } from '../../../../server/lib/logger/system'; import { Apps } from '../../../apps/server'; import { parseUrlsInMessage } from './parseUrlsInMessage'; -import { isE2EEMessage } from '../../../../lib/isE2EEMessage'; - -const { DISABLE_MESSAGE_PARSER = 'false' } = process.env; export const updateMessage = function (message: IMessage, user: IUser, originalMessage?: IMessage): void { if (!originalMessage) { @@ -50,14 +45,6 @@ export const updateMessage = function (message: IMessage, user: IUser, originalM message = callbacks.run('beforeSaveMessage', message); - try { - if (message.msg && DISABLE_MESSAGE_PARSER !== 'true' && !isE2EEMessage(message)) { - message.md = parser(message.msg); - } - } catch (e: unknown) { - SystemLogger.error(String(e)); // errors logged while the parser is at experimental stage - } - const { _id, ...editedMessage } = message; Messages.update({ _id }, { $set: editedMessage }); diff --git a/apps/meteor/app/lib/server/index.js b/apps/meteor/app/lib/server/index.js index bd6b00d2751c..a67ce3fb2f63 100644 --- a/apps/meteor/app/lib/server/index.js +++ b/apps/meteor/app/lib/server/index.js @@ -1,11 +1,3 @@ -import './startup/email'; -import './startup/oAuthServicesUpdate'; -import './startup/rateLimiter'; -import './startup/robots'; -import './startup/settings'; -import './startup/settingsOnLoadCdnPrefix'; -import './startup/settingsOnLoadDirectReply'; -import './startup/settingsOnLoadSMTP'; import '../lib/MessageTypes'; import './lib/bugsnag'; import './lib/debug'; diff --git a/apps/meteor/app/lib/server/startup/index.ts b/apps/meteor/app/lib/server/startup/index.ts new file mode 100644 index 000000000000..06a310bb733f --- /dev/null +++ b/apps/meteor/app/lib/server/startup/index.ts @@ -0,0 +1,8 @@ +import './email'; +import './oAuthServicesUpdate'; +import './rateLimiter'; +import './robots'; +import './settings'; +import './settingsOnLoadCdnPrefix'; +import './settingsOnLoadDirectReply'; +import './settingsOnLoadSMTP'; diff --git a/apps/meteor/app/lib/server/startup/settings.ts b/apps/meteor/app/lib/server/startup/settings.ts index bf3a97cfa14d..b81c4e52d237 100644 --- a/apps/meteor/app/lib/server/startup/settings.ts +++ b/apps/meteor/app/lib/server/startup/settings.ts @@ -9,6 +9,11 @@ settingsRegistry.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), { public: true, }); +settingsRegistry.add('Initial_Channel_Created', false, { + type: 'boolean', + hidden: true, +}); + // When you define a setting and want to add a description, you don't need to automatically define the i18nDescription // if you add a node to the i18n.json with the same setting name but with `_Description` it will automatically work. diff --git a/apps/meteor/app/livechat/client/externalFrame/tabBar.ts b/apps/meteor/app/livechat/client/externalFrame/tabBar.ts index f79b36c521bd..2d5baf5caead 100644 --- a/apps/meteor/app/livechat/client/externalFrame/tabBar.ts +++ b/apps/meteor/app/livechat/client/externalFrame/tabBar.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; +import { useSetting } from '@rocket.chat/ui-contexts'; -import { useSetting } from '../../../../client/contexts/SettingsContext'; import { addAction } from '../../../../client/views/room/lib/Toolbox'; addAction('omnichannel-external-frame', () => { diff --git a/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.js b/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.js index 7990980bf65c..ebc4a018144c 100644 --- a/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.js +++ b/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.js @@ -58,7 +58,7 @@ const getSecondsSinceLastAgentResponse = async (room, agentLastMessage) => { callbacks.add( 'livechat.closeRoom', - async (room) => { + (room) => { const closedByAgent = room.closer !== 'visitor'; const wasTheLastMessageSentByAgent = room.lastMessage && !room.lastMessage.token; if (!closedByAgent || !wasTheLastMessageSentByAgent) { @@ -68,8 +68,10 @@ callbacks.add( if (!agentLastMessage) { return; } - const secondsSinceLastAgentResponse = await getSecondsSinceLastAgentResponse(room, agentLastMessage); + const secondsSinceLastAgentResponse = Promise.await(getSecondsSinceLastAgentResponse(room, agentLastMessage)); LivechatRooms.setVisitorInactivityInSecondsById(room._id, secondsSinceLastAgentResponse); + + return room; }, callbacks.priority.HIGH, 'process-room-abandonment', diff --git a/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts b/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts index 6372de157738..a5c43e7397cb 100644 --- a/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts +++ b/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts @@ -7,7 +7,7 @@ import { RoutingManager } from '../lib/RoutingManager'; callbacks.add( 'afterSaveMessage', - async (message, room) => { + (message, room) => { if (!isOmnichannelRoom(room)) { return message; } @@ -26,7 +26,7 @@ callbacks.add( return message; } - await LivechatInquiry.setLastMessageByRoomId(room._id, message); + Promise.await(LivechatInquiry.setLastMessageByRoomId(room._id, message)); return message; }, diff --git a/apps/meteor/app/livestream/client/tabBar.tsx b/apps/meteor/app/livestream/client/tabBar.tsx index 7305f3a87056..d06e2ee90aa6 100644 --- a/apps/meteor/app/livestream/client/tabBar.tsx +++ b/apps/meteor/app/livestream/client/tabBar.tsx @@ -1,8 +1,7 @@ import React, { ReactNode, useMemo } from 'react'; import { Option, Badge } from '@rocket.chat/fuselage'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; -import { useSetting } from '../../../client/contexts/SettingsContext'; -import { useTranslation } from '../../../client/contexts/TranslationContext'; import { addAction } from '../../../client/views/room/lib/Toolbox'; import Header from '../../../client/components/Header'; diff --git a/apps/meteor/app/mail-messages/client/index.js b/apps/meteor/app/mail-messages/client/index.js deleted file mode 100644 index 8ad6156d106a..000000000000 --- a/apps/meteor/app/mail-messages/client/index.js +++ /dev/null @@ -1 +0,0 @@ -import './startup'; diff --git a/apps/meteor/app/mail-messages/client/startup.js b/apps/meteor/app/mail-messages/client/startup.js deleted file mode 100644 index 6df18c09aa2b..000000000000 --- a/apps/meteor/app/mail-messages/client/startup.js +++ /dev/null @@ -1,9 +0,0 @@ -import { hasAllPermission } from '../../authorization'; -import { registerAdminSidebarItem } from '../../../client/views/admin'; - -registerAdminSidebarItem({ - href: 'admin-mailer', - i18nLabel: 'Mailer', - icon: 'mail', - permissionGranted: () => hasAllPermission('access-mailer'), -}); diff --git a/apps/meteor/app/mail-messages/server/functions/sendMail.js b/apps/meteor/app/mail-messages/server/functions/sendMail.ts similarity index 66% rename from apps/meteor/app/mail-messages/server/functions/sendMail.js rename to apps/meteor/app/mail-messages/server/functions/sendMail.ts index c99eaba655ff..dbce8cc9caf0 100644 --- a/apps/meteor/app/mail-messages/server/functions/sendMail.js +++ b/apps/meteor/app/mail-messages/server/functions/sendMail.ts @@ -2,20 +2,23 @@ import { Meteor } from 'meteor/meteor'; import { EJSON } from 'meteor/ejson'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { escapeHTML } from '@rocket.chat/string-helpers'; +import { FilterQuery } from 'mongodb'; +import { IUser } from '@rocket.chat/core-typings'; import { placeholders } from '../../../utils/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import * as Mailer from '../../../mailer'; -export const sendMail = function (from, subject, body, dryrun, query) { +export const sendMail = function (from: string, subject: string, body: string, dryrun: boolean, query: string): void { Mailer.checkAddressFormatAndThrow(from, 'Mailer.sendMail'); + if (body.indexOf('[unsubscribe]') === -1) { throw new Meteor.Error('error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', { function: 'Mailer.sendMail', }); } - let userQuery = { 'mailer.unsubscribed': { $exists: 0 } }; + let userQuery: FilterQuery = { 'mailer.unsubscribed': { $exists: 0 } }; if (query) { userQuery = { $and: [userQuery, EJSON.parse(query)] }; } @@ -25,13 +28,14 @@ export const sendMail = function (from, subject, body, dryrun, query) { .find({ 'emails.address': from, }) - .forEach((user) => { - const email = `${user.name} <${user.emails[0].address}>`; + .forEach((u): void => { + const user: Partial & Pick = u; + const email = `${user.name} <${user.emails?.[0].address}>`; const html = placeholders.replace(body, { unsubscribe: Meteor.absoluteUrl( FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, - createdAt: user.createdAt.getTime(), + createdAt: user.createdAt?.getTime().toString() || '', }), ), name: user.name, @@ -47,18 +51,20 @@ export const sendMail = function (from, subject, body, dryrun, query) { }); }); } - return Meteor.users.find(userQuery).forEach(function (user) { - if (user && user.emails && Array.isArray(user.emails) && user.emails.length) { + + return Meteor.users.find(userQuery).forEach(function (u) { + const user: Partial & Pick = u; + if (user?.emails && Array.isArray(user.emails) && user.emails.length) { const email = `${user.name} <${user.emails[0].address}>`; const html = placeholders.replace(body, { unsubscribe: Meteor.absoluteUrl( FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, - createdAt: user.createdAt.getTime(), + createdAt: user.createdAt?.getTime().toString() || '', }), ), - name: escapeHTML(user.name), + name: escapeHTML(user.name || ''), email: escapeHTML(email), }); SystemLogger.debug(`Sending email to ${email}`); diff --git a/apps/meteor/app/mail-messages/server/functions/unsubscribe.js b/apps/meteor/app/mail-messages/server/functions/unsubscribe.ts similarity index 82% rename from apps/meteor/app/mail-messages/server/functions/unsubscribe.js rename to apps/meteor/app/mail-messages/server/functions/unsubscribe.ts index 62a9da462672..0415e41773ba 100644 --- a/apps/meteor/app/mail-messages/server/functions/unsubscribe.js +++ b/apps/meteor/app/mail-messages/server/functions/unsubscribe.ts @@ -1,7 +1,7 @@ import { Users } from '../../../models/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; -export const unsubscribe = function (_id, createdAt) { +export const unsubscribe = function (_id: string, createdAt: string): boolean { if (_id && createdAt) { const affectedRows = Users.rocketMailUnsubscribe(_id, createdAt) === 1; diff --git a/apps/meteor/app/mail-messages/server/index.js b/apps/meteor/app/mail-messages/server/index.ts similarity index 100% rename from apps/meteor/app/mail-messages/server/index.js rename to apps/meteor/app/mail-messages/server/index.ts diff --git a/apps/meteor/app/mail-messages/server/lib/Mailer.js b/apps/meteor/app/mail-messages/server/lib/Mailer.ts similarity index 100% rename from apps/meteor/app/mail-messages/server/lib/Mailer.js rename to apps/meteor/app/mail-messages/server/lib/Mailer.ts diff --git a/apps/meteor/app/mail-messages/server/methods/sendMail.js b/apps/meteor/app/mail-messages/server/methods/sendMail.ts similarity index 100% rename from apps/meteor/app/mail-messages/server/methods/sendMail.js rename to apps/meteor/app/mail-messages/server/methods/sendMail.ts diff --git a/apps/meteor/app/mail-messages/server/methods/unsubscribe.js b/apps/meteor/app/mail-messages/server/methods/unsubscribe.ts similarity index 100% rename from apps/meteor/app/mail-messages/server/methods/unsubscribe.js rename to apps/meteor/app/mail-messages/server/methods/unsubscribe.ts diff --git a/apps/meteor/app/mailer/index.js b/apps/meteor/app/mailer/index.ts similarity index 100% rename from apps/meteor/app/mailer/index.js rename to apps/meteor/app/mailer/index.ts diff --git a/apps/meteor/app/mailer/server/api.ts b/apps/meteor/app/mailer/server/api.ts index 73bc8933b15d..ae4634fff0ae 100644 --- a/apps/meteor/app/mailer/server/api.ts +++ b/apps/meteor/app/mailer/server/api.ts @@ -205,7 +205,9 @@ export const send = ({ headers, }); -export const checkAddressFormatAndThrow = (from: string, func: Function): asserts from => { +// Needed because of https://github.com/microsoft/TypeScript/issues/36931 +type Assert = (input: string, func: string) => asserts input; +export const checkAddressFormatAndThrow: Assert = (from: string, func: string): asserts from => { if (checkAddressFormat(from)) { return; } diff --git a/apps/meteor/app/message-pin/client/tabBar.ts b/apps/meteor/app/message-pin/client/tabBar.ts index 9b0f978f45c3..a5b5242d83b2 100644 --- a/apps/meteor/app/message-pin/client/tabBar.ts +++ b/apps/meteor/app/message-pin/client/tabBar.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; +import { useSetting } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../client/views/room/lib/Toolbox'; -import { useSetting } from '../../../client/contexts/SettingsContext'; addAction('pinned-messages', () => { const pinningAllowed = useSetting('Message_AllowPinning'); diff --git a/apps/meteor/app/message-snippet/client/tabBar/tabBar.ts b/apps/meteor/app/message-snippet/client/tabBar/tabBar.ts index 1416a2ef2c2a..6e36a47833a8 100644 --- a/apps/meteor/app/message-snippet/client/tabBar/tabBar.ts +++ b/apps/meteor/app/message-snippet/client/tabBar/tabBar.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; +import { useSetting } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../../client/views/room/lib/Toolbox'; -import { useSetting } from '../../../../client/contexts/SettingsContext'; addAction('snippeted-messages', () => { const snippetingEnabled = useSetting('Message_AllowSnippeting'); diff --git a/apps/meteor/app/models/server/models/Messages.js b/apps/meteor/app/models/server/models/Messages.js index dae73072ba18..ebb91426337e 100644 --- a/apps/meteor/app/models/server/models/Messages.js +++ b/apps/meteor/app/models/server/models/Messages.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import { Base } from './_Base'; import Rooms from './Rooms'; -import { settings } from '../../../settings/server/functions/settings'; +import { settings } from '../../../settings/server'; import { otrSystemMessages } from '../../../otr/lib/constants'; export class Messages extends Base { diff --git a/apps/meteor/app/models/server/models/Users.js b/apps/meteor/app/models/server/models/Users.js index 633fe3188450..0a794af7e32f 100644 --- a/apps/meteor/app/models/server/models/Users.js +++ b/apps/meteor/app/models/server/models/Users.js @@ -6,7 +6,7 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Base } from './_Base'; import Subscriptions from './Subscriptions'; -import { settings } from '../../../settings/server/functions/settings'; +import { settings } from '../../../settings/server'; const queryStatusAgentOnline = (extraFilters = {}) => ({ statusLivechat: 'available', diff --git a/apps/meteor/app/models/server/raw/BaseRaw.ts b/apps/meteor/app/models/server/raw/BaseRaw.ts index a77dca60c353..a1121f34cdb9 100644 --- a/apps/meteor/app/models/server/raw/BaseRaw.ts +++ b/apps/meteor/app/models/server/raw/BaseRaw.ts @@ -85,7 +85,9 @@ export class BaseRaw = undefined> implements IBase const indexes = this.modelIndexes(); if (indexes?.length) { - this.col.createIndexes(indexes); + this.col.createIndexes(indexes).catch((e) => { + console.warn(`Error creating indexes for ${this.name}`, e); + }); } this.preventSetUpdatedAt = options?.preventSetUpdatedAt ?? false; diff --git a/apps/meteor/app/models/server/raw/NpsVote.ts b/apps/meteor/app/models/server/raw/NpsVote.ts index a112b6e8da42..999b96311191 100644 --- a/apps/meteor/app/models/server/raw/NpsVote.ts +++ b/apps/meteor/app/models/server/raw/NpsVote.ts @@ -6,7 +6,7 @@ import { BaseRaw, IndexSpecification } from './BaseRaw'; type T = INpsVote; export class NpsVoteRaw extends BaseRaw { modelIndexes(): IndexSpecification[] { - return [{ key: { npsId: 1, status: 1, sentAt: 1 } }, { key: { npsId: 1, identifier: 1 } }]; + return [{ key: { npsId: 1, status: 1, sentAt: 1 } }, { key: { npsId: 1, identifier: 1 }, unique: true }]; } findNotSentByNpsId(npsId: string, options?: WithoutProjection>): Cursor { diff --git a/apps/meteor/app/models/server/raw/Rooms.js b/apps/meteor/app/models/server/raw/Rooms.js index 1f28fcfcf433..a7e338db5f68 100644 --- a/apps/meteor/app/models/server/raw/Rooms.js +++ b/apps/meteor/app/models/server/raw/Rooms.js @@ -1,3 +1,4 @@ +import { ReadPreference } from 'mongodb'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { BaseRaw } from './BaseRaw'; @@ -300,6 +301,7 @@ export class RoomsRaw extends BaseRaw { } findChannelsWithNumberOfMessagesBetweenDate({ start, end, startOfLastWeek, endOfLastWeek, onlyCount = false, options = {} }) { + const readPreference = ReadPreference.SECONDARY_PREFERRED; const lookup = { $lookup: { from: 'rocketchat_analytics', @@ -403,7 +405,7 @@ export class RoomsRaw extends BaseRaw { params.push({ $limit: options.count }); } - return this.col.aggregate(params); + return this.col.aggregate(params, { allowDiskUse: true, readPreference }); } findOneByName(name, options = {}) { diff --git a/apps/meteor/app/oauth2-server-config/client/admin/startup.js b/apps/meteor/app/oauth2-server-config/client/admin/startup.js deleted file mode 100644 index 39f8266a7b9a..000000000000 --- a/apps/meteor/app/oauth2-server-config/client/admin/startup.js +++ /dev/null @@ -1,11 +0,0 @@ -import { hasAllPermission } from '../../../authorization'; -import { registerAdminSidebarItem } from '../../../../client/views/admin'; - -registerAdminSidebarItem({ - href: 'admin-oauth-apps', - i18nLabel: 'OAuth Apps', - icon: 'discover', - permissionGranted() { - return hasAllPermission('manage-oauth-apps'); - }, -}); diff --git a/apps/meteor/app/oauth2-server-config/client/index.js b/apps/meteor/app/oauth2-server-config/client/index.js index 5911484bf1c4..17dd1b333b86 100644 --- a/apps/meteor/app/oauth2-server-config/client/index.js +++ b/apps/meteor/app/oauth2-server-config/client/index.js @@ -1,4 +1,3 @@ import './oauth/oauth2-client.html'; import './oauth/oauth2-client'; -import './admin/startup'; import './oauth/stylesheets/oauth2.css'; diff --git a/apps/meteor/app/otr/client/tabBar.ts b/apps/meteor/app/otr/client/tabBar.ts index 213aaf78980d..d3234636e344 100644 --- a/apps/meteor/app/otr/client/tabBar.ts +++ b/apps/meteor/app/otr/client/tabBar.ts @@ -1,7 +1,7 @@ import { useMemo, lazy, useEffect } from 'react'; +import { useSetting } from '@rocket.chat/ui-contexts'; import { OTR } from './rocketchat.otr'; -import { useSetting } from '../../../client/contexts/SettingsContext'; import { addAction } from '../../../client/views/room/lib/Toolbox'; const template = lazy(() => import('../../../client/views/room/contextualBar/OTR')); diff --git a/apps/meteor/app/push-notifications/client/tabBar.ts b/apps/meteor/app/push-notifications/client/tabBar.ts index bdbb64a27498..1312018a1cde 100644 --- a/apps/meteor/app/push-notifications/client/tabBar.ts +++ b/apps/meteor/app/push-notifications/client/tabBar.ts @@ -1,6 +1,6 @@ import { lazy, useMemo } from 'react'; +import { useUserSubscription } from '@rocket.chat/ui-contexts'; -import { useUserSubscription } from '../../../client/contexts/UserContext'; import { addAction } from '../../../client/views/room/lib/Toolbox'; addAction('push-notifications', ({ room }) => { diff --git a/apps/meteor/app/push-notifications/server/lib/PushNotification.js b/apps/meteor/app/push-notifications/server/lib/PushNotification.ts similarity index 56% rename from apps/meteor/app/push-notifications/server/lib/PushNotification.js rename to apps/meteor/app/push-notifications/server/lib/PushNotification.ts index c19bba2a31df..50752d7b1d77 100644 --- a/apps/meteor/app/push-notifications/server/lib/PushNotification.js +++ b/apps/meteor/app/push-notifications/server/lib/PushNotification.ts @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { IMessage, IPushNotificationConfig, IRoom, IUser } from '@rocket.chat/core-typings'; import { Push } from '../../../push/server'; import { settings } from '../../../settings/server'; @@ -9,19 +10,62 @@ import { replaceMentionedUsernamesWithFullNames, parseMessageTextPerUser } from import { callbacks } from '../../../../lib/callbacks'; import { getPushData } from '../../../lib/server/functions/notifications/mobile'; +type PushNotificationData = { + rid: string; + uid: string; + mid: string; + roomName: string; + username: string; + message: string; + payload: Record; + badge: number; + category: string; +}; + +type GetNotificationConfigParam = PushNotificationData & { + idOnly: boolean; +}; + +type NotificationPayload = { + message: IMessage; + notification: IPushNotificationConfig; +}; + +function hash(str: string): number { + let hash = 0; + let i = str.length; + + while (i) { + hash = (hash << 5) - hash + str.charCodeAt(--i); + hash &= hash; // Convert to 32bit integer + } + return hash; +} + export class PushNotification { - getNotificationId(roomId) { + getNotificationId(roomId: string): number { const serverId = settings.get('uniqueID'); - return this.hash(`${serverId}|${roomId}`); // hash + return hash(`${serverId}|${roomId}`); // hash } - getNotificationConfig({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category, idOnly = false }) { + private getNotificationConfig({ + rid, + uid: userId, + mid: messageId, + roomName, + username, + message, + payload, + badge = 1, + category, + idOnly = false, + }: GetNotificationConfigParam): IPushNotificationConfig { const title = idOnly ? '' : roomName || username; // message is being redacted already by 'getPushData' if idOnly is true const text = !idOnly && roomName !== '' ? `${username}: ${message}` : message; - const config = { + const config: IPushNotificationConfig = { from: 'push', badge, sound: 'default', @@ -32,7 +76,7 @@ export class PushNotification { host: Meteor.absoluteUrl(), messageId, notificationType: idOnly ? 'message-id-only' : 'message', - ...(idOnly || { rid, ...payload }), + ...(!idOnly && { rid, ...payload }), }, userId, notId: this.getNotificationId(rid), @@ -51,19 +95,8 @@ export class PushNotification { return config; } - hash(str) { - let hash = 0; - let i = str.length; - - while (i) { - hash = (hash << 5) - hash + str.charCodeAt(--i); - hash &= hash; // Convert to 32bit integer - } - return hash; - } - - send({ rid, uid, mid, roomName, username, message, payload, badge = 1, category }) { - const idOnly = settings.get('Push_request_content_from_server'); + send({ rid, uid, mid, roomName, username, message, payload, badge = 1, category }: PushNotificationData): void { + const idOnly = settings.get('Push_request_content_from_server'); const config = this.getNotificationConfig({ rid, uid, @@ -77,34 +110,41 @@ export class PushNotification { idOnly, }); + // eslint-disable-next-line @typescript-eslint/camelcase metrics.notificationsSent.inc({ notification_type: 'mobile' }); - return Push.send(config); + Push.send(config); } - getNotificationForMessageId({ receiver, message, room }) { + async getNotificationForMessageId({ + receiver, + message, + room, + }: { + receiver: IUser; + message: IMessage; + room: IRoom; + }): Promise { const sender = Users.findOne(message.u._id, { fields: { username: 1, name: 1 } }); if (!sender) { throw new Error('Message sender not found'); } let notificationMessage = callbacks.run('beforeSendMessageNotifications', message.msg); - if (message.mentions?.length > 0 && settings.get('UI_Use_Real_Name')) { + if (message.mentions && Object.keys(message.mentions).length > 0 && settings.get('UI_Use_Real_Name')) { notificationMessage = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions); } notificationMessage = parseMessageTextPerUser(notificationMessage, message, receiver); - const pushData = Promise.await( - getPushData({ - room, - message, - userId: receiver._id, - receiver, - senderUsername: sender.username, - senderName: sender.name, - notificationMessage, - shouldOmitMessage: false, - }), - ); + const pushData = await getPushData({ + room, + message, + userId: receiver._id, + receiver, + senderUsername: sender.username, + senderName: sender.name, + notificationMessage, + shouldOmitMessage: false, + }); return { message, diff --git a/apps/meteor/app/settings/server/SettingsRegistry.ts b/apps/meteor/app/settings/server/SettingsRegistry.ts index 86bde0d764f7..d6799596ed9a 100644 --- a/apps/meteor/app/settings/server/SettingsRegistry.ts +++ b/apps/meteor/app/settings/server/SettingsRegistry.ts @@ -163,6 +163,7 @@ export class SettingsRegistry { }), }, ); + return; } diff --git a/apps/meteor/app/settings/server/applyMiddlewares.ts b/apps/meteor/app/settings/server/applyMiddlewares.ts new file mode 100644 index 000000000000..21ee517dc0cc --- /dev/null +++ b/apps/meteor/app/settings/server/applyMiddlewares.ts @@ -0,0 +1,63 @@ +import { Meteor } from 'meteor/meteor'; + +import { use } from './Middleware'; +import { settings } from './cached'; + +const getProcessingTimeInMS = (time: [number, number]): number => time[0] * 1000 + time[1] / 1e6; + +settings.watch = use(settings.watch, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); + +if (process.env.DEBUG_SETTINGS === 'true') { + settings.watch = use(settings.watch, function watch(context, next) { + const [_id, callback, options] = context; + return next( + _id, + (...args) => { + const start = process.hrtime(); + callback(...args); + const elapsed = process.hrtime(start); + console.log(`settings.watch: ${_id} ${getProcessingTimeInMS(elapsed)}ms`); + }, + options, + ); + }); +} +settings.watchMultiple = use(settings.watchMultiple, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); +settings.watchOnce = use(settings.watchOnce, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); + +settings.watchByRegex = use(settings.watchByRegex, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); + +settings.change = use(settings.change, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); +settings.changeMultiple = use(settings.changeMultiple, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); +settings.changeOnce = use(settings.changeOnce, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); + +settings.changeByRegex = use(settings.changeByRegex, (context, next) => { + const [_id, callback, ...args] = context; + return next(_id, Meteor.bindEnvironment(callback), ...args); +}); + +settings.onReady = use(settings.onReady, (context, next) => { + const [callback, ...args] = context; + return next(Meteor.bindEnvironment(callback), ...args); +}); diff --git a/apps/meteor/app/settings/server/cached.ts b/apps/meteor/app/settings/server/cached.ts new file mode 100644 index 000000000000..078bb1c12088 --- /dev/null +++ b/apps/meteor/app/settings/server/cached.ts @@ -0,0 +1,3 @@ +import { CachedSettings } from './CachedSettings'; + +export const settings = new CachedSettings(); diff --git a/apps/meteor/app/settings/server/functions/settings.ts b/apps/meteor/app/settings/server/functions/settings.ts deleted file mode 100644 index d5deac757c0a..000000000000 --- a/apps/meteor/app/settings/server/functions/settings.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ISetting } from '@rocket.chat/core-typings'; - -import SettingsModel from '../../../models/server/models/Settings'; -import { CachedSettings } from '../CachedSettings'; -import { SettingsRegistry } from '../SettingsRegistry'; - -export const settings = new CachedSettings(); -SettingsModel.find().forEach((record: ISetting) => { - settings.set(record); -}); - -settings.initilized(); - -export const settingsRegistry = new SettingsRegistry({ store: settings, model: SettingsModel }); diff --git a/apps/meteor/app/settings/server/index.ts b/apps/meteor/app/settings/server/index.ts index f581b33a1af6..765516219eaa 100644 --- a/apps/meteor/app/settings/server/index.ts +++ b/apps/meteor/app/settings/server/index.ts @@ -1,4 +1,13 @@ -import { settings, settingsRegistry } from './functions/settings'; -import { SettingsEvents } from './SettingsRegistry'; +import SettingsModel from '../../models/server/models/Settings'; +import { SettingsRegistry } from './SettingsRegistry'; +import { initializeSettings } from './startup'; +import { settings } from './cached'; +import './applyMiddlewares'; -export { settings, settingsRegistry, SettingsEvents }; +export { SettingsEvents } from './SettingsRegistry'; + +export { settings }; + +export const settingsRegistry = new SettingsRegistry({ store: settings, model: SettingsModel }); + +initializeSettings({ SettingsModel, settings }); diff --git a/apps/meteor/app/settings/server/startup.ts b/apps/meteor/app/settings/server/startup.ts index 74a71bb9e11e..191f831b4513 100644 --- a/apps/meteor/app/settings/server/startup.ts +++ b/apps/meteor/app/settings/server/startup.ts @@ -1,63 +1,15 @@ -import { Meteor } from 'meteor/meteor'; +import type { ISetting } from '@rocket.chat/core-typings'; -import { use } from './Middleware'; -import { settings } from './functions/settings'; +import { Settings } from '../../models/server/models/Settings'; +import { ICachedSettings } from './CachedSettings'; -const getProcessingTimeInMS = (time: [number, number]): number => time[0] * 1000 + time[1] / 1e6; - -settings.watch = use(settings.watch, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); - -if (process.env.DEBUG_SETTINGS === 'true') { - settings.watch = use(settings.watch, function watch(context, next) { - const [_id, callback, options] = context; - return next( - _id, - (...args) => { - const start = process.hrtime(); - callback(...args); - const elapsed = process.hrtime(start); - console.log(`settings.watch: ${_id} ${getProcessingTimeInMS(elapsed)}ms`); - }, - options, - ); +export function initializeSettings({ SettingsModel, settings }: { SettingsModel: Settings; settings: ICachedSettings }): void { + SettingsModel.find().forEach((record: ISetting) => { + if (record._id.startsWith('Prometheus')) { + console.log('store cache', record); + } + settings.set(record); }); -} -settings.watchMultiple = use(settings.watchMultiple, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); -settings.watchOnce = use(settings.watchOnce, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); - -settings.watchByRegex = use(settings.watchByRegex, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); -settings.change = use(settings.change, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); -settings.changeMultiple = use(settings.changeMultiple, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); -settings.changeOnce = use(settings.changeOnce, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); - -settings.changeByRegex = use(settings.changeByRegex, (context, next) => { - const [_id, callback, ...args] = context; - return next(_id, Meteor.bindEnvironment(callback), ...args); -}); - -settings.onReady = use(settings.onReady, (context, next) => { - const [callback, ...args] = context; - return next(Meteor.bindEnvironment(callback), ...args); -}); + settings.initilized(); +} diff --git a/apps/meteor/app/threads/client/flextab/threadlist.tsx b/apps/meteor/app/threads/client/flextab/threadlist.tsx index f00eeb46991d..52e8930b159c 100644 --- a/apps/meteor/app/threads/client/flextab/threadlist.tsx +++ b/apps/meteor/app/threads/client/flextab/threadlist.tsx @@ -1,9 +1,9 @@ import React, { useMemo, lazy, LazyExoticComponent, FC, ReactNode } from 'react'; import { BadgeProps } from '@rocket.chat/fuselage'; import type { ISubscription } from '@rocket.chat/core-typings'; +import { useSetting } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../../client/views/room/lib/Toolbox'; -import { useSetting } from '../../../../client/contexts/SettingsContext'; import Header from '../../../../client/components/Header'; const getVariant = (tunreadUser: number, tunreadGroup: number): BadgeProps['variant'] => { diff --git a/apps/meteor/app/ui-clean-history/client/lib/tabBar.ts b/apps/meteor/app/ui-clean-history/client/lib/tabBar.ts index e1adc91d12df..fdbe64940ff3 100644 --- a/apps/meteor/app/ui-clean-history/client/lib/tabBar.ts +++ b/apps/meteor/app/ui-clean-history/client/lib/tabBar.ts @@ -1,7 +1,7 @@ import { useMemo, lazy } from 'react'; +import { usePermission } from '@rocket.chat/ui-contexts'; import { addAction } from '../../../../client/views/room/lib/Toolbox'; -import { usePermission } from '../../../../client/contexts/AuthorizationContext'; const template = lazy(() => import('../../../../client/views/room/contextualBar/PruneMessages')); diff --git a/apps/meteor/app/ui-utils/client/lib/AccountBox.d.ts b/apps/meteor/app/ui-utils/client/lib/AccountBox.d.ts index f3e32ce7ddf7..30790cd88564 100644 --- a/apps/meteor/app/ui-utils/client/lib/AccountBox.d.ts +++ b/apps/meteor/app/ui-utils/client/lib/AccountBox.d.ts @@ -1,8 +1,7 @@ import type { ComponentProps } from 'react'; import { Option } from '@rocket.chat/fuselage'; import type { IUser } from '@rocket.chat/core-typings'; - -import { TranslationKey } from '../../../../client/contexts/TranslationContext'; +import { TranslationKey } from '@rocket.chat/ui-contexts'; export declare const AccountBox: { setStatus: (status: IUser['status'], statusText?: IUser['statusText']) => void; diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 703af64a903c..28cf22684bf2 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -6,11 +6,11 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { Icon } from '@rocket.chat/fuselage'; import { IMessage, IUser, ISubscription, IRoom, SettingValue } from '@rocket.chat/core-typings'; +import { TranslationKey } from '@rocket.chat/ui-contexts'; import { Messages, Rooms, Subscriptions } from '../../../models/client'; import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import { ToolboxContextValue } from '../../../../client/views/room/lib/Toolbox/ToolboxContext'; -import { TranslationKey } from '../../../../client/contexts/TranslationContext'; const call = (method: string, ...args: any[]): Promise => new Promise((resolve, reject) => { diff --git a/apps/meteor/app/ui-utils/client/lib/SideNav.ts b/apps/meteor/app/ui-utils/client/lib/SideNav.ts index 5d3a6801fce6..94bcc6f4b179 100644 --- a/apps/meteor/app/ui-utils/client/lib/SideNav.ts +++ b/apps/meteor/app/ui-utils/client/lib/SideNav.ts @@ -27,7 +27,7 @@ export const SideNav = new (class extends Emitter<{ private flexNav: JQuery; - toggleFlex(status: 1 | -1, callback: () => void): void { + toggleFlex(status: 1 | -1, callback?: () => void): void { if (this.animating === true) { return; } diff --git a/apps/meteor/app/ui-utils/lib/MessageTypes.ts b/apps/meteor/app/ui-utils/lib/MessageTypes.ts index 29f051a16b8c..d7d4d8e3c98d 100644 --- a/apps/meteor/app/ui-utils/lib/MessageTypes.ts +++ b/apps/meteor/app/ui-utils/lib/MessageTypes.ts @@ -1,6 +1,5 @@ import { IMessage, MessageTypesValues } from '@rocket.chat/core-typings'; - -import { TranslationKey } from '../../../client/contexts/TranslationContext'; +import { TranslationKey } from '@rocket.chat/ui-contexts'; export type MessageType = { id: MessageTypesValues; diff --git a/apps/meteor/app/user-status/client/admin/startup.js b/apps/meteor/app/user-status/client/admin/startup.js deleted file mode 100644 index 049ac2c1a61a..000000000000 --- a/apps/meteor/app/user-status/client/admin/startup.js +++ /dev/null @@ -1,11 +0,0 @@ -import { hasAtLeastOnePermission } from '../../../authorization'; -import { registerAdminSidebarItem } from '../../../../client/views/admin'; - -registerAdminSidebarItem({ - href: 'custom-user-status', - i18nLabel: 'Custom_User_Status', - icon: 'user', - permissionGranted() { - return hasAtLeastOnePermission(['manage-user-status']); - }, -}); diff --git a/apps/meteor/app/user-status/client/index.js b/apps/meteor/app/user-status/client/index.js index ecada9a17f9f..221a2decfc0b 100644 --- a/apps/meteor/app/user-status/client/index.js +++ b/apps/meteor/app/user-status/client/index.js @@ -1,5 +1,3 @@ -import './admin/startup'; - import './notifications/deleteCustomUserStatus'; import './notifications/updateCustomUserStatus'; diff --git a/apps/meteor/app/videobridge/client/tabBar.tsx b/apps/meteor/app/videobridge/client/tabBar.tsx index 0544d2da17d7..06f85ca80614 100644 --- a/apps/meteor/app/videobridge/client/tabBar.tsx +++ b/apps/meteor/app/videobridge/client/tabBar.tsx @@ -1,14 +1,12 @@ import React, { useMemo, lazy, ReactNode } from 'react'; import { useStableArray } from '@rocket.chat/fuselage-hooks'; import { Option, Badge } from '@rocket.chat/fuselage'; +import { useUser, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; -import { useSetting } from '../../../client/contexts/SettingsContext'; import { addAction, ToolboxActionConfig } from '../../../client/views/room/lib/Toolbox'; -import { useTranslation } from '../../../client/contexts/TranslationContext'; -import { useUser } from '../../../client/contexts/UserContext'; import Header from '../../../client/components/Header'; -const templateBBB = lazy(() => import('../../../client/views/room/contextualBar/Call/BBB')); +const templateBBB = lazy(() => import('../../../client/views/room/contextualBar/VideoConference/BBB')); addAction('bbb_video', ({ room }) => { const enabled = useSetting('bigbluebutton_Enabled'); @@ -62,7 +60,7 @@ addAction('bbb_video', ({ room }) => { ); }); -const templateJitsi = lazy(() => import('../../../client/views/room/contextualBar/Call/Jitsi')); +const templateJitsi = lazy(() => import('../../../client/views/room/contextualBar/VideoConference/Jitsi')); addAction('video', ({ room }) => { const enabled = useSetting('Jitsi_Enabled'); diff --git a/apps/meteor/app/videobridge/server/methods/jitsiSetTimeout.js b/apps/meteor/app/videobridge/server/methods/jitsiSetTimeout.js index 61bc09f33d8d..0789b9d82c69 100644 --- a/apps/meteor/app/videobridge/server/methods/jitsiSetTimeout.js +++ b/apps/meteor/app/videobridge/server/methods/jitsiSetTimeout.js @@ -75,10 +75,10 @@ Meteor.methods({ } return jitsiTimeout || nextTimeOut; - } catch (error) { - SystemLogger.error('Error starting video call:', error.message); + } catch (err) { + SystemLogger.error({ msg: 'Error starting video call:', err }); - throw new Meteor.Error('error-starting-video-call', error.message, { + throw new Meteor.Error('error-starting-video-call', err.message, { method: 'jitsi:updateTimeout', }); } diff --git a/apps/meteor/app/webrtc/client/tabBar.tsx b/apps/meteor/app/webrtc/client/tabBar.tsx index 15305db33070..13f5ff1e0329 100644 --- a/apps/meteor/app/webrtc/client/tabBar.tsx +++ b/apps/meteor/app/webrtc/client/tabBar.tsx @@ -1,6 +1,6 @@ import { useMemo, useCallback } from 'react'; +import { useSetting } from '@rocket.chat/ui-contexts'; -import { useSetting } from '../../../client/contexts/SettingsContext'; import { addAction } from '../../../client/views/room/lib/Toolbox'; import { APIClient } from '../../utils/client'; diff --git a/apps/meteor/client/UIKit/hooks/useUIKitHandleAction.tsx b/apps/meteor/client/UIKit/hooks/useUIKitHandleAction.tsx index 1268932f65ff..b23a0b76e223 100644 --- a/apps/meteor/client/UIKit/hooks/useUIKitHandleAction.tsx +++ b/apps/meteor/client/UIKit/hooks/useUIKitHandleAction.tsx @@ -7,7 +7,7 @@ import { UiKitPayload, UIKitActionEvent } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; // import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer'; -// import { useEndpoint } from '../../contexts/ServerContext'; +// import { useEndpoint } from '@rocket.chat/ui-contexts'; import * as ActionManager from '../../../app/ui-message/client/ActionManager'; const useUIKitHandleAction = (state: S): ((event: UIKitActionEvent) => Promise) => diff --git a/apps/meteor/client/UIKit/hooks/useUIKitHandleClose.tsx b/apps/meteor/client/UIKit/hooks/useUIKitHandleClose.tsx index c24d0758c899..5bd5838f5fc0 100644 --- a/apps/meteor/client/UIKit/hooks/useUIKitHandleClose.tsx +++ b/apps/meteor/client/UIKit/hooks/useUIKitHandleClose.tsx @@ -5,11 +5,12 @@ import { UIKitInteractionType } from '@rocket.chat/apps-engine/definition/uikit' // import React, { Context, FC, useMemo } from 'react'; import { UiKitPayload } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; // import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer'; -// import { useEndpoint } from '../../contexts/ServerContext'; +// import { useEndpoint } from '@rocket.chat/ui-contexts'; + import * as ActionManager from '../../../app/ui-message/client/ActionManager'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const emptyFn = (_error: any, _result: UIKitInteractionType | void): void => undefined; diff --git a/apps/meteor/client/components/AutoCompleteDepartment.js b/apps/meteor/client/components/AutoCompleteDepartment.js index c67e2b068808..fb9ed3a48ecb 100644 --- a/apps/meteor/client/components/AutoCompleteDepartment.js +++ b/apps/meteor/client/components/AutoCompleteDepartment.js @@ -3,9 +3,9 @@ // fuselage release is OoS of this regression import { PaginatedSelectFiltered } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, useMemo, useState } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; import { useRecordList } from '../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../hooks/useAsyncState'; import { useDepartmentsList } from './Omnichannel/hooks/useDepartmentsList'; diff --git a/apps/meteor/client/components/AutoCompleteDepartmentMultiple.js b/apps/meteor/client/components/AutoCompleteDepartmentMultiple.js index 467eeba0bab9..0ccbab0e0c21 100644 --- a/apps/meteor/client/components/AutoCompleteDepartmentMultiple.js +++ b/apps/meteor/client/components/AutoCompleteDepartmentMultiple.js @@ -1,8 +1,8 @@ import { PaginatedMultiSelectFiltered } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, useMemo, useState } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; import { useRecordList } from '../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../hooks/useAsyncState'; import { useDepartmentsList } from './Omnichannel/hooks/useDepartmentsList'; diff --git a/apps/meteor/client/components/BurgerMenu/BurgerMenu.tsx b/apps/meteor/client/components/BurgerMenu/BurgerMenu.tsx index 785f943282c6..aca62a4578a6 100644 --- a/apps/meteor/client/components/BurgerMenu/BurgerMenu.tsx +++ b/apps/meteor/client/components/BurgerMenu/BurgerMenu.tsx @@ -1,8 +1,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLayout, useSession } from '@rocket.chat/ui-contexts'; import React, { memo, ReactElement } from 'react'; -import { useLayout } from '../../contexts/LayoutContext'; -import { useSession } from '../../contexts/SessionContext'; import { useEmbeddedLayout } from '../../hooks/useEmbeddedLayout'; import BurgerMenuButton from './BurgerMenuButton'; diff --git a/apps/meteor/client/components/BurgerMenu/BurgerMenuButton.tsx b/apps/meteor/client/components/BurgerMenu/BurgerMenuButton.tsx index a92c368489be..53c923f9a4df 100644 --- a/apps/meteor/client/components/BurgerMenu/BurgerMenuButton.tsx +++ b/apps/meteor/client/components/BurgerMenu/BurgerMenuButton.tsx @@ -1,8 +1,8 @@ import { css } from '@rocket.chat/css-in-js'; import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import BurgerBadge from './BurgerBadge'; import BurgerIcon from './BurgerIcon'; diff --git a/apps/meteor/client/components/Card/Body.tsx b/apps/meteor/client/components/Card/Body.tsx index c57bbf9d6e76..6182e809e0e8 100644 --- a/apps/meteor/client/components/Card/Body.tsx +++ b/apps/meteor/client/components/Card/Body.tsx @@ -1,12 +1,13 @@ import { Box } from '@rocket.chat/fuselage'; -import React, { FC, CSSProperties } from 'react'; +import React, { FC, CSSProperties, ComponentProps } from 'react'; type BodyProps = { flexDirection?: CSSProperties['flexDirection']; + height?: ComponentProps['height']; }; -const Body: FC = ({ children, flexDirection = 'row' }) => ( - +const Body: FC = ({ children, flexDirection = 'row', height }) => ( + {children} ); diff --git a/apps/meteor/client/components/Card/Card.tsx b/apps/meteor/client/components/Card/Card.tsx index f44b06517eef..9c678bbab34d 100644 --- a/apps/meteor/client/components/Card/Card.tsx +++ b/apps/meteor/client/components/Card/Card.tsx @@ -1,10 +1,22 @@ import { Box } from '@rocket.chat/fuselage'; import React, { FC } from 'react'; -const Card: FC = ({ children, ...props }) => ( - - {children} - +type CardProps = { + variant?: 'light' | 'tint'; +}; + +const Card: FC = ({ variant, ...props }: CardProps) => ( + ); export default Card; diff --git a/apps/meteor/client/components/Card/Footer.tsx b/apps/meteor/client/components/Card/Footer.tsx index 22c4304cc64c..d4fa8146e271 100644 --- a/apps/meteor/client/components/Card/Footer.tsx +++ b/apps/meteor/client/components/Card/Footer.tsx @@ -1,6 +1,6 @@ -import { Box } from '@rocket.chat/fuselage'; +import { ButtonGroup } from '@rocket.chat/fuselage'; import React, { FC } from 'react'; -const Footer: FC = ({ children }) => {children}; +const Footer: FC = ({ children }) => {children}; export default Footer; diff --git a/apps/meteor/client/components/ConfirmOwnerChangeWarningModal.tsx b/apps/meteor/client/components/ConfirmOwnerChangeWarningModal.tsx index c981a9819d0a..694ca1b9905c 100644 --- a/apps/meteor/client/components/ConfirmOwnerChangeWarningModal.tsx +++ b/apps/meteor/client/components/ConfirmOwnerChangeWarningModal.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; import GenericModal from './GenericModal'; import RawText from './RawText'; diff --git a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx index 9609f1ba118d..0b684b4d1488 100644 --- a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx +++ b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx @@ -1,9 +1,9 @@ import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import { Modal, Field, FieldGroup, ToggleSwitch, TextInput, TextAreaInput, ButtonGroup, Button, Icon, Box } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointActionExperimental } from '../../hooks/useEndpointActionExperimental'; import { useForm } from '../../hooks/useForm'; import { goToRoomById } from '../../lib/utils/goToRoomById'; diff --git a/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx b/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx index 8c788e55128c..bdbb32108457 100644 --- a/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx +++ b/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx @@ -1,7 +1,7 @@ import { Skeleton, TextInput, Callout } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, ReactElement } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../hooks/useAsyncState'; import { useEndpointData } from '../../hooks/useEndpointData'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; diff --git a/apps/meteor/client/components/CustomFieldsForm.js b/apps/meteor/client/components/CustomFieldsForm.js index db7d410f11d3..9fdb06bab816 100644 --- a/apps/meteor/client/components/CustomFieldsForm.js +++ b/apps/meteor/client/components/CustomFieldsForm.js @@ -1,9 +1,8 @@ import { TextInput, Select, Field } from '@rocket.chat/fuselage'; import { capitalize } from '@rocket.chat/string-helpers'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useEffect, useState } from 'react'; -import { useSetting } from '../contexts/SettingsContext'; -import { useTranslation } from '../contexts/TranslationContext'; import { useComponentDidUpdate } from '../hooks/useComponentDidUpdate'; import { useForm } from '../hooks/useForm'; diff --git a/apps/meteor/client/components/FilterByText.tsx b/apps/meteor/client/components/FilterByText.tsx index 04abdd00a0a8..d75050a57843 100644 --- a/apps/meteor/client/components/FilterByText.tsx +++ b/apps/meteor/client/components/FilterByText.tsx @@ -1,8 +1,7 @@ import { Box, Icon, TextInput, Button } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, ChangeEvent, FormEvent, memo, useCallback, useEffect, useState } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; - type FilterByTextProps = { placeholder?: string; onChange: (filter: { text: string }) => void; diff --git a/apps/meteor/client/components/GenericModal.tsx b/apps/meteor/client/components/GenericModal.tsx index 1ae3eabd6d46..afd8467aee08 100644 --- a/apps/meteor/client/components/GenericModal.tsx +++ b/apps/meteor/client/components/GenericModal.tsx @@ -1,7 +1,7 @@ import { Box, Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, ComponentProps, ReactElement, ReactNode } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; import { withDoNotAskAgain, RequiredModalProps } from './withDoNotAskAgain'; type VariantType = 'danger' | 'warning' | 'info' | 'success'; diff --git a/apps/meteor/client/components/GenericTable/GenericTable.tsx b/apps/meteor/client/components/GenericTable/GenericTable.tsx index 06781fed7431..588c3cd6be40 100644 --- a/apps/meteor/client/components/GenericTable/GenericTable.tsx +++ b/apps/meteor/client/components/GenericTable/GenericTable.tsx @@ -1,9 +1,9 @@ import { Pagination, Tile } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useEffect, forwardRef, ReactNode, ReactElement, Key, useMemo, Ref } from 'react'; import flattenChildren from 'react-keyed-flatten-children'; -import { useTranslation } from '../../contexts/TranslationContext'; import { GenericTable as GenericTableV2 } from './V2/GenericTable'; import { GenericTableBody } from './V2/GenericTableBody'; import { GenericTableHeader } from './V2/GenericTableHeader'; diff --git a/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts b/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts index 79e65d88f370..0a8a8deb8262 100644 --- a/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts +++ b/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts @@ -1,7 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - export const useItemsPerPageLabel = (): (() => string) => { const t = useTranslation(); return useCallback(() => t('Items_per_page:'), [t]); diff --git a/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts b/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts index 030929bb5bdd..4a4e2b3c0236 100644 --- a/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts +++ b/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts @@ -1,8 +1,7 @@ import { Pagination } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import { ComponentProps, useCallback } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - type Props['showingResultsLabel'] = ComponentProps['showingResultsLabel']> = T extends (...args: any[]) => any ? Parameters : never; diff --git a/apps/meteor/client/components/Header/Header.stories.tsx b/apps/meteor/client/components/Header/Header.stories.tsx index c199eff25893..0f7905c3d0a6 100644 --- a/apps/meteor/client/components/Header/Header.stories.tsx +++ b/apps/meteor/client/components/Header/Header.stories.tsx @@ -1,10 +1,10 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { SettingsContext } from '@rocket.chat/ui-contexts'; import { action } from '@storybook/addon-actions'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; import Header from '.'; -import { SettingsContext } from '../../contexts/SettingsContext'; import { useRoomIcon } from '../../hooks/useRoomIcon'; import ToolBox from '../../views/room/Header/ToolBox'; import { ActionRenderer, addAction } from '../../views/room/lib/Toolbox'; diff --git a/apps/meteor/client/components/Header/Header.tsx b/apps/meteor/client/components/Header/Header.tsx index 9c14618c44b3..c9c33e2ddaf1 100644 --- a/apps/meteor/client/components/Header/Header.tsx +++ b/apps/meteor/client/components/Header/Header.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useLayout } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useLayout } from '../../contexts/LayoutContext'; import HeaderDivider from './HeaderDivider'; const Header: FC = (props) => { diff --git a/apps/meteor/client/components/LocalTime.tsx b/apps/meteor/client/components/LocalTime.tsx index bb2ccec35554..d4953717e010 100644 --- a/apps/meteor/client/components/LocalTime.tsx +++ b/apps/meteor/client/components/LocalTime.tsx @@ -1,6 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, ReactElement } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; import { useUTCClock } from '../hooks/useUTCClock'; type LocalTimeProps = { diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Attachment.tsx b/apps/meteor/client/components/Message/Attachments/Attachment/Attachment.tsx index 2de670483b9d..f7d5bcdce81d 100644 --- a/apps/meteor/client/components/Message/Attachments/Attachment/Attachment.tsx +++ b/apps/meteor/client/components/Message/Attachments/Attachment/Attachment.tsx @@ -1,8 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useAttachmentDimensions } from '@rocket.chat/ui-contexts'; import React, { ComponentProps, FC } from 'react'; -import { useAttachmentDimensions } from '../context/AttachmentContext'; - const Attachment: FC> = (props) => { const { width } = useAttachmentDimensions(); return ( diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Collapse.tsx b/apps/meteor/client/components/Message/Attachments/Attachment/Collapse.tsx index 21cfc6309b33..dd1477d157cb 100644 --- a/apps/meteor/client/components/Message/Attachments/Attachment/Collapse.tsx +++ b/apps/meteor/client/components/Message/Attachments/Attachment/Collapse.tsx @@ -1,6 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ComponentProps, FC } from 'react'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import Action from './Action'; const Collapse: FC, 'icon'> & { collapsed?: boolean }> = ({ collapsed = false, ...props }) => { diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Download.tsx b/apps/meteor/client/components/Message/Attachments/Attachment/Download.tsx index c88d6d840ed8..1d2b46fb2f23 100644 --- a/apps/meteor/client/components/Message/Attachments/Attachment/Download.tsx +++ b/apps/meteor/client/components/Message/Attachments/Attachment/Download.tsx @@ -1,6 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ComponentProps, FC } from 'react'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import Action from './Action'; const Download: FC, 'icon'> & { title?: string | undefined; href: string }> = ({ diff --git a/apps/meteor/client/components/Message/Attachments/Files/AudioAttachment.tsx b/apps/meteor/client/components/Message/Attachments/Files/AudioAttachment.tsx index 6e7450cc51ce..771042e517aa 100644 --- a/apps/meteor/client/components/Message/Attachments/Files/AudioAttachment.tsx +++ b/apps/meteor/client/components/Message/Attachments/Files/AudioAttachment.tsx @@ -1,9 +1,9 @@ import { AudioAttachmentProps } from '@rocket.chat/core-typings'; +import { useMediaUrl } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; import MarkdownText from '../../../MarkdownText'; import Attachment from '../Attachment'; -import { useMediaUrl } from '../context/AttachmentContext'; import { useCollapse } from '../hooks/useCollapse'; export const AudioAttachment: FC = ({ diff --git a/apps/meteor/client/components/Message/Attachments/Files/GenericFileAttachment.tsx b/apps/meteor/client/components/Message/Attachments/Files/GenericFileAttachment.tsx index 2cb6c95ab25f..bb571a379a29 100644 --- a/apps/meteor/client/components/Message/Attachments/Files/GenericFileAttachment.tsx +++ b/apps/meteor/client/components/Message/Attachments/Files/GenericFileAttachment.tsx @@ -1,9 +1,9 @@ import { FileProp, MessageAttachmentBase } from '@rocket.chat/core-typings'; +import { useMediaUrl } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; import MarkdownText from '../../../MarkdownText'; import Attachment from '../Attachment'; -import { useMediaUrl } from '../context/AttachmentContext'; export type GenericFileAttachmentProps = { file?: FileProp; diff --git a/apps/meteor/client/components/Message/Attachments/Files/ImageAttachment.tsx b/apps/meteor/client/components/Message/Attachments/Files/ImageAttachment.tsx index 44ca6cf25705..a45a96c4b22c 100644 --- a/apps/meteor/client/components/Message/Attachments/Files/ImageAttachment.tsx +++ b/apps/meteor/client/components/Message/Attachments/Files/ImageAttachment.tsx @@ -1,10 +1,10 @@ import { ImageAttachmentProps } from '@rocket.chat/core-typings'; +import { useMediaUrl } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; import MarkdownText from '../../../MarkdownText'; import Attachment from '../Attachment'; import Image from '../components/Image'; -import { useMediaUrl } from '../context/AttachmentContext'; import { useCollapse } from '../hooks/useCollapse'; import { useLoadImage } from '../hooks/useLoadImage'; diff --git a/apps/meteor/client/components/Message/Attachments/Files/PDFAttachment.tsx b/apps/meteor/client/components/Message/Attachments/Files/PDFAttachment.tsx index 93aa27ce7120..5efedf8df273 100644 --- a/apps/meteor/client/components/Message/Attachments/Files/PDFAttachment.tsx +++ b/apps/meteor/client/components/Message/Attachments/Files/PDFAttachment.tsx @@ -1,7 +1,7 @@ import { PDFAttachmentProps } from '@rocket.chat/core-typings'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import MarkdownText from '../../../MarkdownText'; import Attachment from '../Attachment'; import { useCollapse } from '../hooks/useCollapse'; diff --git a/apps/meteor/client/components/Message/Attachments/Files/VideoAttachment.tsx b/apps/meteor/client/components/Message/Attachments/Files/VideoAttachment.tsx index 9d67035cfc2f..2205d54f5b1c 100644 --- a/apps/meteor/client/components/Message/Attachments/Files/VideoAttachment.tsx +++ b/apps/meteor/client/components/Message/Attachments/Files/VideoAttachment.tsx @@ -1,10 +1,10 @@ import { VideoAttachmentProps } from '@rocket.chat/core-typings'; import { Box } from '@rocket.chat/fuselage'; +import { useMediaUrl } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; import MarkdownText from '../../../MarkdownText'; import Attachment from '../Attachment'; -import { useMediaUrl } from '../context/AttachmentContext'; import { useCollapse } from '../hooks/useCollapse'; export const VideoAttachment: FC = ({ diff --git a/apps/meteor/client/components/Message/Attachments/components/Image.tsx b/apps/meteor/client/components/Message/Attachments/components/Image.tsx index b664b3467fef..8da784cd4f9f 100644 --- a/apps/meteor/client/components/Message/Attachments/components/Image.tsx +++ b/apps/meteor/client/components/Message/Attachments/components/Image.tsx @@ -1,7 +1,7 @@ import { Dimensions } from '@rocket.chat/core-typings'; +import { useAttachmentDimensions } from '@rocket.chat/ui-contexts'; import React, { memo, FC, useState, useMemo } from 'react'; -import { useAttachmentDimensions } from '../context/AttachmentContext'; import ImageBox from './ImageBox'; import Load from './Load'; import Retry from './Retry'; diff --git a/apps/meteor/client/components/Message/Attachments/components/Load.tsx b/apps/meteor/client/components/Message/Attachments/components/Load.tsx index 5a901efb99bc..83e38ea2a802 100644 --- a/apps/meteor/client/components/Message/Attachments/components/Load.tsx +++ b/apps/meteor/client/components/Message/Attachments/components/Load.tsx @@ -1,9 +1,9 @@ import { css } from '@rocket.chat/css-in-js'; import { Box, Icon } from '@rocket.chat/fuselage'; import colors from '@rocket.chat/fuselage-tokens/colors'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ComponentProps, FC } from 'react'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import ImageBox from './ImageBox'; type LoadProps = ComponentProps & { load: () => void }; diff --git a/apps/meteor/client/components/Message/Attachments/components/Retry.tsx b/apps/meteor/client/components/Message/Attachments/components/Retry.tsx index 3f6351295343..4359cdd35d03 100644 --- a/apps/meteor/client/components/Message/Attachments/components/Retry.tsx +++ b/apps/meteor/client/components/Message/Attachments/components/Retry.tsx @@ -1,9 +1,9 @@ import { css } from '@rocket.chat/css-in-js'; import { Box, Icon } from '@rocket.chat/fuselage'; import colors from '@rocket.chat/fuselage-tokens/colors'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, ComponentProps } from 'react'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import ImageBox from './ImageBox'; type RetryProps = ComponentProps & { retry: () => void }; diff --git a/apps/meteor/client/components/Message/Attachments/context/AttachmentContext.tsx b/apps/meteor/client/components/Message/Attachments/context/AttachmentContext.tsx deleted file mode 100644 index 8e3d4f2d7189..000000000000 --- a/apps/meteor/client/components/Message/Attachments/context/AttachmentContext.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { createContext, useContext } from 'react'; - -export type AttachmentContextValue = { - getURL: (url: string) => string; - dimensions: { - width: number; - height: number; - }; - collapsedByDefault: boolean; - autoLoadEmbedMedias: boolean; -}; - -export const AttachmentContext = createContext({ - getURL: (url: string) => url, - dimensions: { - width: 480, - height: 360, - }, - collapsedByDefault: false, - autoLoadEmbedMedias: true, -}); - -export const useMediaUrl = (): ((path: string) => string) => { - const { getURL } = useContext(AttachmentContext); - return getURL; -}; - -export const useAttachmentDimensions = (): { - width: number; - height: number; -} => useContext(AttachmentContext).dimensions; - -export const useAttachmentIsCollapsedByDefault = (): boolean => useContext(AttachmentContext).collapsedByDefault; -export const useAttachmentAutoLoadEmbedMedia = (): boolean => useContext(AttachmentContext).autoLoadEmbedMedias; diff --git a/apps/meteor/client/components/Message/Attachments/hooks/useCollapse.tsx b/apps/meteor/client/components/Message/Attachments/hooks/useCollapse.tsx index 09911f68788f..5f320a87480d 100644 --- a/apps/meteor/client/components/Message/Attachments/hooks/useCollapse.tsx +++ b/apps/meteor/client/components/Message/Attachments/hooks/useCollapse.tsx @@ -1,8 +1,8 @@ import { useToggle } from '@rocket.chat/fuselage-hooks'; +import { useAttachmentIsCollapsedByDefault } from '@rocket.chat/ui-contexts'; import React from 'react'; import Attachment from '../Attachment'; -import { useAttachmentIsCollapsedByDefault } from '../context/AttachmentContext'; export const useCollapse = (attachmentCollapsed: boolean): [boolean, JSX.Element] => { const collpaseByDefault = useAttachmentIsCollapsedByDefault(); diff --git a/apps/meteor/client/components/Message/Attachments/hooks/useLoadImage.tsx b/apps/meteor/client/components/Message/Attachments/hooks/useLoadImage.tsx index e9e803afc99e..83a4fff5e181 100644 --- a/apps/meteor/client/components/Message/Attachments/hooks/useLoadImage.tsx +++ b/apps/meteor/client/components/Message/Attachments/hooks/useLoadImage.tsx @@ -1,7 +1,6 @@ +import { useAttachmentAutoLoadEmbedMedia } from '@rocket.chat/ui-contexts'; import { useCallback, useState } from 'react'; -import { useAttachmentAutoLoadEmbedMedia } from '../context/AttachmentContext'; - export const useLoadImage = (): [boolean, () => void] => { const [loadImage, setLoadImage] = useState(useAttachmentAutoLoadEmbedMedia()); return [loadImage, useCallback(() => setLoadImage(true), [])]; diff --git a/apps/meteor/client/components/Message/Attachments/providers/AttachmentProvider.tsx b/apps/meteor/client/components/Message/Attachments/providers/AttachmentProvider.tsx index df43e66c9c55..a287017b85bb 100644 --- a/apps/meteor/client/components/Message/Attachments/providers/AttachmentProvider.tsx +++ b/apps/meteor/client/components/Message/Attachments/providers/AttachmentProvider.tsx @@ -1,10 +1,8 @@ import { usePrefersReducedData } from '@rocket.chat/fuselage-hooks'; +import { AttachmentContext, AttachmentContextValue, useLayout, useUserPreference } from '@rocket.chat/ui-contexts'; import React, { useMemo, FC } from 'react'; import { getURL } from '../../../../../app/utils/client'; -import { useLayout } from '../../../../contexts/LayoutContext'; -import { useUserPreference } from '../../../../contexts/UserContext'; -import { AttachmentContext, AttachmentContextValue } from '../context/AttachmentContext'; const AttachmentProvider: FC = ({ children }) => { const { isMobile } = useLayout(); diff --git a/apps/meteor/client/components/Message/MessageActions/Action.tsx b/apps/meteor/client/components/Message/MessageActions/Action.tsx index 38706446305d..942ab68c0b5e 100644 --- a/apps/meteor/client/components/Message/MessageActions/Action.tsx +++ b/apps/meteor/client/components/Message/MessageActions/Action.tsx @@ -1,8 +1,7 @@ import { IconProps, Icon, Button } from '@rocket.chat/fuselage'; +import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { TranslationKey, useTranslation } from '../../../contexts/TranslationContext'; - type RunAction = (action: string) => () => void; type ActionOptions = { diff --git a/apps/meteor/client/components/Message/MessageActions/Actions.tsx b/apps/meteor/client/components/Message/MessageActions/Actions.tsx index f5d88ab21ed2..bf9c27674cb9 100644 --- a/apps/meteor/client/components/Message/MessageActions/Actions.tsx +++ b/apps/meteor/client/components/Message/MessageActions/Actions.tsx @@ -1,7 +1,7 @@ import { IconProps, ButtonGroup } from '@rocket.chat/fuselage'; +import { TranslationKey } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { TranslationKey } from '../../../contexts/TranslationContext'; import Content from '../Metrics/Content'; import Action from './Action'; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx index 5a660bca8c47..b1a539e24cbe 100644 --- a/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx +++ b/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx @@ -1,7 +1,7 @@ import { UserMention as ASTUserMention } from '@rocket.chat/message-parser'; +import { useUserId } from '@rocket.chat/ui-contexts'; import React, { FC, memo } from 'react'; -import { useUserId } from '../../../contexts/UserContext'; import { useMessageBodyUserMentions, useMessageBodyMentionClick } from './contexts/MessageBodyContext'; const Mention: FC<{ value: ASTUserMention['value'] }> = ({ value: { value: mention } }) => { diff --git a/apps/meteor/client/components/Message/Metrics/Broadcast.tsx b/apps/meteor/client/components/Message/Metrics/Broadcast.tsx index d479dbb8755c..191de087004c 100644 --- a/apps/meteor/client/components/Message/Metrics/Broadcast.tsx +++ b/apps/meteor/client/components/Message/Metrics/Broadcast.tsx @@ -1,6 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useBlockRendered } from '../hooks/useBlockRendered'; import Content from './Content'; import Reply from './Reply'; diff --git a/apps/meteor/client/components/Message/Metrics/Discussion.tsx b/apps/meteor/client/components/Message/Metrics/Discussion.tsx index 553b9cb67f9f..d375030f371c 100644 --- a/apps/meteor/client/components/Message/Metrics/Discussion.tsx +++ b/apps/meteor/client/components/Message/Metrics/Discussion.tsx @@ -1,7 +1,7 @@ import { Message } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useTimeAgo } from '../../../hooks/useTimeAgo'; import { useBlockRendered } from '../hooks/useBlockRendered'; diff --git a/apps/meteor/client/components/Message/Metrics/Thread.tsx b/apps/meteor/client/components/Message/Metrics/Thread.tsx index d271ebf49482..1f33834f1f2a 100644 --- a/apps/meteor/client/components/Message/Metrics/Thread.tsx +++ b/apps/meteor/client/components/Message/Metrics/Thread.tsx @@ -1,8 +1,7 @@ import { Message, MessageMetricsItem, MessageBlock } from '@rocket.chat/fuselage'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, FC, MouseEvent as ReactMouseEvent } from 'react'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useTimeAgo } from '../../../hooks/useTimeAgo'; import * as NotificationStatus from '../NotificationStatus'; import { followStyle, anchor } from '../helpers/followSyle'; diff --git a/apps/meteor/client/components/Message/NotificationStatus/NotificationStatus.tsx b/apps/meteor/client/components/Message/NotificationStatus/NotificationStatus.tsx index 0cda4e72d272..aee9f48b8e4d 100644 --- a/apps/meteor/client/components/Message/NotificationStatus/NotificationStatus.tsx +++ b/apps/meteor/client/components/Message/NotificationStatus/NotificationStatus.tsx @@ -1,8 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { TranslationKey, useTranslation } from '../../../contexts/TranslationContext'; - const NotificationStatus: FC<{ label: TranslationKey; bg: string; diff --git a/apps/meteor/client/components/Omnichannel/Tags.tsx b/apps/meteor/client/components/Omnichannel/Tags.tsx index 1d315ead0913..eb4ca95ecd78 100644 --- a/apps/meteor/client/components/Omnichannel/Tags.tsx +++ b/apps/meteor/client/components/Omnichannel/Tags.tsx @@ -1,10 +1,9 @@ import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ChangeEvent, ReactElement, useState } from 'react'; import { useSubscription } from 'use-subscription'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../hooks/useAsyncState'; import { useEndpointData } from '../../hooks/useEndpointData'; import { formsSubscription } from '../../views/omnichannel/additionalForms'; diff --git a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts index d56096fd9876..c5436312abc8 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts @@ -1,8 +1,7 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback, useState } from 'react'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; import { RecordList } from '../../../lib/lists/RecordList'; diff --git a/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts index 76520fe36e42..d0c2db186100 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts @@ -1,7 +1,7 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback, useState } from 'react'; -import { useEndpoint } from '../../../contexts/ServerContext'; import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; import { RecordList } from '../../../lib/lists/RecordList'; diff --git a/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts index e9ecc3d41520..a80ea72447ab 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts @@ -1,8 +1,7 @@ import type { ILivechatDepartmentRecord } from '@rocket.chat/core-typings'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback, useState } from 'react'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; import { RecordList } from '../../../lib/lists/RecordList'; diff --git a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx index 4493b19ea2d9..261ae721109d 100644 --- a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx @@ -1,10 +1,9 @@ import { ILivechatDepartment } from '@rocket.chat/core-typings'; import { Field, Button, TextInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, useEffect, ReactElement, useMemo } from 'react'; import { useForm } from 'react-hook-form'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import GenericModal from '../../GenericModal'; import Tags from '../Tags'; diff --git a/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx index 1697b0b4c31c..616895ddc771 100644 --- a/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx @@ -1,11 +1,10 @@ import { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Field, Button, TextAreaInput, Icon, ButtonGroup, Modal, Box, PaginatedSelectFiltered } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useRecordList } from '../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../hooks/useAsyncState'; import UserAutoComplete from '../../UserAutoComplete'; diff --git a/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx b/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx index 04fe0249ef91..758295e4012f 100644 --- a/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx @@ -1,8 +1,7 @@ import { Box, Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - type ReturnChatQueueModalProps = { onMoveChat: () => void; onCancel: () => void; diff --git a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx index d39d1abdd310..c25e8cc23aaa 100644 --- a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx @@ -1,9 +1,9 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Field, Button, TextInput, Icon, ButtonGroup, Modal } from '@rocket.chat/fuselage'; import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; import { useForm } from '../../../hooks/useForm'; diff --git a/apps/meteor/client/components/Page/PageBlockWithBorder.tsx b/apps/meteor/client/components/Page/PageBlockWithBorder.tsx new file mode 100644 index 000000000000..f2966dbeadca --- /dev/null +++ b/apps/meteor/client/components/Page/PageBlockWithBorder.tsx @@ -0,0 +1,21 @@ +import { Box } from '@rocket.chat/fuselage'; +import React, { ComponentProps, forwardRef, useContext } from 'react'; + +import PageContent from './PageContent'; +import PageContext from './PageContext'; + +const PageBlockWithBorder = forwardRef>(function PageBlockWithBorder(props, ref) { + const [border] = useContext(PageContext); + return ( + + ); +}); + +export default PageBlockWithBorder; diff --git a/apps/meteor/client/components/Page/PageHeader.tsx b/apps/meteor/client/components/Page/PageHeader.tsx index 8158657bcc3f..9517eaae1d3a 100644 --- a/apps/meteor/client/components/Page/PageHeader.tsx +++ b/apps/meteor/client/components/Page/PageHeader.tsx @@ -1,21 +1,24 @@ -import { Box } from '@rocket.chat/fuselage'; +import { Box, ActionButton } from '@rocket.chat/fuselage'; +import { useLayout, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useContext, FC, ReactNode } from 'react'; -import { useLayout } from '../../contexts/LayoutContext'; import BurgerMenu from '../BurgerMenu'; import TemplateHeader from '../Header'; import PageContext from './PageContext'; type PageHeaderProps = { title: ReactNode; + onClickBack?: () => void; + borderBlockEndColor?: string; }; -const PageHeader: FC = ({ children = undefined, title, ...props }) => { +const PageHeader: FC = ({ children = undefined, title, onClickBack, borderBlockEndColor, ...props }) => { + const t = useTranslation(); const [border] = useContext(PageContext); const { isMobile } = useLayout(); return ( - + = ({ children = undefined, title, ...props )} + {onClickBack && } {title} diff --git a/apps/meteor/client/components/Page/PageHeaderWithBorder.tsx b/apps/meteor/client/components/Page/PageHeaderWithBorder.tsx new file mode 100644 index 000000000000..da51786f3911 --- /dev/null +++ b/apps/meteor/client/components/Page/PageHeaderWithBorder.tsx @@ -0,0 +1,16 @@ +import React, { useContext, FC, ReactNode } from 'react'; + +import PageContext from './PageContext'; +import PageHeader from './PageHeader'; + +type PageHeaderWithBorderProps = { + title: ReactNode; +}; + +const PageHeaderWithBorder: FC = (props) => { + const [border] = useContext(PageContext); + + return ; +}; + +export default PageHeaderWithBorder; diff --git a/apps/meteor/client/components/Page/PageScrollableContent.tsx b/apps/meteor/client/components/Page/PageScrollableContent.tsx index 6d35af969569..15315375ca91 100644 --- a/apps/meteor/client/components/Page/PageScrollableContent.tsx +++ b/apps/meteor/client/components/Page/PageScrollableContent.tsx @@ -8,11 +8,19 @@ type PageScrollableContentProps = { } & ComponentProps; const PageScrollableContent = forwardRef(function PageScrollableContent( - { onScrollContent, ...props }, + { onScrollContent, borderBlockEndColor, ...props }, ref, ) { return ( - + diff --git a/apps/meteor/client/components/PlanTag.tsx b/apps/meteor/client/components/PlanTag.tsx index 30e7f1993e68..4a0ad84c45cf 100644 --- a/apps/meteor/client/components/PlanTag.tsx +++ b/apps/meteor/client/components/PlanTag.tsx @@ -1,9 +1,9 @@ import { Box, Tag } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useMethod } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useEffect, useState } from 'react'; import { ILicenseTag } from '../../ee/app/license/definitions/ILicenseTag'; -import { useMethod } from '../contexts/ServerContext'; function PlanTag(): ReactElement { const [plans, setPlans] = useSafely( diff --git a/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts b/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts index 7f3c42ac5cb4..8b5cc7fbd320 100644 --- a/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts +++ b/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts @@ -1,7 +1,7 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback, useState } from 'react'; -import { useEndpoint } from '../../../contexts/ServerContext'; import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; import { RecordList } from '../../../lib/lists/RecordList'; diff --git a/apps/meteor/client/components/RoomForeword.js b/apps/meteor/client/components/RoomForeword.tsx similarity index 65% rename from apps/meteor/client/components/RoomForeword.js rename to apps/meteor/client/components/RoomForeword.tsx index e6ed0be9c7dd..3993a1ddf194 100644 --- a/apps/meteor/client/components/RoomForeword.js +++ b/apps/meteor/client/components/RoomForeword.tsx @@ -1,24 +1,34 @@ +import { IRoom, isVoipRoom, isDirectMessageRoom } from '@rocket.chat/core-typings'; import { Avatar, Margins, Flex, Box, Tag } from '@rocket.chat/fuselage'; -import React, { useCallback } from 'react'; +import { useUser, useUserRoom, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement } from 'react'; -import { Rooms, Users } from '../../app/models/client'; +import { Users } from '../../app/models/client'; import { getUserAvatarURL } from '../../app/utils/client'; -import { useTranslation } from '../contexts/TranslationContext'; -import { useUser } from '../contexts/UserContext'; -import { useReactiveValue } from '../hooks/useReactiveValue'; import { VoipRoomForeword } from './voip/room/VoipRoomForeword'; -const RoomForeword = ({ _id, rid = _id }) => { +type RoomForewordProps = { _id: IRoom['_id']; rid?: IRoom['_id'] } | { rid: IRoom['_id']; _id?: IRoom['_id'] }; + +const RoomForeword = ({ _id, rid }: RoomForewordProps): ReactElement | null => { + const roomId = _id || rid; + if (!roomId) { + throw new Error('Room id required - RoomForeword'); + } + const t = useTranslation(); const user = useUser(); - const room = useReactiveValue(useCallback(() => Rooms.findOne({ _id: rid }), [rid])); + const room = useUserRoom(roomId); + + if (!room) { + return null; + } - if (room?.t === 'v') { + if (isVoipRoom(room)) { return ; } - if (room?.t !== 'd') { + if (!isDirectMessageRoom(room)) { return ( {t('Start_of_conversation')} @@ -26,8 +36,8 @@ const RoomForeword = ({ _id, rid = _id }) => { ); } - const usernames = room.usernames.filter((username) => username !== user.username); - if (usernames.length < 1) { + const usernames = room.usernames?.filter((username) => username !== user?.username); + if (!usernames || usernames.length < 1) { return null; } diff --git a/apps/meteor/client/components/Sidebar/ItemsAssembler.js b/apps/meteor/client/components/Sidebar/ItemsAssembler.js index 798875bc66bb..98e64fe7e30f 100644 --- a/apps/meteor/client/components/Sidebar/ItemsAssembler.js +++ b/apps/meteor/client/components/Sidebar/ItemsAssembler.js @@ -1,6 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import Sidebar from './Sidebar'; const ItemsAssembler = ({ items, currentPath }) => { diff --git a/apps/meteor/client/components/Sidebar/NavigationItem.js b/apps/meteor/client/components/Sidebar/NavigationItem.js index c4bab5255d38..01bd0b415e0a 100644 --- a/apps/meteor/client/components/Sidebar/NavigationItem.js +++ b/apps/meteor/client/components/Sidebar/NavigationItem.js @@ -1,7 +1,7 @@ import { Box, Icon, Tag } from '@rocket.chat/fuselage'; +import { useRoutePath } from '@rocket.chat/ui-contexts'; import React, { memo, useMemo } from 'react'; -import { useRoutePath } from '../../contexts/RouterContext'; import Sidebar from './Sidebar'; const NavigationItem = ({ permissionGranted, pathGroup, pathSection, icon, label, currentPath, tag }) => { diff --git a/apps/meteor/client/components/SortList/GroupingList.tsx b/apps/meteor/client/components/SortList/GroupingList.tsx index 443e65a03792..686b61336bdd 100644 --- a/apps/meteor/client/components/SortList/GroupingList.tsx +++ b/apps/meteor/client/components/SortList/GroupingList.tsx @@ -1,9 +1,7 @@ import { CheckBox, OptionTitle } from '@rocket.chat/fuselage'; +import { useUserPreference, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, ReactElement } from 'react'; -import { useMethod } from '../../contexts/ServerContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUserPreference } from '../../contexts/UserContext'; import ListItem from '../Sidebar/ListItem'; const style = { diff --git a/apps/meteor/client/components/SortList/SortModeList.tsx b/apps/meteor/client/components/SortList/SortModeList.tsx index 2e430609d3d7..0b42a73ce5a8 100644 --- a/apps/meteor/client/components/SortList/SortModeList.tsx +++ b/apps/meteor/client/components/SortList/SortModeList.tsx @@ -1,9 +1,7 @@ import { RadioButton, OptionTitle } from '@rocket.chat/fuselage'; +import { useUserPreference, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useCallback } from 'react'; -import { useMethod } from '../../contexts/ServerContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUserPreference } from '../../contexts/UserContext'; import ListItem from '../Sidebar/ListItem'; const style = { diff --git a/apps/meteor/client/components/SortList/ViewModeList.tsx b/apps/meteor/client/components/SortList/ViewModeList.tsx index f0dbe4751e78..617e3f123c43 100644 --- a/apps/meteor/client/components/SortList/ViewModeList.tsx +++ b/apps/meteor/client/components/SortList/ViewModeList.tsx @@ -1,9 +1,7 @@ import { ToggleSwitch, RadioButton, OptionTitle } from '@rocket.chat/fuselage'; +import { useUserPreference, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, ReactElement } from 'react'; -import { useMethod } from '../../contexts/ServerContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUserPreference } from '../../contexts/UserContext'; import ListItem from '../Sidebar/ListItem'; const style = { diff --git a/apps/meteor/client/components/TextCopy.js b/apps/meteor/client/components/TextCopy.tsx similarity index 74% rename from apps/meteor/client/components/TextCopy.js rename to apps/meteor/client/components/TextCopy.tsx index c4425f430a85..89d4300687f1 100644 --- a/apps/meteor/client/components/TextCopy.js +++ b/apps/meteor/client/components/TextCopy.tsx @@ -1,16 +1,19 @@ import { Box, Icon, Button, Scrollable } from '@rocket.chat/fuselage'; -import React, { useCallback } from 'react'; +import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback, ComponentProps, ReactElement } from 'react'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; -import { useTranslation } from '../contexts/TranslationContext'; - -const defaultWrapperRenderer = (text) => ( +const defaultWrapperRenderer = (text: string): ReactElement => ( {text} ); -const TextCopy = ({ text, wrapper = defaultWrapperRenderer, ...props }) => { +type TextCopyProps = { + text: string; + wrapper?: (text: string) => ReactElement; +} & ComponentProps; + +const TextCopy = ({ text, wrapper = defaultWrapperRenderer, ...props }: TextCopyProps): ReactElement => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx b/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx index c29cd9be72e9..fa61a2a3dd12 100644 --- a/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx +++ b/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx @@ -1,10 +1,8 @@ import { Box, TextInput, Icon } from '@rocket.chat/fuselage'; import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useState, ChangeEvent } from 'react'; -import { useEndpoint } from '../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; import GenericModal from '../GenericModal'; import { Method, OnConfirm } from './TwoFactorModal'; diff --git a/apps/meteor/client/components/TwoFactorModal/TwoFactorPasswordModal.tsx b/apps/meteor/client/components/TwoFactorModal/TwoFactorPasswordModal.tsx index 2324e9a82616..c9c376b05dbd 100644 --- a/apps/meteor/client/components/TwoFactorModal/TwoFactorPasswordModal.tsx +++ b/apps/meteor/client/components/TwoFactorModal/TwoFactorPasswordModal.tsx @@ -1,8 +1,8 @@ import { Box, PasswordInput, Icon } from '@rocket.chat/fuselage'; import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useState, ChangeEvent, Ref } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import GenericModal from '../GenericModal'; import { Method, OnConfirm } from './TwoFactorModal'; diff --git a/apps/meteor/client/components/TwoFactorModal/TwoFactorTotpModal.tsx b/apps/meteor/client/components/TwoFactorModal/TwoFactorTotpModal.tsx index d69500115fe4..8d514ec6dbc2 100644 --- a/apps/meteor/client/components/TwoFactorModal/TwoFactorTotpModal.tsx +++ b/apps/meteor/client/components/TwoFactorModal/TwoFactorTotpModal.tsx @@ -1,8 +1,8 @@ import { Box, TextInput, Icon } from '@rocket.chat/fuselage'; import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useState, ChangeEvent } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import GenericModal from '../GenericModal'; import { Method, OnConfirm } from './TwoFactorModal'; diff --git a/apps/meteor/client/components/UrlChangeModal.tsx b/apps/meteor/client/components/UrlChangeModal.tsx index 9f16444247dc..194759202533 100644 --- a/apps/meteor/client/components/UrlChangeModal.tsx +++ b/apps/meteor/client/components/UrlChangeModal.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; import GenericModal from './GenericModal'; type UrlChangeModalProps = { diff --git a/apps/meteor/client/components/UserCard/UserCard.tsx b/apps/meteor/client/components/UserCard/UserCard.tsx index 42a938ac81e4..0758c75bed11 100644 --- a/apps/meteor/client/components/UserCard/UserCard.tsx +++ b/apps/meteor/client/components/UserCard/UserCard.tsx @@ -1,8 +1,8 @@ import { css } from '@rocket.chat/css-in-js'; import { Box, ActionButton, Skeleton } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { forwardRef, ReactNode, ComponentProps } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import MarkdownText from '../MarkdownText'; import * as Status from '../UserStatus'; import UserAvatar from '../avatar/UserAvatar'; @@ -70,7 +70,7 @@ const UserCard = forwardRef(function UserCard( const t = useTranslation(); return ( - + {!username ? : } {actions && ( diff --git a/apps/meteor/client/components/UserStatus/UserStatus.tsx b/apps/meteor/client/components/UserStatus/UserStatus.tsx index fde47403baa0..046903e840f1 100644 --- a/apps/meteor/client/components/UserStatus/UserStatus.tsx +++ b/apps/meteor/client/components/UserStatus/UserStatus.tsx @@ -1,8 +1,7 @@ import { StatusBullet } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, ComponentProps, ReactElement } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; - export type UserStatusProps = { small?: boolean; statusText?: string; diff --git a/apps/meteor/client/components/UserStatusMenu.js b/apps/meteor/client/components/UserStatusMenu.js deleted file mode 100644 index a95dda44fd58..000000000000 --- a/apps/meteor/client/components/UserStatusMenu.js +++ /dev/null @@ -1,71 +0,0 @@ -import { Button, PositionAnimated, Options, useCursor, Box } from '@rocket.chat/fuselage'; -import React, { useRef, useCallback, useState, useMemo, useEffect } from 'react'; - -import { useSetting } from '../contexts/SettingsContext'; -import { useTranslation } from '../contexts/TranslationContext'; -import { UserStatus } from './UserStatus'; - -const UserStatusMenu = ({ onChange, optionWidth = undefined, initialStatus = 'offline', placement = 'bottom-end', ...props }) => { - const t = useTranslation(); - const [status, setStatus] = useState(initialStatus); - const allowInvisibleStatus = useSetting('Accounts_AllowInvisibleStatusOption'); - - const options = useMemo(() => { - const renderOption = (status, label) => ( - - - - - {label} - - ); - - const statuses = [ - ['online', renderOption('online', t('Online'))], - ['away', renderOption('away', t('Away'))], - ['busy', renderOption('busy', t('Busy'))], - ]; - - if (allowInvisibleStatus) { - statuses.push(['offline', renderOption('offline', t('Invisible'))]); - } - - return statuses; - }, [t, allowInvisibleStatus]); - - const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = useCursor(-1, options, ([selected], [, hide]) => { - setStatus(selected); - reset(); - hide(); - }); - - const ref = useRef(); - const onClick = useCallback(() => { - ref.current.focus() & show(); - ref.current.classList.add('focus-visible'); - }, [show]); - - const handleSelection = useCallback( - ([selected]) => { - setStatus(selected); - reset(); - hide(); - }, - [hide, reset], - ); - - useEffect(() => onChange(status), [status, onChange]); - - return ( - <> - - - - - - ); -}; - -export default UserStatusMenu; diff --git a/apps/meteor/client/components/UserStatusMenu.tsx b/apps/meteor/client/components/UserStatusMenu.tsx new file mode 100644 index 000000000000..c435f26cc8e1 --- /dev/null +++ b/apps/meteor/client/components/UserStatusMenu.tsx @@ -0,0 +1,90 @@ +import { UserStatus as UserStatusType } from '@rocket.chat/core-typings'; +import { Button, PositionAnimated, Options, useCursor, Box } from '@rocket.chat/fuselage'; +import type { Placements } from '@rocket.chat/fuselage-hooks'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement, ComponentProps, useRef, useCallback, useState, useMemo, useEffect } from 'react'; + +import { UserStatus } from './UserStatus'; + +type UserStatusMenuProps = { + margin: ComponentProps['margin']; + onChange: (type: UserStatusType) => void; + initialStatus?: UserStatusType; + optionWidth?: ComponentProps['width']; + placement?: Placements; +}; + +const UserStatusMenu = ({ + margin, + onChange, + initialStatus = UserStatusType.OFFLINE, + optionWidth = undefined, + placement = 'bottom-end', +}: UserStatusMenuProps): ReactElement => { + const t = useTranslation(); + const [status, setStatus] = useState(initialStatus); + const allowInvisibleStatus = useSetting('Accounts_AllowInvisibleStatusOption') as boolean; + + const options = useMemo(() => { + const renderOption = (status: UserStatusType, label: string): ReactElement => ( + + + + + {label} + + ); + + const statuses: Array<[value: UserStatusType, label: ReactElement]> = [ + [UserStatusType.ONLINE, renderOption(UserStatusType.ONLINE, t('Online'))], + [UserStatusType.AWAY, renderOption(UserStatusType.AWAY, t('Away'))], + [UserStatusType.BUSY, renderOption(UserStatusType.BUSY, t('Busy'))], + ]; + + if (allowInvisibleStatus) { + statuses.push([UserStatusType.OFFLINE, renderOption(UserStatusType.OFFLINE, t('Invisible'))]); + } + + return statuses; + }, [t, allowInvisibleStatus]); + + const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = useCursor(-1, options, ([selected], [, hide]) => { + setStatus(selected); + reset(); + hide(); + }); + + const ref = useRef(null); + const onClick = useCallback(() => { + if (!ref?.current) { + return; + } + ref.current.focus(); + show(); + ref.current.classList.add('focus-visible'); + }, [show]); + + const handleSelection = useCallback( + ([selected]) => { + setStatus(selected); + reset(); + hide(); + }, + [hide, reset], + ); + + useEffect(() => onChange(status), [status, onChange]); + + return ( + <> + + + + + + ); +}; + +export default UserStatusMenu; diff --git a/apps/meteor/client/components/VerticalBar/VerticalBar.tsx b/apps/meteor/client/components/VerticalBar/VerticalBar.tsx index 6e34172c6cb2..725dd6e99571 100644 --- a/apps/meteor/client/components/VerticalBar/VerticalBar.tsx +++ b/apps/meteor/client/components/VerticalBar/VerticalBar.tsx @@ -1,8 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useLayoutSizes, useLayoutContextualBarPosition } from '@rocket.chat/ui-contexts'; import React, { FC, ComponentProps, memo } from 'react'; -import { useLayoutContextualBarPosition, useLayoutSizes } from '../../providers/LayoutProvider'; - const VerticalBar: FC> = ({ children, ...props }) => { const sizes = useLayoutSizes(); const position = useLayoutContextualBarPosition(); diff --git a/apps/meteor/client/components/VerticalBar/VerticalBarClose.tsx b/apps/meteor/client/components/VerticalBar/VerticalBarClose.tsx index a5ab5202e42b..e6d908d8150e 100644 --- a/apps/meteor/client/components/VerticalBar/VerticalBarClose.tsx +++ b/apps/meteor/client/components/VerticalBar/VerticalBarClose.tsx @@ -1,6 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, ComponentProps, ReactElement } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import VerticalBarAction from './VerticalBarAction'; type VerticalBarCloseProps = Partial>; diff --git a/apps/meteor/client/components/avatar/RoomAvatar.tsx b/apps/meteor/client/components/avatar/RoomAvatar.tsx index d2c59721f88c..0c4a1105a721 100644 --- a/apps/meteor/client/components/avatar/RoomAvatar.tsx +++ b/apps/meteor/client/components/avatar/RoomAvatar.tsx @@ -1,13 +1,13 @@ +import { useRoomAvatarPath } from '@rocket.chat/ui-contexts'; import React, { memo, ReactElement } from 'react'; -import { useRoomAvatarPath } from '../../contexts/AvatarUrlContext'; import BaseAvatar from './BaseAvatar'; // TODO: frontend chapter day - Remove inline Styling type RoomAvatarProps = { /* @deprecated */ - size?: 'x16' | 'x20' | 'x28' | 'x36' | 'x40' | 'x124'; + size?: 'x16' | 'x20' | 'x28' | 'x36' | 'x40' | 'x124' | 'x332'; /* @deprecated */ url?: string; diff --git a/apps/meteor/client/components/avatar/RoomAvatarEditor.js b/apps/meteor/client/components/avatar/RoomAvatarEditor.tsx similarity index 76% rename from apps/meteor/client/components/avatar/RoomAvatarEditor.js rename to apps/meteor/client/components/avatar/RoomAvatarEditor.tsx index b3105d2f4009..9f51e36b56eb 100644 --- a/apps/meteor/client/components/avatar/RoomAvatarEditor.js +++ b/apps/meteor/client/components/avatar/RoomAvatarEditor.tsx @@ -1,21 +1,30 @@ +import { IRoom } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Box, Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React, { useEffect } from 'react'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useEffect, ReactElement } from 'react'; import { getAvatarURL } from '../../../app/utils/lib/getAvatarURL'; -import { useTranslation } from '../../contexts/TranslationContext'; import { useFileInput } from '../../hooks/useFileInput'; import RoomAvatar from './RoomAvatar'; -const RoomAvatarEditor = ({ room, roomAvatar, onChangeAvatar = () => {}, ...props }) => { +type RoomAvatarEditorProps = { + room: IRoom; + roomAvatar?: string; + onChangeAvatar: (url: string | null) => void; +}; + +const RoomAvatarEditor = ({ room, roomAvatar, onChangeAvatar }: RoomAvatarEditorProps): ReactElement => { const t = useTranslation(); const handleChangeAvatar = useMutableCallback((file) => { const reader = new FileReader(); reader.readAsDataURL(file); - reader.onloadend = () => { - onChangeAvatar(reader.result); + reader.onloadend = (): void => { + if (typeof reader.result === 'string') { + onChangeAvatar(reader.result); + } }; }); @@ -32,7 +41,7 @@ const RoomAvatarEditor = ({ room, roomAvatar, onChangeAvatar = () => {}, ...prop const defaultUrl = room.prid ? getAvatarURL({ roomId: room.prid }) : getAvatarURL({ username: `@${room.name}` }); // Discussions inherit avatars from the parent room return ( - + & { diff --git a/apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js b/apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js index 106d36ece21f..0868c9a4a422 100644 --- a/apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js +++ b/apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js @@ -1,9 +1,7 @@ import { Box, Button, Icon, TextInput, Margins, Avatar } from '@rocket.chat/fuselage'; +import { useToastMessageDispatch, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback } from 'react'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useFileInput } from '../../../hooks/useFileInput'; import UserAvatar from '../UserAvatar'; import UserAvatarSuggestions from './UserAvatarSuggestions'; diff --git a/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.stories.tsx b/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.stories.tsx index 472bfba126bb..fe66879b8b6d 100644 --- a/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.stories.tsx +++ b/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.stories.tsx @@ -1,9 +1,9 @@ +import { ConnectionStatusContext } from '@rocket.chat/ui-contexts'; import { action } from '@storybook/addon-actions'; import { Title, Description, Stories } from '@storybook/addon-docs'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React, { ContextType, ReactElement } from 'react'; -import { ConnectionStatusContext } from '../../contexts/ConnectionStatusContext'; import ConnectionStatusBar from './ConnectionStatusBar'; export default { diff --git a/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.tsx b/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.tsx index 801c62768624..2dc854547efc 100644 --- a/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.tsx +++ b/apps/meteor/client/components/connectionStatus/ConnectionStatusBar.tsx @@ -1,8 +1,7 @@ import { Icon } from '@rocket.chat/fuselage'; +import { useConnectionStatus, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect, useRef, useState, MouseEventHandler, FC } from 'react'; -import { useConnectionStatus } from '../../contexts/ConnectionStatusContext'; -import { useTranslation } from '../../contexts/TranslationContext'; import './ConnectionStatusBar.styles.css'; // TODO: frontend chapter day - fix unknown translation keys diff --git a/apps/meteor/client/components/voip/composer/NotAvailableOnCall/NotAvailableOnCall.tsx b/apps/meteor/client/components/voip/composer/NotAvailableOnCall/NotAvailableOnCall.tsx index d4e3c6dbb206..c7ec9d722c06 100644 --- a/apps/meteor/client/components/voip/composer/NotAvailableOnCall/NotAvailableOnCall.tsx +++ b/apps/meteor/client/components/voip/composer/NotAvailableOnCall/NotAvailableOnCall.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import NotAvailableContent from './NotAvailableContent'; import NotAvailableContentWrapper from './NotAvailableContentWrapper'; diff --git a/apps/meteor/client/components/voip/modal/WrapUpCallModal.tsx b/apps/meteor/client/components/voip/modal/WrapUpCallModal.tsx index 49e585a449ee..104fd5427070 100644 --- a/apps/meteor/client/components/voip/modal/WrapUpCallModal.tsx +++ b/apps/meteor/client/components/voip/modal/WrapUpCallModal.tsx @@ -1,10 +1,9 @@ import { Button, ButtonGroup, Field, Modal, TextAreaInput } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useEffect } from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; import { useCallCloseRoom } from '../../../contexts/CallContext'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import Tags from '../../Omnichannel/Tags'; type WrapUpCallPayload = { diff --git a/apps/meteor/client/components/voip/room/VoipRoomForeword.tsx b/apps/meteor/client/components/voip/room/VoipRoomForeword.tsx index 352c07b5b106..94046bd40a5a 100644 --- a/apps/meteor/client/components/voip/room/VoipRoomForeword.tsx +++ b/apps/meteor/client/components/voip/room/VoipRoomForeword.tsx @@ -1,9 +1,9 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { Avatar, Box, Tag } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; import { getUserAvatarURL } from '../../../../app/utils/client'; -import { useTranslation } from '../../../contexts/TranslationContext'; export const VoipRoomForeword = ({ room }: { room: IRoom }): ReactElement => { const t = useTranslation(); diff --git a/apps/meteor/client/components/withDoNotAskAgain.tsx b/apps/meteor/client/components/withDoNotAskAgain.tsx index 916b1bbd6087..c66f31be61ab 100644 --- a/apps/meteor/client/components/withDoNotAskAgain.tsx +++ b/apps/meteor/client/components/withDoNotAskAgain.tsx @@ -1,9 +1,7 @@ import { Box, CheckBox } from '@rocket.chat/fuselage'; +import { useUserPreference, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, FC, ReactElement, ComponentType } from 'react'; -import { useMethod } from '../contexts/ServerContext'; -import { useTranslation } from '../contexts/TranslationContext'; -import { useUserPreference } from '../contexts/UserContext'; import { DontAskAgainList } from '../hooks/useDontAskAgain'; type DoNotAskAgainProps = { diff --git a/apps/meteor/client/contexts/AuthorizationContext.ts b/apps/meteor/client/contexts/AuthorizationContext.ts deleted file mode 100644 index 4e5e8c44ce93..000000000000 --- a/apps/meteor/client/contexts/AuthorizationContext.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { IRole } from '@rocket.chat/core-typings'; -import { Emitter } from '@rocket.chat/emitter'; -import { createContext, useContext, useMemo, useCallback } from 'react'; -import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; - -type IRoles = { [_id: string]: IRole }; - -export class RoleStore extends Emitter<{ - change: IRoles; -}> { - roles: IRoles = {}; -} - -export type AuthorizationContextValue = { - queryPermission(permission: string | Mongo.ObjectID, scope?: string | Mongo.ObjectID): Subscription; - queryAtLeastOnePermission(permission: (string | Mongo.ObjectID)[], scope?: string | Mongo.ObjectID): Subscription; - queryAllPermissions(permission: (string | Mongo.ObjectID)[], scope?: string | Mongo.ObjectID): Subscription; - queryRole(role: string | Mongo.ObjectID): Subscription; - roleStore: RoleStore; -}; - -export const AuthorizationContext = createContext({ - queryPermission: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryAtLeastOnePermission: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryAllPermissions: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryRole: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - roleStore: new RoleStore(), -}); - -export const usePermission = (permission: string | Mongo.ObjectID, scope?: string | Mongo.ObjectID): boolean => { - const { queryPermission } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryPermission(permission, scope), [queryPermission, permission, scope]); - return useSubscription(subscription); -}; - -export const useAtLeastOnePermission = (permissions: (string | Mongo.ObjectID)[], scope?: string | Mongo.ObjectID): boolean => { - const { queryAtLeastOnePermission } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryAtLeastOnePermission(permissions, scope), [queryAtLeastOnePermission, permissions, scope]); - return useSubscription(subscription); -}; - -export const useAllPermissions = (permissions: (string | Mongo.ObjectID)[], scope?: string | Mongo.ObjectID): boolean => { - const { queryAllPermissions } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryAllPermissions(permissions, scope), [queryAllPermissions, permissions, scope]); - return useSubscription(subscription); -}; - -export const useRolesDescription = (): ((ids: Array) => [string]) => { - const { roleStore } = useContext(AuthorizationContext); - - const subscription = useMemo( - () => ({ - getCurrentValue: (): IRoles => roleStore.roles, - subscribe: (callback: () => void): (() => void) => { - roleStore.on('change', callback); - return (): void => { - roleStore.off('change', callback); - }; - }, - }), - [roleStore], - ); - - const roles = useSubscription(subscription); - - return useCallback((values) => values.map((role) => roles[role]?.description || roles[role]?.name || role), [roles]) as ( - ids: Array, - ) => [string]; -}; - -export const useRole = (role: string | Mongo.ObjectID): boolean => { - const { queryRole } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryRole(role), [queryRole, role]); - return useSubscription(subscription); -}; diff --git a/apps/meteor/client/contexts/AvatarUrlContext.ts b/apps/meteor/client/contexts/AvatarUrlContext.ts deleted file mode 100644 index 985346f774ff..000000000000 --- a/apps/meteor/client/contexts/AvatarUrlContext.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createContext, useContext } from 'react'; - -const dummy = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2Oora39DwAFaQJ3y3rKeAAAAABJRU5ErkJggg=='; - -type AvatarContextValue = { - getUserPathAvatar: (uid: string, etag?: string) => string; - getRoomPathAvatar: (...args: any) => string; -}; -const AvatarUrlContextValueDefault: AvatarContextValue = { - getUserPathAvatar: () => dummy, - getRoomPathAvatar: () => dummy, -}; - -export const AvatarUrlContext = createContext(AvatarUrlContextValueDefault); - -export const useRoomAvatarPath = (): ((...args: any) => string) => useContext(AvatarUrlContext).getRoomPathAvatar; - -export const useUserAvatarPath = (): ((uid: string, etag?: string) => string) => useContext(AvatarUrlContext).getUserPathAvatar; diff --git a/apps/meteor/client/contexts/ModalContext.ts b/apps/meteor/client/contexts/ModalContext.ts deleted file mode 100644 index 0f36c6ece8ae..000000000000 --- a/apps/meteor/client/contexts/ModalContext.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext, useContext, ReactNode } from 'react'; - -import { modal } from '../../app/ui-utils/client'; - -type ModalContextValue = typeof modal & { - setModal: (modal: ReactNode) => void; -}; - -export const ModalContext = createContext( - Object.assign(modal, { - setModal: () => undefined, - }), -); - -export const useModal = (): ModalContextValue => useContext(ModalContext); - -export const useSetModal = (): ((modal?: ReactNode) => void) => useContext(ModalContext).setModal; diff --git a/apps/meteor/client/contexts/OmnichannelContext.ts b/apps/meteor/client/contexts/OmnichannelContext.ts index fed64e9f1383..f9cf3c53b60b 100644 --- a/apps/meteor/client/contexts/OmnichannelContext.ts +++ b/apps/meteor/client/contexts/OmnichannelContext.ts @@ -1,5 +1,5 @@ -import { OmichannelRoutingConfig, Inquiries } from '@rocket.chat/core-typings'; -import { createContext, useContext } from 'react'; +import type { OmichannelRoutingConfig, Inquiries } from '@rocket.chat/core-typings'; +import { createContext } from 'react'; export type OmnichannelContextValue = { inquiries: Inquiries; @@ -15,10 +15,3 @@ export const OmnichannelContext = createContext({ agentAvailable: false, showOmnichannelQueueLink: false, }); - -export const useOmnichannel = (): OmnichannelContextValue => useContext(OmnichannelContext); -export const useOmnichannelShowQueueLink = (): boolean => useOmnichannel().showOmnichannelQueueLink; -export const useOmnichannelRouteConfig = (): OmichannelRoutingConfig | undefined => useOmnichannel().routeConfig; -export const useOmnichannelAgentAvailable = (): boolean => useOmnichannel().agentAvailable; -export const useQueuedInquiries = (): Inquiries => useOmnichannel().inquiries; -export const useOmnichannelEnabled = (): boolean => useOmnichannel().enabled; diff --git a/apps/meteor/client/contexts/RouterContext.ts b/apps/meteor/client/contexts/RouterContext.ts deleted file mode 100644 index 9909c97d968a..000000000000 --- a/apps/meteor/client/contexts/RouterContext.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { createContext, useContext, useMemo } from 'react'; -import { useSubscription, Subscription } from 'use-subscription'; - -type RouteName = string; - -type RouteParameters = Record; - -type QueryStringParameters = Record; - -type RouteGroupName = string; - -export type RouterContextValue = { - queryRoutePath: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters: QueryStringParameters | undefined, - ) => Subscription; - queryRouteUrl: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters: QueryStringParameters | undefined, - ) => Subscription; - pushRoute: (name: RouteName, parameters: RouteParameters | undefined, queryStringParameters: QueryStringParameters | undefined) => void; - replaceRoute: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters: QueryStringParameters | undefined, - ) => void; - queryRouteParameter: (name: string) => Subscription; - queryQueryStringParameter: (name: string) => Subscription; - queryCurrentRoute: () => Subscription<[RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?]>; -}; - -export const RouterContext = createContext({ - queryRoutePath: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryRouteUrl: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - pushRoute: () => undefined, - replaceRoute: () => undefined, - queryRouteParameter: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryQueryStringParameter: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryCurrentRoute: () => ({ - getCurrentValue: (): [undefined, {}, {}, undefined] => [undefined, {}, {}, undefined], - subscribe: () => (): void => undefined, - }), -}); - -type Route = { - getPath: (parameters?: RouteParameters, queryStringParameters?: QueryStringParameters) => string | undefined; - getUrl: (parameters?: RouteParameters, queryStringParameters?: QueryStringParameters) => string | undefined; - push: (parameters?: RouteParameters, queryStringParameters?: QueryStringParameters) => void; - replace: (parameters?: RouteParameters, queryStringParameters?: QueryStringParameters) => void; -}; - -export const useRoute = (name: string): Route => { - const { queryRoutePath, queryRouteUrl, pushRoute, replaceRoute } = useContext(RouterContext); - - return useMemo( - () => ({ - getPath: (parameters, queryStringParameters): string | undefined => - queryRoutePath(name, parameters, queryStringParameters).getCurrentValue(), - getUrl: (parameters, queryStringParameters): ReturnType => - queryRouteUrl(name, parameters, queryStringParameters).getCurrentValue(), - push: (parameters, queryStringParameters): ReturnType => pushRoute(name, parameters, queryStringParameters), - replace: (parameters, queryStringParameters): ReturnType => replaceRoute(name, parameters, queryStringParameters), - }), - [queryRoutePath, queryRouteUrl, name, pushRoute, replaceRoute], - ); -}; - -export const useRoutePath = ( - name: string, - parameters?: RouteParameters, - queryStringParameters?: QueryStringParameters, -): string | undefined => { - const { queryRoutePath } = useContext(RouterContext); - - return useSubscription( - useMemo(() => queryRoutePath(name, parameters, queryStringParameters), [queryRoutePath, name, parameters, queryStringParameters]), - ); -}; - -export const useRouteUrl = ( - name: string, - parameters?: RouteParameters, - queryStringParameters?: QueryStringParameters, -): string | undefined => { - const { queryRouteUrl } = useContext(RouterContext); - - return useSubscription( - useMemo(() => queryRouteUrl(name, parameters, queryStringParameters), [queryRouteUrl, name, parameters, queryStringParameters]), - ); -}; - -export const useRouteParameter = (name: string): string | undefined => { - const { queryRouteParameter } = useContext(RouterContext); - - return useSubscription(useMemo(() => queryRouteParameter(name), [queryRouteParameter, name])); -}; - -export const useQueryStringParameter = (name: string): string | undefined => { - const { queryQueryStringParameter } = useContext(RouterContext); - - return useSubscription(useMemo(() => queryQueryStringParameter(name), [queryQueryStringParameter, name])); -}; - -export const useCurrentRoute = (): [RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?] => { - const { queryCurrentRoute } = useContext(RouterContext); - - return useSubscription(useMemo(() => queryCurrentRoute(), [queryCurrentRoute])); -}; diff --git a/apps/meteor/client/contexts/ServerContext/ServerContext.ts b/apps/meteor/client/contexts/ServerContext/ServerContext.ts deleted file mode 100644 index 395b5b344f3a..000000000000 --- a/apps/meteor/client/contexts/ServerContext/ServerContext.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { IServerInfo, Serialized } from '@rocket.chat/core-typings'; -import type { Method, PathFor, OperationParams, MatchPathPattern, OperationResult, PathPattern } from '@rocket.chat/rest-typings'; -import { createContext, useCallback, useContext, useMemo } from 'react'; - -import { ServerMethodFunction, ServerMethodName, ServerMethodParameters, ServerMethodReturn, ServerMethods } from './methods'; - -export type UploadResult = { - success: boolean; - status: string; - [key: string]: unknown; -}; -type ServerContextValue = { - info?: IServerInfo; - absoluteUrl: (path: string) => string; - callMethod?: ( - methodName: MethodName, - ...args: ServerMethodParameters - ) => Promise>; - callEndpoint: >( - method: TMethod, - path: TPath, - params: Serialized>>, - ) => Promise>>>; - uploadToEndpoint: ( - endpoint: string, - params: any, - formData: any, - ) => - | Promise - | { - promise: Promise; - }; - getStream: (streamName: string, options?: {}) => (eventName: string, callback: (data: T) => void) => () => void; -}; - -export const ServerContext = createContext({ - info: undefined, - absoluteUrl: (path) => path, - callEndpoint: () => { - throw new Error('not implemented'); - }, - uploadToEndpoint: async () => { - throw new Error('not implemented'); - }, - getStream: () => () => (): void => undefined, -}); - -export const useServerInformation = (): IServerInfo => { - const { info } = useContext(ServerContext); - if (!info) { - throw new Error('useServerInformation: no info available'); - } - return info; -}; - -export const useAbsoluteUrl = (): ((path: string) => string) => useContext(ServerContext).absoluteUrl; - -export const useMethod = (methodName: MethodName): ServerMethodFunction => { - const { callMethod } = useContext(ServerContext); - - return useCallback( - (...args: ServerMethodParameters) => { - if (!callMethod) { - throw new Error(`cannot use useMethod(${methodName}) hook without a wrapping ServerContext`); - } - - return callMethod(methodName, ...args); - }, - [callMethod, methodName], - ); -}; - -type EndpointFunction = ( - params: void extends OperationParams ? void : Serialized>, -) => Promise>>; - -export const useEndpoint = >( - method: TMethod, - path: TPath, -): EndpointFunction> => { - const { callEndpoint } = useContext(ServerContext); - - return useCallback( - (params: Serialized>>) => callEndpoint(method, path, params), - [callEndpoint, path, method], - ); -}; - -export const useUpload = ( - endpoint: string, -): ((params: any, formData: any) => Promise | { promise: Promise }) => { - const { uploadToEndpoint } = useContext(ServerContext); - return useCallback((params, formData: any) => uploadToEndpoint(endpoint, params, formData), [endpoint, uploadToEndpoint]); -}; - -export const useStream = (streamName: string, options?: {}): ((eventName: string, callback: (data: T) => void) => () => void) => { - const { getStream } = useContext(ServerContext); - return useMemo(() => getStream(streamName, options), [getStream, streamName, options]); -}; diff --git a/apps/meteor/client/contexts/SessionContext.ts b/apps/meteor/client/contexts/SessionContext.ts deleted file mode 100644 index 4d12f2aa87ef..000000000000 --- a/apps/meteor/client/contexts/SessionContext.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; - -type SessionContextValue = { - query: (name: string) => Subscription; - dispatch: (name: string, value: unknown) => void; -}; - -export const SessionContext = createContext({ - query: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - dispatch: (): void => undefined, -}); - -export const useSession = (name: string): unknown => { - const { query } = useContext(SessionContext); - const subscription = useMemo(() => query(name), [query, name]); - return useSubscription(subscription); -}; - -export const useSessionDispatch = (name: string): ((value: unknown) => void) => { - const { dispatch } = useContext(SessionContext); - return useCallback((value) => dispatch(name, value), [dispatch, name]); -}; diff --git a/apps/meteor/client/contexts/SettingsContext.ts b/apps/meteor/client/contexts/SettingsContext.ts deleted file mode 100644 index 17c76b06d849..000000000000 --- a/apps/meteor/client/contexts/SettingsContext.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { SettingId, ISetting, GroupId, SectionName, TabId } from '@rocket.chat/core-typings'; -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; - -export type SettingsContextQuery = { - readonly _id?: SettingId[]; - readonly group?: GroupId; - readonly section?: SectionName; - readonly tab?: TabId; -}; - -export type SettingsContextValue = { - readonly hasPrivateAccess: boolean; - readonly isLoading: boolean; - readonly querySetting: (_id: SettingId) => Subscription; - readonly querySettings: (query: SettingsContextQuery) => Subscription; - readonly dispatch: (changes: Partial[]) => Promise; -}; - -export const SettingsContext = createContext({ - hasPrivateAccess: false, - isLoading: false, - querySetting: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - querySettings: () => ({ - getCurrentValue: (): ISetting[] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), - dispatch: async () => undefined, -}); - -export const useIsPrivilegedSettingsContext = (): boolean => useContext(SettingsContext).hasPrivateAccess; - -export const useIsSettingsContextLoading = (): boolean => useContext(SettingsContext).isLoading; - -export const useSettingStructure = (_id: SettingId): ISetting | undefined => { - const { querySetting } = useContext(SettingsContext); - const subscription = useMemo(() => querySetting(_id), [querySetting, _id]); - return useSubscription(subscription); -}; - -export const useSetting = (_id: SettingId): unknown | undefined => useSettingStructure(_id)?.value; - -export const useSettings = (query?: SettingsContextQuery): ISetting[] => { - const { querySettings } = useContext(SettingsContext); - const subscription = useMemo(() => querySettings(query ?? {}), [querySettings, query]); - return useSubscription(subscription); -}; - -export const useSettingsDispatch = (): ((changes: Partial[]) => Promise) => useContext(SettingsContext).dispatch; - -export const useSettingSetValue = (_id: SettingId): ((value: T) => Promise) => { - const dispatch = useSettingsDispatch(); - return useCallback((value: T) => dispatch([{ _id, value }]), [dispatch, _id]); -}; diff --git a/apps/meteor/client/contexts/SidebarContext.ts b/apps/meteor/client/contexts/SidebarContext.ts deleted file mode 100644 index 30182fbb7f74..000000000000 --- a/apps/meteor/client/contexts/SidebarContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createContext, useContext } from 'react'; - -type SidebarContextValue = [boolean, (open: boolean | ((isOpen: boolean) => boolean)) => void]; - -export const SidebarContext = createContext([false, (): void => undefined]); - -export const useSidebar = (): SidebarContextValue => useContext(SidebarContext); diff --git a/apps/meteor/client/contexts/TooltipContext.ts b/apps/meteor/client/contexts/TooltipContext.ts deleted file mode 100644 index 38c46f46fe4f..000000000000 --- a/apps/meteor/client/contexts/TooltipContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createContext, ReactElement, useContext } from 'react'; - -type TooltipPayload = ReactElement; - -type TooltipContextValue = { - open: (payload: TooltipPayload, anchor: HTMLElement) => void; - close: () => void; -}; - -export const TooltipContext = createContext({ - open: () => undefined, - close: () => undefined, -}); - -export const useTooltipOpen = (): TooltipContextValue['open'] => useContext(TooltipContext).open; -export const useTooltipClose = (): TooltipContextValue['close'] => useContext(TooltipContext).close; diff --git a/apps/meteor/client/contexts/UserContext.ts b/apps/meteor/client/contexts/UserContext.ts deleted file mode 100644 index f42934aae608..000000000000 --- a/apps/meteor/client/contexts/UserContext.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { FilterQuery } from 'mongodb'; -import { createContext, useContext, useMemo } from 'react'; -import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; - -import { useRoute } from './RouterContext'; - -type SubscriptionQuery = - | { - rid: string | Mongo.ObjectID; - } - | { - name: string; - } - | { - open: boolean; - } - | object; - -type Fields = { - [key: string]: boolean; -}; - -type Sort = { - [key: string]: -1 | 1 | number; -}; - -type FindOptions = { - fields?: Fields; - sort?: Sort; -}; - -type UserContextValue = { - userId: string | null; - user: IUser | null; - loginWithPassword: (user: string | object, password: string) => Promise; - logout: () => Promise; - queryPreference: (key: string | Mongo.ObjectID, defaultValue?: T) => Subscription; - querySubscription: (query: FilterQuery, fields?: Fields, sort?: Sort) => Subscription; - queryRoom: (query: FilterQuery, fields?: Fields, sort?: Sort) => Subscription; - querySubscriptions: (query: SubscriptionQuery, options?: FindOptions) => Subscription | []>; -}; - -export const UserContext = createContext({ - userId: null, - user: null, - loginWithPassword: async () => undefined, - logout: () => Promise.resolve(), - queryPreference: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - querySubscription: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryRoom: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - querySubscriptions: () => ({ - getCurrentValue: (): [] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), -}); - -export const useUserId = (): string | null => useContext(UserContext).userId; - -// TODO: Use IUser instead -export const useUser = (): IUser | null => useContext(UserContext).user; - -export const useLoginWithPassword = (): ((user: string | object, password: string) => Promise) => - useContext(UserContext).loginWithPassword; - -export const useLogout = (): (() => void) => { - const router = useRoute('home'); - const { logout } = useContext(UserContext); - - const handleLogout = useMutableCallback(() => { - logout(); - router.push({}); - }); - - return handleLogout; -}; - -export const useUserPreference = (key: string, defaultValue?: T): T | undefined => { - const { queryPreference } = useContext(UserContext); - const subscription = useMemo(() => queryPreference(key, defaultValue), [queryPreference, key, defaultValue]); - return useSubscription(subscription); -}; - -export const useUserSubscription = (rid: string, fields?: Fields): ISubscription | undefined => { - const { querySubscription } = useContext(UserContext); - const subscription = useMemo(() => querySubscription({ rid }, fields), [querySubscription, rid, fields]); - return useSubscription(subscription); -}; - -export const useUserRoom = (rid: string, fields?: Fields): IRoom | undefined => { - const { queryRoom } = useContext(UserContext); - const subscription = useMemo(() => queryRoom({ _id: rid }, fields), [queryRoom, rid, fields]); - return useSubscription(subscription); -}; - -export const useUserSubscriptions = (query: SubscriptionQuery, options?: FindOptions): Array | [] => { - const { querySubscriptions } = useContext(UserContext); - const subscription = useMemo(() => querySubscriptions(query, options), [querySubscriptions, query, options]); - return useSubscription(subscription); -}; - -export const useUserSubscriptionByName = (name: string, fields: Fields, sort?: Sort): ISubscription | undefined => { - const { querySubscription } = useContext(UserContext); - const subscription = useMemo(() => querySubscription({ name }, fields, sort), [querySubscription, name, fields, sort]); - return useSubscription(subscription); -}; diff --git a/apps/meteor/client/hooks/lists/useStreamUpdatesForMessageList.ts b/apps/meteor/client/hooks/lists/useStreamUpdatesForMessageList.ts index dcb4eb79bf81..b134bb090e30 100644 --- a/apps/meteor/client/hooks/lists/useStreamUpdatesForMessageList.ts +++ b/apps/meteor/client/hooks/lists/useStreamUpdatesForMessageList.ts @@ -1,7 +1,7 @@ import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import { useStream } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; -import { useStream } from '../../contexts/ServerContext'; import { MessageList } from '../../lib/lists/MessageList'; import { createFilterFromQuery, FieldExpression, Query } from '../../lib/minimongo'; diff --git a/apps/meteor/client/hooks/omnichannel/useOmnichannel.ts b/apps/meteor/client/hooks/omnichannel/useOmnichannel.ts new file mode 100644 index 000000000000..140df083dabe --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useOmnichannel.ts @@ -0,0 +1,5 @@ +import { useContext } from 'react'; + +import { OmnichannelContext, OmnichannelContextValue } from '../../contexts/OmnichannelContext'; + +export const useOmnichannel = (): OmnichannelContextValue => useContext(OmnichannelContext); diff --git a/apps/meteor/client/hooks/omnichannel/useOmnichannelAgentAvailable.ts b/apps/meteor/client/hooks/omnichannel/useOmnichannelAgentAvailable.ts new file mode 100644 index 000000000000..f00ee0116f07 --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useOmnichannelAgentAvailable.ts @@ -0,0 +1,3 @@ +import { useOmnichannel } from './useOmnichannel'; + +export const useOmnichannelAgentAvailable = (): boolean => useOmnichannel().agentAvailable; diff --git a/apps/meteor/client/hooks/omnichannel/useOmnichannelEnabled.ts b/apps/meteor/client/hooks/omnichannel/useOmnichannelEnabled.ts new file mode 100644 index 000000000000..47b461ee3e9c --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useOmnichannelEnabled.ts @@ -0,0 +1,3 @@ +import { useOmnichannel } from './useOmnichannel'; + +export const useOmnichannelEnabled = (): boolean => useOmnichannel().enabled; diff --git a/apps/meteor/client/hooks/omnichannel/useOmnichannelRouteConfig.ts b/apps/meteor/client/hooks/omnichannel/useOmnichannelRouteConfig.ts new file mode 100644 index 000000000000..ec1234d0a5d5 --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useOmnichannelRouteConfig.ts @@ -0,0 +1,5 @@ +import type { OmichannelRoutingConfig } from '@rocket.chat/core-typings'; + +import { useOmnichannel } from './useOmnichannel'; + +export const useOmnichannelRouteConfig = (): OmichannelRoutingConfig | undefined => useOmnichannel().routeConfig; diff --git a/apps/meteor/client/hooks/omnichannel/useOmnichannelShowQueueLink.ts b/apps/meteor/client/hooks/omnichannel/useOmnichannelShowQueueLink.ts new file mode 100644 index 000000000000..da32e9b11449 --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useOmnichannelShowQueueLink.ts @@ -0,0 +1,3 @@ +import { useOmnichannel } from './useOmnichannel'; + +export const useOmnichannelShowQueueLink = (): boolean => useOmnichannel().showOmnichannelQueueLink; diff --git a/apps/meteor/client/hooks/omnichannel/useQueuedInquiries.ts b/apps/meteor/client/hooks/omnichannel/useQueuedInquiries.ts new file mode 100644 index 000000000000..ed9f911a2ba5 --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useQueuedInquiries.ts @@ -0,0 +1,5 @@ +import type { Inquiries } from '@rocket.chat/core-typings'; + +import { useOmnichannel } from './useOmnichannel'; + +export const useQueuedInquiries = (): Inquiries => useOmnichannel().inquiries; diff --git a/apps/meteor/client/hooks/useClipboardWithToast.ts b/apps/meteor/client/hooks/useClipboardWithToast.ts index e862ec0b4f7c..51d5f4b66869 100644 --- a/apps/meteor/client/hooks/useClipboardWithToast.ts +++ b/apps/meteor/client/hooks/useClipboardWithToast.ts @@ -1,7 +1,5 @@ import { useClipboard, UseClipboardReturn, useMutableCallback } from '@rocket.chat/fuselage-hooks'; - -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; -import { useTranslation } from '../contexts/TranslationContext'; +import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; export default function useClipboardWithToast(text: string): UseClipboardReturn { const t = useTranslation(); diff --git a/apps/meteor/client/hooks/useDontAskAgain.ts b/apps/meteor/client/hooks/useDontAskAgain.ts index d073af6cc340..b0c926cd7dfd 100644 --- a/apps/meteor/client/hooks/useDontAskAgain.ts +++ b/apps/meteor/client/hooks/useDontAskAgain.ts @@ -1,4 +1,4 @@ -import { useUserPreference } from '../contexts/UserContext'; +import { useUserPreference } from '@rocket.chat/ui-contexts'; export type DontAskAgainList = Array<{ action: string; label: string }>; diff --git a/apps/meteor/client/hooks/useEmbeddedLayout.ts b/apps/meteor/client/hooks/useEmbeddedLayout.ts index 5563535d3ded..d326e221eb94 100644 --- a/apps/meteor/client/hooks/useEmbeddedLayout.ts +++ b/apps/meteor/client/hooks/useEmbeddedLayout.ts @@ -1,3 +1,3 @@ -import { useQueryStringParameter } from '../contexts/RouterContext'; +import { useQueryStringParameter } from '@rocket.chat/ui-contexts'; export const useEmbeddedLayout = (): boolean => useQueryStringParameter('layout') === 'embedded'; diff --git a/apps/meteor/client/hooks/useEndpointAction.ts b/apps/meteor/client/hooks/useEndpointAction.ts index 4ccb9b35815e..76c86d648dfe 100644 --- a/apps/meteor/client/hooks/useEndpointAction.ts +++ b/apps/meteor/client/hooks/useEndpointAction.ts @@ -1,10 +1,8 @@ import { Serialized } from '@rocket.chat/core-typings'; import type { MatchPathPattern, Method, OperationParams, OperationResult, PathFor } from '@rocket.chat/rest-typings'; +import { useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; -import { useEndpoint } from '../contexts/ServerContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; - export const useEndpointAction = >( method: TMethod, path: TPath, diff --git a/apps/meteor/client/hooks/useEndpointActionExperimental.ts b/apps/meteor/client/hooks/useEndpointActionExperimental.ts index 7b1c2014b016..45ed08906f71 100644 --- a/apps/meteor/client/hooks/useEndpointActionExperimental.ts +++ b/apps/meteor/client/hooks/useEndpointActionExperimental.ts @@ -1,10 +1,8 @@ import { Serialized } from '@rocket.chat/core-typings'; import type { MatchPathPattern, Method, OperationParams, OperationResult, PathFor } from '@rocket.chat/rest-typings'; +import { useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; -import { useEndpoint } from '../contexts/ServerContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; - export const useEndpointActionExperimental = >( method: TMethod, path: TPath, diff --git a/apps/meteor/client/hooks/useEndpointData.ts b/apps/meteor/client/hooks/useEndpointData.ts index e9e9d70aa29f..4c7a289da029 100644 --- a/apps/meteor/client/hooks/useEndpointData.ts +++ b/apps/meteor/client/hooks/useEndpointData.ts @@ -1,9 +1,8 @@ import { Serialized } from '@rocket.chat/core-typings'; import type { MatchPathPattern, OperationParams, OperationResult, PathFor } from '@rocket.chat/rest-typings'; +import { useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback, useEffect } from 'react'; -import { useEndpoint } from '../contexts/ServerContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; import { AsyncState, useAsyncState } from './useAsyncState'; export const useEndpointData = >( diff --git a/apps/meteor/client/hooks/useEndpointUpload.ts b/apps/meteor/client/hooks/useEndpointUpload.ts index 713397b04373..8a699171aa92 100644 --- a/apps/meteor/client/hooks/useEndpointUpload.ts +++ b/apps/meteor/client/hooks/useEndpointUpload.ts @@ -1,8 +1,6 @@ +import { useToastMessageDispatch, UploadResult, useUpload } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; -import { UploadResult, useUpload } from '../contexts/ServerContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; - export const useEndpointUpload = ( endpoint: string, params = {}, diff --git a/apps/meteor/client/hooks/useFileInput.js b/apps/meteor/client/hooks/useFileInput.ts similarity index 66% rename from apps/meteor/client/hooks/useFileInput.js rename to apps/meteor/client/hooks/useFileInput.ts index 60bbeb025d4e..4bb572318991 100644 --- a/apps/meteor/client/hooks/useFileInput.js +++ b/apps/meteor/client/hooks/useFileInput.ts @@ -1,8 +1,12 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useRef, useEffect } from 'react'; -export const useFileInput = (onSetFile, fileType = 'image/*', fileField = 'image') => { - const ref = useRef(); +export const useFileInput = ( + onSetFile: (file: FileList[number], formData: FormData) => void, + fileType = 'image/*', + fileField = 'image', +): [onClick: () => void, reset: () => void] => { + const ref = useRef(); useEffect(() => { const fileInput = document.createElement('input'); @@ -11,8 +15,8 @@ export const useFileInput = (onSetFile, fileType = 'image/*', fileField = 'image document.body.appendChild(fileInput); ref.current = fileInput; - return () => { - ref.current = null; + return (): void => { + ref.current = undefined; fileInput.remove(); }; }, []); @@ -32,7 +36,10 @@ export const useFileInput = (onSetFile, fileType = 'image/*', fileField = 'image return; } - const handleFiles = () => { + const handleFiles = (): void => { + if (!fileInput?.files?.length) { + return; + } const formData = new FormData(); formData.append(fileField, fileInput.files[0]); onSetFile(fileInput.files[0], formData); @@ -40,14 +47,16 @@ export const useFileInput = (onSetFile, fileType = 'image/*', fileField = 'image fileInput.addEventListener('change', handleFiles, false); - return () => { + return (): void => { fileInput.removeEventListener('change', handleFiles, false); }; }, [fileField, fileType, onSetFile]); - const onClick = useMutableCallback(() => ref.current.click()); + const onClick = useMutableCallback(() => ref?.current?.click()); const reset = useMutableCallback(() => { - ref.current.value = ''; + if (ref.current) { + ref.current.value = ''; + } }); return [onClick, reset]; }; diff --git a/apps/meteor/client/hooks/useFormatDate.ts b/apps/meteor/client/hooks/useFormatDate.ts index a58b3a30bfeb..f3390f5ac879 100644 --- a/apps/meteor/client/hooks/useFormatDate.ts +++ b/apps/meteor/client/hooks/useFormatDate.ts @@ -1,8 +1,7 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import { useCallback } from 'react'; -import { useSetting } from '../contexts/SettingsContext'; - export const useFormatDate = (): ((time: string | Date | number) => string) => { const format = useSetting('Message_DateFormat'); return useCallback((time) => moment(time).format(String(format)), [format]); diff --git a/apps/meteor/client/hooks/useFormatDateAndTime.ts b/apps/meteor/client/hooks/useFormatDateAndTime.ts index 0bda2b2350e4..8028a9ac3046 100644 --- a/apps/meteor/client/hooks/useFormatDateAndTime.ts +++ b/apps/meteor/client/hooks/useFormatDateAndTime.ts @@ -1,9 +1,7 @@ +import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; import moment, { MomentInput } from 'moment'; import { useCallback } from 'react'; -import { useSetting } from '../contexts/SettingsContext'; -import { useUserPreference } from '../contexts/UserContext'; - type UseFormatDateAndTimeParams = { withSeconds?: boolean; }; diff --git a/apps/meteor/client/hooks/useFormatDuration.ts b/apps/meteor/client/hooks/useFormatDuration.ts index 76d870e4cbe4..61215074dfbd 100644 --- a/apps/meteor/client/hooks/useFormatDuration.ts +++ b/apps/meteor/client/hooks/useFormatDuration.ts @@ -1,7 +1,6 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; -import { useTranslation } from '../contexts/TranslationContext'; - export const useFormatDuration = (): ((duration: number) => string) => { const t = useTranslation(); diff --git a/apps/meteor/client/hooks/useFormatTime.ts b/apps/meteor/client/hooks/useFormatTime.ts index 092773ec2eb6..b1f7e2288ab8 100644 --- a/apps/meteor/client/hooks/useFormatTime.ts +++ b/apps/meteor/client/hooks/useFormatTime.ts @@ -1,9 +1,7 @@ +import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import { useCallback } from 'react'; -import { useSetting } from '../contexts/SettingsContext'; -import { useUserPreference } from '../contexts/UserContext'; - const dayFormat = ['h:mm A', 'H:mm'] as const; export const useFormatTime = (): ((input: moment.MomentInput) => string) => { diff --git a/apps/meteor/client/hooks/useLocalePercentage.ts b/apps/meteor/client/hooks/useLocalePercentage.ts index 23323a077911..902ae7011e7f 100644 --- a/apps/meteor/client/hooks/useLocalePercentage.ts +++ b/apps/meteor/client/hooks/useLocalePercentage.ts @@ -1,4 +1,5 @@ -import { useLanguage } from '../contexts/TranslationContext'; +import { useLanguage } from '@rocket.chat/ui-contexts'; + import { getLocalePercentage } from '../lib/getLocalePercentage'; export const useLocalePercentage = (total: number, fraction: number, decimalCount: number | undefined): string => { diff --git a/apps/meteor/client/hooks/useMethodData.ts b/apps/meteor/client/hooks/useMethodData.ts index f875b9e15485..3c7388cc17a6 100644 --- a/apps/meteor/client/hooks/useMethodData.ts +++ b/apps/meteor/client/hooks/useMethodData.ts @@ -1,8 +1,7 @@ import type { Awaited } from '@rocket.chat/core-typings'; +import { useToastMessageDispatch, ServerMethodFunction, ServerMethodParameters, ServerMethods, useMethod } from '@rocket.chat/ui-contexts'; import { useCallback, useEffect } from 'react'; -import { ServerMethodFunction, ServerMethodParameters, ServerMethods, useMethod } from '../contexts/ServerContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; import { AsyncState, useAsyncState } from './useAsyncState'; export const useMethodData = >>>( diff --git a/apps/meteor/client/hooks/usePolledMethodData.ts b/apps/meteor/client/hooks/usePolledMethodData.ts index da5197f6b156..dce09ebba935 100644 --- a/apps/meteor/client/hooks/usePolledMethodData.ts +++ b/apps/meteor/client/hooks/usePolledMethodData.ts @@ -1,7 +1,7 @@ import { Awaited } from '@rocket.chat/core-typings'; +import { ServerMethodFunction, ServerMethodParameters, ServerMethods } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; -import { ServerMethodFunction, ServerMethodParameters, ServerMethods } from '../contexts/ServerContext'; import { AsyncState } from './useAsyncState'; import { useMethodData } from './useMethodData'; diff --git a/apps/meteor/client/hooks/useUpdateAvatar.js b/apps/meteor/client/hooks/useUpdateAvatar.ts similarity index 60% rename from apps/meteor/client/hooks/useUpdateAvatar.js rename to apps/meteor/client/hooks/useUpdateAvatar.ts index 57215793ee0d..ae9eb2336d3e 100644 --- a/apps/meteor/client/hooks/useUpdateAvatar.js +++ b/apps/meteor/client/hooks/useUpdateAvatar.ts @@ -1,14 +1,32 @@ +import { IUser } from '@rocket.chat/core-typings'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import { useMemo, useCallback } from 'react'; -import { useMethod } from '../contexts/ServerContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; -import { useTranslation } from '../contexts/TranslationContext'; import { useEndpointAction } from './useEndpointAction'; import { useEndpointUpload } from './useEndpointUpload'; -export const useUpdateAvatar = (avatarObj, userId) => { +type AvatarUrlObj = { + avatarUrl: string; +}; + +type AvatarReset = 'reset'; + +type AvatarServiceObject = { + blob: Blob; + contentType: string; + service: string; +}; + +type AvatarObject = AvatarReset | AvatarUrlObj | FormData | AvatarServiceObject; + +const isAvatarReset = (avatarObj: AvatarObject): avatarObj is AvatarReset => typeof avatarObj === 'string'; +const isServiceObject = (avatarObj: AvatarObject): avatarObj is AvatarServiceObject => !isAvatarReset(avatarObj) && 'service' in avatarObj; +const isAvatarUrl = (avatarObj: AvatarObject): avatarObj is AvatarUrlObj => + !isAvatarReset(avatarObj) && 'service' && 'avatarUrl' in avatarObj; + +export const useUpdateAvatar = (avatarObj: AvatarObject, userId: IUser['_id']): (() => void) => { const t = useTranslation(); - const avatarUrl = avatarObj?.avatarUrl; + const avatarUrl = isAvatarUrl(avatarObj) ? avatarObj.avatarUrl : ''; const successText = t('Avatar_changed_successfully'); const setAvatarFromService = useMethod('setAvatarFromService'); @@ -35,13 +53,13 @@ export const useUpdateAvatar = (avatarObj, userId) => { const resetAvatarAction = useEndpointAction('POST', 'users.resetAvatar', resetAvatarQuery, successText); const updateAvatar = useCallback(async () => { - if (avatarObj === 'reset') { + if (isAvatarReset(avatarObj)) { return resetAvatarAction(); } - if (avatarObj.avatarUrl) { + if (isAvatarUrl(avatarObj)) { return saveAvatarUrlAction(); } - if (avatarObj.service) { + if (isServiceObject(avatarObj)) { const { blob, contentType, service } = avatarObj; try { await setAvatarFromService(blob, contentType, service); diff --git a/apps/meteor/client/hooks/useUserDisplayName.ts b/apps/meteor/client/hooks/useUserDisplayName.ts index 13fee5952f4e..5d04600cdecf 100644 --- a/apps/meteor/client/hooks/useUserDisplayName.ts +++ b/apps/meteor/client/hooks/useUserDisplayName.ts @@ -1,6 +1,6 @@ import type { IUser } from '@rocket.chat/core-typings'; +import { useSetting } from '@rocket.chat/ui-contexts'; -import { useSetting } from '../contexts/SettingsContext'; import { getUserDisplayName } from '../lib/getUserDisplayName'; export const useUserDisplayName = ({ name, username }: Pick): string | undefined => { diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts index fc5877e0faf4..4ee4fc8b4688 100644 --- a/apps/meteor/client/importPackages.ts +++ b/apps/meteor/client/importPackages.ts @@ -24,7 +24,6 @@ import '../app/importer-csv/client'; import '../app/importer-hipchat-enterprise/client'; import '../app/importer-slack/client'; import '../app/importer-slack-users/client'; -import '../app/integrations/client/startup'; import '../app/lib/client'; import '../app/livestream/client'; import '../app/logger/client'; @@ -77,7 +76,6 @@ import '../app/chatpal-search/client'; import '../app/lazy-load/client'; import '../app/discussion/client'; import '../app/threads/client'; -import '../app/mail-messages/client'; import '../app/user-status/client'; import '../app/utils/client'; import '../app/settings/client'; diff --git a/apps/meteor/client/lib/RoomManager.ts b/apps/meteor/client/lib/RoomManager.ts index 44303d3dd7bf..31a0d5f0b78a 100644 --- a/apps/meteor/client/lib/RoomManager.ts +++ b/apps/meteor/client/lib/RoomManager.ts @@ -1,10 +1,10 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; +import { useUserId, useUserRoom, useUserSubscription } from '@rocket.chat/ui-contexts'; import { useEffect, useMemo } from 'react'; import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; import { RoomHistoryManager } from '../../app/ui-utils/client/lib/RoomHistoryManager'; -import { useUserId, useUserRoom, useUserSubscription } from '../contexts/UserContext'; import { useAsyncState } from '../hooks/useAsyncState'; import { AsyncState } from './asyncState'; import { getConfig } from './utils/getConfig'; diff --git a/apps/meteor/client/lib/utils/call.ts b/apps/meteor/client/lib/utils/call.ts index 093928d18c72..1cf72a66f3e6 100644 --- a/apps/meteor/client/lib/utils/call.ts +++ b/apps/meteor/client/lib/utils/call.ts @@ -1,7 +1,6 @@ +import { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '../../contexts/ServerContext'; - export const call = (method: M, ...params: ServerMethodParameters): Promise> => new Promise((resolve, reject) => { Meteor.call(method, ...params, (error: Error, result: ServerMethodReturn) => { diff --git a/apps/meteor/client/lib/utils/callWithErrorHandling.ts b/apps/meteor/client/lib/utils/callWithErrorHandling.ts index 141c6fe10735..2436a2fac6ac 100644 --- a/apps/meteor/client/lib/utils/callWithErrorHandling.ts +++ b/apps/meteor/client/lib/utils/callWithErrorHandling.ts @@ -1,4 +1,5 @@ -import { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '../../contexts/ServerContext'; +import { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '@rocket.chat/ui-contexts'; + import { call } from './call'; import { handleError } from './handleError'; diff --git a/apps/meteor/client/main.ts b/apps/meteor/client/main.ts index fa275fb1610e..628050600a12 100644 --- a/apps/meteor/client/main.ts +++ b/apps/meteor/client/main.ts @@ -1,4 +1,6 @@ import '../ee/definition/rest'; +import '../ee/definition/methods'; +import '../definition/methods'; import '../ee/client/ecdh'; import './polyfills'; diff --git a/apps/meteor/client/providers/AuthorizationProvider.tsx b/apps/meteor/client/providers/AuthorizationProvider.tsx index 4cbc80f53d58..c5e360169b62 100644 --- a/apps/meteor/client/providers/AuthorizationProvider.tsx +++ b/apps/meteor/client/providers/AuthorizationProvider.tsx @@ -1,12 +1,20 @@ +import { IRole } from '@rocket.chat/core-typings'; +import { Emitter } from '@rocket.chat/emitter'; +import { AuthorizationContext } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import React, { FC, useCallback, useEffect } from 'react'; import { hasPermission, hasAtLeastOnePermission, hasAllPermission, hasRole } from '../../app/authorization/client'; import { Roles } from '../../app/models/client/models/Roles'; -import { AuthorizationContext, RoleStore } from '../contexts/AuthorizationContext'; import { useReactiveValue } from '../hooks/useReactiveValue'; import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; +class RoleStore extends Emitter<{ + change: { [_id: string]: IRole }; +}> { + roles: { [_id: string]: IRole } = {}; +} + const contextValue = { queryPermission: createReactiveSubscriptionFactory((permission, scope) => hasPermission(permission, scope)), queryAtLeastOnePermission: createReactiveSubscriptionFactory((permissions, scope) => hasAtLeastOnePermission(permissions, scope)), diff --git a/apps/meteor/client/providers/AvatarUrlProvider.tsx b/apps/meteor/client/providers/AvatarUrlProvider.tsx index cdf383dcf7d4..d043ad18b681 100644 --- a/apps/meteor/client/providers/AvatarUrlProvider.tsx +++ b/apps/meteor/client/providers/AvatarUrlProvider.tsx @@ -1,8 +1,7 @@ +import { AvatarUrlContext, useSetting } from '@rocket.chat/ui-contexts'; import React, { useMemo, FC } from 'react'; import { getURL } from '../../app/utils/lib/getURL'; -import { AvatarUrlContext } from '../contexts/AvatarUrlContext'; -import { useSetting } from '../contexts/SettingsContext'; import { roomCoordinator } from '../lib/rooms/roomCoordinator'; const AvatarUrlProvider: FC = ({ children }) => { diff --git a/apps/meteor/client/providers/CallProvider/CallProvider.tsx b/apps/meteor/client/providers/CallProvider/CallProvider.tsx index 0f560e859cfa..3b9825908bc0 100644 --- a/apps/meteor/client/providers/CallProvider/CallProvider.tsx +++ b/apps/meteor/client/providers/CallProvider/CallProvider.tsx @@ -1,5 +1,6 @@ import type { IVoipRoom, IUser } from '@rocket.chat/core-typings'; import { ICallerInfo } from '@rocket.chat/core-typings'; +import { useSetModal, useRoute, useUser, useSetting, useEndpoint, useStream } from '@rocket.chat/ui-contexts'; import { Random } from 'meteor/random'; import React, { useMemo, FC, useRef, useCallback, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; @@ -9,11 +10,6 @@ import { CustomSounds } from '../../../app/custom-sounds/client'; import { getUserPreference } from '../../../app/utils/client'; import { WrapUpCallModal } from '../../components/voip/modal/WrapUpCallModal'; import { CallContext, CallContextValue } from '../../contexts/CallContext'; -import { useSetModal } from '../../contexts/ModalContext'; -import { useRoute } from '../../contexts/RouterContext'; -import { useEndpoint, useStream } from '../../contexts/ServerContext'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useUser } from '../../contexts/UserContext'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; import { QueueAggregator } from '../../lib/voip/QueueAggregator'; import { useVoipClient } from './hooks/useVoipClient'; diff --git a/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts b/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts index 56ca3ad2c525..689fb1b0fd0d 100644 --- a/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts +++ b/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts @@ -1,11 +1,9 @@ import { IRegistrationInfo, WorkflowTypes } from '@rocket.chat/core-typings'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useUser, useSetting, useEndpoint, useStream } from '@rocket.chat/ui-contexts'; import { KJUR } from 'jsrsasign'; import { useEffect, useState } from 'react'; -import { useEndpoint, useStream } from '../../../contexts/ServerContext'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useUser } from '../../../contexts/UserContext'; import { SimpleVoipUser } from '../../../lib/voip/SimpleVoipUser'; import { VoIPUser } from '../../../lib/voip/VoIPUser'; import { useWebRtcServers } from './useWebRtcServers'; diff --git a/apps/meteor/client/providers/CallProvider/hooks/useWebRtcServers.ts b/apps/meteor/client/providers/CallProvider/hooks/useWebRtcServers.ts index 5de4a9838167..e843d0811bb2 100644 --- a/apps/meteor/client/providers/CallProvider/hooks/useWebRtcServers.ts +++ b/apps/meteor/client/providers/CallProvider/hooks/useWebRtcServers.ts @@ -1,6 +1,6 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; -import { useSetting } from '../../../contexts/SettingsContext'; import { IceServer } from '../definitions/IceServer'; import { parseStringToIceServers } from '../lib/parseStringToIceServers'; diff --git a/apps/meteor/client/providers/ConnectionStatusProvider.tsx b/apps/meteor/client/providers/ConnectionStatusProvider.tsx index 2763ce8eac48..f279042d5510 100644 --- a/apps/meteor/client/providers/ConnectionStatusProvider.tsx +++ b/apps/meteor/client/providers/ConnectionStatusProvider.tsx @@ -1,7 +1,7 @@ +import { ConnectionStatusContext, ConnectionStatusContextValue } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import React, { FC } from 'react'; -import { ConnectionStatusContext, ConnectionStatusContextValue } from '../contexts/ConnectionStatusContext'; import { useReactiveValue } from '../hooks/useReactiveValue'; const getValue = (): ConnectionStatusContextValue => ({ diff --git a/apps/meteor/client/providers/CustomSoundProvider.tsx b/apps/meteor/client/providers/CustomSoundProvider.tsx index 784ab15b356b..a7581df6a20a 100644 --- a/apps/meteor/client/providers/CustomSoundProvider.tsx +++ b/apps/meteor/client/providers/CustomSoundProvider.tsx @@ -1,7 +1,7 @@ +import { CustomSoundContext } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; import { CustomSounds } from '../../app/custom-sounds/client/lib/CustomSounds'; -import { CustomSoundContext } from '../contexts/CustomSoundContext'; const CustomSoundProvider: FC = ({ children }) => ; diff --git a/apps/meteor/client/providers/LayoutProvider.tsx b/apps/meteor/client/providers/LayoutProvider.tsx index df045e7c89a6..1eafe3000b31 100644 --- a/apps/meteor/client/providers/LayoutProvider.tsx +++ b/apps/meteor/client/providers/LayoutProvider.tsx @@ -1,10 +1,8 @@ import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; -import React, { FC, useContext, useMemo } from 'react'; +import { LayoutContext, useQueryStringParameter, useSetting } from '@rocket.chat/ui-contexts'; +import React, { FC, useMemo } from 'react'; import { menu } from '../../app/ui-utils/client'; -import { LayoutContext, SizeLayout, LayoutContextValue } from '../contexts/LayoutContext'; -import { useQueryStringParameter } from '../contexts/RouterContext'; -import { useSetting } from '../contexts/SettingsContext'; const LayoutProvider: FC = ({ children }) => { const showTopNavbarEmbeddedLayout = Boolean(useSetting('UI_Show_top_navbar_embedded_layout')); @@ -35,9 +33,5 @@ const LayoutProvider: FC = ({ children }) => { /> ); }; -export default LayoutProvider; -export const useLayoutSizes = (): SizeLayout => useContext(LayoutContext).size; -export const useLayoutContextualBarExpanded = (): boolean => useContext(LayoutContext).contextualBarExpanded; -export const useLayoutContextualBarPosition = (): LayoutContextValue['contextualBarPosition'] => - useContext(LayoutContext).contextualBarPosition; +export default LayoutProvider; diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index 5eaa0f39837b..89aef340fdae 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -13,7 +13,6 @@ import RouterProvider from './RouterProvider'; import ServerProvider from './ServerProvider'; import SessionProvider from './SessionProvider'; import SettingsProvider from './SettingsProvider'; -import SidebarProvider from './SidebarProvider'; import ToastMessagesProvider from './ToastMessagesProvider'; import TooltipProvider from './TooltipProvider'; import TranslationProvider from './TranslationProvider'; @@ -25,31 +24,29 @@ const MeteorProvider: FC = ({ children }) => ( - - - - - - - - - - - - - {children} - - - - - - - - - - - - + + + + + + + + + + + + {children} + + + + + + + + + + + diff --git a/apps/meteor/client/providers/ModalProvider.tsx b/apps/meteor/client/providers/ModalProvider.tsx index 794da9a1a56d..e3bab9d296b3 100644 --- a/apps/meteor/client/providers/ModalProvider.tsx +++ b/apps/meteor/client/providers/ModalProvider.tsx @@ -1,9 +1,9 @@ +import { ModalContext } from '@rocket.chat/ui-contexts'; import React, { useState, useMemo, memo, ReactNode, useCallback, ReactElement } from 'react'; import { modal } from '../../app/ui-utils/client/lib/modal'; import ModalBackdrop from '../components/modal/ModalBackdrop'; import ModalPortal from '../components/modal/ModalPortal'; -import { ModalContext } from '../contexts/ModalContext'; import { useImperativeModal } from '../views/hooks/useImperativeModal'; type ModalProviderProps = { diff --git a/apps/meteor/client/providers/OmnichannelProvider.tsx b/apps/meteor/client/providers/OmnichannelProvider.tsx index 90ddc73e6ae6..16585aea1a02 100644 --- a/apps/meteor/client/providers/OmnichannelProvider.tsx +++ b/apps/meteor/client/providers/OmnichannelProvider.tsx @@ -1,17 +1,14 @@ import type { IOmnichannelAgent, IRoom } from '@rocket.chat/core-typings'; import { OmichannelRoutingConfig } from '@rocket.chat/core-typings'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useUser, useSetting, usePermission, useMethod } from '@rocket.chat/ui-contexts'; import React, { useState, useEffect, FC, useMemo, useCallback, memo, useRef } from 'react'; import { LivechatInquiry } from '../../app/livechat/client/collections/LivechatInquiry'; import { initializeLivechatInquiryStream } from '../../app/livechat/client/lib/stream/queueManager'; import { Notifications } from '../../app/notifications/client'; import { ClientLogger } from '../../lib/ClientLogger'; -import { usePermission } from '../contexts/AuthorizationContext'; import { OmnichannelContext, OmnichannelContextValue } from '../contexts/OmnichannelContext'; -import { useMethod } from '../contexts/ServerContext'; -import { useSetting } from '../contexts/SettingsContext'; -import { useUser } from '../contexts/UserContext'; import { useReactiveValue } from '../hooks/useReactiveValue'; const emptyContextValue: OmnichannelContextValue = { diff --git a/apps/meteor/client/providers/RouterProvider.tsx b/apps/meteor/client/providers/RouterProvider.tsx index 7efea20b01ce..b6d4b83cf260 100644 --- a/apps/meteor/client/providers/RouterProvider.tsx +++ b/apps/meteor/client/providers/RouterProvider.tsx @@ -1,10 +1,9 @@ +import { RouterContext, RouterContextValue } from '@rocket.chat/ui-contexts'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Tracker } from 'meteor/tracker'; import React, { FC } from 'react'; import { Subscription, Unsubscribe } from 'use-subscription'; -import { RouterContext, RouterContextValue } from '../contexts/RouterContext'; - const createSubscription = function (getValue: () => T): Subscription { let currentValue = Tracker.nonreactive(getValue); return { diff --git a/apps/meteor/client/providers/ServerProvider.tsx b/apps/meteor/client/providers/ServerProvider.tsx index dfa1acd4120c..1c5ec443195d 100644 --- a/apps/meteor/client/providers/ServerProvider.tsx +++ b/apps/meteor/client/providers/ServerProvider.tsx @@ -1,10 +1,10 @@ import { Serialized } from '@rocket.chat/core-typings'; import type { Method, PathFor, MatchPathPattern, OperationParams, OperationResult } from '@rocket.chat/rest-typings'; +import { ServerContext, ServerMethodName, ServerMethodParameters, ServerMethodReturn, UploadResult } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import React, { FC } from 'react'; import { Info as info, APIClient } from '../../app/utils/client'; -import { ServerContext, ServerMethodName, ServerMethodParameters, ServerMethodReturn, UploadResult } from '../contexts/ServerContext'; const absoluteUrl = (path: string): string => Meteor.absoluteUrl(path); diff --git a/apps/meteor/client/providers/SessionProvider.tsx b/apps/meteor/client/providers/SessionProvider.tsx index 565806577568..447cd93c806e 100644 --- a/apps/meteor/client/providers/SessionProvider.tsx +++ b/apps/meteor/client/providers/SessionProvider.tsx @@ -1,7 +1,7 @@ +import { SessionContext } from '@rocket.chat/ui-contexts'; import { Session } from 'meteor/session'; import React, { FC } from 'react'; -import { SessionContext } from '../contexts/SessionContext'; import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; const contextValue = { diff --git a/apps/meteor/client/providers/SettingsProvider.tsx b/apps/meteor/client/providers/SettingsProvider.tsx index 5356670e2f4e..0ed442dabc11 100644 --- a/apps/meteor/client/providers/SettingsProvider.tsx +++ b/apps/meteor/client/providers/SettingsProvider.tsx @@ -1,9 +1,7 @@ +import { SettingsContext, SettingsContextValue, useAtLeastOnePermission, useMethod } from '@rocket.chat/ui-contexts'; import { Tracker } from 'meteor/tracker'; import React, { useCallback, useEffect, useMemo, useState, FunctionComponent } from 'react'; -import { useAtLeastOnePermission } from '../contexts/AuthorizationContext'; -import { useMethod } from '../contexts/ServerContext'; -import { SettingsContext, SettingsContextValue } from '../contexts/SettingsContext'; import { PrivateSettingsCachedCollection } from '../lib/settings/PrivateSettingsCachedCollection'; import { PublicSettingsCachedCollection } from '../lib/settings/PublicSettingsCachedCollection'; import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; diff --git a/apps/meteor/client/providers/SidebarProvider.tsx b/apps/meteor/client/providers/SidebarProvider.tsx deleted file mode 100644 index 7142b01cc801..000000000000 --- a/apps/meteor/client/providers/SidebarProvider.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; - -import { menu } from '../../app/ui-utils/client'; -import { SidebarContext } from '../contexts/SidebarContext'; -import { useReactiveValue } from '../hooks/useReactiveValue'; - -const getOpen = (): boolean => menu.isOpen(); - -const setOpen = (open: boolean | ((isOpen: boolean) => boolean)): void => { - if (typeof open === 'function') { - open = open(menu.isOpen()); - } - - return open ? menu.open() : menu.close(); -}; - -const SidebarProvider: FC = ({ children }) => ( - (getOpen), setOpen]} /> -); - -export default SidebarProvider; diff --git a/apps/meteor/client/providers/ToastMessagesProvider.tsx b/apps/meteor/client/providers/ToastMessagesProvider.tsx index 7c7be1dd4eef..509dabece81e 100644 --- a/apps/meteor/client/providers/ToastMessagesProvider.tsx +++ b/apps/meteor/client/providers/ToastMessagesProvider.tsx @@ -1,7 +1,7 @@ +import { ToastMessagesContext } from '@rocket.chat/ui-contexts'; import React, { FC, useEffect } from 'react'; import toastr from 'toastr'; -import { ToastMessagesContext } from '../contexts/ToastMessagesContext'; import { dispatchToastMessage, subscribeToToastMessages } from '../lib/toast'; import { handleError } from '../lib/utils/handleError'; diff --git a/apps/meteor/client/providers/TooltipProvider.tsx b/apps/meteor/client/providers/TooltipProvider.tsx index 16490bc14b44..e21259f5109a 100644 --- a/apps/meteor/client/providers/TooltipProvider.tsx +++ b/apps/meteor/client/providers/TooltipProvider.tsx @@ -1,9 +1,9 @@ import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import { TooltipContext } from '@rocket.chat/ui-contexts'; import React, { FC, useEffect, useState, useMemo, ReactNode, useRef, memo } from 'react'; import { TooltipComponent } from '../components/TooltipComponent'; import TooltipPortal from '../components/TooltipPortal'; -import { TooltipContext } from '../contexts/TooltipContext'; const TooltipProvider: FC = ({ children }) => { const lastAnchor = useRef(); diff --git a/apps/meteor/client/providers/TranslationProvider.js b/apps/meteor/client/providers/TranslationProvider.js index 60a6bfb8177c..3e4ef59b46d6 100644 --- a/apps/meteor/client/providers/TranslationProvider.js +++ b/apps/meteor/client/providers/TranslationProvider.js @@ -1,7 +1,7 @@ +import { TranslationContext } from '@rocket.chat/ui-contexts'; import { TAPi18n, TAPi18next } from 'meteor/rocketchat:tap-i18n'; import React, { useMemo } from 'react'; -import { TranslationContext } from '../contexts/TranslationContext'; import { useReactiveValue } from '../hooks/useReactiveValue'; const createTranslateFunction = (language) => { diff --git a/apps/meteor/client/providers/UserProvider.tsx b/apps/meteor/client/providers/UserProvider.tsx index 8c4064bb2b90..2f7cb37a784f 100644 --- a/apps/meteor/client/providers/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider.tsx @@ -1,11 +1,11 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; +import { UserContext } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import React, { useMemo, FC } from 'react'; import { Subscriptions, Rooms } from '../../app/models/client'; import { getUserPreference } from '../../app/utils/client'; import { callbacks } from '../../lib/callbacks'; -import { UserContext } from '../contexts/UserContext'; import { useReactiveValue } from '../hooks/useReactiveValue'; import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; diff --git a/apps/meteor/client/sidebar/RoomList/RoomList.js b/apps/meteor/client/sidebar/RoomList/RoomList.js index 5f34289e7467..136bff2ac01b 100644 --- a/apps/meteor/client/sidebar/RoomList/RoomList.js +++ b/apps/meteor/client/sidebar/RoomList/RoomList.js @@ -1,11 +1,9 @@ import { Box } from '@rocket.chat/fuselage'; import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import { useSession, useUserPreference, useUserId, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useRef, useEffect, useMemo } from 'react'; import { Virtuoso } from 'react-virtuoso'; -import { useSession } from '../../contexts/SessionContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUserPreference, useUserId } from '../../contexts/UserContext'; import { useAvatarTemplate } from '../hooks/useAvatarTemplate'; import { usePreventDefault } from '../hooks/usePreventDefault'; import { useRoomList } from '../hooks/useRoomList'; diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.js b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.js index 8efa329a3779..4cccb6cd177d 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.js +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.js @@ -1,8 +1,8 @@ import { Badge, Sidebar } from '@rocket.chat/fuselage'; +import { useLayout } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; import { RoomIcon } from '../../components/RoomIcon'; -import { useLayout } from '../../contexts/LayoutContext'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; import RoomMenu from '../RoomMenu'; import { normalizeSidebarMessage } from './normalizeSidebarMessage'; diff --git a/apps/meteor/client/sidebar/RoomMenu.js b/apps/meteor/client/sidebar/RoomMenu.js index 93ed2156fd8e..5e703f011e23 100644 --- a/apps/meteor/client/sidebar/RoomMenu.js +++ b/apps/meteor/client/sidebar/RoomMenu.js @@ -1,18 +1,20 @@ import { Option, Menu } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { + useSetModal, + useToastMessageDispatch, + useRoute, + useUserSubscription, + useSetting, + usePermission, + useMethod, + useTranslation, +} from '@rocket.chat/ui-contexts'; import React, { memo, useMemo } from 'react'; import { RoomManager } from '../../app/ui-utils/client/lib/RoomManager'; import { UiTextContext } from '../../definition/IRoomTypeConfig'; import { GenericModalDoNotAskAgain } from '../components/GenericModal'; -import { usePermission } from '../contexts/AuthorizationContext'; -import { useSetModal } from '../contexts/ModalContext'; -import { useRoute } from '../contexts/RouterContext'; -import { useMethod } from '../contexts/ServerContext'; -import { useSetting } from '../contexts/SettingsContext'; -import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; -import { useTranslation } from '../contexts/TranslationContext'; -import { useUserSubscription } from '../contexts/UserContext'; import { useDontAskAgain } from '../hooks/useDontAskAgain'; import { roomCoordinator } from '../lib/rooms/roomCoordinator'; import WarningModal from '../views/admin/apps/WarningModal'; diff --git a/apps/meteor/client/sidebar/Sidebar.stories.tsx b/apps/meteor/client/sidebar/Sidebar.stories.tsx index 8d58636ef6f9..8a9e70d9c20e 100644 --- a/apps/meteor/client/sidebar/Sidebar.stories.tsx +++ b/apps/meteor/client/sidebar/Sidebar.stories.tsx @@ -1,9 +1,9 @@ import type { ISetting, ISubscription } from '@rocket.chat/core-typings'; +import { UserContext, SettingsContext } from '@rocket.chat/ui-contexts'; import { Meta, Story } from '@storybook/react'; +import type { ObjectId } from 'mongodb'; import React, { ContextType } from 'react'; -import { SettingsContext } from '../contexts/SettingsContext'; -import { UserContext } from '../contexts/UserContext'; import RoomList from './RoomList/index'; import Header from './header'; @@ -87,7 +87,7 @@ const userContextValue: ContextType = { roles: ['admin'], type: 'user', }, - queryPreference: (pref: string | Mongo.ObjectID, defaultValue: T) => ({ + queryPreference: (pref: string | ObjectId, defaultValue: T) => ({ getCurrentValue: () => (typeof pref === 'string' ? (userPreferences[pref] as T) : defaultValue), subscribe: () => () => undefined, }), diff --git a/apps/meteor/client/sidebar/footer/voip/index.tsx b/apps/meteor/client/sidebar/footer/voip/index.tsx index e7ccd217afdb..29ba87935ac1 100644 --- a/apps/meteor/client/sidebar/footer/voip/index.tsx +++ b/apps/meteor/client/sidebar/footer/voip/index.tsx @@ -1,3 +1,4 @@ +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useCallback, useMemo, useState } from 'react'; import { @@ -10,8 +11,6 @@ import { useQueueName, useWrapUpModal, } from '../../../contexts/CallContext'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { VoipFooter as VoipFooterComponent } from './VoipFooter'; export const VoipFooter = (): ReactElement | null => { diff --git a/apps/meteor/client/sidebar/header/CreateChannel.js b/apps/meteor/client/sidebar/header/CreateChannel.js index d2a913f0472b..f5a2f25dec49 100644 --- a/apps/meteor/client/sidebar/header/CreateChannel.js +++ b/apps/meteor/client/sidebar/header/CreateChannel.js @@ -1,11 +1,9 @@ import { Box, Modal, ButtonGroup, Button, TextInput, Icon, Field, ToggleSwitch, FieldGroup } from '@rocket.chat/fuselage'; import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; +import { useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect, useMemo, useState } from 'react'; import UserAutoCompleteMultiple from '../../components/UserAutoCompleteMultiple'; -import { useMethod } from '../../contexts/ServerContext'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useTranslation } from '../../contexts/TranslationContext'; const CreateChannel = ({ values, diff --git a/apps/meteor/client/sidebar/header/CreateChannelWithData.js b/apps/meteor/client/sidebar/header/CreateChannelWithData.js index e5f820126627..346f2ab1d15a 100644 --- a/apps/meteor/client/sidebar/header/CreateChannelWithData.js +++ b/apps/meteor/client/sidebar/header/CreateChannelWithData.js @@ -1,8 +1,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useSetting, usePermission } from '@rocket.chat/ui-contexts'; import React, { memo, useCallback, useMemo } from 'react'; -import { usePermission } from '../../contexts/AuthorizationContext'; -import { useSetting } from '../../contexts/SettingsContext'; import { useEndpointActionExperimental } from '../../hooks/useEndpointActionExperimental'; import { useForm } from '../../hooks/useForm'; import { goToRoomById } from '../../lib/utils/goToRoomById'; diff --git a/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx b/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx index 5efd90574c7f..566360cc1520 100644 --- a/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx +++ b/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx @@ -1,10 +1,10 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Box, Modal, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useState, memo } from 'react'; import UserAutoCompleteMultiple from '../../components/UserAutoCompleteMultiple'; -import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointActionExperimental } from '../../hooks/useEndpointActionExperimental'; import { goToRoomById } from '../../lib/utils/goToRoomById'; diff --git a/apps/meteor/client/sidebar/header/EditStatusModal.tsx b/apps/meteor/client/sidebar/header/EditStatusModal.tsx index 2cfdd611d62b..16ec76b24bd5 100644 --- a/apps/meteor/client/sidebar/header/EditStatusModal.tsx +++ b/apps/meteor/client/sidebar/header/EditStatusModal.tsx @@ -1,14 +1,11 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Field, TextInput, FieldGroup, Modal, Icon, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useState, ChangeEvent, useCallback } from 'react'; import { USER_STATUS_TEXT_MAX_LENGTH } from '../../components/UserStatus'; import UserStatusMenu from '../../components/UserStatusMenu'; -import { useMethod } from '../../contexts/ServerContext'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; type EditStatusModalProps = { onClose: () => void; diff --git a/apps/meteor/client/sidebar/header/UserAvatarButton.tsx b/apps/meteor/client/sidebar/header/UserAvatarButton.tsx index 560c646fc418..dbe98f95fce9 100644 --- a/apps/meteor/client/sidebar/header/UserAvatarButton.tsx +++ b/apps/meteor/client/sidebar/header/UserAvatarButton.tsx @@ -1,12 +1,12 @@ import type { IUser } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Box, Dropdown } from '@rocket.chat/fuselage'; +import { useUser } from '@rocket.chat/ui-contexts'; import React, { memo, useRef, ReactElement } from 'react'; import { createPortal } from 'react-dom'; import { UserStatus } from '../../components/UserStatus'; import UserAvatar from '../../components/avatar/UserAvatar'; -import { useUser } from '../../contexts/UserContext'; import UserDropdown from './UserDropdown'; import { useDropdownVisibility } from './hooks/useDropdownVisibility'; diff --git a/apps/meteor/client/sidebar/header/UserDropdown.tsx b/apps/meteor/client/sidebar/header/UserDropdown.tsx index bde32a2dda18..3d78f7f81cb8 100644 --- a/apps/meteor/client/sidebar/header/UserDropdown.tsx +++ b/apps/meteor/client/sidebar/header/UserDropdown.tsx @@ -2,6 +2,7 @@ import type { IUser } from '@rocket.chat/core-typings'; import { UserStatus as UserStatusEnum, ValueOf } from '@rocket.chat/core-typings'; import { Box, Margins, Option } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLayout, useRoute, useLogout, useSetting, useAtLeastOnePermission, useTranslation } from '@rocket.chat/ui-contexts'; import { FlowRouter } from 'meteor/kadira:flow-router'; import React, { ReactElement } from 'react'; @@ -11,12 +12,6 @@ import { callbacks } from '../../../lib/callbacks'; import MarkdownText from '../../components/MarkdownText'; import { UserStatus } from '../../components/UserStatus'; import UserAvatar from '../../components/avatar/UserAvatar'; -import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; -import { useLayout } from '../../contexts/LayoutContext'; -import { useRoute } from '../../contexts/RouterContext'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useLogout } from '../../contexts/UserContext'; import { useReactiveValue } from '../../hooks/useReactiveValue'; import { useUserDisplayName } from '../../hooks/useUserDisplayName'; import { imperativeModal } from '../../lib/imperativeModal'; @@ -67,7 +62,7 @@ type UserDropdownProps = { const UserDropdown = ({ user, onClose }: UserDropdownProps): ReactElement => { const t = useTranslation(); const accountRoute = useRoute('account'); - const adminRoute = useRoute('admin'); + const adminRoute = useRoute('admin-index'); const logout = useLogout(); const { sidebar, isMobile } = useLayout(); diff --git a/apps/meteor/client/sidebar/header/actions/CreateRoom.js b/apps/meteor/client/sidebar/header/actions/CreateRoom.js index 37a6c024de6d..c1ee83a9590c 100644 --- a/apps/meteor/client/sidebar/header/actions/CreateRoom.js +++ b/apps/meteor/client/sidebar/header/actions/CreateRoom.js @@ -1,8 +1,8 @@ import { Box, Sidebar, Dropdown } from '@rocket.chat/fuselage'; +import { useAtLeastOnePermission } from '@rocket.chat/ui-contexts'; import React, { useRef } from 'react'; import { createPortal } from 'react-dom'; -import { useAtLeastOnePermission } from '../../../contexts/AuthorizationContext'; import { useDropdownVisibility } from '../hooks/useDropdownVisibility'; import CreateRoomList from './CreateRoomList'; diff --git a/apps/meteor/client/sidebar/header/actions/CreateRoomList.js b/apps/meteor/client/sidebar/header/actions/CreateRoomList.js index c8d8d8aeaa2c..3ad249407f05 100644 --- a/apps/meteor/client/sidebar/header/actions/CreateRoomList.js +++ b/apps/meteor/client/sidebar/header/actions/CreateRoomList.js @@ -1,14 +1,11 @@ import { OptionTitle } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useSetModal, useSetting, useAtLeastOnePermission, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import { popover } from '../../../../app/ui-utils/client'; import CreateDiscussion from '../../../components/CreateDiscussion'; import ListItem from '../../../components/Sidebar/ListItem'; -import { useAtLeastOnePermission } from '../../../contexts/AuthorizationContext'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import CreateTeamModal from '../../../views/teams/CreateTeamModal'; import CreateChannelWithData from '../CreateChannelWithData'; import CreateDirectMessage from '../CreateDirectMessage'; diff --git a/apps/meteor/client/sidebar/header/actions/Directory.js b/apps/meteor/client/sidebar/header/actions/Directory.js index 4b1a03cf0157..ba6f67f9b7fe 100644 --- a/apps/meteor/client/sidebar/header/actions/Directory.js +++ b/apps/meteor/client/sidebar/header/actions/Directory.js @@ -1,10 +1,8 @@ import { Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLayout, useRoute } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useLayout } from '../../../contexts/LayoutContext'; -import { useRoute } from '../../../contexts/RouterContext'; - const Directory = (props) => { const directoryRoute = useRoute('directory'); const { sidebar } = useLayout(); diff --git a/apps/meteor/client/sidebar/header/actions/Home.js b/apps/meteor/client/sidebar/header/actions/Home.js index 4b308af1aaeb..405ed61d2552 100644 --- a/apps/meteor/client/sidebar/header/actions/Home.js +++ b/apps/meteor/client/sidebar/header/actions/Home.js @@ -1,11 +1,8 @@ import { Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLayout, useRoute, useSetting } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useLayout } from '../../../contexts/LayoutContext'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useSetting } from '../../../contexts/SettingsContext'; - const Home = (props) => { const homeRoute = useRoute('home'); const { sidebar } = useLayout(); diff --git a/apps/meteor/client/sidebar/header/actions/Login.js b/apps/meteor/client/sidebar/header/actions/Login.js index 47ca074ff484..6eb6a0737080 100644 --- a/apps/meteor/client/sidebar/header/actions/Login.js +++ b/apps/meteor/client/sidebar/header/actions/Login.js @@ -1,9 +1,7 @@ import { Sidebar } from '@rocket.chat/fuselage'; +import { useSessionDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useSessionDispatch } from '../../../contexts/SessionContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; - const Login = (props) => { const setForceLogin = useSessionDispatch('forceLogin'); const t = useTranslation(); diff --git a/apps/meteor/client/sidebar/header/index.js b/apps/meteor/client/sidebar/header/index.js index 524071d4e0e6..30ad7be3f1a5 100644 --- a/apps/meteor/client/sidebar/header/index.js +++ b/apps/meteor/client/sidebar/header/index.js @@ -1,8 +1,7 @@ import { Sidebar } from '@rocket.chat/fuselage'; +import { useUser, useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUser } from '../../contexts/UserContext'; import { useSidebarPaletteColor } from '../hooks/useSidebarPaletteColor'; import UserAvatarButton from './UserAvatarButton'; import CreateRoom from './actions/CreateRoom'; diff --git a/apps/meteor/client/sidebar/hooks/useAvatarTemplate.tsx b/apps/meteor/client/sidebar/hooks/useAvatarTemplate.tsx index f8b1c9c177bd..09836e19250b 100644 --- a/apps/meteor/client/sidebar/hooks/useAvatarTemplate.tsx +++ b/apps/meteor/client/sidebar/hooks/useAvatarTemplate.tsx @@ -1,8 +1,8 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { useUserPreference } from '@rocket.chat/ui-contexts'; import React, { ReactNode, useMemo } from 'react'; import RoomAvatar from '../../components/avatar/RoomAvatar'; -import { useUserPreference } from '../../contexts/UserContext'; export const useAvatarTemplate = ( sidebarViewMode?: 'extended' | 'medium' | 'condensed', diff --git a/apps/meteor/client/sidebar/hooks/useQueryOptions.js b/apps/meteor/client/sidebar/hooks/useQueryOptions.js index 8ff69f7b6bb4..6bd31aad23f2 100644 --- a/apps/meteor/client/sidebar/hooks/useQueryOptions.js +++ b/apps/meteor/client/sidebar/hooks/useQueryOptions.js @@ -1,8 +1,6 @@ +import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useUserPreference } from '../../contexts/UserContext'; - export const useQueryOptions = () => { const sortBy = useUserPreference('sidebarSortby'); const showRealName = useSetting('UI_Use_Real_Name'); diff --git a/apps/meteor/client/sidebar/hooks/useRoomList.ts b/apps/meteor/client/sidebar/hooks/useRoomList.ts index 7e7fcc43fc17..3abc6ce70b4c 100644 --- a/apps/meteor/client/sidebar/hooks/useRoomList.ts +++ b/apps/meteor/client/sidebar/hooks/useRoomList.ts @@ -1,10 +1,10 @@ import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; import { useDebouncedState } from '@rocket.chat/fuselage-hooks'; +import { useUserPreference, useUserSubscriptions, useSetting } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; -import { useQueuedInquiries, useOmnichannelEnabled } from '../../contexts/OmnichannelContext'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useUserPreference, useUserSubscriptions } from '../../contexts/UserContext'; +import { useOmnichannelEnabled } from '../../hooks/omnichannel/useOmnichannelEnabled'; +import { useQueuedInquiries } from '../../hooks/omnichannel/useQueuedInquiries'; import { useQueryOptions } from './useQueryOptions'; const query = { open: { $ne: false } }; diff --git a/apps/meteor/client/sidebar/hooks/useSidebarPaletteColor.js b/apps/meteor/client/sidebar/hooks/useSidebarPaletteColor.js index 3d935e154a48..c824c95b5b8a 100644 --- a/apps/meteor/client/sidebar/hooks/useSidebarPaletteColor.js +++ b/apps/meteor/client/sidebar/hooks/useSidebarPaletteColor.js @@ -1,7 +1,7 @@ import colors from '@rocket.chat/fuselage-tokens/colors'; +import { useSettings } from '@rocket.chat/ui-contexts'; import { useLayoutEffect, useEffect, useMemo } from 'react'; -import { useSettings } from '../../contexts/SettingsContext'; import { isIE11 } from '../../lib/utils/isIE11'; const oldPallet = { diff --git a/apps/meteor/client/sidebar/hooks/useTemplateByViewMode.js b/apps/meteor/client/sidebar/hooks/useTemplateByViewMode.js index a06c435dafd0..2e518ee2dec8 100644 --- a/apps/meteor/client/sidebar/hooks/useTemplateByViewMode.js +++ b/apps/meteor/client/sidebar/hooks/useTemplateByViewMode.js @@ -1,6 +1,6 @@ +import { useUserPreference } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; -import { useUserPreference } from '../../contexts/UserContext'; import Condensed from '../Item/Condensed'; import Extended from '../Item/Extended'; import Medium from '../Item/Medium'; diff --git a/apps/meteor/client/sidebar/search/SearchList.js b/apps/meteor/client/sidebar/search/SearchList.js index 6eeb6a4473b3..4544dcdd68e5 100644 --- a/apps/meteor/client/sidebar/search/SearchList.js +++ b/apps/meteor/client/sidebar/search/SearchList.js @@ -2,14 +2,12 @@ import { css } from '@rocket.chat/css-in-js'; import { Sidebar, TextInput, Box, Icon } from '@rocket.chat/fuselage'; import { useMutableCallback, useDebouncedValue, useStableArray, useAutoFocus, useUniqueId } from '@rocket.chat/fuselage-hooks'; import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { useUserPreference, useUserSubscriptions, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import React, { forwardRef, useState, useMemo, useEffect, useRef } from 'react'; import { Virtuoso } from 'react-virtuoso'; import tinykeys from 'tinykeys'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUserPreference, useUserSubscriptions } from '../../contexts/UserContext'; import { AsyncStatePhase } from '../../hooks/useAsyncState'; import { useMethodData } from '../../hooks/useMethodData'; import { useAvatarTemplate } from '../hooks/useAvatarTemplate'; diff --git a/apps/meteor/client/sidebar/sections/OmnichannelSection.tsx b/apps/meteor/client/sidebar/sections/OmnichannelSection.tsx index dacd2cf12fc4..117c913f27eb 100644 --- a/apps/meteor/client/sidebar/sections/OmnichannelSection.tsx +++ b/apps/meteor/client/sidebar/sections/OmnichannelSection.tsx @@ -1,15 +1,11 @@ import { Box, Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLayout, useToastMessageDispatch, useRoute, usePermission, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, ReactElement } from 'react'; -import { usePermission } from '../../contexts/AuthorizationContext'; import { useIsCallEnabled } from '../../contexts/CallContext'; -import { useLayout } from '../../contexts/LayoutContext'; -import { useOmnichannelShowQueueLink, useOmnichannelAgentAvailable } from '../../contexts/OmnichannelContext'; -import { useRoute } from '../../contexts/RouterContext'; -import { useMethod } from '../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; +import { useOmnichannelAgentAvailable } from '../../hooks/omnichannel/useOmnichannelAgentAvailable'; +import { useOmnichannelShowQueueLink } from '../../hooks/omnichannel/useOmnichannelShowQueueLink'; import { OmnichannelCallToggle } from './components/OmnichannelCallToggle'; const OmnichannelSection = (props: typeof Box): ReactElement => { diff --git a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleError.tsx b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleError.tsx index 4ec543a670f9..ecaa9684e67c 100644 --- a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleError.tsx +++ b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleError.tsx @@ -1,8 +1,7 @@ import { Sidebar } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - export const OmnichannelCallToggleError = (): ReactElement => { const t = useTranslation(); return ; diff --git a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleLoading.tsx b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleLoading.tsx index d4362c393814..61b08a1b2926 100644 --- a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleLoading.tsx +++ b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleLoading.tsx @@ -1,8 +1,7 @@ import { Sidebar } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - export const OmnichannelCallToggleLoading = (): ReactElement => { const t = useTranslation(); return ; diff --git a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx index 43d335214a0c..0b1da0fccb50 100644 --- a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx +++ b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx @@ -1,9 +1,9 @@ import { Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useEffect, useState } from 'react'; import { useCallClient, useCallerInfo, useCallActions } from '../../../contexts/CallContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; type NetworkState = 'online' | 'offline'; export const OmnichannelCallToggleReady = (): ReactElement => { diff --git a/apps/meteor/client/startup/contextualBar/exportMessages.ts b/apps/meteor/client/startup/contextualBar/exportMessages.ts index 6a8dafabaa84..8422f7b482cc 100644 --- a/apps/meteor/client/startup/contextualBar/exportMessages.ts +++ b/apps/meteor/client/startup/contextualBar/exportMessages.ts @@ -1,6 +1,6 @@ +import { usePermission } from '@rocket.chat/ui-contexts'; import { useMemo, lazy, LazyExoticComponent, FC } from 'react'; -import { usePermission } from '../../contexts/AuthorizationContext'; import { addAction } from '../../views/room/lib/Toolbox'; addAction('export-messages', ({ room }) => { diff --git a/apps/meteor/client/stories/contexts/ModalContextMock.tsx b/apps/meteor/client/stories/contexts/ModalContextMock.tsx index 3d0a03254cfd..7abc158dcc1d 100644 --- a/apps/meteor/client/stories/contexts/ModalContextMock.tsx +++ b/apps/meteor/client/stories/contexts/ModalContextMock.tsx @@ -1,8 +1,7 @@ +import { ModalContext } from '@rocket.chat/ui-contexts'; import { action } from '@storybook/addon-actions'; import React, { ContextType, ReactElement, ReactNode, useContext, useMemo } from 'react'; -import { ModalContext } from '../../contexts/ModalContext'; - const logAction = action('ModalContext'); type ModalContextMockProps = { diff --git a/apps/meteor/client/stories/contexts/RouterContextMock.tsx b/apps/meteor/client/stories/contexts/RouterContextMock.tsx index 8ee0c2553f23..3da1b7cb407a 100644 --- a/apps/meteor/client/stories/contexts/RouterContextMock.tsx +++ b/apps/meteor/client/stories/contexts/RouterContextMock.tsx @@ -1,8 +1,7 @@ +import { RouterContext } from '@rocket.chat/ui-contexts'; import { action } from '@storybook/addon-actions'; import React, { ContextType, ReactElement, ReactNode, useContext, useMemo } from 'react'; -import { RouterContext } from '../../contexts/RouterContext'; - const logAction = action('RouterContext'); type RouterContextMockProps = { diff --git a/apps/meteor/client/stories/contexts/ServerContextMock.tsx b/apps/meteor/client/stories/contexts/ServerContextMock.tsx index 195465764e40..0a01e883ace2 100644 --- a/apps/meteor/client/stories/contexts/ServerContextMock.tsx +++ b/apps/meteor/client/stories/contexts/ServerContextMock.tsx @@ -1,11 +1,10 @@ import { Serialized } from '@rocket.chat/core-typings'; import type { MatchPathPattern, Method, OperationParams, OperationResult, Path, PathFor } from '@rocket.chat/rest-typings'; +import { ServerContext, ServerMethodName, ServerMethodParameters, ServerMethodReturn, UploadResult } from '@rocket.chat/ui-contexts'; import { action } from '@storybook/addon-actions'; import { pathToRegexp } from 'path-to-regexp'; import React, { ContextType, ReactElement, ReactNode, useContext, useMemo } from 'react'; -import { ServerContext, ServerMethodName, ServerMethodParameters, ServerMethodReturn, UploadResult } from '../../contexts/ServerContext'; - const logAction = action('ServerContext'); const randomDelay = (): Promise => new Promise((resolve) => setTimeout(resolve, Math.random() * 1000)); diff --git a/apps/meteor/client/stories/contexts/TranslationContextMock.tsx b/apps/meteor/client/stories/contexts/TranslationContextMock.tsx index 5d88fbb22505..d4ffd1459154 100644 --- a/apps/meteor/client/stories/contexts/TranslationContextMock.tsx +++ b/apps/meteor/client/stories/contexts/TranslationContextMock.tsx @@ -1,8 +1,7 @@ +import { TranslationContext } from '@rocket.chat/ui-contexts'; import i18next from 'i18next'; import React, { ContextType, ReactElement, ReactNode, useContext, useMemo } from 'react'; -import { TranslationContext } from '../../contexts/TranslationContext'; - type TranslationContextMockProps = { children: ReactNode; }; diff --git a/apps/meteor/client/views/InfoPanel/RetentionPolicyCallout.tsx b/apps/meteor/client/views/InfoPanel/RetentionPolicyCallout.tsx index 6ce9088700fb..c97e0bb07567 100644 --- a/apps/meteor/client/views/InfoPanel/RetentionPolicyCallout.tsx +++ b/apps/meteor/client/views/InfoPanel/RetentionPolicyCallout.tsx @@ -1,7 +1,7 @@ import { Callout } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../contexts/TranslationContext'; import { useFormattedRelativeTime } from '../../hooks/useFormattedRelativeTime'; type RetentionPolicyCalloutProps = { diff --git a/apps/meteor/client/views/account/AccountIntegrationsPage.tsx b/apps/meteor/client/views/account/AccountIntegrationsPage.tsx index 2f66b23679ff..4fd35110f3ff 100644 --- a/apps/meteor/client/views/account/AccountIntegrationsPage.tsx +++ b/apps/meteor/client/views/account/AccountIntegrationsPage.tsx @@ -1,12 +1,10 @@ import type { IWebdavAccount } from '@rocket.chat/core-typings'; import { Box, Select, SelectOption, Field, Button } from '@rocket.chat/fuselage'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useCallback, ReactElement } from 'react'; import { WebdavAccounts } from '../../../app/models/client'; import Page from '../../components/Page'; -import { useMethod } from '../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; import { useForm } from '../../hooks/useForm'; import { useReactiveValue } from '../../hooks/useReactiveValue'; diff --git a/apps/meteor/client/views/account/AccountProfileForm.js b/apps/meteor/client/views/account/AccountProfileForm.js index c54e67296910..cf26eab7d4fb 100644 --- a/apps/meteor/client/views/account/AccountProfileForm.js +++ b/apps/meteor/client/views/account/AccountProfileForm.js @@ -12,6 +12,7 @@ import { Margins, } from '@rocket.chat/fuselage'; import { useDebouncedCallback, useSafely } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useMemo, useEffect, useState } from 'react'; import { validateEmail } from '../../../lib/emailValidator'; @@ -20,9 +21,6 @@ import CustomFieldsForm from '../../components/CustomFieldsForm'; import { USER_STATUS_TEXT_MAX_LENGTH } from '../../components/UserStatus'; import UserStatusMenu from '../../components/UserStatusMenu'; import UserAvatarEditor from '../../components/avatar/UserAvatarEditor'; -import { useMethod } from '../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; function AccountProfileForm({ values, handlers, user, settings, onSaveStateChange, ...props }) { const t = useTranslation(); diff --git a/apps/meteor/client/views/account/AccountProfilePage.js b/apps/meteor/client/views/account/AccountProfilePage.js index 314ec215a436..7238d21f5861 100644 --- a/apps/meteor/client/views/account/AccountProfilePage.js +++ b/apps/meteor/client/views/account/AccountProfilePage.js @@ -1,16 +1,20 @@ import { ButtonGroup, Button, Box, Icon } from '@rocket.chat/fuselage'; +import { + useSetModal, + useToastMessageDispatch, + useUser, + useLogout, + useSetting, + useEndpoint, + useMethod, + useTranslation, +} from '@rocket.chat/ui-contexts'; import { SHA256 } from 'meteor/sha'; import React, { useMemo, useState, useCallback } from 'react'; import { getUserEmailAddress } from '../../../lib/getUserEmailAddress'; import ConfirmOwnerChangeWarningModal from '../../components/ConfirmOwnerChangeWarningModal'; import Page from '../../components/Page'; -import { useSetModal } from '../../contexts/ModalContext'; -import { useEndpoint, useMethod } from '../../contexts/ServerContext'; -import { useSetting } from '../../contexts/SettingsContext'; -import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { useUser, useLogout } from '../../contexts/UserContext'; import { useForm } from '../../hooks/useForm'; import { useUpdateAvatar } from '../../hooks/useUpdateAvatar'; import AccountProfileForm from './AccountProfileForm'; @@ -224,7 +228,7 @@ const AccountProfilePage = () => { - diff --git a/apps/meteor/client/views/account/AccountRoute.js b/apps/meteor/client/views/account/AccountRoute.js index 641c5dd45986..7daab6e21cde 100644 --- a/apps/meteor/client/views/account/AccountRoute.js +++ b/apps/meteor/client/views/account/AccountRoute.js @@ -1,9 +1,7 @@ +import { useRouteParameter, useRoute, useCurrentRoute, useSetting, usePermission } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; import { SideNav } from '../../../app/ui-utils/client'; -import { usePermission } from '../../contexts/AuthorizationContext'; -import { useRouteParameter, useRoute, useCurrentRoute } from '../../contexts/RouterContext'; -import { useSetting } from '../../contexts/SettingsContext'; import NotAuthorizedPage from '../notAuthorized/NotAuthorizedPage'; import AccountIntegrationsPage from './AccountIntegrationsPage'; import AccountProfilePage from './AccountProfilePage'; diff --git a/apps/meteor/client/views/account/AccountSidebar.js b/apps/meteor/client/views/account/AccountSidebar.js index 03e5d8709fbb..dc78560b2c2f 100644 --- a/apps/meteor/client/views/account/AccountSidebar.js +++ b/apps/meteor/client/views/account/AccountSidebar.js @@ -1,10 +1,9 @@ +import { useRoutePath, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, useCallback, useEffect } from 'react'; import { useSubscription } from 'use-subscription'; import { menu, SideNav } from '../../../app/ui-utils/client'; import Sidebar from '../../components/Sidebar'; -import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext'; -import { useTranslation } from '../../contexts/TranslationContext'; import { isLayoutEmbedded } from '../../lib/utils/isLayoutEmbedded'; import SettingsProvider from '../../providers/SettingsProvider'; import { itemsSubscription } from './sidebarItems'; diff --git a/apps/meteor/client/views/account/ActionConfirmModal.tsx b/apps/meteor/client/views/account/ActionConfirmModal.tsx index 10a0261fd182..35b1a2208f6e 100644 --- a/apps/meteor/client/views/account/ActionConfirmModal.tsx +++ b/apps/meteor/client/views/account/ActionConfirmModal.tsx @@ -1,8 +1,8 @@ import { Box, PasswordInput, TextInput, FieldGroup, Field } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, FC } from 'react'; import GenericModal from '../../components/GenericModal'; -import { useTranslation } from '../../contexts/TranslationContext'; type ActionConfirmModalProps = { isPassword: boolean; diff --git a/apps/meteor/client/views/account/preferences/AccountPreferencesPage.js b/apps/meteor/client/views/account/preferences/AccountPreferencesPage.js index a79201ff1498..406c06695341 100644 --- a/apps/meteor/client/views/account/preferences/AccountPreferencesPage.js +++ b/apps/meteor/client/views/account/preferences/AccountPreferencesPage.js @@ -1,11 +1,8 @@ import { ButtonGroup, Button, Box, Accordion } from '@rocket.chat/fuselage'; +import { useToastMessageDispatch, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, useRef } from 'react'; import Page from '../../../components/Page'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import PreferencesGlobalSection from './PreferencesGlobalSection'; import PreferencesHighlightsSection from './PreferencesHighlightsSection'; import PreferencesLocalizationSection from './PreferencesLocalizationSection'; diff --git a/apps/meteor/client/views/account/preferences/MyDataModal.tsx b/apps/meteor/client/views/account/preferences/MyDataModal.tsx index c5f280675d51..1e05ae4f17d2 100644 --- a/apps/meteor/client/views/account/preferences/MyDataModal.tsx +++ b/apps/meteor/client/views/account/preferences/MyDataModal.tsx @@ -1,8 +1,7 @@ import { ButtonGroup, Button, Icon, Box, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - type MyDataModalProps = { onCancel: () => void; title: string; diff --git a/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.js b/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.js index d5d89f83f90b..b1715e072914 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.js @@ -1,8 +1,7 @@ import { Accordion, Field, FieldGroup, MultiSelect, ToggleSwitch, Callout } from '@rocket.chat/fuselage'; +import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const PreferencesGlobalSection = ({ onChange, commitRef, ...props }) => { diff --git a/apps/meteor/client/views/account/preferences/PreferencesHighlightsSection.js b/apps/meteor/client/views/account/preferences/PreferencesHighlightsSection.js index 09e11b3e2242..cbce6731d2a4 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesHighlightsSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesHighlightsSection.js @@ -1,8 +1,7 @@ import { Accordion, Field, FieldGroup, TextAreaInput } from '@rocket.chat/fuselage'; +import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const PreferencesHighlightsSection = ({ onChange, commitRef, ...props }) => { diff --git a/apps/meteor/client/views/account/preferences/PreferencesLocalizationSection.js b/apps/meteor/client/views/account/preferences/PreferencesLocalizationSection.js index 9fb0d12d4882..9819fe6ab97e 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesLocalizationSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesLocalizationSection.js @@ -1,8 +1,7 @@ import { Accordion, Field, Select, FieldGroup } from '@rocket.chat/fuselage'; +import { useUserPreference, useLanguages, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo } from 'react'; -import { useLanguages, useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const PreferencesLocalizationSection = ({ onChange, commitRef, ...props }) => { diff --git a/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.js b/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.js index bcb4aec88181..eebedb8ba77e 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.js @@ -1,9 +1,7 @@ import { Accordion, Field, Select, FieldGroup, ToggleSwitch } from '@rocket.chat/fuselage'; +import { useUserPreference, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo } from 'react'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const PreferencesMessagesSection = ({ onChange, commitRef, ...props }) => { diff --git a/apps/meteor/client/views/account/preferences/PreferencesMyDataSection.js b/apps/meteor/client/views/account/preferences/PreferencesMyDataSection.js index bb085fbc1c0d..f66f3e637fba 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesMyDataSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesMyDataSection.js @@ -1,10 +1,7 @@ import { Accordion, Field, FieldGroup, ButtonGroup, Button, Icon, Box } from '@rocket.chat/fuselage'; +import { useSetModal, useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import MyDataModal from './MyDataModal'; const PreferencesMyDataSection = ({ onChange, ...props }) => { diff --git a/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.js b/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.js index 8c0163636508..8a16109b6101 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.js @@ -1,10 +1,8 @@ import { Accordion, Field, Select, FieldGroup, ToggleSwitch, Button, Box } from '@rocket.chat/fuselage'; +import { useUserPreference, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { KonchatNotification } from '../../../../app/ui'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const notificationOptionsLabelMap = { diff --git a/apps/meteor/client/views/account/preferences/PreferencesSoundSection.js b/apps/meteor/client/views/account/preferences/PreferencesSoundSection.js index 619b56261185..51af78255461 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesSoundSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesSoundSection.js @@ -1,9 +1,8 @@ import { Accordion, Field, Select, FieldGroup, ToggleSwitch, Tooltip, Box } from '@rocket.chat/fuselage'; +import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useCallback } from 'react'; import { CustomSounds } from '../../../../app/custom-sounds/client'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const useCustomSoundsOptions = () => diff --git a/apps/meteor/client/views/account/preferences/PreferencesUserPresenceSection.js b/apps/meteor/client/views/account/preferences/PreferencesUserPresenceSection.js index f486d126758f..520850bf2b2d 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesUserPresenceSection.js +++ b/apps/meteor/client/views/account/preferences/PreferencesUserPresenceSection.js @@ -1,8 +1,7 @@ import { Accordion, Field, NumberInput, FieldGroup, ToggleSwitch } from '@rocket.chat/fuselage'; +import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserPreference } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const PreferencesUserPresenceSection = ({ onChange, commitRef, ...props }) => { diff --git a/apps/meteor/client/views/account/security/AccountSecurityPage.js b/apps/meteor/client/views/account/security/AccountSecurityPage.js index 06f22448ffbc..5f0ac42860aa 100644 --- a/apps/meteor/client/views/account/security/AccountSecurityPage.js +++ b/apps/meteor/client/views/account/security/AccountSecurityPage.js @@ -1,9 +1,8 @@ import { Box, Accordion } from '@rocket.chat/fuselage'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import Page from '../../../components/Page'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import EndToEnd from './EndToEnd'; import TwoFactorEmail from './TwoFactorEmail'; diff --git a/apps/meteor/client/views/account/security/BackupCodesModal.tsx b/apps/meteor/client/views/account/security/BackupCodesModal.tsx index 0663a7abd969..e3a46939ed52 100644 --- a/apps/meteor/client/views/account/security/BackupCodesModal.tsx +++ b/apps/meteor/client/views/account/security/BackupCodesModal.tsx @@ -1,8 +1,8 @@ import { Box, Button, Icon, ButtonGroup, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useMemo } from 'react'; import TextCopy from '../../../components/TextCopy'; -import { useTranslation } from '../../../contexts/TranslationContext'; type BackupCodesModalProps = { codes: string[]; @@ -25,7 +25,7 @@ const BackupCodesModal: FC = ({ codes, onClose, ...props {t('Make_sure_you_have_a_copy_of_your_codes_1')} - + {t('Make_sure_you_have_a_copy_of_your_codes_2')} diff --git a/apps/meteor/client/views/account/security/EndToEnd.js b/apps/meteor/client/views/account/security/EndToEnd.js index 367f3f1c823d..5c8c381d520b 100644 --- a/apps/meteor/client/views/account/security/EndToEnd.js +++ b/apps/meteor/client/views/account/security/EndToEnd.js @@ -1,15 +1,11 @@ import { Box, Margins, PasswordInput, Field, FieldGroup, Button } from '@rocket.chat/fuselage'; import { useLocalStorage, useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useRoute, useUser, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import React, { useCallback, useEffect } from 'react'; import { e2e } from '../../../../app/e2e/client/rocketchat.e2e'; import { callbacks } from '../../../../lib/callbacks'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUser } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const EndToEnd = (props) => { diff --git a/apps/meteor/client/views/account/security/TwoFactorEmail.js b/apps/meteor/client/views/account/security/TwoFactorEmail.js index 13fdcebe2a85..767b5afcdae2 100644 --- a/apps/meteor/client/views/account/security/TwoFactorEmail.js +++ b/apps/meteor/client/views/account/security/TwoFactorEmail.js @@ -1,8 +1,7 @@ import { Box, Button, Margins } from '@rocket.chat/fuselage'; +import { useUser, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUser } from '../../../contexts/UserContext'; import { useEndpointAction } from '../../../hooks/useEndpointAction'; const TwoFactorEmail = (props) => { diff --git a/apps/meteor/client/views/account/security/TwoFactorTOTP.js b/apps/meteor/client/views/account/security/TwoFactorTOTP.js index 5ba796588e9a..1b17170ccaed 100644 --- a/apps/meteor/client/views/account/security/TwoFactorTOTP.js +++ b/apps/meteor/client/views/account/security/TwoFactorTOTP.js @@ -1,15 +1,11 @@ import { Box, Button, TextInput, Margins } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useSetModal, useToastMessageDispatch, useUser, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, useEffect } from 'react'; import qrcode from 'yaqrcode'; import TextCopy from '../../../components/TextCopy'; import TwoFactorTotpModal from '../../../components/TwoFactorModal/TwoFactorTotpModal'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUser } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; import BackupCodesModal from './BackupCodesModal'; diff --git a/apps/meteor/client/views/account/tokens/AccountTokensPage.js b/apps/meteor/client/views/account/tokens/AccountTokensPage.js index 61df855c1699..ba7a939753d4 100644 --- a/apps/meteor/client/views/account/tokens/AccountTokensPage.js +++ b/apps/meteor/client/views/account/tokens/AccountTokensPage.js @@ -1,7 +1,7 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import Page from '../../../components/Page'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useEndpointData } from '../../../hooks/useEndpointData'; import AccountTokensTable from './AccountTokensTable'; import AddToken from './AddToken'; diff --git a/apps/meteor/client/views/account/tokens/AccountTokensRow.tsx b/apps/meteor/client/views/account/tokens/AccountTokensRow.tsx index 7f6f93bd0c25..188ca546ed7e 100644 --- a/apps/meteor/client/views/account/tokens/AccountTokensRow.tsx +++ b/apps/meteor/client/views/account/tokens/AccountTokensRow.tsx @@ -1,8 +1,8 @@ import { Button, ButtonGroup, Icon, Table } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import type { MomentInput } from 'moment'; import React, { useCallback, FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; type AccountTokensRowProps = { diff --git a/apps/meteor/client/views/account/tokens/AccountTokensTable.js b/apps/meteor/client/views/account/tokens/AccountTokensTable.js index 82200377dfd8..70fb7f80aad6 100644 --- a/apps/meteor/client/views/account/tokens/AccountTokensTable.js +++ b/apps/meteor/client/views/account/tokens/AccountTokensTable.js @@ -1,12 +1,8 @@ import { Box } from '@rocket.chat/fuselage'; +import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useCallback, useState } from 'react'; import GenericTable from '../../../components/GenericTable'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserId } from '../../../contexts/UserContext'; import { useResizeInlineBreakpoint } from '../../../hooks/useResizeInlineBreakpoint'; import AccountTokensRow from './AccountTokensRow'; import InfoModal from './InfoModal'; diff --git a/apps/meteor/client/views/account/tokens/AddToken.js b/apps/meteor/client/views/account/tokens/AddToken.js index e4f0689fe35f..dbcdbcae0a0f 100644 --- a/apps/meteor/client/views/account/tokens/AddToken.js +++ b/apps/meteor/client/views/account/tokens/AddToken.js @@ -1,12 +1,8 @@ import { Box, TextInput, Button, Field, FieldGroup, Margins, CheckBox } from '@rocket.chat/fuselage'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useUserId } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; import InfoModal from './InfoModal'; diff --git a/apps/meteor/client/views/admin/AdministrationRouter.tsx b/apps/meteor/client/views/admin/AdministrationRouter.tsx index 132704acc81a..6a9095a83617 100644 --- a/apps/meteor/client/views/admin/AdministrationRouter.tsx +++ b/apps/meteor/client/views/admin/AdministrationRouter.tsx @@ -1,7 +1,7 @@ +import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; import React, { Suspense, ReactElement, useEffect } from 'react'; import PageSkeleton from '../../components/PageSkeleton'; -import { useCurrentRoute, useRoute } from '../../contexts/RouterContext'; import SettingsProvider from '../../providers/SettingsProvider'; import { useUpgradeTabParams } from '../hooks/useUpgradeTabParams'; import AdministrationLayout from './AdministrationLayout'; diff --git a/apps/meteor/client/contexts/EditableSettingsContext.ts b/apps/meteor/client/views/admin/EditableSettingsContext.ts similarity index 97% rename from apps/meteor/client/contexts/EditableSettingsContext.ts rename to apps/meteor/client/views/admin/EditableSettingsContext.ts index 66ade8b1baf6..af21e8d7f4d4 100644 --- a/apps/meteor/client/contexts/EditableSettingsContext.ts +++ b/apps/meteor/client/views/admin/EditableSettingsContext.ts @@ -1,9 +1,8 @@ import { ISettingBase, SectionName, SettingId, GroupId, TabId } from '@rocket.chat/core-typings'; +import { SettingsContextQuery } from '@rocket.chat/ui-contexts'; import { createContext, useContext, useMemo } from 'react'; import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; -import { SettingsContextQuery } from './SettingsContext'; - export interface IEditableSetting extends ISettingBase { disabled: boolean; changed: boolean; diff --git a/apps/meteor/client/views/admin/apps/APIsDisplay.tsx b/apps/meteor/client/views/admin/apps/APIsDisplay.tsx index ed8f5506a098..11869cd5fa7f 100644 --- a/apps/meteor/client/views/admin/apps/APIsDisplay.tsx +++ b/apps/meteor/client/views/admin/apps/APIsDisplay.tsx @@ -1,9 +1,8 @@ import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; import { Box, Divider } from '@rocket.chat/fuselage'; +import { useAbsoluteUrl, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, Fragment } from 'react'; -import { useAbsoluteUrl } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { apiCurlGetter } from './helpers'; type APIsDisplayProps = { diff --git a/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx b/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx index 3ae2daf98c0e..920efb1373c4 100644 --- a/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx +++ b/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx @@ -1,11 +1,10 @@ import { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { Button, ButtonGroup, Icon, Box, Throbber } from '@rocket.chat/fuselage'; +import { useRoute, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, useRef, FC } from 'react'; import { Apps } from '../../../../app/apps/client/orchestrator'; import Page from '../../../components/Page'; -import { useRoute, useCurrentRoute } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import APIsDisplay from './APIsDisplay'; import AppDetailsPageContent from './AppDetailsPageContent'; import LoadingDetails from './LoadingDetails'; diff --git a/apps/meteor/client/views/admin/apps/AppDetailsPageContent.tsx b/apps/meteor/client/views/admin/apps/AppDetailsPageContent.tsx index 3c97bfdbcce7..3288a0904586 100644 --- a/apps/meteor/client/views/admin/apps/AppDetailsPageContent.tsx +++ b/apps/meteor/client/views/admin/apps/AppDetailsPageContent.tsx @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ import { Box, Callout, Chip, Divider, Margins } from '@rocket.chat/fuselage'; +import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; import ExternalLink from '../../../components/ExternalLink'; import AppAvatar from '../../../components/avatar/AppAvatar'; -import { TranslationKey, useTranslation } from '../../../contexts/TranslationContext'; import AppMenu from './AppMenu'; import AppStatus from './AppStatus'; import PriceDisplay from './PriceDisplay'; diff --git a/apps/meteor/client/views/admin/apps/AppInstallPage.js b/apps/meteor/client/views/admin/apps/AppInstallPage.js index 0a6d4168ab7e..24ff2b6dae09 100644 --- a/apps/meteor/client/views/admin/apps/AppInstallPage.js +++ b/apps/meteor/client/views/admin/apps/AppInstallPage.js @@ -1,12 +1,9 @@ import { Button, ButtonGroup, Icon, Field, FieldGroup, TextInput, Throbber } from '@rocket.chat/fuselage'; +import { useSetModal, useRoute, useQueryStringParameter, useEndpoint, useUpload, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useEffect, useState } from 'react'; import { Apps } from '../../../../app/apps/client/orchestrator'; import Page from '../../../components/Page'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useRoute, useQueryStringParameter } from '../../../contexts/RouterContext'; -import { useEndpoint, useUpload } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useFileInput } from '../../../hooks/useFileInput'; import { useForm } from '../../../hooks/useForm'; import AppPermissionsReviewModal from './AppPermissionsReviewModal'; diff --git a/apps/meteor/client/views/admin/apps/AppLogsPage.js b/apps/meteor/client/views/admin/apps/AppLogsPage.js index 17f0d3f604cb..5d754de2aa30 100644 --- a/apps/meteor/client/views/admin/apps/AppLogsPage.js +++ b/apps/meteor/client/views/admin/apps/AppLogsPage.js @@ -1,11 +1,9 @@ import { Box, Button, ButtonGroup, Icon, Accordion, Pagination } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useCurrentRoute, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, useEffect } from 'react'; import Page from '../../../components/Page'; -import { useCurrentRoute, useRoute } from '../../../contexts/RouterContext'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; import LogItem from './LogItem'; import LogsLoading from './LogsLoading'; diff --git a/apps/meteor/client/views/admin/apps/AppMenu.js b/apps/meteor/client/views/admin/apps/AppMenu.js index 1a5d87a6b342..1352d4dc2d9f 100644 --- a/apps/meteor/client/views/admin/apps/AppMenu.js +++ b/apps/meteor/client/views/admin/apps/AppMenu.js @@ -1,10 +1,7 @@ import { Box, Icon, Menu } from '@rocket.chat/fuselage'; +import { useSetModal, useRoute, useMethod, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useCallback } from 'react'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useMethod, useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import CloudLoginModal from './CloudLoginModal'; import IframeModal from './IframeModal'; import WarningModal from './WarningModal'; diff --git a/apps/meteor/client/views/admin/apps/AppPermissionsReviewModal.js b/apps/meteor/client/views/admin/apps/AppPermissionsReviewModal.js index 4185c0f993be..82f4e74c4d4c 100644 --- a/apps/meteor/client/views/admin/apps/AppPermissionsReviewModal.js +++ b/apps/meteor/client/views/admin/apps/AppPermissionsReviewModal.js @@ -1,8 +1,7 @@ import { Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - const AppPermissionsReviewModal = ({ appPermissions, cancel, confirm, modalProps = {} }) => { const t = useTranslation(); diff --git a/apps/meteor/client/views/admin/apps/AppRow.tsx b/apps/meteor/client/views/admin/apps/AppRow.tsx index 23d988aab82d..b03fb6569a05 100644 --- a/apps/meteor/client/views/admin/apps/AppRow.tsx +++ b/apps/meteor/client/views/admin/apps/AppRow.tsx @@ -1,9 +1,8 @@ import { Box, Table, Tag } from '@rocket.chat/fuselage'; +import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useState, memo, KeyboardEvent, MouseEvent } from 'react'; import AppAvatar from '../../../components/avatar/AppAvatar'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import AppMenu from './AppMenu'; import AppStatus from './AppStatus'; import { App } from './types'; diff --git a/apps/meteor/client/views/admin/apps/AppSetting.js b/apps/meteor/client/views/admin/apps/AppSetting.js index 1dfd4f824a58..27d144c14817 100644 --- a/apps/meteor/client/views/admin/apps/AppSetting.js +++ b/apps/meteor/client/views/admin/apps/AppSetting.js @@ -1,8 +1,7 @@ +import { useRouteParameter, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useCallback } from 'react'; import MarkdownText from '../../../components/MarkdownText'; -import { useRouteParameter } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import MemoizedSetting from '../settings/MemoizedSetting'; const useAppTranslation = (appId) => { diff --git a/apps/meteor/client/views/admin/apps/AppStatus.js b/apps/meteor/client/views/admin/apps/AppStatus.js index e99da2654191..179b7d23aa63 100644 --- a/apps/meteor/client/views/admin/apps/AppStatus.js +++ b/apps/meteor/client/views/admin/apps/AppStatus.js @@ -1,11 +1,9 @@ import { Box, Button, Icon, Throbber } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useSetModal, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, memo } from 'react'; import { Apps } from '../../../../app/apps/client/orchestrator'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import AppPermissionsReviewModal from './AppPermissionsReviewModal'; import CloudLoginModal from './CloudLoginModal'; import IframeModal from './IframeModal'; diff --git a/apps/meteor/client/views/admin/apps/AppUpdateModal.tsx b/apps/meteor/client/views/admin/apps/AppUpdateModal.tsx index 72c3efa6bf26..e8ebb15c2dc7 100644 --- a/apps/meteor/client/views/admin/apps/AppUpdateModal.tsx +++ b/apps/meteor/client/views/admin/apps/AppUpdateModal.tsx @@ -1,8 +1,7 @@ import { Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - type AppUpdateModalProps = { confirm: () => void; cancel: () => void; diff --git a/apps/meteor/client/views/admin/apps/AppsPage.tsx b/apps/meteor/client/views/admin/apps/AppsPage.tsx index 578217c79851..44542bee035c 100644 --- a/apps/meteor/client/views/admin/apps/AppsPage.tsx +++ b/apps/meteor/client/views/admin/apps/AppsPage.tsx @@ -1,11 +1,8 @@ import { Button, ButtonGroup, Icon, Skeleton, Tabs } from '@rocket.chat/fuselage'; +import { useRoute, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect, useState, ReactElement } from 'react'; import Page from '../../../components/Page'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import AppsTable from './AppsTable'; type AppsPageProps = { diff --git a/apps/meteor/client/views/admin/apps/AppsRoute.tsx b/apps/meteor/client/views/admin/apps/AppsRoute.tsx index 53a41f68df75..907394388c2e 100644 --- a/apps/meteor/client/views/admin/apps/AppsRoute.tsx +++ b/apps/meteor/client/views/admin/apps/AppsRoute.tsx @@ -1,9 +1,7 @@ +import { useRouteParameter, useRoute, usePermission, useMethod } from '@rocket.chat/ui-contexts'; import React, { useState, useEffect, FC } from 'react'; import PageSkeleton from '../../../components/PageSkeleton'; -import { usePermission } from '../../../contexts/AuthorizationContext'; -import { useRouteParameter, useRoute } from '../../../contexts/RouterContext'; -import { useMethod } from '../../../contexts/ServerContext'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import AppDetailsPage from './AppDetailsPage'; import AppInstallPage from './AppInstallPage'; diff --git a/apps/meteor/client/views/admin/apps/AppsTable.tsx b/apps/meteor/client/views/admin/apps/AppsTable.tsx index dc426fe6c88f..b4d157856a00 100644 --- a/apps/meteor/client/views/admin/apps/AppsTable.tsx +++ b/apps/meteor/client/views/admin/apps/AppsTable.tsx @@ -14,6 +14,7 @@ import { Icon, } from '@rocket.chat/fuselage'; import { useDebouncedState } from '@rocket.chat/fuselage-hooks'; +import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useMemo, useState } from 'react'; import FilterByText from '../../../components/FilterByText'; @@ -25,8 +26,6 @@ import { GenericTableLoadingTable, } from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useResizeInlineBreakpoint } from '../../../hooks/useResizeInlineBreakpoint'; import { AsyncStatePhase } from '../../../lib/asyncState'; import AppRow from './AppRow'; diff --git a/apps/meteor/client/views/admin/apps/AppsWhatIsIt.tsx b/apps/meteor/client/views/admin/apps/AppsWhatIsIt.tsx index 5e1a50efd76f..8cecdcf6cb90 100644 --- a/apps/meteor/client/views/admin/apps/AppsWhatIsIt.tsx +++ b/apps/meteor/client/views/admin/apps/AppsWhatIsIt.tsx @@ -1,12 +1,10 @@ import { Button, Box, Throbber } from '@rocket.chat/fuselage'; +import { useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useState } from 'react'; import { Apps } from '../../../../app/apps/client'; import ExternalLink from '../../../components/ExternalLink'; import Page from '../../../components/Page'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; const readMeUrl = 'https://go.rocket.chat/i/developing-an-app'; diff --git a/apps/meteor/client/views/admin/apps/CloudLoginModal.tsx b/apps/meteor/client/views/admin/apps/CloudLoginModal.tsx index 35a069e3a82d..bf788bc2060d 100644 --- a/apps/meteor/client/views/admin/apps/CloudLoginModal.tsx +++ b/apps/meteor/client/views/admin/apps/CloudLoginModal.tsx @@ -1,9 +1,7 @@ +import { useSetModal, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; import GenericModal from '../../../components/GenericModal'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; const CloudLoginModal = (): ReactElement => { const t = useTranslation(); diff --git a/apps/meteor/client/views/admin/apps/LogEntry.tsx b/apps/meteor/client/views/admin/apps/LogEntry.tsx index 58111c0e803d..80db19ffa8f5 100644 --- a/apps/meteor/client/views/admin/apps/LogEntry.tsx +++ b/apps/meteor/client/views/admin/apps/LogEntry.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useHighlightedCode } from '../../../hooks/useHighlightedCode'; type LogEntryProps = { diff --git a/apps/meteor/client/views/admin/apps/LogItem.tsx b/apps/meteor/client/views/admin/apps/LogItem.tsx index e386bc5a9449..dccf796cb401 100644 --- a/apps/meteor/client/views/admin/apps/LogItem.tsx +++ b/apps/meteor/client/views/admin/apps/LogItem.tsx @@ -1,7 +1,7 @@ import { Box, Accordion } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import LogEntry from './LogEntry'; type LogItemProps = { diff --git a/apps/meteor/client/views/admin/apps/MarketplaceRow.tsx b/apps/meteor/client/views/admin/apps/MarketplaceRow.tsx index a8f0eeaa731f..8c63ed3b7cb7 100644 --- a/apps/meteor/client/views/admin/apps/MarketplaceRow.tsx +++ b/apps/meteor/client/views/admin/apps/MarketplaceRow.tsx @@ -1,9 +1,8 @@ import { Box, Table, Tag } from '@rocket.chat/fuselage'; +import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, memo, FC, KeyboardEvent, MouseEvent } from 'react'; import AppAvatar from '../../../components/avatar/AppAvatar'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import AppMenu from './AppMenu'; import AppStatus from './AppStatus'; import PriceDisplay from './PriceDisplay'; diff --git a/apps/meteor/client/views/admin/apps/PriceDisplay.js b/apps/meteor/client/views/admin/apps/PriceDisplay.js index c2f7b1ecc541..cfdfc956e914 100644 --- a/apps/meteor/client/views/admin/apps/PriceDisplay.js +++ b/apps/meteor/client/views/admin/apps/PriceDisplay.js @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { formatPricingPlan, formatPrice } from './helpers'; const formatPriceAndPurchaseType = (purchaseType, pricingPlans, price) => { diff --git a/apps/meteor/client/views/admin/apps/SettingsDisplay.tsx b/apps/meteor/client/views/admin/apps/SettingsDisplay.tsx index e0612aad6f7e..3556e8d44f58 100644 --- a/apps/meteor/client/views/admin/apps/SettingsDisplay.tsx +++ b/apps/meteor/client/views/admin/apps/SettingsDisplay.tsx @@ -1,8 +1,8 @@ import { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { Box, Divider } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useMemo, useEffect, MutableRefObject } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useForm } from '../../../hooks/useForm'; import AppSettingsAssembler from './AppSettingsAssembler'; diff --git a/apps/meteor/client/views/admin/apps/WarningModal.js b/apps/meteor/client/views/admin/apps/WarningModal.js index f40a69c973a6..982d743dd73c 100644 --- a/apps/meteor/client/views/admin/apps/WarningModal.js +++ b/apps/meteor/client/views/admin/apps/WarningModal.js @@ -1,8 +1,7 @@ import { Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; - const WarningModal = ({ text, confirmText, close, cancel, cancelText, confirm, ...props }) => { const t = useTranslation(); return ( diff --git a/apps/meteor/client/views/admin/apps/components/CategoryFilter/CategoryDropDownAnchor.tsx b/apps/meteor/client/views/admin/apps/components/CategoryFilter/CategoryDropDownAnchor.tsx index fdf9ec9169f0..e91bfae07682 100644 --- a/apps/meteor/client/views/admin/apps/components/CategoryFilter/CategoryDropDownAnchor.tsx +++ b/apps/meteor/client/views/admin/apps/components/CategoryFilter/CategoryDropDownAnchor.tsx @@ -1,8 +1,7 @@ import { Box, Button, Icon, Select } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ComponentProps, forwardRef } from 'react'; -import { useTranslation } from '../../../../../contexts/TranslationContext'; - const CategoryDropDownAnchor = forwardRef> & { selectedCategoriesCount: number }>( function CategoryDropDownAnchor(props, ref) { const t = useTranslation(); diff --git a/apps/meteor/client/views/admin/apps/hooks/useCategories.ts b/apps/meteor/client/views/admin/apps/hooks/useCategories.ts index d7f7d349310b..172d7facebe4 100644 --- a/apps/meteor/client/views/admin/apps/hooks/useCategories.ts +++ b/apps/meteor/client/views/admin/apps/hooks/useCategories.ts @@ -1,7 +1,7 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Apps } from '../../../../../app/apps/client/orchestrator'; -import { useTranslation } from '../../../../contexts/TranslationContext'; import { CategoryDropdownItem, CategoryDropDownListProps } from '../definitions/CategoryDropdownDefinitions'; import { handleAPIError } from '../helpers'; import { useCategoryFlatList } from './useCategoryFlatList'; diff --git a/apps/meteor/client/views/admin/cloud/CloudPage.tsx b/apps/meteor/client/views/admin/cloud/CloudPage.tsx index b99b8a87917f..c12c109705c9 100644 --- a/apps/meteor/client/views/admin/cloud/CloudPage.tsx +++ b/apps/meteor/client/views/admin/cloud/CloudPage.tsx @@ -1,12 +1,16 @@ import { Box, Button, ButtonGroup, Margins } from '@rocket.chat/fuselage'; +import { + useSetModal, + useToastMessageDispatch, + useQueryStringParameter, + useRoute, + useRouteParameter, + useMethod, + useTranslation, +} from '@rocket.chat/ui-contexts'; import React, { useEffect, ReactNode } from 'react'; import Page from '../../../components/Page'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useQueryStringParameter, useRoute, useRouteParameter } from '../../../contexts/RouterContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useMethodData } from '../../../hooks/useMethodData'; import ConnectToCloudSection from './ConnectToCloudSection'; import ManualWorkspaceRegistrationModal from './ManualWorkspaceRegistrationModal'; diff --git a/apps/meteor/client/views/admin/cloud/CloudRoute.js b/apps/meteor/client/views/admin/cloud/CloudRoute.js index 5a469c8fc316..4fb12961d043 100644 --- a/apps/meteor/client/views/admin/cloud/CloudRoute.js +++ b/apps/meteor/client/views/admin/cloud/CloudRoute.js @@ -1,6 +1,6 @@ +import { usePermission } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { usePermission } from '../../../contexts/AuthorizationContext'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import CloudPage from './CloudPage'; diff --git a/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js b/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js index cfdb2f6c6b56..94676b0ebfc8 100644 --- a/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js +++ b/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js @@ -1,12 +1,9 @@ import { Box, Button, ButtonGroup, Throbber, Callout } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState } from 'react'; import Subtitle from '../../../components/Subtitle'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; function ConnectToCloudSection({ onRegisterStatusChange, ...props }) { const t = useTranslation(); diff --git a/apps/meteor/client/views/admin/cloud/CopyStep.tsx b/apps/meteor/client/views/admin/cloud/CopyStep.tsx index acd3a675283f..b69c5cae74b8 100644 --- a/apps/meteor/client/views/admin/cloud/CopyStep.tsx +++ b/apps/meteor/client/views/admin/cloud/CopyStep.tsx @@ -1,11 +1,9 @@ import { Box, Button, ButtonGroup, Icon, Scrollable, Modal } from '@rocket.chat/fuselage'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import Clipboard from 'clipboard'; import React, { useEffect, useState, useRef, FC } from 'react'; import MarkdownText from '../../../components/MarkdownText'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { cloudConsoleUrl } from './constants'; type CopyStepProps = { diff --git a/apps/meteor/client/views/admin/cloud/ManualWorkspaceRegistrationModal.js b/apps/meteor/client/views/admin/cloud/ManualWorkspaceRegistrationModal.js index 2a71c71f387e..8b8c8d4bc6aa 100644 --- a/apps/meteor/client/views/admin/cloud/ManualWorkspaceRegistrationModal.js +++ b/apps/meteor/client/views/admin/cloud/ManualWorkspaceRegistrationModal.js @@ -1,7 +1,7 @@ import { Modal } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import CopyStep from './CopyStep'; import PasteStep from './PasteStep'; diff --git a/apps/meteor/client/views/admin/cloud/PasteStep.tsx b/apps/meteor/client/views/admin/cloud/PasteStep.tsx index 1da05d5bbe12..254882680d32 100644 --- a/apps/meteor/client/views/admin/cloud/PasteStep.tsx +++ b/apps/meteor/client/views/admin/cloud/PasteStep.tsx @@ -1,10 +1,7 @@ import { Box, Button, ButtonGroup, Scrollable, Throbber, Modal } from '@rocket.chat/fuselage'; +import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ChangeEvent, FC, useState } from 'react'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; - type PasteStepProps = { onBackButtonClick: () => void; onFinish: () => void; diff --git a/apps/meteor/client/views/admin/cloud/TroubleshootingSection.js b/apps/meteor/client/views/admin/cloud/TroubleshootingSection.js index 49798444d617..6ce3bf252d81 100644 --- a/apps/meteor/client/views/admin/cloud/TroubleshootingSection.js +++ b/apps/meteor/client/views/admin/cloud/TroubleshootingSection.js @@ -1,11 +1,9 @@ import { Box, Button, ButtonGroup, Throbber } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState } from 'react'; import Subtitle from '../../../components/Subtitle'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { statusPageUrl } from './constants'; function TroubleshootingSection({ onRegisterStatusChange, ...props }) { diff --git a/apps/meteor/client/views/admin/cloud/WhatIsItSection.js b/apps/meteor/client/views/admin/cloud/WhatIsItSection.js index bb601fc8c0db..c9792bef40af 100644 --- a/apps/meteor/client/views/admin/cloud/WhatIsItSection.js +++ b/apps/meteor/client/views/admin/cloud/WhatIsItSection.js @@ -1,8 +1,8 @@ import { Box } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import Subtitle from '../../../components/Subtitle'; -import { useTranslation } from '../../../contexts/TranslationContext'; function WhatIsItSection(props) { const t = useTranslation(); diff --git a/apps/meteor/client/views/admin/cloud/WorkspaceLoginSection.js b/apps/meteor/client/views/admin/cloud/WorkspaceLoginSection.js index e3a67a579a4d..8a2cbdd7f666 100644 --- a/apps/meteor/client/views/admin/cloud/WorkspaceLoginSection.js +++ b/apps/meteor/client/views/admin/cloud/WorkspaceLoginSection.js @@ -1,11 +1,8 @@ import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useEffect } from 'react'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; - function WorkspaceLoginSection({ onRegisterStatusChange, ...props }) { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/views/admin/cloud/WorkspaceRegistrationSection.js b/apps/meteor/client/views/admin/cloud/WorkspaceRegistrationSection.js index e2ab0e51e61b..c883df3371d7 100644 --- a/apps/meteor/client/views/admin/cloud/WorkspaceRegistrationSection.js +++ b/apps/meteor/client/views/admin/cloud/WorkspaceRegistrationSection.js @@ -1,11 +1,8 @@ import { Box, Button, ButtonGroup, Field, Margins, TextInput } from '@rocket.chat/fuselage'; import { useSafely, useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState } from 'react'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; - function WorkspaceRegistrationSection({ token: initialToken, workspaceId, uniqueId, onRegisterStatusChange, ...props }) { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx index a8c9a4751734..6566c69c2243 100644 --- a/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx @@ -1,8 +1,8 @@ import { Box, Button, ButtonGroup, Margins, TextInput, Field, Icon } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, ReactElement, ChangeEvent } from 'react'; import VerticalBar from '../../../components/VerticalBar'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useEndpointUpload } from '../../../hooks/useEndpointUpload'; import { useFileInput } from '../../../hooks/useFileInput'; diff --git a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx index a6761be3be00..2bccdf19ba1c 100644 --- a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx @@ -1,5 +1,6 @@ import { Box, Pagination } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, MutableRefObject, useEffect, useMemo, useState } from 'react'; import FilterByText from '../../../components/FilterByText'; @@ -14,7 +15,6 @@ import { } from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useEndpointData } from '../../../hooks/useEndpointData'; import { AsyncStatePhase } from '../../../lib/asyncState'; diff --git a/apps/meteor/client/views/admin/customEmoji/CustomEmojiRoute.tsx b/apps/meteor/client/views/admin/customEmoji/CustomEmojiRoute.tsx index 6ea6319c11a8..84de4c8ac119 100644 --- a/apps/meteor/client/views/admin/customEmoji/CustomEmojiRoute.tsx +++ b/apps/meteor/client/views/admin/customEmoji/CustomEmojiRoute.tsx @@ -1,11 +1,9 @@ import { Button, Icon } from '@rocket.chat/fuselage'; +import { useRoute, useRouteParameter, usePermission, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useRef, ReactElement } from 'react'; import Page from '../../../components/Page'; import VerticalBar from '../../../components/VerticalBar'; -import { usePermission } from '../../../contexts/AuthorizationContext'; -import { useRoute, useRouteParameter } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import AddCustomEmoji from './AddCustomEmoji'; import CustomEmoji from './CustomEmoji'; diff --git a/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx index f111066d4043..61472d809323 100644 --- a/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx @@ -1,12 +1,9 @@ import { Box, Button, ButtonGroup, Margins, TextInput, Field, Icon, FieldGroup } from '@rocket.chat/fuselage'; +import { useSetModal, useToastMessageDispatch, useAbsoluteUrl, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, useMemo, useEffect, FC, ChangeEvent } from 'react'; import GenericModal from '../../../components/GenericModal'; import VerticalBar from '../../../components/VerticalBar'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useAbsoluteUrl } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useEndpointAction } from '../../../hooks/useEndpointAction'; import { useEndpointUpload } from '../../../hooks/useEndpointUpload'; import { useFileInput } from '../../../hooks/useFileInput'; diff --git a/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx b/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx index f43babbfb05c..8bff47185cb7 100644 --- a/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx +++ b/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx @@ -1,7 +1,7 @@ import { Box, Button, ButtonGroup, Skeleton, Throbber, InputBox, Callout } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, FC } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../../hooks/useAsyncState'; import { useEndpointData } from '../../../hooks/useEndpointData'; import EditCustomEmoji from './EditCustomEmoji'; diff --git a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx index 0b53670c1390..8f9669a9ef1a 100644 --- a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx @@ -1,10 +1,8 @@ import { Field, TextInput, Box, Icon, Margins, Button, ButtonGroup } from '@rocket.chat/fuselage'; +import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, ReactElement, FormEvent } from 'react'; import VerticalBar from '../../../components/VerticalBar'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useFileInput } from '../../../hooks/useFileInput'; import { validate, createSoundData, soundDataType } from './lib'; diff --git a/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx b/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx index 03441862c987..e489eb7b3eeb 100644 --- a/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx +++ b/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx @@ -1,5 +1,6 @@ import { Box, Button, Icon, Pagination } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useCustomSound, useRoute, useRouteParameter, usePermission, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useState, useCallback, ReactElement } from 'react'; import FilterByText from '../../../components/FilterByText'; @@ -14,10 +15,6 @@ import { usePagination } from '../../../components/GenericTable/hooks/usePaginat import { useSort } from '../../../components/GenericTable/hooks/useSort'; import Page from '../../../components/Page'; import VerticalBar from '../../../components/VerticalBar'; -import { usePermission } from '../../../contexts/AuthorizationContext'; -import { useCustomSound } from '../../../contexts/CustomSoundContext'; -import { useRoute, useRouteParameter } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useEndpointData } from '../../../hooks/useEndpointData'; import { AsyncStatePhase } from '../../../lib/asyncState'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; diff --git a/apps/meteor/client/views/admin/customSounds/EditSound.tsx b/apps/meteor/client/views/admin/customSounds/EditSound.tsx index 18f5d3ad8d4e..010f12c28f30 100644 --- a/apps/meteor/client/views/admin/customSounds/EditSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/EditSound.tsx @@ -1,12 +1,9 @@ import { Box, Button, ButtonGroup, Margins, TextInput, Field, Icon } from '@rocket.chat/fuselage'; +import { useSetModal, useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, useMemo, useEffect, ReactElement, SyntheticEvent } from 'react'; import GenericModal from '../../../components/GenericModal'; import VerticalBar from '../../../components/VerticalBar'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { useFileInput } from '../../../hooks/useFileInput'; import { validate, createSoundData } from './lib'; diff --git a/apps/meteor/client/views/admin/customUserStatus/AddCustomUserStatus.tsx b/apps/meteor/client/views/admin/customUserStatus/AddCustomUserStatus.tsx deleted file mode 100644 index e81533603e6a..000000000000 --- a/apps/meteor/client/views/admin/customUserStatus/AddCustomUserStatus.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Button, ButtonGroup, TextInput, Field, Select, SelectOption } from '@rocket.chat/fuselage'; -import React, { ReactElement, SyntheticEvent, useCallback, useState } from 'react'; - -import VerticalBar from '../../../components/VerticalBar'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; - -type AddCustomUserStatusProps = { - goToNew: (id: string) => () => void; - close: () => void; - onChange: () => void; -}; - -function AddCustomUserStatus({ goToNew, close, onChange, ...props }: AddCustomUserStatusProps): ReactElement { - const t = useTranslation(); - const dispatchToastMessage = useToastMessageDispatch(); - - const [name, setName] = useState(''); - const [statusType, setStatusType] = useState('online'); - - const saveStatus = useMethod('insertOrUpdateUserStatus'); - const handleSave = useCallback(async () => { - try { - const result = await saveStatus({ - name, - statusType, - }); - dispatchToastMessage({ - type: 'success', - message: t('Custom_User_Status_Updated_Successfully'), - }); - goToNew(result)(); - onChange(); - } catch (error) { - dispatchToastMessage({ type: 'error', message: String(error) }); - } - }, [dispatchToastMessage, goToNew, name, onChange, saveStatus, statusType, t]); - - const presenceOptions: SelectOption[] = [ - ['online', t('Online')], - ['busy', t('Busy')], - ['away', t('Away')], - ['offline', t('Offline')], - ]; - - return ( - - - {t('Name')} - - ): void => setName(e.currentTarget.value)} - placeholder={t('Name')} - /> - - - - {t('Presence')} - - } + /> + + {errors?.statusType && {t('error-the-field-is-required', { field: t('Presence') })}} + + + + + + + + + + {_id && ( + + + + + + + + )} + + ); +}; + +export default CustomUserStatusForm; diff --git a/apps/meteor/client/views/admin/customUserStatus/EditCustomUserStatusWithData.tsx b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx similarity index 50% rename from apps/meteor/client/views/admin/customUserStatus/EditCustomUserStatusWithData.tsx rename to apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx index 8fe62b626e6b..ace27c478ec8 100644 --- a/apps/meteor/client/views/admin/customUserStatus/EditCustomUserStatusWithData.tsx +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx @@ -1,26 +1,36 @@ -import { Box, Button, ButtonGroup, Skeleton, Throbber, InputBox } from '@rocket.chat/fuselage'; -import React, { useMemo, FC } from 'react'; +import { IUserStatus } from '@rocket.chat/core-typings'; +import { Box, Button, ButtonGroup, Skeleton, Throbber, InputBox, Callout } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useMemo, ReactElement } from 'react'; -import { useTranslation } from '../../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../../hooks/useAsyncState'; import { useEndpointData } from '../../../hooks/useEndpointData'; -import EditCustomUserStatus from './EditCustomUserStatus'; +import CustomUserStatusForm from './CustomUserStatusForm'; -type EditCustomUserStatusWithDataProps = { - _id: string | undefined; - close: () => void; - onChange: () => void; +type CustomUserStatusFormWithDataProps = { + _id?: IUserStatus['_id']; + onClose: () => void; + onReload: () => void; }; -export const EditCustomUserStatusWithData: FC = ({ _id, onChange, ...props }) => { +const CustomUserStatusFormWithData = ({ _id, onReload, onClose }: CustomUserStatusFormWithDataProps): ReactElement => { const t = useTranslation(); const query = useMemo(() => ({ query: JSON.stringify({ _id }) }), [_id]); const { value: data, phase: state, error, reload } = useEndpointData('custom-user-status.list', query); + const handleReload = (): void => { + onReload?.(); + reload?.(); + }; + + if (!_id) { + return ; + } + if (state === AsyncStatePhase.LOADING) { return ( - + @@ -44,18 +54,13 @@ export const EditCustomUserStatusWithData: FC if (error || !data || data.statuses.length < 1) { return ( - - {t('Custom_User_Status_Error_Invalid_User_Status')} + + {t('Custom_User_Status_Error_Invalid_User_Status')} ); } - const handleChange = (): void => { - onChange?.(); - reload?.(); - }; - - return ; + return ; }; -export default EditCustomUserStatusWithData; +export default CustomUserStatusFormWithData; diff --git a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusRoute.tsx b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusRoute.tsx index 34989a0e865b..89695f9f648d 100644 --- a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusRoute.tsx +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusRoute.tsx @@ -1,61 +1,27 @@ import { Button, Icon } from '@rocket.chat/fuselage'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useState, useCallback, ReactNode } from 'react'; +import { useRoute, useRouteParameter, usePermission, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback, ReactNode, useRef } from 'react'; import Page from '../../../components/Page'; import VerticalBar from '../../../components/VerticalBar'; -import { usePermission } from '../../../contexts/AuthorizationContext'; -import { useRoute, useRouteParameter } from '../../../contexts/RouterContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useEndpointData } from '../../../hooks/useEndpointData'; -import { AsyncStatePhase } from '../../../lib/asyncState'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; -import AddCustomUserStatus from './AddCustomUserStatus'; -import CustomUserStatus, { paramsType, SortType } from './CustomUserStatus'; -import EditCustomUserStatusWithData from './EditCustomUserStatusWithData'; +import CustomUserStatusFormWithData from './CustomUserStatusFormWithData'; +import CustomUserStatusTable from './CustomUserStatusTable'; -function CustomUserStatusRoute(): ReactNode { +const CustomUserStatusRoute = (): ReactNode => { + const t = useTranslation(); const route = useRoute('custom-user-status'); const context = useRouteParameter('context'); const id = useRouteParameter('id'); const canManageUserStatus = usePermission('manage-user-status'); - const t = useTranslation(); - - const [params, setParams] = useState(() => ({ text: '', current: 0, itemsPerPage: 25 })); - const [sort, setSort] = useState(() => ['name', 'asc']); - - const { text, itemsPerPage, current } = useDebouncedValue(params, 500); - const [column, direction] = useDebouncedValue(sort, 500); - const query = useMemo( - () => ({ - query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }), - sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }), - ...(itemsPerPage && { count: itemsPerPage }), - ...(current && { offset: current }), - }), - [text, itemsPerPage, current, column, direction], - ); - - const { reload, ...result } = useEndpointData('custom-user-status.list', query); - - const handleItemClick = (id: string) => (): void => { + const handleItemClick = (id: string): void => { route.push({ context: 'edit', id, }); }; - const handleHeaderClick = (id: SortType[0]): void => { - setSort(([sortBy, sortDirection]) => { - if (sortBy === id) { - return [id, sortDirection === 'asc' ? 'desc' : 'asc']; - } - - return [id, 'asc']; - }); - }; - const handleNewButtonClick = useCallback(() => { route.push({ context: 'new' }); }, [route]); @@ -64,18 +30,16 @@ function CustomUserStatusRoute(): ReactNode { route.push({}); }, [route]); - const handleChange = useCallback(() => { - reload(); + const reload = useRef(() => null); + + const handleReload = useCallback(() => { + reload.current(); }, [reload]); if (!canManageUserStatus) { return ; } - if (result.phase === AsyncStatePhase.LOADING || result.phase === AsyncStatePhase.REJECTED) { - return null; - } - return ( @@ -85,30 +49,20 @@ function CustomUserStatusRoute(): ReactNode { - + {context && ( - {context === 'edit' && t('Custom_User_Status_Edit')} - {context === 'new' && t('Custom_User_Status_Add')} + {context === 'edit' ? t('Custom_User_Status_Edit') : t('Custom_User_Status_Add')} - - {context === 'edit' && } - {context === 'new' && } + )} ); -} +}; export default CustomUserStatusRoute; diff --git a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusRow.tsx b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusRow.tsx new file mode 100644 index 000000000000..958f51f5ad20 --- /dev/null +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusRow.tsx @@ -0,0 +1,37 @@ +import { IUserStatus } from '@rocket.chat/core-typings'; +import { TableRow, TableCell } from '@rocket.chat/fuselage'; +import React, { CSSProperties, ReactElement } from 'react'; + +import MarkdownText from '../../../../components/MarkdownText'; + +const style: CSSProperties = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }; + +type CustomUserStatusRowProps = { + status: IUserStatus; + onClick: (id: string) => void; +}; + +const CustomUserStatusRow = ({ status, onClick }: CustomUserStatusRowProps): ReactElement => { + const { _id, name, statusType } = status; + + return ( + onClick(_id)} + onClick={(): void => onClick(_id)} + tabIndex={0} + role='link' + action + qa-user-id={_id} + > + + + + + {statusType} + + + ); +}; + +export default CustomUserStatusRow; diff --git a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx new file mode 100644 index 000000000000..4407b664695a --- /dev/null +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx @@ -0,0 +1,103 @@ +import { States, StatesIcon, StatesTitle, Pagination } from '@rocket.chat/fuselage'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement, useState, useMemo, MutableRefObject, useEffect } from 'react'; + +import FilterByText from '../../../../components/FilterByText'; +import { + GenericTable, + GenericTableHeader, + GenericTableHeaderCell, + GenericTableBody, + GenericTableLoadingTable, +} from '../../../../components/GenericTable'; +import { usePagination } from '../../../../components/GenericTable/hooks/usePagination'; +import { useSort } from '../../../../components/GenericTable/hooks/useSort'; +import { useEndpointData } from '../../../../hooks/useEndpointData'; +import { AsyncStatePhase } from '../../../../lib/asyncState'; +import CustomUserStatusRow from './CustomUserStatusRow'; + +type CustomUserStatusProps = { + reload: MutableRefObject<() => void>; + onClick: (id: string) => void; +}; + +const CustomUserStatus = ({ reload, onClick }: CustomUserStatusProps): ReactElement | null => { + const t = useTranslation(); + const [text, setText] = useState(''); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + const { sortBy, sortDirection, setSort } = useSort<'name' | 'statusType'>('name'); + + const query = useDebouncedValue( + useMemo( + () => ({ + query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }), + sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, + count: itemsPerPage, + offset: current, + }), + [text, itemsPerPage, current, sortBy, sortDirection], + ), + 500, + ); + + const { value, reload: reloadEndpoint, phase } = useEndpointData('custom-user-status.list', query); + + useEffect(() => { + reload.current = reloadEndpoint; + }, [reload, reloadEndpoint]); + + if (phase === AsyncStatePhase.REJECTED) { + return null; + } + + return ( + <> + setText(text)} /> + {value?.statuses.length === 0 && ( + + + {t('No_results_found')} + + )} + {value?.statuses && value.statuses.length > 0 && ( + <> + + + + {t('Name')} + + + {t('Presence')} + + + + {phase === AsyncStatePhase.LOADING && } + {value?.statuses.map((status) => ( + + ))} + + + {phase === AsyncStatePhase.RESOLVED && ( + + )} + + )} + + ); +}; + +export default CustomUserStatus; diff --git a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/index.ts b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/index.ts new file mode 100644 index 000000000000..814ae75ec5a9 --- /dev/null +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/index.ts @@ -0,0 +1 @@ +export { default } from './CustomUserStatusTable'; diff --git a/apps/meteor/client/views/admin/customUserStatus/EditCustomUserStatus.tsx b/apps/meteor/client/views/admin/customUserStatus/EditCustomUserStatus.tsx deleted file mode 100644 index 64801f6153f7..000000000000 --- a/apps/meteor/client/views/admin/customUserStatus/EditCustomUserStatus.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Button, ButtonGroup, TextInput, Field, Select, Icon, SelectOption } from '@rocket.chat/fuselage'; -import React, { useCallback, useState, useMemo, useEffect, ReactElement, SyntheticEvent } from 'react'; - -import GenericModal from '../../../components/GenericModal'; -import VerticalBar from '../../../components/VerticalBar'; -import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; - -type EditCustomUserStatusProps = { - close: () => void; - onChange: () => void; - data?: { - _id: string; - name: string; - statusType: string; - }; -}; -export function EditCustomUserStatus({ close, onChange, data, ...props }: EditCustomUserStatusProps): ReactElement { - const t = useTranslation(); - const dispatchToastMessage = useToastMessageDispatch(); - const setModal = useSetModal(); - - const { _id, name: previousName, statusType: previousStatusType } = data || {}; - - const [name, setName] = useState(() => data?.name ?? ''); - const [statusType, setStatusType] = useState(() => data?.statusType ?? ''); - - useEffect(() => { - setName(previousName || ''); - setStatusType(previousStatusType || ''); - }, [previousName, previousStatusType, _id]); - - const saveStatus = useMethod('insertOrUpdateUserStatus'); - const deleteStatus = useMethod('deleteCustomUserStatus'); - - const hasUnsavedChanges = useMemo( - () => previousName !== name || previousStatusType !== statusType, - [name, previousName, previousStatusType, statusType], - ); - const handleSave = useCallback(async () => { - try { - await saveStatus({ - _id, - previousName, - previousStatusType, - name, - statusType, - }); - dispatchToastMessage({ - type: 'success', - message: t('Custom_User_Status_Updated_Successfully'), - }); - onChange(); - } catch (error) { - dispatchToastMessage({ type: 'error', message: String(error) }); - } - }, [saveStatus, _id, previousName, previousStatusType, name, statusType, dispatchToastMessage, t, onChange]); - - const handleDeleteButtonClick = useCallback(() => { - const handleClose = (): void => { - setModal(null); - close(); - onChange(); - }; - - const handleDelete = async (): Promise => { - try { - await deleteStatus(_id); - setModal(() => ( - - {t('Custom_User_Status_Has_Been_Deleted')} - - )); - } catch (error) { - dispatchToastMessage({ type: 'error', message: String(error) }); - onChange(); - } - }; - - const handleCancel = (): void => { - setModal(null); - }; - - setModal(() => ( - - {t('Custom_User_Status_Delete_Warning')} - - )); - }, [_id, close, deleteStatus, dispatchToastMessage, onChange, setModal, t]); - - const presenceOptions: SelectOption[] = [ - ['online', t('Online')], - ['busy', t('Busy')], - ['away', t('Away')], - ['offline', t('Offline')], - ]; - - return ( - - - {t('Name')} - - ): void => setName(e.currentTarget.value)} - placeholder={t('Name')} - /> - - - - {t('Presence')} - - + {children} + + ) +} diff --git a/packages/livechat/src/components/FilesDropTarget/stories.js b/packages/livechat/src/components/FilesDropTarget/stories.js new file mode 100644 index 000000000000..c12ee30a4996 --- /dev/null +++ b/packages/livechat/src/components/FilesDropTarget/stories.js @@ -0,0 +1,104 @@ +import { action } from '@storybook/addon-actions'; +import { withKnobs, boolean, button, text } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; + +import { FilesDropTarget } from '.'; +import { centered } from '../../helpers.stories'; + + +const DummyContent = () => ( +
+ Drop files here + Or into this span +
+); + +storiesOf('Components/FilesDropTarget', module) + .addDecorator(centered) + .addDecorator(withKnobs) + .add('default', () => ( + + + + )) + .add('overlayed', () => ( + + + + )) + .add('overlayed with text', () => ( + + + + )) + .add('accepting only images', () => ( + + + + )) + .add('accepting multiple', () => ( + + + + )) + .add('triggering browse action', () => { + let filesDropTarget; + + function handleRef(ref) { + filesDropTarget = ref; + } + + button('Browse for files', () => { + filesDropTarget.browse(); + }); + + return ( + + ); + }); diff --git a/packages/livechat/src/components/FilesDropTarget/styles.scss b/packages/livechat/src/components/FilesDropTarget/styles.scss new file mode 100644 index 000000000000..b36f6d72411f --- /dev/null +++ b/packages/livechat/src/components/FilesDropTarget/styles.scss @@ -0,0 +1,77 @@ +@import '../../styles/colors'; +@import '../../styles/variables'; + +$drop-overlay-animation-time: $default-time-animation; +$drop-overlay-background-color: rgba($bg-color-white, 0.9); +$drop-overlay-gap: $default-padding; +$drop-overlay-border-width: 4px; +$drop-overlay-border-style: dashed; +$drop-overlay-text-font-size: 1.375rem; + +.drop { + position: relative; + + display: flex; + overflow: hidden; + + flex-direction: column; + flex: 1 1 auto; + + &.drop--overlayed.drop--dragover { + &::before { + position: absolute; + z-index: 10; + top: 0; + right: 0; + bottom: 0; + left: 0; + + content: ""; + animation: fadein $drop-overlay-animation-time; + pointer-events: none; + + background-color: $drop-overlay-background-color; + } + + &::after { + position: absolute; + z-index: 10; + top: $drop-overlay-gap; + right: $drop-overlay-gap; + bottom: $drop-overlay-gap; + left: $drop-overlay-gap; + + display: flex; + + box-sizing: border-box; + padding: $drop-overlay-gap; + + content: attr(data-overlay-text) ""; + animation: fadein $drop-overlay-animation-time; + text-align: center; + pointer-events: none; + + color: var(--color, $color-blue); + border: $drop-overlay-border-width var(--color, $color-blue) $drop-overlay-border-style; + + font-size: $drop-overlay-text-font-size; + font-weight: 500; + align-items: center; + justify-content: center; + } + } + + &__input { + display: none; + } +} + +@keyframes fadein { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/packages/livechat/src/components/Footer/index.js b/packages/livechat/src/components/Footer/index.js new file mode 100644 index 000000000000..88c1fac5c00d --- /dev/null +++ b/packages/livechat/src/components/Footer/index.js @@ -0,0 +1,57 @@ +import { withTranslation } from 'react-i18next'; + +import { PopoverMenu } from '../Menu'; +import { createClassName } from '../helpers'; +import Logo from './logo.svg'; +import styles from './styles.scss'; + + +export const Footer = ({ children, className, ...props }) => ( +
+ {children} +
+); + + +export const FooterContent = ({ children, className, ...props }) => ( +
+ {children} +
+); + + +export const PoweredBy = withTranslation()(({ className, t, ...props }) => ( +

+ {t('powered_by_rocket_chat').split('Rocket.Chat')[0]} + + + + {t('powered_by_rocket_chat').split('Rocket.Chat')[1]} +

+)); + + +const handleMouseUp = ({ target }) => target.blur(); + +const OptionsTrigger = withTranslation()(({ pop, t }) => ( + +)); + + +export const FooterOptions = ({ children }) => ( + + {children} + +); + + +export const CharCounter = ({ className, style = {}, textLength, limitTextLength }) => ( + + {textLength} / {limitTextLength} + +); diff --git a/packages/livechat/src/components/Footer/logo.svg b/packages/livechat/src/components/Footer/logo.svg new file mode 100644 index 000000000000..2d943df79795 --- /dev/null +++ b/packages/livechat/src/components/Footer/logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/livechat/src/components/Footer/stories.js b/packages/livechat/src/components/Footer/stories.js new file mode 100644 index 000000000000..9235a553a3ac --- /dev/null +++ b/packages/livechat/src/components/Footer/stories.js @@ -0,0 +1,48 @@ +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import '../../i18next'; + +import { Footer, FooterContent, FooterOptions, PoweredBy } from '.'; +import ChangeIcon from '../../icons/change.svg'; +import FinishIcon from '../../icons/finish.svg'; +import RemoveIcon from '../../icons/remove.svg'; +import { Composer } from '../Composer'; +import Menu from '../Menu'; +import { PopoverContainer } from '../Popover'; + + +const bottomWithPopoverContainer = (storyFn) => ( +
+ +
+ {storyFn()} + +
+); + +storiesOf('Components/Footer', module) + .addDecorator(bottomWithPopoverContainer) + .add('simple', () => ( +
+ + + +
+ )) + .add('with Composer and options', () => ( +
+ + + + + + + Change department + Forget/Remove my personal data + Finish this chat + + + + +
+ )); diff --git a/packages/livechat/src/components/Footer/styles.scss b/packages/livechat/src/components/Footer/styles.scss new file mode 100644 index 000000000000..3af8365a2003 --- /dev/null +++ b/packages/livechat/src/components/Footer/styles.scss @@ -0,0 +1,96 @@ +@import '../../styles/colors'; +@import '../../styles/helpers'; +@import '../../styles/variables'; + +.footer { + display: flex; + flex-direction: column; + flex: 0 0 auto; + + width: 100%; + padding: 4px 8px; + + color: $color-text-grey; + + font-size: 0.625rem; + align-items: stretch; + justify-content: space-between; + + &__content { + display: flex; + + padding: 4px 8px; + } + + &__options { + padding: 0; + + cursor: pointer; + user-select: none; + transition: trasform $default-time-animation; + text-align: left; + letter-spacing: 0.2px; + + color: $color-text-grey; + border: none; + background: none; + + font-size: 0.625rem; + font-weight: bold; + line-height: 1; + + &:hover, + &:focus { + color: black; + } + + @include pressable-button(2px); + } + + &__remainder { + min-width: 100px; + margin-left: 10px; + + font-weight: bold; + + &--highlight { + transition: color 0.2s; + + color: $color-red; + } + } +} + +.powered-by { + width: 100%; + margin: 0; + + user-select: none; + text-align: end; + + font-size: 10px; + font-weight: 500; + align-self: flex-end; + + .powered-by__logo { + margin: 0 5px; + + vertical-align: middle; + + :global(.text) { + fill: #{$color-text-grey}; + } + + &:hover :global(.text) { + fill: #2f343d; + } + + :global(.rocket) { + fill: #{$color-text-grey}; + } + + &:hover :global(.rocket) { + fill: #db2323; + } + } +} diff --git a/packages/livechat/src/components/Form/DateInput/index.js b/packages/livechat/src/components/Form/DateInput/index.js new file mode 100644 index 000000000000..c05cf5c5a5a5 --- /dev/null +++ b/packages/livechat/src/components/Form/DateInput/index.js @@ -0,0 +1,29 @@ +import { createClassName, memo } from '../../helpers'; +import styles from './styles.scss'; + +const DateInput = ({ + name, + value, + placeholder, + disabled, + small, + error, + onChange, + onInput, + className, + style = {}, +}) => ( + +); + +export default memo(DateInput); diff --git a/packages/livechat/src/components/Form/DateInput/stories.js b/packages/livechat/src/components/Form/DateInput/stories.js new file mode 100644 index 000000000000..135cf6e3a3cf --- /dev/null +++ b/packages/livechat/src/components/Form/DateInput/stories.js @@ -0,0 +1,87 @@ +import { action } from '@storybook/addon-actions'; +import { withKnobs, boolean, text } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; + +import DateInput from '.'; +import { Form, FormField } from '..'; + +storiesOf('Forms/DateInput', module) + .addDecorator(withKnobs) + .addParameters({ + layout: 'centered', + }) + .add('default', () => ( +
+ + + +
+ )) + .add('filled', () => ( +
+ + + +
+ )) + .add('disabled', () => ( +
+ + + +
+ )) + .add('small', () => ( +
+ + + +
+ )) + .add('with error', () => ( +
+ + + +
+ )); diff --git a/packages/livechat/src/components/Form/DateInput/styles.scss b/packages/livechat/src/components/Form/DateInput/styles.scss new file mode 100644 index 000000000000..f660055b10c2 --- /dev/null +++ b/packages/livechat/src/components/Form/DateInput/styles.scss @@ -0,0 +1,5 @@ +@import '../mixins'; + +.date-input { + @include form__input-box; +} diff --git a/packages/livechat/src/components/Form/FormField/index.js b/packages/livechat/src/components/Form/FormField/index.js new file mode 100644 index 000000000000..1f3e13535d53 --- /dev/null +++ b/packages/livechat/src/components/Form/FormField/index.js @@ -0,0 +1,35 @@ +import { cloneElement } from 'preact'; + +import { createClassName } from '../../helpers'; +import styles from './styles.scss'; + + +export const FormField = ({ + required, + label, + description, + error, + className, + style = {}, + children, +}) => ( +
+ + + {error || description} + +
+); diff --git a/packages/livechat/src/components/Form/FormField/stories.js b/packages/livechat/src/components/Form/FormField/stories.js new file mode 100644 index 000000000000..8f12916115fb --- /dev/null +++ b/packages/livechat/src/components/Form/FormField/stories.js @@ -0,0 +1,47 @@ +import { withKnobs, boolean, text } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; + +import { FormField } from '.'; +import { Form, TextInput } from '..'; +import { loremIpsum, centered } from '../../../helpers.stories'; + + +storiesOf('Forms/FormField', module) + .addDecorator(centered) + .addDecorator(withKnobs) + .add('normal', () => ( +
+ + + +
+ )) + .add('required', () => ( +
+ + + +
+ )) + .add('with error', () => ( +
+ + + +
+ )); diff --git a/packages/livechat/src/components/Form/FormField/styles.scss b/packages/livechat/src/components/Form/FormField/styles.scss new file mode 100644 index 000000000000..335a5946de7e --- /dev/null +++ b/packages/livechat/src/components/Form/FormField/styles.scss @@ -0,0 +1,85 @@ +@import '../../../styles/colors'; +@import '../../../styles/variables'; + +$form-field-label-color: $color-text-dark; +$form-field-label-error-color: $color-red; +$form-field-label-font-size: 0.75rem; +$form-field-label-font-weight: 600; +$form-field-label-line-height: 1rem; + +$form-field-description-color: $color-text-grey; +$form-field-description-font-size: 0.75rem; +$form-field-description-font-weight: 500; +$form-field-description-line-height: 1rem; + +$form-field-error-color: $color-red; +$form-field-error-border-color: $color-red; + +.form-field { + display: flex; + + width: 100%; + margin: 5px 0; + flex-flow: column nowrap; + + &__label-wrapper { + display: flex; + flex: 1 0 auto; + flex-flow: column nowrap; + } + + &__label, + &__input, + &__description { + margin: 3px 0; + } + + &__label { + flex: 0 0 auto; + + transition: color $default-time-animation; + text-align: left; + white-space: nowrap; + letter-spacing: 0; + text-overflow: ellipsis; + + color: $form-field-label-color; + + font-size: $form-field-label-font-size; + font-weight: $form-field-label-font-weight; + line-height: $form-field-label-line-height; + } + + &__input { + display: flex; + flex: 1 0 auto; + } + + &__description { + flex: 0 0 auto; + + min-height: $form-field-description-line-height; + + transition: color $default-time-animation; + + color: $form-field-description-color; + + font-size: $form-field-description-font-size; + font-weight: $form-field-description-font-weight; + line-height: $form-field-description-line-height; + } + + &--error { + .form-field__label, + .form-field__input, + .form-field__description { + color: $form-field-error-color; + } + } + + &--required { + .form-field__label::after { + content: " *"; + } + } +} diff --git a/packages/livechat/src/components/Form/PasswordInput/index.js b/packages/livechat/src/components/Form/PasswordInput/index.js new file mode 100644 index 000000000000..688b00e4bd02 --- /dev/null +++ b/packages/livechat/src/components/Form/PasswordInput/index.js @@ -0,0 +1,28 @@ +import { createClassName, memo } from '../../helpers'; +import styles from './styles.scss'; + + +export const PasswordInput = memo(({ + name, + value, + placeholder, + disabled, + small, + error, + onChange, + onInput, + className, + style = {}, +}) => ( + +)); diff --git a/packages/livechat/src/components/Form/PasswordInput/stories.js b/packages/livechat/src/components/Form/PasswordInput/stories.js new file mode 100644 index 000000000000..8ef93211e519 --- /dev/null +++ b/packages/livechat/src/components/Form/PasswordInput/stories.js @@ -0,0 +1,87 @@ +import { action } from '@storybook/addon-actions'; +import { withKnobs, boolean, text } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; + +import { PasswordInput } from '.'; +import { Form, FormField } from '..'; +import { centered } from '../../../helpers.stories'; + + +storiesOf('Forms/PasswordInput', module) + .addDecorator(centered) + .addDecorator(withKnobs) + .add('default', () => ( +
+ + + +
+ )) + .add('filled', () => ( +
+ + + +
+ )) + .add('disabled', () => ( +
+ + + +
+ )) + .add('small', () => ( +
+ + + +
+ )) + .add('with error', () => ( +
+ + + +
+ )); diff --git a/packages/livechat/src/components/Form/PasswordInput/styles.scss b/packages/livechat/src/components/Form/PasswordInput/styles.scss new file mode 100644 index 000000000000..32d1dfa1bc6c --- /dev/null +++ b/packages/livechat/src/components/Form/PasswordInput/styles.scss @@ -0,0 +1,5 @@ +@import '../mixins'; + +.password-input { + @include form__input-box; +} diff --git a/packages/livechat/src/components/Form/SelectInput/index.js b/packages/livechat/src/components/Form/SelectInput/index.js new file mode 100644 index 000000000000..3362c3cc08ce --- /dev/null +++ b/packages/livechat/src/components/Form/SelectInput/index.js @@ -0,0 +1,70 @@ +import { Component } from 'preact'; + +import ArrowIcon from '../../../icons/arrowDown.svg'; +import { createClassName } from '../../helpers'; +import styles from './styles.scss'; + + +export class SelectInput extends Component { + static getDerivedStateFromProps(props, state) { + if (props.value !== state.value) { + return { value: props.value }; + } + + return null; + } + + state = { + value: this.props.value, + } + + handleChange = (event) => { + const { onChange } = this.props; + onChange && onChange(event); + + if (event.defaultPrevented) { + return; + } + + this.setState({ value: event.target.value }); + } + + render = ({ + name, + placeholder, + options = [], + disabled, + small, + error, + onInput, + className, + style = {}, + ...props + }) => ( +
+ + +
+ ) +} diff --git a/packages/livechat/src/components/Form/SelectInput/stories.js b/packages/livechat/src/components/Form/SelectInput/stories.js new file mode 100644 index 000000000000..6ec401a1037b --- /dev/null +++ b/packages/livechat/src/components/Form/SelectInput/stories.js @@ -0,0 +1,112 @@ +import { action } from '@storybook/addon-actions'; +import { withKnobs, boolean, object, text } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; + +import { SelectInput } from '.'; +import { Form, FormField } from '..'; +import { centered } from '../../../helpers.stories'; + + +storiesOf('Forms/SelectInput', module) + .addDecorator(centered) + .addDecorator(withKnobs) + .add('empty', () => ( +
+ + + +
+ )) + .add('selected', () => ( +
+ + + +
+ )) + .add('disabled', () => ( +
+ + + +
+ )) + .add('small', () => ( +
+ + + +
+ )) + .add('with error', () => ( +
+ + + +
+ )); diff --git a/packages/livechat/src/components/Form/SelectInput/styles.scss b/packages/livechat/src/components/Form/SelectInput/styles.scss new file mode 100644 index 000000000000..a313b0538234 --- /dev/null +++ b/packages/livechat/src/components/Form/SelectInput/styles.scss @@ -0,0 +1,56 @@ +@import '../mixins'; + +$form-input-select-arrow-size: $form-input-padding; +$form-input-select-arrow-padding: $form-input-padding; +$form-input-select-arrow-color: $color-text-light; + +.select-input { + position: relative; + + display: flex; + flex: 1; + + &__select { + @include form__input-box; + + flex: 1; + + padding-right: (3 * $form-input-select-arrow-padding + $form-input-select-arrow-size); + + color: $form-input-color; + + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + &::-ms-expand { + display: none; + } + + &--placeholder { + color: $form-input-placeholder-color; + } + + &--small { + padding-right: (3 * $form-input-select-arrow-padding + $form-input-select-arrow-size); + } + } + + &__option { + color: $form-input-color; + } + + &__arrow { + position: absolute; + top: 50%; + right: $form-input-select-arrow-padding; + + width: $form-input-select-arrow-size; + height: $form-input-select-arrow-size; + + transform: translateY(-50%) translateY(2px); + pointer-events: none; + + color: $form-input-select-arrow-color; + } +} diff --git a/packages/livechat/src/components/Form/TextInput/index.js b/packages/livechat/src/components/Form/TextInput/index.js new file mode 100644 index 000000000000..d92ab3334736 --- /dev/null +++ b/packages/livechat/src/components/Form/TextInput/index.js @@ -0,0 +1,49 @@ +import { createClassName, memo } from '../../helpers'; +import styles from './styles.scss'; + + +export const TextInput = memo(({ + name, + value, + placeholder, + disabled, + small, + multiline = false, + rows = 1, + error, + onChange, + onInput, + className, + style = {}, + ...props +}) => ( + multiline + ? ( +