From 29a50b751eb3b82f09f67bf7fc72f3768e9f8970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Wed, 29 Nov 2023 10:22:00 -0300 Subject: [PATCH] feat: Implement users page deactivated tab (#30532) * feat: :sparkles: Filter users list by active users Implemented the necessary resources to filter the list of users and to return only those who are active, when the active tab is enabled. * feat: :sparkles: Implement new roles filter in the admin users page Implemented a new filter on the users' page that retrieves the roles list and creates a dropdown menu. When any of its options is selected, it will filter the users' list to display only those with the selected roles. Additionally, I made some minor adjustments to the MultiSelectCustom component and introduced some new hooks. * WIP: Remove filters * feat: :sparkles: WIP: Implement new actions menu for users page table * feat: :sparkles: Finish users table menu Completed the implementation of the users table actions menu by preventing the propagation of the click event that triggered the opening of the contextual bar when the menu was clicked. Also, ensured that the contextual bar only opens when the "Enter" or "Space" keys are used for keyboard navigation and enhanced the options menu in the contextual bar as specified in Figma. * refactor: :recycle: Remove status badge from contextual bar and reorg Removed the status from the users page contextual bar (the need to remove this may not be concrete, for now it will be only commented until the final decision is made). Also changed the order of the contextual bar info and changed some minor styles to follow figma specs. * Typecheck * Re-add icon back to InfoPanelTitle * Hide registration status and justify actions menu * feat: :sparkles: Implement deactivated tab logic and specifications Implemented the filtering function that will show deactivated users when the deactivated tab has been selected and hid the actions menu options that are unnecessary for deactivated users. Didn't implement the empty state because it has been implemented in the pending tab PR. * Fix tab types * Re-add spacing, change components and add save user --- .../components/UserCard/UserCardRoles.tsx | 2 +- .../client/components/UserInfo/UserInfo.tsx | 130 +++++++++--------- .../views/admin/users/AdminUserForm.tsx | 2 +- .../admin/users/AdminUserInfoActions.tsx | 17 ++- .../admin/users/AdminUserInfoWithData.tsx | 4 +- .../views/admin/users/AdminUsersPage.tsx | 2 +- .../admin/users/UsersTable/UsersTableRow.tsx | 7 +- .../useFilterActiveOrDeactivatedUsers.ts | 10 ++ .../rocketchat-i18n/i18n/en.i18n.json | 1 + .../MultiSelectCustom/MultiSelectCustom.tsx | 1 - 10 files changed, 96 insertions(+), 80 deletions(-) create mode 100644 apps/meteor/client/views/admin/users/hooks/useFilterActiveOrDeactivatedUsers.ts diff --git a/apps/meteor/client/components/UserCard/UserCardRoles.tsx b/apps/meteor/client/components/UserCard/UserCardRoles.tsx index 9cd22ebdff353..f7a977d4a4dc8 100644 --- a/apps/meteor/client/components/UserCard/UserCardRoles.tsx +++ b/apps/meteor/client/components/UserCard/UserCardRoles.tsx @@ -6,7 +6,7 @@ import UserCardInfo from './UserCardInfo'; const UserCardRoles = ({ children }: { children: ReactNode }): ReactElement => ( - + {children} diff --git a/apps/meteor/client/components/UserInfo/UserInfo.tsx b/apps/meteor/client/components/UserInfo/UserInfo.tsx index 01edbe9503ceb..4ff97f3c826f5 100644 --- a/apps/meteor/client/components/UserInfo/UserInfo.tsx +++ b/apps/meteor/client/components/UserInfo/UserInfo.tsx @@ -10,6 +10,12 @@ import { useUserCustomFields } from '../../hooks/useUserCustomFields'; import { useUserDisplayName } from '../../hooks/useUserDisplayName'; import { ContextualbarScrollableContent } from '../Contextualbar'; import InfoPanel from '../InfoPanel'; +import InfoPanelAvatar from '../InfoPanel/InfoPanelAvatar'; +import InfoPanelField from '../InfoPanel/InfoPanelField'; +import InfoPanelLabel from '../InfoPanel/InfoPanelLabel'; +import InfoPanelSection from '../InfoPanel/InfoPanelSection'; +import InfoPanelText from '../InfoPanel/InfoPanelText'; +import InfoPanelTitle from '../InfoPanel/InfoPanelTitle'; import MarkdownText from '../MarkdownText'; import UTCClock from '../UTCClock'; import UserCard from '../UserCard'; @@ -58,7 +64,6 @@ const UserInfo = ({ status, statusText, customFields, - canViewAllInfo, actions, reason, ...props @@ -72,124 +77,117 @@ const UserInfo = ({ {username && ( - + - + )} - {actions && {actions}} + {actions && {actions}} - - {userDisplayName && } + + {userDisplayName && } {statusText && ( - + - + )} - + - + {reason && ( - - {t('Reason_for_joining')} - {reason} - + + {t('Reason_for_joining')} + {reason} + )} {nickname && ( - - {t('Nickname')} - {nickname} - + + {t('Nickname')} + {nickname} + )} {roles.length !== 0 && ( - - {t('Roles')} + + {t('Roles')} {roles} - + )} {username && username !== name && ( - - {t('Username')} - {username} - + + {t('Username')} + {username} + )} {Number.isInteger(utcOffset) && ( - - {t('Local_Time')} - {utcOffset && } - + + {t('Local_Time')} + {utcOffset && } + )} {bio && ( - - {t('Bio')} - + + {t('Bio')} + - - + + )} - {canViewAllInfo && ( - - {t('Last_login')} - {lastLogin ? timeAgo(lastLogin) : t('Never')} - - )} - - {name && ( - - {t('Full_Name')} - {name} - + {Number.isInteger(utcOffset) && ( + + {t('Last_login')} + {lastLogin ? timeAgo(lastLogin) : t('Never')} + )} {phone && ( - - {t('Phone')} - + + {t('Phone')} + {phone} - - + + )} {email && ( - - {t('Email')} - + + {t('Email')} + {email} {verified ? t('Verified') : t('Not_verified')} - - + + )} {userCustomFields?.map( (customField) => customField?.value && ( - - {t(customField.label as TranslationKey)} - + + {t(customField.label as TranslationKey)} + - - + + ), )} {createdAt && ( - - {t('Created_at')} - {timeAgo(createdAt)} - + + {t('Created_at')} + {timeAgo(createdAt)} + )} - + ); diff --git a/apps/meteor/client/views/admin/users/AdminUserForm.tsx b/apps/meteor/client/views/admin/users/AdminUserForm.tsx index 35991573886cd..023aaed21e850 100644 --- a/apps/meteor/client/views/admin/users/AdminUserForm.tsx +++ b/apps/meteor/client/views/admin/users/AdminUserForm.tsx @@ -475,7 +475,7 @@ const AdminUserForm = ({ userData, onReload, setCreatedUsersCount, context, refe diff --git a/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx b/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx index dd0bf833f72ba..505a9814cc3b7 100644 --- a/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx +++ b/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx @@ -20,6 +20,7 @@ type AdminUserInfoActionsProps = { isAdmin: boolean; onChange: () => void; onReload: () => void; + tab: 'all' | 'invited' | 'active' | 'deactivated' | 'pending'; }; const AdminUserInfoActions = ({ @@ -30,6 +31,7 @@ const AdminUserInfoActions = ({ isAdmin, onChange, onReload, + tab, }: AdminUserInfoActionsProps): ReactElement => { const t = useTranslation(); const directRoute = useRoute('direct'); @@ -80,24 +82,25 @@ const AdminUserInfoActions = ({ disabled: isFederatedUser, }, }), - ...(changeAdminStatusAction && !isFederatedUser && { makeAdmin: changeAdminStatusAction }), - ...(resetE2EKeyAction && !isFederatedUser && { resetE2EKey: resetE2EKeyAction }), - ...(resetTOTPAction && !isFederatedUser && { resetTOTP: resetTOTPAction }), + ...(changeAdminStatusAction && !isFederatedUser && tab !== 'deactivated' && { makeAdmin: changeAdminStatusAction }), + ...(resetE2EKeyAction && !isFederatedUser && tab !== 'deactivated' && { resetE2EKey: resetE2EKeyAction }), + ...(resetTOTPAction && !isFederatedUser && tab !== 'deactivated' && { resetTOTP: resetTOTPAction }), ...(changeUserStatusAction && !isFederatedUser && { changeActiveStatus: changeUserStatusAction }), ...(deleteUserAction && { delete: deleteUserAction }), }), [ - t, canDirectMessage, + t, directMessageClick, canEditOtherUserInfo, + isFederatedUser, editUserClick, changeAdminStatusAction, - changeUserStatusAction, - deleteUserAction, + tab, resetE2EKeyAction, resetTOTPAction, - isFederatedUser, + changeUserStatusAction, + deleteUserAction, ], ); diff --git a/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx b/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx index 693cccf0df050..0eb252f9f0f1f 100644 --- a/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx +++ b/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx @@ -19,9 +19,10 @@ import AdminUserInfoActions from './AdminUserInfoActions'; type AdminUserInfoWithDataProps = { uid: IUser['_id']; onReload: () => void; + tab: 'all' | 'invited' | 'active' | 'deactivated' | 'pending'; }; -const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): ReactElement => { +const AdminUserInfoWithData = ({ uid, onReload, tab }: AdminUserInfoWithDataProps): ReactElement => { const t = useTranslation(); const getRoles = useRolesDescription(); const approveManuallyUsers = useSetting('Accounts_ManuallyApproveNewUsers'); @@ -124,6 +125,7 @@ const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): R isFederatedUser={isUserFederated(data?.user as unknown as IUser)} 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 7f48d00d6d1ee..c93861911e9af 100644 --- a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx +++ b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx @@ -106,7 +106,7 @@ const UsersPage = (): ReactElement => { router.navigate('/admin/users')} /> - {context === 'info' && id && } + {context === 'info' && id && } {context === 'edit' && id && } {context === 'new' && } {context === 'created' && id && } diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx index c9df04e3bad2b..4b18c9289f776 100644 --- a/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx +++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx @@ -55,6 +55,7 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab const menuOptions = { ...(tab !== 'pending' && + tab !== 'deactivated' && changeAdminStatusAction && !isFederatedUser && { makeAdmin: { @@ -63,11 +64,13 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab }, }), ...(tab !== 'pending' && + tab !== 'deactivated' && resetE2EKeyAction && !isFederatedUser && { resetE2EKey: { label: { label: resetE2EKeyAction.label, icon: resetE2EKeyAction.icon }, action: resetE2EKeyAction.action }, }), ...(tab !== 'pending' && + tab !== 'deactivated' && resetTOTPAction && !isFederatedUser && { resetTOTP: { label: { label: resetTOTPAction.label, icon: resetTOTPAction.icon }, action: resetTOTPAction.action }, @@ -91,7 +94,7 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab dispatchToastMessage({ type: 'success', message: t('Welcome_email_resent') }); }; - const checkPendingButton = (): ReactElement => { + const renderPendingButton = (): ReactElement => { if (active) { return (