diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts
index 633b0b1a3422..5c7cf193f3a8 100644
--- a/apps/meteor/app/api/server/v1/misc.ts
+++ b/apps/meteor/app/api/server/v1/misc.ts
@@ -13,8 +13,10 @@ import {
isMethodCallProps,
isMethodCallAnonProps,
isMeteorCall,
+ validateParamsPwGetPolicyRest,
} from '@rocket.chat/rest-typings';
import type { IUser } from '@rocket.chat/core-typings';
+import { Users as UsersRaw } from '@rocket.chat/models';
import { hasPermission } from '../../../authorization/server';
import { Users } from '../../../models/server';
@@ -24,6 +26,7 @@ import { getDefaultUserFields } from '../../../utils/server/functions/getDefault
import { getURL } from '../../../utils/lib/getURL';
import { getLogs } from '../../../../server/stream/stdout';
import { SystemLogger } from '../../../../server/lib/logger/system';
+import { passwordPolicy } from '../../../lib/server';
/**
* @openapi
@@ -383,6 +386,44 @@ API.v1.addRoute(
},
);
+API.v1.addRoute(
+ 'pw.getPolicy',
+ {
+ authRequired: true,
+ },
+ {
+ get() {
+ return API.v1.success(passwordPolicy.getPasswordPolicy());
+ },
+ },
+);
+
+API.v1.addRoute(
+ 'pw.getPolicyReset',
+ {
+ authRequired: false,
+ validateParams: validateParamsPwGetPolicyRest,
+ },
+ {
+ async get() {
+ check(
+ this.queryParams,
+ Match.ObjectIncluding({
+ token: String,
+ }),
+ );
+ const { token } = this.queryParams;
+
+ const user = await UsersRaw.findOneByResetToken(token, { projection: { _id: 1 } });
+ if (!user) {
+ return API.v1.unauthorized();
+ }
+
+ return API.v1.success(passwordPolicy.getPasswordPolicy());
+ },
+ },
+);
+
/**
* @openapi
* /api/v1/stdout.queue:
diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts
index 0403418db6a5..6d570ae49b0f 100644
--- a/apps/meteor/app/api/server/v1/teams.ts
+++ b/apps/meteor/app/api/server/v1/teams.ts
@@ -15,8 +15,8 @@ import type { ITeam } from '@rocket.chat/core-typings';
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFromRoom';
-import { Users } from '../../../models/server';
-import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/server';
+import { Rooms, Users } from '../../../models/server';
+import { canAccessRoom, hasAtLeastOnePermission, hasPermission } from '../../../authorization/server';
import { Team } from '../../../../server/sdk';
import { API } from '../api';
@@ -576,6 +576,18 @@ API.v1.addRoute(
return API.v1.failure('Team not found');
}
+ const room = Rooms.findOneById(teamInfo.roomId);
+
+ if (!room) {
+ return API.v1.failure('Room not found');
+ }
+
+ const canViewInfo = canAccessRoom(room, { _id: this.userId }) || hasPermission(this.userId, 'view-all-teams');
+
+ if (!canViewInfo) {
+ return API.v1.unauthorized();
+ }
+
return API.v1.success({ teamInfo });
},
},
diff --git a/apps/meteor/client/views/login/ResetPassword/ResetPassword.tsx b/apps/meteor/client/views/login/ResetPassword/ResetPassword.tsx
index ac1942afd0b4..437a0cbf39dd 100644
--- a/apps/meteor/client/views/login/ResetPassword/ResetPassword.tsx
+++ b/apps/meteor/client/views/login/ResetPassword/ResetPassword.tsx
@@ -8,6 +8,7 @@ import {
useTranslation,
TranslationKey,
useToastMessageDispatch,
+ useEndpoint,
} from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { Meteor } from 'meteor/meteor';
@@ -27,13 +28,14 @@ const ResetPassword = (): ReactElement => {
const resetPassword = useMethod('resetPassword');
const token = useRouteParameter('token');
- const getPasswordPolicy = useMethod('getPasswordPolicy');
+ const getPasswordPolicy = useEndpoint('GET', '/v1/pw.getPolicy');
+ const getPasswordPolicyRest = useEndpoint('GET', '/v1/pw.getPolicyReset');
const dispatchToastMessage = useToastMessageDispatch();
const { data: { enabled: policyEnabled, policy: policies } = {} } = useQuery(
['login/password-policy', token],
- async () => getPasswordPolicy(token ? { token } : undefined),
+ async () => (user || !token ? getPasswordPolicy() : getPasswordPolicyRest({ token })),
{
onError: (error: any) => {
dispatchToastMessage({ type: 'error', message: error });
@@ -103,7 +105,7 @@ const ResetPassword = (): ReactElement => {
{policies?.map((policy, index) => (
- {t(...policy)}
+ {t(...(policy as unknown as [name: TranslationKey, options?: Record]))}
))}
diff --git a/apps/meteor/client/views/room/Header/ToolBox/ToolBox.tsx b/apps/meteor/client/views/room/Header/ToolBox/ToolBox.tsx
index b54ce28b2611..10dc9efe6b31 100644
--- a/apps/meteor/client/views/room/Header/ToolBox/ToolBox.tsx
+++ b/apps/meteor/client/views/room/Header/ToolBox/ToolBox.tsx
@@ -11,7 +11,7 @@ import { useToolboxContext } from '../../lib/Toolbox/ToolboxContext';
import { useTab, useTabBarOpen } from '../../providers/ToolboxProvider';
const renderMenuOption: OptionRenderer = ({ label: { title, icon }, ...props }: any): ReactNode => (
-
+
);
type ToolBoxProps = {
diff --git a/apps/meteor/client/views/room/MessageList/MessageListErrorBoundary.tsx b/apps/meteor/client/views/room/MessageList/MessageListErrorBoundary.tsx
index 62f9ddafb124..a312fe5a2217 100644
--- a/apps/meteor/client/views/room/MessageList/MessageListErrorBoundary.tsx
+++ b/apps/meteor/client/views/room/MessageList/MessageListErrorBoundary.tsx
@@ -1,7 +1,7 @@
import { States, StatesIcon, StatesTitle, StatesSubtitle, StatesActions, StatesAction, Icon } from '@rocket.chat/fuselage';
-import { ErrorBoundary } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, ReactNode } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
const MessageListErrorBoundary = ({ children }: { children: ReactNode }): ReactElement => {
const t = useTranslation();
diff --git a/apps/meteor/client/views/room/Room/Room.tsx b/apps/meteor/client/views/room/Room/Room.tsx
index 141fec6beead..82bfaa845d1d 100644
--- a/apps/meteor/client/views/room/Room/Room.tsx
+++ b/apps/meteor/client/views/room/Room/Room.tsx
@@ -1,7 +1,7 @@
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
-import { ErrorBoundary } from '@rocket.chat/ui-client';
import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo, ReactElement } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout';
import Announcement from '../Announcement';
@@ -43,7 +43,7 @@ export const Room = (): ReactElement => {
{tab && (
-
+
{typeof tab.template === 'string' && (
@@ -58,7 +58,7 @@ export const Room = (): ReactElement => {
{appsContextualBarContext && (
-
+
}
+ data-qa-id={`${device}-notifications`}
>
{children}
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts
index 8731a6187332..58701263388c 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts
@@ -1,6 +1,13 @@
import { IRoom, IUser } from '@rocket.chat/core-typings';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
-import { useTranslation, useMethod, useUserSubscription, useUserRoom, useUserId, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
+import {
+ useTranslation,
+ useUserSubscription,
+ useUserRoom,
+ useUserId,
+ useToastMessageDispatch,
+ useEndpoint,
+} from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { Action } from '../../../../hooks/useActionSpread';
@@ -13,7 +20,7 @@ export const useIgnoreUserAction = (user: Pick, rid:
const ownUserId = useUserId();
const dispatchToastMessage = useToastMessageDispatch();
const currentSubscription = useUserSubscription(rid);
- const ignoreUser = useMethod('ignoreUser');
+ const ignoreUser = useEndpoint('GET', '/v1/chat.ignoreUser');
const isIgnored = currentSubscription?.ignored && currentSubscription.ignored.indexOf(uid) > -1;
@@ -25,7 +32,7 @@ export const useIgnoreUserAction = (user: Pick, rid:
const ignoreUserAction = useMutableCallback(async () => {
try {
- await ignoreUser({ rid, userId: uid, ignore: !isIgnored });
+ await ignoreUser({ rid, userId: uid, ignore: String(!isIgnored) });
if (isIgnored) {
dispatchToastMessage({ type: 'success', message: t('User_has_been_unignored') });
} else {
diff --git a/apps/meteor/client/views/room/threads/ThreadComponent.tsx b/apps/meteor/client/views/room/threads/ThreadComponent.tsx
index 68470654f135..3fc77eab21dc 100644
--- a/apps/meteor/client/views/room/threads/ThreadComponent.tsx
+++ b/apps/meteor/client/views/room/threads/ThreadComponent.tsx
@@ -1,6 +1,6 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
-import { useToastMessageDispatch, useRoute, useUserId, useUserSubscription, useEndpoint, useMethod } from '@rocket.chat/ui-contexts';
+import { useToastMessageDispatch, useRoute, useUserId, useUserSubscription, useEndpoint } from '@rocket.chat/ui-contexts';
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
@@ -72,8 +72,8 @@ const ThreadComponent: FC<{
const following = !uid ? false : threadMessage?.replies?.includes(uid) ?? false;
const dispatchToastMessage = useToastMessageDispatch();
- const followMessage = useMethod('followMessage');
- const unfollowMessage = useMethod('unfollowMessage');
+ const followMessage = useEndpoint('POST', '/v1/chat.followMessage');
+ const unfollowMessage = useEndpoint('POST', '/v1/chat.unfollowMessage');
const setFollowing = useCallback<(following: boolean) => void>(
async (following) => {
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx
new file mode 100644
index 000000000000..bb630d5772bd
--- /dev/null
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx
@@ -0,0 +1,27 @@
+import { Box } from '@rocket.chat/fuselage';
+import React, { ReactElement, ReactNode } from 'react';
+
+import Card from '../../../../../client/components/Card';
+import EngagementDashboardCardErrorBoundary from './EngagementDashboardCardErrorBoundary';
+
+type EngagementDashboardCardProps = {
+ children?: ReactNode;
+ title?: string;
+};
+
+const EngagementDashboardCard = ({ children, title = undefined }: EngagementDashboardCardProps): ReactElement => (
+
+
+ {title && {title}}
+
+
+
+ {children}
+
+
+
+
+
+);
+
+export default EngagementDashboardCard;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardErrorBoundary.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardErrorBoundary.tsx
new file mode 100644
index 000000000000..80236e1487fa
--- /dev/null
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardErrorBoundary.tsx
@@ -0,0 +1,45 @@
+import { States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
+import { useTranslation } from '@rocket.chat/ui-contexts';
+import { QueryErrorResetBoundary } from '@tanstack/react-query';
+import React, { ReactElement, ReactNode, useState } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
+
+export type EngagementDashboardCardErrorBoundaryProps = {
+ children?: ReactNode;
+};
+
+const EngagementDashboardCardErrorBoundary = ({ children }: EngagementDashboardCardErrorBoundaryProps): ReactElement => {
+ const t = useTranslation();
+
+ const [error, setError] = useState();
+ const isError = (error: unknown): error is Error => error instanceof Error;
+
+ const errorHandler = (error: Error, info: { componentStack: string }): void => {
+ setError(error);
+ console.error('Uncaught Error:', error, info);
+ };
+
+ return (
+
+ {({ reset }): ReactElement => (
+ (
+
+
+ {t('Something_Went_Wrong')}
+ {isError(error) && error?.message}
+
+ resetErrorBoundary()}>{t('Retry')}
+
+
+ )}
+ />
+ )}
+
+ );
+};
+
+export default EngagementDashboardCardErrorBoundary;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx
new file mode 100644
index 000000000000..55a702b6671a
--- /dev/null
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCardFilter.tsx
@@ -0,0 +1,14 @@
+import { Box, Flex, InputBox } from '@rocket.chat/fuselage';
+import React, { ReactElement, ReactNode } from 'react';
+
+type EngagementDashboardCardFilterProps = {
+ children?: ReactNode;
+};
+
+const EngagementDashboardCardFilter = ({ children = }: EngagementDashboardCardFilterProps): ReactElement => (
+
+ {children && {children}}
+
+);
+
+export default EngagementDashboardCardFilter;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx
index 437ba1e1baf9..fd25ccb1cea0 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx
@@ -32,18 +32,18 @@ const EngagementDashboardPage = ({ tab = 'users', onSelectTab }: EngagementDashb
);
return (
-
+
-
+
{t('Users')}
-
+
{t('Messages')}
-
+
{t('Channels')}
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/Section.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/Section.tsx
deleted file mode 100644
index 36fd8d1e5dff..000000000000
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/Section.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Box, Flex, InputBox, Margins } from '@rocket.chat/fuselage';
-import React, { ReactElement, ReactNode } from 'react';
-
-type SectionProps = {
- children?: ReactNode;
- title?: ReactNode;
- filter?: ReactNode;
-};
-
-const Section = ({ children, title = undefined, filter = }: SectionProps): ReactElement => (
-
-
-
- {title && (
-
- {title}
-
- )}
- {filter && {filter}}
-
- {children}
-
-
-);
-
-export default Section;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx
new file mode 100644
index 000000000000..5a979669a91f
--- /dev/null
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsOverview.tsx
@@ -0,0 +1,134 @@
+import { Box, Icon, Margins, Pagination, Skeleton, Table, Tile } from '@rocket.chat/fuselage';
+import { useTranslation } from '@rocket.chat/ui-contexts';
+import moment from 'moment';
+import React, { ReactElement, useMemo, useState } from 'react';
+
+import Growth from '../../../../../../client/components/dataView/Growth';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
+import DownloadDataButton from '../dataView/DownloadDataButton';
+import PeriodSelector from '../dataView/PeriodSelector';
+import { usePeriodSelectorState } from '../dataView/usePeriodSelectorState';
+import { useChannelsList } from './useChannelsList';
+
+const ChannelsOverview = (): ReactElement => {
+ const [period, periodSelectorProps] = usePeriodSelectorState('last 7 days', 'last 30 days', 'last 90 days');
+
+ const t = useTranslation();
+
+ const [current, setCurrent] = useState(0);
+ const [itemsPerPage, setItemsPerPage] = useState<25 | 50 | 100>(25);
+
+ const { data } = useChannelsList({
+ period,
+ offset: current,
+ count: itemsPerPage,
+ });
+
+ const channels = useMemo(() => {
+ if (!data) {
+ return;
+ }
+
+ return data?.channels?.map(({ room: { t, name, usernames, ts, _updatedAt }, messages, diffFromLastWeek }) => ({
+ t,
+ name: name || usernames?.join(' × '),
+ createdAt: ts,
+ updatedAt: _updatedAt,
+ messagesCount: messages,
+ messagesVariation: diffFromLastWeek,
+ }));
+ }, [data]);
+
+ return (
+ <>
+
+
+
+ data?.channels?.map(({ room: { t, name, usernames, ts, _updatedAt }, messages }) => [
+ t,
+ name || usernames?.join(' × '),
+ messages,
+ _updatedAt,
+ ts,
+ ])
+ }
+ />
+
+
+ {channels && !channels.length && (
+
+ {t('No_data_found')}
+
+ )}
+ {(!channels || channels.length) && (
+
+
+
+ {'#'}
+ {t('Channel')}
+ {t('Created')}
+ {t('Last_active')}
+ {t('Messages_sent')}
+
+
+
+ {channels?.map(({ t, name, createdAt, updatedAt, messagesCount, messagesVariation }, i) => (
+
+ {i + 1}.
+
+
+ {(t === 'd' && ) || (t === 'p' && ) || (t === 'c' && )}
+
+ {name}
+
+ {moment(createdAt).format('L')}
+ {moment(updatedAt).format('L')}
+
+ {messagesCount} {messagesVariation}
+
+
+ ))}
+ {!channels &&
+ Array.from({ length: 5 }, (_, i) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ )}
+ t('Items_per_page:')}
+ showingResultsLabel={({ count, current, itemsPerPage }): string =>
+ t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count)
+ }
+ count={data?.total || 0}
+ onSetItemsPerPage={setItemsPerPage}
+ onSetCurrent={setCurrent}
+ />
+
+ >
+ );
+};
+
+export default ChannelsOverview;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.stories.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.stories.tsx
index 6d70ec51e3bf..e6343059a303 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.stories.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.stories.tsx
@@ -2,7 +2,7 @@ import { Margins } from '@rocket.chat/fuselage';
import { Meta, Story } from '@storybook/react';
import React from 'react';
-import ChannelsTab from './ChannelsTab';
+import ChannelsTab from './ChannelsOverview';
export default {
title: 'Enterprise/Admin/Engagement Dashboard/ChannelsTab',
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.tsx
index 577148391d0d..986c76bf52e3 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/ChannelsTab.tsx
@@ -1,137 +1,12 @@
-import { Box, Icon, Margins, Pagination, Skeleton, Table, Tile } from '@rocket.chat/fuselage';
-import { useTranslation } from '@rocket.chat/ui-contexts';
-import moment from 'moment';
-import React, { ReactElement, useMemo, useState } from 'react';
+import React, { ReactElement } from 'react';
-import Growth from '../../../../../../client/components/dataView/Growth';
-import Section from '../Section';
-import DownloadDataButton from '../dataView/DownloadDataButton';
-import PeriodSelector from '../dataView/PeriodSelector';
-import { usePeriodSelectorState } from '../dataView/usePeriodSelectorState';
-import { useChannelsList } from './useChannelsList';
+import EngagementDashboardCard from '../EngagementDashboardCard';
+import ChannelsOverview from './ChannelsOverview';
-const ChannelsTab = (): ReactElement => {
- const [period, periodSelectorProps] = usePeriodSelectorState('last 7 days', 'last 30 days', 'last 90 days');
-
- const t = useTranslation();
-
- const [current, setCurrent] = useState(0);
- const [itemsPerPage, setItemsPerPage] = useState<25 | 50 | 100>(25);
-
- const { data } = useChannelsList({
- period,
- offset: current,
- count: itemsPerPage,
- });
-
- const channels = useMemo(() => {
- if (!data) {
- return;
- }
-
- return data?.channels?.map(({ room: { t, name, usernames, ts, _updatedAt }, messages, diffFromLastWeek }) => ({
- t,
- name: name || usernames?.join(' × '),
- createdAt: ts,
- updatedAt: _updatedAt,
- messagesCount: messages,
- messagesVariation: diffFromLastWeek,
- }));
- }, [data]);
-
- return (
-
-
-
- data?.channels?.map(({ room: { t, name, usernames, ts, _updatedAt }, messages }) => [
- t,
- name || usernames?.join(' × '),
- messages,
- _updatedAt,
- ts,
- ])
- }
- />
- >
- }
- >
-
- {channels && !channels.length && (
-
- {t('No_data_found')}
-
- )}
- {(!channels || channels.length) && (
-
-
-
- {'#'}
- {t('Channel')}
- {t('Created')}
- {t('Last_active')}
- {t('Messages_sent')}
-
-
-
- {channels?.map(({ t, name, createdAt, updatedAt, messagesCount, messagesVariation }, i) => (
-
- {i + 1}.
-
-
- {(t === 'd' && ) || (t === 'p' && ) || (t === 'c' && )}
-
- {name}
-
- {moment(createdAt).format('L')}
- {moment(updatedAt).format('L')}
-
- {messagesCount} {messagesVariation}
-
-
- ))}
- {!channels &&
- Array.from({ length: 5 }, (_, i) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
-
- )}
- t('Items_per_page:')}
- showingResultsLabel={({ count, current, itemsPerPage }): string =>
- t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count)
- }
- count={data?.total || 0}
- onSetItemsPerPage={setItemsPerPage}
- onSetCurrent={setCurrent}
- />
-
-
- );
-};
+const ChannelsTab = (): ReactElement => (
+
+
+
+);
export default ChannelsTab;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/useChannelsList.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/useChannelsList.ts
index 02108bcc2311..584a42425c2c 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/channels/useChannelsList.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/channels/useChannelsList.ts
@@ -36,6 +36,7 @@ export const useChannelsList = ({ period, offset, count }: UseChannelsListOption
{
keepPreviousData: true,
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx
index 245fc35940c4..157eb8917641 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx
@@ -4,7 +4,7 @@ import colors from '@rocket.chat/fuselage-tokens/colors';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, useMemo } from 'react';
-import Section from '../Section';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
import DownloadDataButton from '../dataView/DownloadDataButton';
import LegendSymbol from '../dataView/LegendSymbol';
import PeriodSelector from '../dataView/PeriodSelector';
@@ -39,20 +39,17 @@ const MessagesPerChannelSection = (): ReactElement => {
);
return (
-
-
- messageOriginsData?.origins.map(({ t, messages }) => [t, messages])}
- />
- >
- }
- >
+ <>
+
+
+ messageOriginsData?.origins.map(({ t, messages }) => [t, messages])}
+ />
+
+
@@ -224,7 +221,7 @@ const MessagesPerChannelSection = (): ReactElement => {
-
+ >
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx
index 64a01bdb4eaa..ee980db70d17 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx
@@ -6,7 +6,7 @@ import moment from 'moment';
import React, { ReactElement, useMemo } from 'react';
import CounterSet from '../../../../../../client/components/dataView/CounterSet';
-import Section from '../Section';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
import DownloadDataButton from '../dataView/DownloadDataButton';
import PeriodSelector from '../dataView/PeriodSelector';
import { usePeriodLabel } from '../dataView/usePeriodLabel';
@@ -42,20 +42,17 @@ const MessagesSentSection = (): ReactElement => {
}, [data]);
return (
-
-
- values?.map(({ date, newMessages }) => [date, newMessages])}
- />
- >
- }
- >
+ <>
+
+
+ values?.map(({ date, newMessages }) => [date, newMessages])}
+ />
+
+
{
)}
-
+ >
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx
index 438ebd5938ba..a0dc1227dd3f 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx
@@ -1,15 +1,23 @@
-import { Divider } from '@rocket.chat/fuselage';
+import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement } from 'react';
+import EngagementDashboardCard from '../EngagementDashboardCard';
import MessagesPerChannelSection from './MessagesPerChannelSection';
import MessagesSentSection from './MessagesSentSection';
-const MessagesTab = (): ReactElement => (
- <>
-
-
-
- >
-);
+const MessagesTab = (): ReactElement => {
+ const t = useTranslation();
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+};
export default MessagesTab;
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts
index 4d061b9cdddd..6fbd61ca33c4 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts
@@ -29,6 +29,7 @@ export const useMessageOrigins = ({ period }: UseMessageOriginsOptions) => {
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts
index e03498cb3b1a..1503b48027d2 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts
@@ -29,6 +29,7 @@ export const useMessagesSent = ({ period }: UseMessagesSentOptions) => {
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts
index 9cd3a4a6c3e0..5d8b3d88a1b8 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts
@@ -29,6 +29,7 @@ export const useTopFivePopularChannels = ({ period }: UseTopFivePopularChannelsO
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx
index 6d8464e9a644..0b9566a5fbaa 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx
@@ -7,7 +7,7 @@ import React, { ReactElement, useMemo } from 'react';
import CounterSet from '../../../../../../client/components/dataView/CounterSet';
import { useFormatDate } from '../../../../../../client/hooks/useFormatDate';
-import Section from '../Section';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
import DownloadDataButton from '../dataView/DownloadDataButton';
import LegendSymbol from '../dataView/LegendSymbol';
import { useActiveUsers } from './useActiveUsers';
@@ -102,9 +102,8 @@ const ActiveUsersSection = ({ timezone }: ActiveUsersSectionProps): ReactElement
const t = useTranslation();
return (
-
+ >
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx
index 9f610723be60..232ae03bb79d 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx
@@ -2,7 +2,7 @@ import { Select } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, useMemo, useState } from 'react';
-import Section from '../Section';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
import ContentForDays from './ContentForDays';
import ContentForHours from './ContentForHours';
@@ -42,17 +42,17 @@ const BusiestChatTimesSection = ({ timezone }: BusiestChatTimesSectionProps): Re
)[timeUnit];
return (
- }
- >
+ <>
+
+
+
-
+ >
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx
index 17197cad329f..e95bdd02bbb4 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx
@@ -8,7 +8,7 @@ import React, { ReactElement, useMemo } from 'react';
import CounterSet from '../../../../../../client/components/dataView/CounterSet';
import { useFormatDate } from '../../../../../../client/hooks/useFormatDate';
-import Section from '../Section';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
import DownloadDataButton from '../dataView/DownloadDataButton';
import PeriodSelector from '../dataView/PeriodSelector';
import { usePeriodLabel } from '../dataView/usePeriodLabel';
@@ -78,20 +78,16 @@ const NewUsersSection = ({ timezone }: NewUsersSectionProps): ReactElement => {
}, [data, utc]);
return (
-
-
- values?.map(({ date, newUsers }) => [date, newUsers])}
- />
- >
- }
- >
+ <>
+
+
+ values?.map(({ date, newUsers }) => [date, newUsers])}
+ />
+
{
)}
-
+ >
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx
index 2b255feaecca..112ddf09c47a 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from '@rocket.chat/ui-contexts';
import moment from 'moment';
import React, { ReactElement, useMemo } from 'react';
-import Section from '../Section';
+import EngagementDashboardCardFilter from '../EngagementDashboardCardFilter';
import DownloadDataButton from '../dataView/DownloadDataButton';
import PeriodSelector from '../dataView/PeriodSelector';
import { usePeriodSelectorState } from '../dataView/usePeriodSelectorState';
@@ -75,28 +75,25 @@ const UsersByTimeOfTheDaySection = ({ timezone }: UsersByTimeOfTheDaySectionProp
}, [data, utc]);
return (
-
-
-
- data?.week
- ?.map(({ users, hour, day, month, year }) => ({
- date: moment([year, month - 1, day, hour, 0, 0, 0]),
- users,
- }))
- ?.sort((a, b) => a.date.diff(b.date))
- ?.map(({ date, users }) => [date.toISOString(), users])
- }
- />
- >
- }
- >
+ <>
+
+
+
+ data?.week
+ ?.map(({ users, hour, day, month, year }) => ({
+ date: moment([year, month - 1, day, hour, 0, 0, 0]),
+ users,
+ }))
+ ?.sort((a, b) => a.date.diff(b.date))
+ ?.map(({ date, users }) => [date.toISOString(), users])
+ }
+ />
+
+
{values ? (
@@ -187,7 +184,7 @@ const UsersByTimeOfTheDaySection = ({ timezone }: UsersByTimeOfTheDaySectionProp
) : (
)}
-
+ >
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx
index f870c4fa3593..831cec62beee 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx
@@ -1,7 +1,9 @@
-import { Box, Divider, Flex, Margins } from '@rocket.chat/fuselage';
+import { Box, Flex } from '@rocket.chat/fuselage';
import { useBreakpoints } from '@rocket.chat/fuselage-hooks';
+import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement } from 'react';
+import EngagementDashboardCard from '../EngagementDashboardCard';
import ActiveUsersSection from './ActiveUsersSection';
import BusiestChatTimesSection from './BusiestChatTimesSection';
import NewUsersSection from './NewUsersSection';
@@ -12,23 +14,29 @@ type UsersTabProps = {
};
const UsersTab = ({ timezone }: UsersTabProps): ReactElement => {
+ const t = useTranslation();
+
const isXxlScreen = useBreakpoints().includes('xxl');
return (
<>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
+
+
>
);
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts
index 04db2798d161..2e3381f2e085 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts
@@ -30,6 +30,7 @@ export const useActiveUsers = ({ utc }: UseActiveUsersOptions) => {
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts
index 9513c9f59f2c..56dd4b883c83 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts
@@ -29,6 +29,7 @@ export const useHourlyChatActivity = ({ displacement, utc }: UseHourlyChatActivi
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts
index 6be8b2b5805d..2ce2c0938d23 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { getPeriodRange, Period } from '../dataView/periods';
-type UseNewUsersOptions = { period: Period['key']; utc: boolean };
+export type UseNewUsersOptions = { period: Period['key']; utc: boolean };
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export const useNewUsers = ({ period, utc }: UseNewUsersOptions) => {
@@ -29,6 +29,7 @@ export const useNewUsers = ({ period, utc }: UseNewUsersOptions) => {
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts
index c842cf97c880..937627528258 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts
@@ -29,6 +29,7 @@ export const useUsersByTimeOfTheDay = ({ period, utc }: UseUsersByTimeOfTheDayOp
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts
index 3d5dd31d0215..a04e9d5da633 100644
--- a/apps/meteor/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts
+++ b/apps/meteor/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts
@@ -29,6 +29,7 @@ export const useWeeklyChatActivity = ({ displacement, utc }: UseWeeklyChatActivi
},
{
refetchInterval: 5 * 60 * 1000,
+ useErrorBoundary: true,
},
);
};
diff --git a/apps/meteor/package.json b/apps/meteor/package.json
index 292edefb7dae..9f46223a184e 100644
--- a/apps/meteor/package.json
+++ b/apps/meteor/package.json
@@ -342,6 +342,7 @@
"rc-scrollbars": "^1.1.5",
"react": "~17.0.2",
"react-dom": "~17.0.2",
+ "react-error-boundary": "^3.1.4",
"react-hook-form": "^7.30.0",
"react-i18next": "^11.16.7",
"react-keyed-flatten-children": "^1.3.0",
diff --git a/apps/meteor/server/methods/getPasswordPolicy.js b/apps/meteor/server/methods/getPasswordPolicy.js
index df14573ef014..363ad83989c1 100644
--- a/apps/meteor/server/methods/getPasswordPolicy.js
+++ b/apps/meteor/server/methods/getPasswordPolicy.js
@@ -3,9 +3,12 @@ import { check } from 'meteor/check';
import { Users } from '../../app/models/server';
import { passwordPolicy } from '../../app/lib';
+import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger';
Meteor.methods({
getPasswordPolicy(params = {}) {
+ methodDeprecationLogger.warn('getPasswordPolicy is deprecated and will be removed in future versions of Rocket.Chat');
+
check(params, { token: String });
const user = Users.findOne({ 'services.password.reset.token': params.token });
diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js
index 8585675e06a0..cb930c1d691d 100644
--- a/apps/meteor/server/models/raw/Users.js
+++ b/apps/meteor/server/models/raw/Users.js
@@ -1126,4 +1126,8 @@ export class UsersRaw extends BaseRaw {
};
return this.updateOne(query, update);
}
+
+ findOneByResetToken(token, options) {
+ return this.findOne({ 'services.password.reset.token': token }, options);
+ }
}
diff --git a/apps/meteor/server/modules/watchers/publishFields.ts b/apps/meteor/server/modules/watchers/publishFields.ts
index 6de9272a28ee..c3ea240507bb 100644
--- a/apps/meteor/server/modules/watchers/publishFields.ts
+++ b/apps/meteor/server/modules/watchers/publishFields.ts
@@ -21,6 +21,9 @@ export const subscriptionFields = {
desktopNotifications: 1,
mobilePushNotifications: 1,
emailNotifications: 1,
+ desktopPrefOrigin: 1,
+ mobilePrefOrigin: 1,
+ emailPrefOrigin: 1,
unreadAlert: 1,
_updatedAt: 1,
blocked: 1,
diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts
index 86b0b4bd9080..fcdb56f64f30 100644
--- a/apps/meteor/tests/e2e/channel-management.spec.ts
+++ b/apps/meteor/tests/e2e/channel-management.spec.ts
@@ -82,4 +82,24 @@ test.describe.serial('channel-management', () => {
await expect(page).toHaveURL(`/channel/NAME-EDITED-${targetChannel}`);
});
+
+ test('expect edit notification preferences of "targetChannel"', async () => {
+ await poHomeChannel.sidenav.openChat(targetChannel);
+ await poHomeChannel.tabs.btnMoreItems.click({ force: true });
+ await poHomeChannel.tabs.btnNotificationPreferences.click({ force: true });
+ await poHomeChannel.tabs.notificationPreferences.updateAllNotificaitonPreferences();
+ await poHomeChannel.tabs.notificationPreferences.btnSave.click();
+
+ await expect(poHomeChannel.toastSuccess).toBeVisible();
+ });
+
+ test('expect all notification preferences of "targetChannel" to be "Mentions"', async () => {
+ await poHomeChannel.sidenav.openChat(targetChannel);
+ await poHomeChannel.tabs.btnMoreItems.click({ force: true });
+ await poHomeChannel.tabs.btnNotificationPreferences.click({ force: true });
+
+ await expect(poHomeChannel.tabs.notificationPreferences.getPreferenceByDevice('Desktop')).toContainText('Mentions');
+ await expect(poHomeChannel.tabs.notificationPreferences.getPreferenceByDevice('Mobile')).toContainText('Mentions');
+ await expect(poHomeChannel.tabs.notificationPreferences.getPreferenceByDevice('Email')).toContainText('Mentions');
+ });
});
diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-notificationPreferences.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-notificationPreferences.ts
new file mode 100644
index 000000000000..2dd316268f3c
--- /dev/null
+++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-notificationPreferences.ts
@@ -0,0 +1,41 @@
+import type { Locator, Page } from '@playwright/test';
+
+export class HomeFlextabNotificationPreferences {
+ private readonly page: Page;
+
+ constructor(page: Page) {
+ this.page = page;
+ }
+
+ get btnSave(): Locator {
+ return this.page.locator('//aside//button[contains(text(), "Save")]');
+ }
+
+ getPreferenceByDevice(device: string): Locator {
+ return this.page.locator(`//div[@id="${device}Alert"]`);
+ }
+
+ async selectDropdownById(text: string): Promise {
+ await this.page.locator(`//div[@id="${text}"]`).click();
+ }
+
+ async selectOptionByLabel(text: string): Promise {
+ await this.page.locator(`li.rcx-option >> text="${text}"`).click();
+ }
+
+ async selectDevice(text: string): Promise {
+ await this.page.locator(`[data-qa-id="${text}-notifications"]`).click();
+ }
+
+ async updateDevicePreference(device: string): Promise {
+ await this.selectDevice(device);
+ await this.selectDropdownById(`${device}Alert`);
+ await this.selectOptionByLabel('Mentions');
+ }
+
+ async updateAllNotificaitonPreferences(): Promise {
+ await this.updateDevicePreference('Desktop');
+ await this.updateDevicePreference('Mobile');
+ await this.updateDevicePreference('Email');
+ }
+}
diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts
index 10e74dbe53d9..8823435b1896 100644
--- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts
+++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts
@@ -1,6 +1,7 @@
import type { Locator, Page } from '@playwright/test';
import { HomeFlextabMembers } from './home-flextab-members';
+import { HomeFlextabNotificationPreferences } from './home-flextab-notificationPreferences';
import { HomeFlextabRoom } from './home-flextab-room';
export class HomeFlextab {
@@ -10,10 +11,13 @@ export class HomeFlextab {
readonly room: HomeFlextabRoom;
+ readonly notificationPreferences: HomeFlextabNotificationPreferences;
+
constructor(page: Page) {
this.page = page;
this.members = new HomeFlextabMembers(page);
this.room = new HomeFlextabRoom(page);
+ this.notificationPreferences = new HomeFlextabNotificationPreferences(page);
}
get btnTabMembers(): Locator {
@@ -24,6 +28,14 @@ export class HomeFlextab {
return this.page.locator('[data-qa-id=ToolBoxAction-info-circled]');
}
+ get btnMoreItems(): Locator {
+ return this.page.locator('[data-qa-id=ToolBox-Menu]');
+ }
+
+ get btnNotificationPreferences(): Locator {
+ return this.page.locator('[data-qa-id=ToolBoxAction-bell]');
+ }
+
get flexTabViewThreadMessage(): Locator {
return this.page.locator(
'div.thread-list.js-scroll-thread ul.thread [data-qa-type="message"]:last-child div.message-body-wrapper [data-qa-type="message-body"]',
diff --git a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js
index 521c3d3231b1..e998990a03c8 100644
--- a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js
+++ b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js
@@ -656,4 +656,73 @@ describe('miscellaneous', function () {
});
});
});
+
+ describe('/pw.getPolicy', () => {
+ it('should fail if not logged in', (done) => {
+ request
+ .get(api('pw.getPolicy'))
+ .expect('Content-Type', 'application/json')
+ .expect(401)
+ .expect((res) => {
+ expect(res.body).to.have.property('status', 'error');
+ expect(res.body).to.have.property('message');
+ })
+ .end(done);
+ });
+
+ it('should return policies', (done) => {
+ request
+ .get(api('pw.getPolicy'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('enabled');
+ expect(res.body).to.have.property('policy').and.to.be.an('array');
+ })
+ .end(done);
+ });
+ });
+
+ describe('/pw.getPolicyReset', () => {
+ it('should fail if no token provided', (done) => {
+ request
+ .get(api('pw.getPolicyReset'))
+ .expect('Content-Type', 'application/json')
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('errorType', 'invalid-params');
+ })
+ .end(done);
+ });
+
+ it('should fail if no token is invalid format', (done) => {
+ request
+ .get(api('pw.getPolicyReset?token=123'))
+ .expect('Content-Type', 'application/json')
+ .expect(403)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'unauthorized');
+ })
+ .end(done);
+ });
+
+ // not sure we have a way to get the reset token, looks like it is only sent via email by Meteor
+ it.skip('should return policies if correct token is provided', (done) => {
+ request
+ .get(api('pw.getPolicyReset?token'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .expect(403)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('enabled');
+ expect(res.body).to.have.property('policy').and.to.be.an('array');
+ })
+ .end(done);
+ });
+ });
});
diff --git a/apps/meteor/tests/end-to-end/api/25-teams.js b/apps/meteor/tests/end-to-end/api/25-teams.js
index dd4419432ddb..49bdc86af6bc 100644
--- a/apps/meteor/tests/end-to-end/api/25-teams.js
+++ b/apps/meteor/tests/end-to-end/api/25-teams.js
@@ -819,6 +819,77 @@ describe('[Teams]', () => {
});
});
+ describe('/teams.info', () => {
+ it('should successfully get a team info by name', (done) => {
+ request
+ .get(api('teams.info'))
+ .set(credentials)
+ .query({
+ teamName: publicTeam.name,
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(200)
+ .expect((response) => {
+ expect(response.body).to.have.property('success', true);
+ expect(response.body).to.have.property('teamInfo');
+ expect(response.body.teamInfo).to.have.property('_id', publicTeam._id);
+ expect(response.body.teamInfo).to.have.property('name', publicTeam.name);
+ })
+ .then(() => done())
+ .catch(done);
+ });
+ it('should successfully get a team info by id', (done) => {
+ request
+ .get(api('teams.info'))
+ .set(credentials)
+ .query({
+ teamId: publicTeam._id,
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(200)
+ .expect((response) => {
+ expect(response.body).to.have.property('success', true);
+ expect(response.body).to.have.property('teamInfo');
+ expect(response.body.teamInfo).to.have.property('_id', publicTeam._id);
+ expect(response.body.teamInfo).to.have.property('name', publicTeam.name);
+ })
+ .then(() => done())
+ .catch(done);
+ });
+ it('should fail if a team is not found', (done) => {
+ request
+ .get(api('teams.info'))
+ .set(credentials)
+ .query({
+ teamName: '',
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(400)
+ .expect((response) => {
+ expect(response.body).to.have.property('success', false);
+ expect(response.body).to.have.property('error', 'Team not found');
+ })
+ .then(() => done())
+ .catch(done);
+ });
+ it('should fail if a user doesnt belong to a team', (done) => {
+ request
+ .get(api('teams.info'))
+ .set(testUserCredentials)
+ .query({
+ teamName: privateTeam.name,
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(403)
+ .expect((response) => {
+ expect(response.body).to.have.property('success', false);
+ expect(response.body).to.have.property('error', 'unauthorized');
+ })
+ .then(() => done())
+ .catch(done);
+ });
+ });
+
describe('/teams.delete', () => {
describe('deleting an empty team', () => {
let roomId;
diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json
index d294330e9e84..4e063a043d5c 100644
--- a/packages/gazzodown/package.json
+++ b/packages/gazzodown/package.json
@@ -73,6 +73,7 @@
"react": "~17.0.2"
},
"dependencies": {
- "highlight.js": "^11.5.1"
+ "highlight.js": "^11.5.1",
+ "react-error-boundary": "^3.1.4"
}
}
diff --git a/packages/gazzodown/src/katex/KatexErrorBoundary.tsx b/packages/gazzodown/src/katex/KatexErrorBoundary.tsx
index 8201c2583481..ba735a35b4f1 100644
--- a/packages/gazzodown/src/katex/KatexErrorBoundary.tsx
+++ b/packages/gazzodown/src/katex/KatexErrorBoundary.tsx
@@ -1,7 +1,7 @@
import colors from '@rocket.chat/fuselage-tokens/colors.json';
import styled from '@rocket.chat/styled';
-import { ErrorBoundary } from '@rocket.chat/ui-client';
import { PropsWithChildren, ReactElement, useState } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
type KatexErrorBoundaryProps = PropsWithChildren<{ code: string }>;
diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts
index e2809e30394a..4706c403dbdb 100644
--- a/packages/model-typings/src/models/IUsersModel.ts
+++ b/packages/model-typings/src/models/IUsersModel.ts
@@ -149,4 +149,6 @@ export interface IUsersModel extends IBaseModel {
findActiveByIdsOrUsernames(userIds: string[], options?: any): FindCursor;
setAsFederated(userId: string): any;
+
+ findOneByResetToken(token: string, options: FindOptions): Promise;
}
diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts
index 1e7ce6e95fba..bd7dbd7484b4 100644
--- a/packages/rest-typings/src/v1/misc.ts
+++ b/packages/rest-typings/src/v1/misc.ts
@@ -162,6 +162,21 @@ const MethodCallAnonSchema = {
export const isMethodCallAnonProps = ajv.compile(MethodCallAnonSchema);
+type PwGetPolicyReset = { token: string };
+
+const PwGetPolicyResetSchema = {
+ type: 'object',
+ properties: {
+ token: {
+ type: 'string',
+ },
+ },
+ required: ['token'],
+ additionalProperties: false,
+};
+
+export const validateParamsPwGetPolicyRest = ajv.compile(PwGetPolicyResetSchema);
+
export type MiscEndpoints = {
'/v1/stdout.queue': {
GET: () => {
@@ -192,6 +207,20 @@ export type MiscEndpoints = {
}>;
};
+ '/v1/pw.getPolicy': {
+ GET: () => {
+ enabled: boolean;
+ policy: [name: string, options?: Record][];
+ };
+ };
+
+ '/v1/pw.getPolicyReset': {
+ GET: (params: PwGetPolicyReset) => {
+ enabled: boolean;
+ policy: [name: string, options?: Record][];
+ };
+ };
+
'/v1/method.call/:method': {
POST: (params: { message: string }) => {
message: unknown;
diff --git a/packages/ui-client/src/components/ErrorBoundary.tsx b/packages/ui-client/src/components/ErrorBoundary.tsx
deleted file mode 100644
index 9e09c02cf0d4..000000000000
--- a/packages/ui-client/src/components/ErrorBoundary.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Component, ReactNode, ErrorInfo } from 'react';
-
-export class ErrorBoundary extends Component<
- { fallback?: ReactNode; onError?: (error: Error, errorInfo: ErrorInfo) => void },
- { hasError: boolean }
-> {
- state = { hasError: false };
-
- static getDerivedStateFromError(): { hasError: boolean } {
- return { hasError: true };
- }
-
- componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
- this.props.onError?.(error, errorInfo);
- console.error('Uncaught Error:', error, errorInfo);
- }
-
- render(): ReactNode {
- if (this.state.hasError) {
- return this.props.fallback || null;
- }
-
- return this.props.children;
- }
-}
diff --git a/packages/ui-client/src/components/index.ts b/packages/ui-client/src/components/index.ts
index b5f98691781b..73e9adbbf71d 100644
--- a/packages/ui-client/src/components/index.ts
+++ b/packages/ui-client/src/components/index.ts
@@ -1,4 +1,3 @@
export * from './ExternalLink';
-export * from './ErrorBoundary';
export * from './DotLeader';
export * from './TooltipComponent';
diff --git a/yarn.lock b/yarn.lock
index ddc2822e157e..c881be3aeaaf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4076,6 +4076,7 @@ __metadata:
katex: ~0.16.0
outdent: ^0.8.0
react-dom: ~17.0.2
+ react-error-boundary: ^3.1.4
ts-jest: ^27.1.4
typescript: ~4.5.5
peerDependencies:
@@ -4496,6 +4497,7 @@ __metadata:
rc-scrollbars: ^1.1.5
react: ~17.0.2
react-dom: ~17.0.2
+ react-error-boundary: ^3.1.4
react-hook-form: ^7.30.0
react-i18next: ^11.16.7
react-keyed-flatten-children: ^1.3.0
@@ -26204,7 +26206,7 @@ __metadata:
languageName: node
linkType: hard
-"react-error-boundary@npm:^3.1.0":
+"react-error-boundary@npm:^3.1.4":
version: 3.1.4
resolution: "react-error-boundary@npm:3.1.4"
dependencies: