diff --git a/apps/meteor/app/canned-responses/client/collections/CannedResponse.ts b/apps/meteor/app/canned-responses/client/collections/CannedResponse.ts
deleted file mode 100644
index 6ea1f70e9897e..0000000000000
--- a/apps/meteor/app/canned-responses/client/collections/CannedResponse.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Mongo } from 'meteor/mongo';
-
-export const CannedResponse = new Mongo.Collection<{
- _id: string;
- shortcut: string;
- text: string;
-}>(null);
diff --git a/apps/meteor/app/canned-responses/client/index.ts b/apps/meteor/app/canned-responses/client/index.ts
deleted file mode 100644
index 36fd7b5cc6af9..0000000000000
--- a/apps/meteor/app/canned-responses/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './startup/responses';
diff --git a/apps/meteor/app/canned-responses/client/startup/responses.ts b/apps/meteor/app/canned-responses/client/startup/responses.ts
deleted file mode 100644
index 6d761adb890da..0000000000000
--- a/apps/meteor/app/canned-responses/client/startup/responses.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { Tracker } from 'meteor/tracker';
-
-import { hasPermission } from '../../../authorization/client';
-import { settings } from '../../../settings/client';
-import { sdk } from '../../../utils/client/lib/SDKClient';
-import { CannedResponse } from '../collections/CannedResponse';
-
-Meteor.startup(() => {
- Tracker.autorun(async (c) => {
- if (!Meteor.userId()) {
- return;
- }
- if (!settings.get('Canned_Responses_Enable')) {
- return;
- }
- if (!hasPermission('view-canned-responses')) {
- return;
- }
- Tracker.afterFlush(() => {
- try {
- // TODO: check options
- sdk.stream('canned-responses', ['canned-responses'], (...[response, options]) => {
- const { agentsId } = options || {};
- if (Array.isArray(agentsId) && !agentsId.includes(Meteor.userId())) {
- return;
- }
-
- switch (response.type) {
- case 'changed': {
- const { type, ...fields } = response;
- CannedResponse.upsert({ _id: response._id }, fields);
- break;
- }
-
- case 'removed': {
- CannedResponse.remove({ _id: response._id });
- break;
- }
- }
- });
- } catch (error) {
- console.log(error);
- }
- });
-
- const { responses } = await sdk.rest.get('/v1/canned-responses.get');
- responses.forEach((response) => CannedResponse.insert(response));
- c.stop();
- });
-});
diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts
index ad7f06e15603a..d47cc47eae4d7 100644
--- a/apps/meteor/client/importPackages.ts
+++ b/apps/meteor/client/importPackages.ts
@@ -1,7 +1,6 @@
import '../app/apple/client';
import '../app/authorization/client';
import '../app/autotranslate/client';
-import '../app/canned-responses/client';
import '../app/custom-sounds/client';
import '../app/emoji/client';
import '../app/emoji-emojione/client';
diff --git a/apps/meteor/client/lib/queryKeys.ts b/apps/meteor/client/lib/queryKeys.ts
index a9217935b8219..ad15b225ab88d 100644
--- a/apps/meteor/client/lib/queryKeys.ts
+++ b/apps/meteor/client/lib/queryKeys.ts
@@ -14,3 +14,7 @@ export const subscriptionsQueryKeys = {
all: ['subscriptions'] as const,
subscription: (rid: IRoom['_id']) => [...subscriptionsQueryKeys.all, { rid }] as const,
};
+
+export const cannedResponsesQueryKeys = {
+ all: ['canned-responses'] as const,
+};
diff --git a/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx b/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx
index 22557f4a6d15b..6293a401da8b0 100644
--- a/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx
+++ b/apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx
@@ -3,16 +3,17 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings';
import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { useMethod, useSetting, useUserPreference } from '@rocket.chat/ui-contexts';
+import { useQueryClient } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
import type { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { hasAtLeastOnePermission } from '../../../../app/authorization/client';
-import { CannedResponse } from '../../../../app/canned-responses/client/collections/CannedResponse';
import { emoji } from '../../../../app/emoji/client';
import { Subscriptions } from '../../../../app/models/client';
import { usersFromRoomMessages } from '../../../../app/ui-message/client/popup/messagePopupConfig';
import { slashCommands } from '../../../../app/utils/client';
+import { cannedResponsesQueryKeys } from '../../../lib/queryKeys';
import ComposerBoxPopupCannedResponse from '../composer/ComposerBoxPopupCannedResponse';
import type { ComposerBoxPopupEmojiProps } from '../composer/ComposerBoxPopupEmoji';
import ComposerBoxPopupEmoji from '../composer/ComposerBoxPopupEmoji';
@@ -24,6 +25,9 @@ import ComposerBoxPopupUser from '../composer/ComposerBoxPopupUser';
import type { ComposerBoxPopupUserProps } from '../composer/ComposerBoxPopupUser';
import type { ComposerPopupContextValue } from '../contexts/ComposerPopupContext';
import { ComposerPopupContext, createMessageBoxPopupConfig } from '../contexts/ComposerPopupContext';
+import useCannedResponsesQuery from './hooks/useCannedResponsesQuery';
+
+export type CannedResponse = { _id: string; shortcut: string; text: string };
type ComposerPopupProviderProps = {
children: ReactNode;
@@ -32,6 +36,11 @@ type ComposerPopupProviderProps = {
const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) => {
const { _id: rid, encrypted: isRoomEncrypted } = room;
+
+ // TODO: this is awful because we are just triggering the query to get the data
+ // and we are not using the data itself, we should find a better way to do this
+ useCannedResponsesQuery(room);
+
const userSpotlight = useMethod('spotlight');
const suggestionsCount = useSetting('Number_of_users_autocomplete_suggestions', 5);
const cannedResponseEnabled = useSetting('Canned_Responses_Enable', true);
@@ -43,6 +52,7 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) =
const e2eEnabled = useSetting('E2E_Enable', false);
const unencryptedMessagesAllowed = useSetting('E2E_Allow_Unencrypted_Messages', false);
const encrypted = isRoomEncrypted && e2eEnabled && !unencryptedMessagesAllowed;
+ const queryClient = useQueryClient();
const call = useMethod('getSlashCommandPreviews');
const value: ComposerPopupContextValue = useMemo(() => {
@@ -334,18 +344,12 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) =
renderItem: ({ item }) => ,
getItemsFromLocal: async (filter: string) => {
const exp = new RegExp(filter, 'i');
- return CannedResponse.find(
- {
- shortcut: exp,
- },
- {
- limit: 12,
- sort: {
- shortcut: -1,
- },
- },
- )
- .fetch()
+ // TODO: this is bad, but can only be fixed by refactoring the whole thing
+ const cannedResponses = queryClient.getQueryData(cannedResponsesQueryKeys.all) ?? [];
+ return cannedResponses
+ .filter((record) => record.shortcut.match(exp))
+ .sort((a, b) => a.shortcut.localeCompare(b.shortcut))
+ .slice(0, 11)
.map((record) => ({
_id: record._id,
text: record.text,
@@ -353,9 +357,7 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) =
}));
},
getItemsFromServer: async () => [],
- getValue: (item) => {
- return item.text;
- },
+ getValue: (item) => item.text,
}),
createMessageBoxPopupConfig({
title: previewTitle,
@@ -388,8 +390,8 @@ const ComposerPopupProvider = ({ children, room }: ComposerPopupProviderProps) =
rid,
recentEmojis,
i18n,
+ queryClient,
call,
- setPreviewTitle,
]);
return ;
diff --git a/apps/meteor/client/views/room/providers/hooks/useCannedResponsesQuery.ts b/apps/meteor/client/views/room/providers/hooks/useCannedResponsesQuery.ts
new file mode 100644
index 0000000000000..0150ecdf9ae50
--- /dev/null
+++ b/apps/meteor/client/views/room/providers/hooks/useCannedResponsesQuery.ts
@@ -0,0 +1,64 @@
+import type { IRoom } from '@rocket.chat/core-typings';
+import { isOmnichannelRoom } from '@rocket.chat/core-typings';
+import { useEndpoint, usePermission, useSetting, useStream, useUserId } from '@rocket.chat/ui-contexts';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
+import { useEffect } from 'react';
+
+import { cannedResponsesQueryKeys } from '../../../../lib/queryKeys';
+
+type CannedResponse = { _id: string; shortcut: string; text: string };
+
+const useCannedResponsesQuery = (room: IRoom) => {
+ const isOmnichannel = isOmnichannelRoom(room);
+ const uid = useUserId();
+ const isCannedResponsesEnabled = useSetting('Canned_Responses_Enable', true);
+ const canViewCannedResponses = usePermission('view-canned-responses');
+ const subscribeToCannedResponses = useStream('canned-responses');
+
+ const enabled = isOmnichannel && !!uid && isCannedResponsesEnabled && canViewCannedResponses;
+
+ const getCannedResponses = useEndpoint('GET', '/v1/canned-responses.get');
+
+ const queryClient = useQueryClient();
+
+ useEffect(() => {
+ if (!enabled) return;
+
+ return subscribeToCannedResponses('canned-responses', (...[response, options]) => {
+ const { agentsId } = options || {};
+ if (Array.isArray(agentsId) && !agentsId.includes(uid)) {
+ return;
+ }
+
+ switch (response.type) {
+ case 'changed': {
+ const { _id, shortcut, text } = response;
+ queryClient.setQueryData(cannedResponsesQueryKeys.all, (responses) =>
+ responses?.filter((response) => response._id !== _id).concat([{ _id, shortcut, text }]),
+ );
+ break;
+ }
+
+ case 'removed': {
+ const { _id } = response;
+ queryClient.setQueryData(cannedResponsesQueryKeys.all, (responses) =>
+ responses?.filter((response) => response._id !== _id),
+ );
+ break;
+ }
+ }
+ });
+ }, [enabled, getCannedResponses, queryClient, subscribeToCannedResponses, uid]);
+
+ return useQuery({
+ queryKey: cannedResponsesQueryKeys.all,
+ queryFn: async () => {
+ const { responses } = await getCannedResponses();
+ return responses.map(({ _id, shortcut, text }) => ({ _id, shortcut, text }));
+ },
+ enabled,
+ staleTime: Infinity,
+ });
+};
+
+export default useCannedResponsesQuery;