diff --git a/.changeset/gentle-news-wonder.md b/.changeset/gentle-news-wonder.md
new file mode 100644
index 000000000000..dd3218003d7a
--- /dev/null
+++ b/.changeset/gentle-news-wonder.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": minor
+---
+
+Added a new 'Deactivated' tab to the users page, this tab lists users who have logged in for the first time but have been deactivated for any reason. Also added the UI code for the Active tab;
diff --git a/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx b/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx
index c1052889f16f..800222282054 100644
--- a/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx
@@ -6,6 +6,7 @@ import React, { useCallback, useMemo } from 'react';
import { UserInfoAction } from '../../../components/UserInfo';
import { useActionSpread } from '../../hooks/useActionSpread';
+import type { AdminUserTab } from './AdminUsersPage';
import { useChangeAdminStatusAction } from './hooks/useChangeAdminStatusAction';
import { useChangeUserStatusAction } from './hooks/useChangeUserStatusAction';
import { useDeleteUserAction } from './hooks/useDeleteUserAction';
@@ -18,6 +19,7 @@ type AdminUserInfoActionsProps = {
isFederatedUser: IUser['federated'];
isActive: boolean;
isAdmin: boolean;
+ tab: AdminUserTab;
onChange: () => void;
onReload: () => void;
};
@@ -29,6 +31,7 @@ const AdminUserInfoActions = ({
isFederatedUser,
isActive,
isAdmin,
+ tab,
onChange,
onReload,
}: AdminUserInfoActionsProps): ReactElement => {
@@ -62,6 +65,7 @@ const AdminUserInfoActions = ({
[userId, userRoute],
);
+ const isNotPendingDeactivatedNorFederated = tab !== 'pending' && tab !== 'deactivated' && !isFederatedUser;
const options = useMemo(
() => ({
...(canDirectMessage && {
@@ -81,24 +85,25 @@ const AdminUserInfoActions = ({
disabled: isFederatedUser,
},
}),
- ...(changeAdminStatusAction && !isFederatedUser && { makeAdmin: changeAdminStatusAction }),
- ...(resetE2EKeyAction && !isFederatedUser && { resetE2EKey: resetE2EKeyAction }),
- ...(resetTOTPAction && !isFederatedUser && { resetTOTP: resetTOTPAction }),
- ...(deleteUserAction && { delete: deleteUserAction }),
+ ...(isNotPendingDeactivatedNorFederated && changeAdminStatusAction && { makeAdmin: changeAdminStatusAction }),
+ ...(isNotPendingDeactivatedNorFederated && resetE2EKeyAction && { resetE2EKey: resetE2EKeyAction }),
+ ...(isNotPendingDeactivatedNorFederated && resetTOTPAction && { resetTOTP: resetTOTPAction }),
...(changeUserStatusAction && !isFederatedUser && { changeActiveStatus: changeUserStatusAction }),
+ ...(deleteUserAction && { delete: deleteUserAction }),
}),
[
- t,
canDirectMessage,
- directMessageClick,
canEditOtherUserInfo,
- editUserClick,
changeAdminStatusAction,
changeUserStatusAction,
deleteUserAction,
+ directMessageClick,
+ editUserClick,
+ isFederatedUser,
+ isNotPendingDeactivatedNorFederated,
resetE2EKeyAction,
resetTOTPAction,
- isFederatedUser,
+ t,
],
);
@@ -117,7 +122,9 @@ const AdminUserInfoActions = ({
secondary
flexShrink={0}
key='menu'
- renderItem={({ label: { label, icon }, ...props }): ReactElement => }
+ renderItem={({ label: { label, icon }, ...props }): ReactElement => (
+
+ )}
options={menuOptions}
/>
);
diff --git a/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx b/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx
index ee7bb4458311..2318e5ae1dc1 100644
--- a/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx
@@ -14,13 +14,15 @@ import { UserInfo } from '../../../components/UserInfo';
import { UserStatus } from '../../../components/UserStatus';
import { getUserEmailVerified } from '../../../lib/utils/getUserEmailVerified';
import AdminUserInfoActions from './AdminUserInfoActions';
+import type { AdminUserTab } from './AdminUsersPage';
type AdminUserInfoWithDataProps = {
uid: IUser['_id'];
onReload: () => void;
+ tab: AdminUserTab;
};
-const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): ReactElement => {
+const AdminUserInfoWithData = ({ uid, onReload, tab }: AdminUserInfoWithDataProps): ReactElement => {
const t = useTranslation();
const getRoles = useRolesDescription();
const approveManuallyUsers = useSetting('Accounts_ManuallyApproveNewUsers');
@@ -123,6 +125,7 @@ const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): R
isFederatedUser={!!data.user.federated}
onChange={onChange}
onReload={onReload}
+ tab={tab}
/>
}
/>
diff --git a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx
index b7243af14739..4ef44122e303 100644
--- a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx
@@ -1,4 +1,4 @@
-import type { IAdminUserTabs, LicenseInfo } from '@rocket.chat/core-typings';
+import type { LicenseInfo } from '@rocket.chat/core-typings';
import { Button, ButtonGroup, Callout, ContextualbarIcon, Skeleton, Tabs, TabsItem } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import type { OptionProp } from '@rocket.chat/ui-client';
@@ -38,6 +38,8 @@ export type UsersFilters = {
roles: OptionProp[];
};
+export type AdminUserTab = 'all' | 'active' | 'deactivated' | 'pending';
+
export type UsersTableSortingOptions = 'name' | 'username' | 'emails.address' | 'status' | 'active';
const AdminUsersPage = (): ReactElement => {
@@ -64,7 +66,7 @@ const AdminUsersPage = (): ReactElement => {
const paginationData = usePagination();
const sortData = useSort('name');
- const [tab, setTab] = useState('all');
+ const [tab, setTab] = useState('all');
const [userFilters, setUserFilters] = useState({ text: '', roles: [] });
const searchTerm = useDebouncedValue(userFilters.text, 500);
@@ -86,9 +88,10 @@ const AdminUsersPage = (): ReactElement => {
filteredUsersQueryResult?.refetch();
};
- const handleTabChangeAndSort = (tab: IAdminUserTabs) => {
+ const handleTabChange = (tab: AdminUserTab) => {
setTab(tab);
+ paginationData.setCurrent(0);
sortData.setSort(tab === 'pending' ? 'active' : 'name', 'asc');
};
@@ -142,14 +145,20 @@ const AdminUsersPage = (): ReactElement => {
)}
- handleTabChangeAndSort('all')}>
+ handleTabChange('all')}>
{t('All')}
- handleTabChangeAndSort('pending')} display='flex' flexDirection='row'>
+ handleTabChange('pending')} display='flex' flexDirection='row'>
{`${t('Pending')} `}
{pendingUsersCount.isLoading && }
{pendingUsersCount.isSuccess && `(${pendingUsersCount.data})`}
+ handleTabChange('active')}>
+ {t('Active')}
+
+ handleTabChange('deactivated')}>
+ {t('Deactivated')}
+
{
router.navigate('/admin/users')} />
- {context === 'info' && id && }
+ {context === 'info' && id && }
{context === 'edit' && id && }
{!isRoutePrevented && context === 'new' && }
{!isRoutePrevented && context === 'invite' && }
diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx
index 01d7007561eb..34d71e6ab371 100644
--- a/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx
+++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx
@@ -1,4 +1,4 @@
-import type { IAdminUserTabs, IRole, Serialized } from '@rocket.chat/core-typings';
+import type { IRole, Serialized } from '@rocket.chat/core-typings';
import { Pagination, States, StatesAction, StatesActions, StatesIcon, StatesTitle } from '@rocket.chat/fuselage';
import { useEffectEvent, useBreakpoints } from '@rocket.chat/fuselage-hooks';
import type { PaginatedResult, DefaultUserInfo } from '@rocket.chat/rest-typings';
@@ -17,12 +17,12 @@ import {
} from '../../../../components/GenericTable';
import type { usePagination } from '../../../../components/GenericTable/hooks/usePagination';
import type { useSort } from '../../../../components/GenericTable/hooks/useSort';
-import type { UsersFilters, UsersTableSortingOptions } from '../AdminUsersPage';
+import type { AdminUserTab, UsersFilters, UsersTableSortingOptions } from '../AdminUsersPage';
import UsersTableFilters from './UsersTableFilters';
import UsersTableRow from './UsersTableRow';
type UsersTableProps = {
- tab: IAdminUserTabs;
+ tab: AdminUserTab;
roleData: { roles: IRole[] } | undefined;
onReload: () => void;
setUserFilters: Dispatch>;
diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx
index 727edbc7a188..0d9a030f1447 100644
--- a/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx
+++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx
@@ -1,5 +1,5 @@
import { UserStatus as Status } from '@rocket.chat/core-typings';
-import type { IAdminUserTabs, IRole, IUser, Serialized } from '@rocket.chat/core-typings';
+import type { IRole, IUser, Serialized } from '@rocket.chat/core-typings';
import { Box, Button, Menu, Option } from '@rocket.chat/fuselage';
import type { DefaultUserInfo } from '@rocket.chat/rest-typings';
import { UserAvatar } from '@rocket.chat/ui-avatar';
@@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
import { Roles } from '../../../../../app/models/client';
import { GenericTableRow, GenericTableCell } from '../../../../components/GenericTable';
import { UserStatus } from '../../../../components/UserStatus';
+import type { AdminUserTab } from '../AdminUsersPage';
import { useChangeAdminStatusAction } from '../hooks/useChangeAdminStatusAction';
import { useChangeUserStatusAction } from '../hooks/useChangeUserStatusAction';
import { useDeleteUserAction } from '../hooks/useDeleteUserAction';
@@ -23,7 +24,7 @@ type UsersTableRowProps = {
isMobile: boolean;
isLaptop: boolean;
onReload: () => void;
- tab: IAdminUserTabs;
+ tab: AdminUserTab;
isSeatsCapExceeded: boolean;
};
@@ -65,33 +66,44 @@ const UsersTableRow = ({ user, onClick, onReload, isMobile, isLaptop, tab, isSea
const resendWelcomeEmail = useSendWelcomeEmailMutation();
const isNotPendingDeactivatedNorFederated = tab !== 'pending' && tab !== 'deactivated' && !isFederatedUser;
- const menuOptions = {
- ...(isNotPendingDeactivatedNorFederated &&
- changeAdminStatusAction && {
- makeAdmin: {
- label: { label: changeAdminStatusAction.label, icon: changeAdminStatusAction.icon },
- action: changeAdminStatusAction.action,
- },
+ const menuOptions = useMemo(
+ () => ({
+ ...(isNotPendingDeactivatedNorFederated &&
+ changeAdminStatusAction && {
+ makeAdmin: {
+ label: { label: changeAdminStatusAction.label, icon: changeAdminStatusAction.icon },
+ action: changeAdminStatusAction.action,
+ },
+ }),
+ ...(isNotPendingDeactivatedNorFederated &&
+ resetE2EKeyAction && {
+ resetE2EKey: { label: { label: resetE2EKeyAction.label, icon: resetE2EKeyAction.icon }, action: resetE2EKeyAction.action },
+ }),
+ ...(isNotPendingDeactivatedNorFederated &&
+ resetTOTPAction && {
+ resetTOTP: { label: { label: resetTOTPAction.label, icon: resetTOTPAction.icon }, action: resetTOTPAction.action },
+ }),
+ ...(changeUserStatusAction &&
+ !isFederatedUser && {
+ changeActiveStatus: {
+ label: { label: changeUserStatusAction.label, icon: changeUserStatusAction.icon },
+ action: changeUserStatusAction.action,
+ },
+ }),
+ ...(deleteUserAction && {
+ delete: { label: { label: deleteUserAction.label, icon: deleteUserAction.icon }, action: deleteUserAction.action },
}),
- ...(isNotPendingDeactivatedNorFederated &&
- resetE2EKeyAction && {
- resetE2EKey: { label: { label: resetE2EKeyAction.label, icon: resetE2EKeyAction.icon }, action: resetE2EKeyAction.action },
- }),
- ...(isNotPendingDeactivatedNorFederated &&
- resetTOTPAction && {
- resetTOTP: { label: { label: resetTOTPAction.label, icon: resetTOTPAction.icon }, action: resetTOTPAction.action },
- }),
- ...(changeUserStatusAction &&
- !isFederatedUser && {
- changeActiveStatus: {
- label: { label: changeUserStatusAction.label, icon: changeUserStatusAction.icon },
- action: changeUserStatusAction.action,
- },
- }),
- ...(deleteUserAction && {
- delete: { label: { label: deleteUserAction.label, icon: deleteUserAction.icon }, action: deleteUserAction.action },
}),
- };
+ [
+ changeAdminStatusAction,
+ changeUserStatusAction,
+ deleteUserAction,
+ isFederatedUser,
+ isNotPendingDeactivatedNorFederated,
+ resetE2EKeyAction,
+ resetTOTPAction,
+ ],
+ );
const handleResendWelcomeEmail = () => resendWelcomeEmail.mutateAsync({ email: emails?.[0].address });
@@ -143,40 +155,36 @@ const UsersTableRow = ({ user, onClick, onReload, isMobile, isLaptop, tab, isSea
)}
{
e.stopPropagation();
}}
>
- {tab === 'pending' && (
- <>
- {active ? (
-
- ) : (
-
+
+ {tab === 'pending' && (
+ <>
+ {active ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+
);
diff --git a/apps/meteor/client/views/admin/users/hooks/useFilteredUsers.ts b/apps/meteor/client/views/admin/users/hooks/useFilteredUsers.ts
index 9a592d5e449f..f6d14a548b46 100644
--- a/apps/meteor/client/views/admin/users/hooks/useFilteredUsers.ts
+++ b/apps/meteor/client/views/admin/users/hooks/useFilteredUsers.ts
@@ -1,4 +1,3 @@
-import type { IAdminUserTabs } from '@rocket.chat/core-typings';
import type { UsersListStatusParamsGET } from '@rocket.chat/rest-typings';
import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
@@ -7,12 +6,12 @@ import { useMemo } from 'react';
import type { usePagination } from '../../../../components/GenericTable/hooks/usePagination';
import type { useSort } from '../../../../components/GenericTable/hooks/useSort';
-import type { UsersTableSortingOptions } from '../AdminUsersPage';
+import type { AdminUserTab, UsersTableSortingOptions } from '../AdminUsersPage';
type UseFilteredUsersOptions = {
searchTerm: string;
prevSearchTerm: MutableRefObject;
- tab: IAdminUserTabs;
+ tab: AdminUserTab;
paginationData: ReturnType;
sortData: ReturnType>;
selectedRoles: string[];
@@ -27,7 +26,7 @@ const useFilteredUsers = ({ searchTerm, prevSearchTerm, sortData, paginationData
setCurrent(0);
}
- const listUsersPayload: Partial> = {
+ const listUsersPayload: Partial> = {
all: {},
pending: {
hasLoggedIn: false,
diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts
index 136146e40a34..3c6d1c890d7b 100644
--- a/packages/core-typings/src/IUser.ts
+++ b/packages/core-typings/src/IUser.ts
@@ -231,5 +231,3 @@ export type AvatarServiceObject = {
};
export type AvatarObject = AvatarReset | AvatarUrlObj | FormData | AvatarServiceObject;
-
-export type IAdminUserTabs = 'all' | 'active' | 'deactivated' | 'pending';