diff --git a/client/admin/users/UserInfo.js b/client/admin/users/UserInfo.js
index 65390cb252c2..80e58fc240e0 100644
--- a/client/admin/users/UserInfo.js
+++ b/client/admin/users/UserInfo.js
@@ -1,14 +1,14 @@
import React, { useMemo, useState, useEffect } from 'react';
-import { Box, Avatar, Button, ButtonGroup, Icon, Margins, Headline, Skeleton, Chip, Tag } from '@rocket.chat/fuselage';
+import { Box, Avatar, Margins, Headline, Skeleton, Chip, Tag } from '@rocket.chat/fuselage';
import moment from 'moment';
import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental';
-import MarkdownText from '../../components/basic/MarkdownText';
import { useTranslation } from '../../contexts/TranslationContext';
import { roomTypes } from '../../../app/utils/client';
import { DateFormat } from '../../../app/lib';
-import { useRoute } from '../../contexts/RouterContext';
+import { UserInfoActions } from './UserInfoActions';
import Page from '../../components/basic/Page';
+import MarkdownText from '../../components/basic/MarkdownText';
const useTimezoneClock = (utcOffset = 0, updateInterval) => {
const [time, setTime] = useState();
@@ -30,8 +30,11 @@ const UTCClock = ({ utcOffset, ...props }) => {
export function UserInfoWithData({ userId, ...props }) {
const t = useTranslation();
+ const [cache, setCache] = useState();
+
+ const onChange = () => setCache(new Date());
- const { data, state, error } = useEndpointDataExperimental('users.info', useMemo(() => ({ userId }), [userId]));
+ const { data, state, error } = useEndpointDataExperimental('users.info', useMemo(() => ({ userId }), [userId, cache]));
if (state === ENDPOINT_STATES.LOADING) {
return
@@ -48,24 +51,13 @@ export function UserInfoWithData({ userId, ...props }) {
return {t('User_not_found')};
}
- return ;
+ return ;
}
-export function UserInfo({ data, ...props }) {
+export function UserInfo({ data, onChange, ...props }) {
const t = useTranslation();
- const directRoute = useRoute('direct');
- const userRoute = useRoute('admin-users');
-
- const directMessageClick = () => directRoute.push({
- rid: data.username,
- });
- const editUserClick = () => userRoute.push({
- context: 'edit',
- id: data._id,
- });
-
const createdAt = DateFormat.formatDateAndTime(data.createdAt);
const lastLogin = data.lastLogin ? DateFormat.formatDateAndTime(data.lastLogin) : '';
@@ -83,21 +75,12 @@ export function UserInfo({ data, ...props }) {
-
-
-
-
-
-
-
+
+ {console.log(MarkdownText)}
-
- {data.bio && data.bio.trim().length > 0 &&
- {data.bio}
- }
-
- {data.roles && <>
+ {data.bio && data.bio.trim().length > 0 && {data.bio}}
+ {!!data.roles.length && <>
{t('Roles')}
diff --git a/client/admin/users/UserInfoActions.js b/client/admin/users/UserInfoActions.js
new file mode 100644
index 000000000000..7dd3551035f5
--- /dev/null
+++ b/client/admin/users/UserInfoActions.js
@@ -0,0 +1,157 @@
+import React, { useCallback, useMemo, useState } from 'react';
+import { Box, Button, ButtonGroup, Icon, Menu } from '@rocket.chat/fuselage';
+
+import { Modal } from '../../components/basic/Modal';
+import { useTranslation } from '../../contexts/TranslationContext';
+import { useRoute } from '../../contexts/RouterContext';
+import { usePermission } from '../../contexts/AuthorizationContext';
+import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
+import { useMethod } from '../../contexts/ServerContext';
+import { useSetting } from '../../contexts/SettingsContext';
+import { useEndpointAction } from '../../hooks/useEndpointAction';
+
+
+const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => {
+ const t = useTranslation();
+ const erasureType = useSetting('Message_ErasureType');
+
+ return
+
+
+ {t('Are_you_sure')}
+
+
+
+ {t(`Delete_User_Warning_${ erasureType }`)}
+
+
+
+
+
+
+
+ ;
+};
+
+const SuccessModal = ({ onClose, ...props }) => {
+ const t = useTranslation();
+ return
+
+
+ {t('Deleted')}
+
+
+
+ {t('User_has_been_deleted')}
+
+
+
+
+
+
+ ;
+};
+
+
+export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange, ...props }) => {
+ const t = useTranslation();
+ const [modal, setModal] = useState();
+
+ const directRoute = useRoute('direct');
+ const userRoute = useRoute('admin-users');
+ const dispatchToastMessage = useToastMessageDispatch();
+
+ const canDirectMessage = usePermission('create-d');
+ const canEditOtherUserInfo = usePermission('edit-other-user-info');
+ const canAssignAdminRole = usePermission('assign-admin-role');
+ const canEditOtherUserActiveStatus = usePermission('edit-other-user-active-status');
+ const canDeleteUser = usePermission('delete-user');
+
+ const deleteUserQuery = useMemo(() => ({ userId: _id }), [_id]);
+ const deleteUser = useEndpointAction('POST', 'users.delete', deleteUserQuery);
+
+ const willDeleteUser = useCallback(async () => {
+ const result = await deleteUser();
+ if (result.success) {
+ setModal( { setModal(); onChange(); }}/>);
+ } else {
+ setModal();
+ }
+ }, [deleteUser]);
+ const confirmDeleteUser = useCallback(() => {
+ setModal( setModal()}/>);
+ }, [deleteUser]);
+
+ const setAdminStatus = useMethod('setAdminStatus');
+ const changeAdminStatus = useCallback(() => {
+ try {
+ setAdminStatus(_id, !isAdmin);
+ const message = isAdmin ? 'User_is_no_longer_an_admin' : 'User_is_now_an_admin';
+ dispatchToastMessage({ type: 'success', message: t(message) });
+ onChange();
+ } catch (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ }
+ }, [isAdmin]);
+
+ const activeStatusQuery = useMemo(() => ({
+ userId: _id,
+ activeStatus: !isActive,
+ }), [_id, isActive]);
+ const changeActiveStatusMessage = isActive ? 'User_has_been_deactivated' : 'User_has_been_activated';
+ const changeActiveStatus = useEndpointAction('POST', 'users.setActiveStatus', activeStatusQuery, t(changeActiveStatusMessage));
+
+ const directMessageClick = () => directRoute.push({
+ rid: username,
+ });
+
+ const editUserClick = () => userRoute.push({
+ context: 'edit',
+ id: _id,
+ });
+
+ const menuOptions = useMemo(() => ({
+ ...canDirectMessage && { directMessage: {
+ label: <>{t('Direct_Message')}>,
+ action: directMessageClick,
+ } },
+ ...canEditOtherUserInfo && { editUser: {
+ label: <>{t('Edit')}>,
+ action: editUserClick,
+ } },
+ ...canAssignAdminRole && { makeAdmin: {
+ label: <>{ isAdmin ? t('Remove_Admin') : t('Make_Admin')}>,
+ action: changeAdminStatus,
+ } },
+ ...canDeleteUser && { delete: {
+ label: {t('Delete')},
+ action: confirmDeleteUser,
+ } },
+ ...canEditOtherUserActiveStatus && { changeActiveStatus: {
+ label: <>{ isActive ? t('Deactivate') : t('Activate')}>,
+ action: async () => {
+ const result = await changeActiveStatus();
+ result.success ? onChange() : undefined;
+ },
+ } },
+ }), [canAssignAdminRole, canDeleteUser, canEditOtherUserActiveStatus, canEditOtherUserInfo, canDirectMessage, isActive, isAdmin]);
+
+ const [actions, moreActions] = useMemo(() => {
+ const keys = Object.keys(menuOptions);
+
+ const firstHalf = keys.slice(0, 2);
+ const secondHalf = keys.slice(2, keys.length);
+
+ return [firstHalf.length && firstHalf.map((key) => menuOptions[key]), secondHalf.length && Object.fromEntries(secondHalf.map((key) => [key, menuOptions[key]]))];
+ }, menuOptions);
+
+ return <>
+
+
+ { actions && actions.map((action, index) => ())}
+ { moreActions && }
+
+
+ { modal }
+ >;
+};
diff --git a/client/hooks/useEndpointAction.js b/client/hooks/useEndpointAction.js
index 0f37ed89b340..60bbffae830d 100644
--- a/client/hooks/useEndpointAction.js
+++ b/client/hooks/useEndpointAction.js
@@ -17,7 +17,7 @@ export const useEndpointAction = (httpMethod, endpoint, params = {}, successMess
throw new Error(data.status);
}
- dispatchToastMessage({ type: 'success', message: successMessage });
+ successMessage && dispatchToastMessage({ type: 'success', message: successMessage });
return data;
} catch (error) {
diff --git a/client/polyfills/index.js b/client/polyfills/index.js
index a9086d631677..31cada9ea9ff 100644
--- a/client/polyfills/index.js
+++ b/client/polyfills/index.js
@@ -1,3 +1,7 @@
import '@rocket.chat/fuselage-polyfills';
import 'url-polyfill';
import './customEventPolyfill';
+
+Object.fromEntries = Object.fromEntries || function fromEntries(iterable) {
+ return [...iterable].reduce((obj, { 0: key, 1: val }) => Object.assign(obj, { [key]: val }), {});
+};
diff --git a/package-lock.json b/package-lock.json
index 0b8f4e29284a..b2312f4a2519 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2799,9 +2799,9 @@
}
},
"@rocket.chat/fuselage": {
- "version": "0.6.3-dev.29",
- "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.29.tgz",
- "integrity": "sha512-XJiw1fpf7bZvvHeu8Ippe4mYuJScmNd52XeMXdHxX/hqXH9RrhTmimr5r86HatfU7dPqcgrVr1aHcZNiyMBz/g==",
+ "version": "0.6.3-dev.31",
+ "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.31.tgz",
+ "integrity": "sha512-ZcfuHluR9NzpvnORV3D/0iIRKLEi8HQfsEypsh4cseJa1sG6F3VoJJ8uO4033EwYYcS1X4SxKYaQURk75/UtOw==",
"requires": {
"@rocket.chat/css-in-js": "^0.8.0",
"@rocket.chat/fuselage-tokens": "^0.8.0",
diff --git a/package.json b/package.json
index ef126486a410..31b0578851bc 100644
--- a/package.json
+++ b/package.json
@@ -131,7 +131,7 @@
"@nivo/line": "^0.61.1",
"@nivo/pie": "^0.61.1",
"@rocket.chat/apps-engine": "^1.14.0-beta.3119",
- "@rocket.chat/fuselage": "^0.6.3-dev.29",
+ "@rocket.chat/fuselage": "^0.6.3-dev.31",
"@rocket.chat/fuselage-hooks": "^0.6.3-dev.23",
"@rocket.chat/fuselage-polyfills": "^0.6.3-dev.23",
"@rocket.chat/fuselage-ui-kit": "^0.6.3-dev.29",