diff --git a/apps/meteor/client/components/UserCard/UserCardRoles.tsx b/apps/meteor/client/components/UserCard/UserCardRoles.tsx index 9cd22ebdff35..f7a977d4a4dc 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 8a42f1b87026..0e04a55b105e 100644 --- a/apps/meteor/client/components/UserInfo/UserInfo.tsx +++ b/apps/meteor/client/components/UserInfo/UserInfo.tsx @@ -54,7 +54,7 @@ const UserInfo = ({ email, verified, createdAt, - status, + // status, statusText, customFields, canViewAllInfo, @@ -78,7 +78,7 @@ const UserInfo = ({ {actions && {actions}} - {userDisplayName && } + {userDisplayName && } {statusText && ( @@ -94,17 +94,26 @@ const UserInfo = ({ )} - {roles.length !== 0 && ( + {username && username !== name && ( + + {t('Username')} + {username} + + )} + + {roles.length !== 0 && ( {t('Roles')} {roles} )} - {username && username !== name && ( + {bio && ( - {t('Username')} - {username} + {t('Bio')} + + + )} @@ -131,12 +140,12 @@ const UserInfo = ({ )} - {name && ( + {/* {name && ( {t('Full_Name')} {name} - )} + )} */} {phone && ( diff --git a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx index e0e88bedcd90..c8cb7f3dc931 100644 --- a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx +++ b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx @@ -13,7 +13,7 @@ import AdminUserFormWithData from './AdminUserFormWithData'; import AdminUserInfoWithData from './AdminUserInfoWithData'; import UsersTable from './UsersTable'; -const UsersPage = (): ReactElement => { +const UsersPage = async (): Promise => { const t = useTranslation(); const seatsCap = useSeatsCap(); const reload = useRef(() => null); @@ -25,7 +25,8 @@ const UsersPage = (): ReactElement => { const canCreateUser = usePermission('create-user'); const canBulkCreateUser = usePermission('bulk-register-user'); - const [tab, setTab] = useState<'all' | 'invited' | 'new' | 'active' | 'deactivated'>('all'); + const [tab, setTab] = useState<'all' | 'invited' | 'pending' | 'new' | 'active' | 'deactivated'>('all'); + const [pendingActionsCount, setPendingActionsCount] = useState(0); useEffect(() => { if (!context || !seatsCap) { @@ -51,7 +52,7 @@ const UsersPage = (): ReactElement => { ) : ( {canCreateUser && ( - )} @@ -68,6 +69,9 @@ const UsersPage = (): ReactElement => { setTab('all')}> {t('All')} + setTab('pending')}> + {pendingActionsCount === 0 ? t('Pending') : `${t('Pending')} (${pendingActionsCount})`} + setTab('invited')}> {t('Invited')} @@ -80,8 +84,11 @@ const UsersPage = (): ReactElement => { setTab('deactivated')}> {t('Deactivated')} + setTab('invited')}> + {t('Invited')} + - + {context && ( @@ -90,14 +97,14 @@ const UsersPage = (): ReactElement => { {context === 'info' && t('User_Info')} {context === 'edit' && t('Edit_User')} - {context === 'new' && t('Add_User')} + {context === 'pending' && t('Add_User')} {context === 'invite' && t('Invite_Users')} router.navigate('/admin/users')} /> {context === 'info' && id && } {context === 'edit' && id && } - {context === 'new' && } + {context === 'pending' && } {context === 'invite' && } )} diff --git a/apps/meteor/client/views/admin/users/UsersTable/ActionsMenu.tsx b/apps/meteor/client/views/admin/users/UsersTable/ActionsMenu.tsx new file mode 100644 index 000000000000..dded94b1bec0 --- /dev/null +++ b/apps/meteor/client/views/admin/users/UsersTable/ActionsMenu.tsx @@ -0,0 +1,97 @@ +import type { IUser } from '@rocket.chat/core-typings'; +import { isUserFederated } from '@rocket.chat/core-typings'; +import { Menu, Option } from '@rocket.chat/fuselage'; +import type { ReactElement } from 'react'; +import React, { useMemo } from 'react'; + +import type { Action } from '../../../hooks/useActionSpread'; +import { useChangeAdminStatusAction } from '../hooks/useChangeAdminStatusAction'; +import { useDeleteUserAction } from '../hooks/useDeleteUserAction'; +import { useResetE2EEKeyAction } from '../hooks/useResetE2EEKeyAction'; +import { useResetTOTPAction } from '../hooks/useResetTOTPAction'; + +type ActionsMenuProps = { + user: Pick; + tab: string; + changeUserStatusAction: Action | undefined; + onChange: () => void; + onReload: () => void; +}; + +const ActionsMenu = ({ user, tab, changeUserStatusAction, onChange, onReload }: ActionsMenuProps): ReactElement | null => { + const userId = user._id; + const isAdmin = user.roles?.includes('admin'); + const isFederatedUser = isUserFederated(user); + + const changeAdminStatusAction = useChangeAdminStatusAction(userId, isAdmin, onChange); + const deleteUserAction = useDeleteUserAction(userId, onChange, onReload); + const resetTOTPAction = useResetTOTPAction(userId); + const resetE2EKeyAction = useResetE2EEKeyAction(userId); + + const activeMenuOptions = useMemo( + () => + tab === 'active' + ? { + ...(changeAdminStatusAction && + !isFederatedUser && { + makeAdmin: { + label: { label: changeAdminStatusAction.label, icon: changeAdminStatusAction.icon }, + action: changeAdminStatusAction.action, + }, + }), + ...(resetE2EKeyAction && + !isFederatedUser && { + resetE2EKey: { label: { label: resetE2EKeyAction.label, icon: resetE2EKeyAction.icon }, action: resetE2EKeyAction.action }, + }), + ...(resetTOTPAction && + !isFederatedUser && { + resetTOTP: { label: { label: resetTOTPAction.label, icon: resetTOTPAction.icon }, action: resetTOTPAction.action }, + }), + } + : {}, + [changeAdminStatusAction, isFederatedUser, resetE2EKeyAction, resetTOTPAction, tab], + ); + + const menuOptions = useMemo( + () => ({ + ...activeMenuOptions, + ...(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 }, + }), + }), + [activeMenuOptions, changeUserStatusAction, deleteUserAction, isFederatedUser], + ); + + return useMemo(() => { + if (!menuOptions) { + return null; + } + + return ( + + label === 'Delete' ? ( +