[FIX] Reload roomslist after successful deletion of a room from admin panel. (#23795)

* fix- delete rooms from admin refresh roomlist and close contextual menu

* added success toast message

* fix-lint

* fix-lint: missing dependency

* Handle failing `eraseRoom` call

* Handle list changes on save/archive too

* Handle teams as a special case

Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat>
This commit is contained in:
Aman-Maheshwari 2022-03-09 05:34:14 +05:30 committed by GitHub
parent a14a305cc7
commit a25766dd7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 73 deletions

View File

@ -1,4 +1,4 @@
import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, Callout, TextAreaInput } from '@rocket.chat/fuselage';
import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, TextAreaInput } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { useState, useMemo } from 'react';
@ -8,11 +8,14 @@ import VerticalBar from '../../../components/VerticalBar';
import RoomAvatarEditor from '../../../components/avatar/RoomAvatarEditor';
import { usePermission } from '../../../contexts/AuthorizationContext';
import { useSetModal } from '../../../contexts/ModalContext';
import { useMethod } from '../../../contexts/ServerContext';
import { useRoute } from '../../../contexts/RouterContext';
import { useEndpoint, useMethod } from '../../../contexts/ServerContext';
import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionExperimental';
import { useForm } from '../../../hooks/useForm';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
import DeleteTeamModal from '../../teams/contextualBar/info/Delete/DeleteTeamModal';
const getInitialValues = (room) => ({
roomName: room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, { type: room.t, ...room }),
@ -28,13 +31,13 @@ const getInitialValues = (room) => ({
roomAvatar: undefined,
});
function EditRoom({ room, onChange }) {
function EditRoom({ room, onChange, onDelete }) {
const t = useTranslation();
const [deleted, setDeleted] = useState(false);
const [deleting, setDeleting] = useState(false);
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const { values, handlers, hasUnsavedChanges, reset } = useForm(getInitialValues(room));
const [canViewName, canViewTopic, canViewAnnouncement, canViewArchived, canViewDescription, canViewType, canViewReadOnly] =
@ -81,6 +84,8 @@ function EditRoom({ room, onChange }) {
const changeArchivation = archived !== !!room.archived;
const roomsRoute = useRoute('admin-rooms');
const canDelete = usePermission(`delete-${room.t}`);
const archiveSelector = room.archived ? 'unarchive' : 'archive';
@ -115,18 +120,57 @@ function EditRoom({ room, onChange }) {
handleRoomType(roomType === 'p' ? 'c' : 'p');
});
const deleteRoom = useMethod('eraseRoom');
const eraseRoom = useMethod('eraseRoom');
const deleteTeam = useEndpoint('POST', 'teams.delete');
const handleDelete = useMutableCallback(() => {
const onCancel = () => setModal(undefined);
const onConfirm = async () => {
await deleteRoom(room._id);
onCancel();
setDeleted(true);
};
if (room.teamMain) {
setModal(
<DeleteTeamModal
onConfirm={async (deletedRooms) => {
const roomsToRemove = Array.isArray(deletedRooms) && deletedRooms.length > 0 ? deletedRooms : [];
try {
setDeleting(true);
setModal(null);
await deleteTeam({ teamId: room.teamId, ...(roomsToRemove.length && { roomsToRemove }) });
dispatchToastMessage({ type: 'success', message: t('Team_has_been_deleted') });
roomsRoute.push({});
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
setDeleting(false);
} finally {
onDelete();
}
}}
onCancel={() => setModal(null)}
teamId={room.teamId}
/>,
);
return;
}
setModal(
<GenericModal variant='danger' onConfirm={onConfirm} onCancel={onCancel} confirmText={t('Yes_delete_it')}>
<GenericModal
variant='danger'
onConfirm={async () => {
try {
setDeleting(true);
setModal(null);
await eraseRoom(room._id);
dispatchToastMessage({ type: 'success', message: t('Room_has_been_deleted') });
roomsRoute.push({});
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
setDeleting(false);
} finally {
onDelete();
}
}}
onCancel={() => setModal(null)}
confirmText={t('Yes_delete_it')}
>
{t('Delete_Room_Warning')}
</GenericModal>,
);
@ -134,7 +178,6 @@ function EditRoom({ room, onChange }) {
return (
<VerticalBar.ScrollableContent is='form' onSubmit={useMutableCallback((e) => e.preventDefault())}>
{deleted && <Callout type='danger' title={t('Room_has_been_deleted')}></Callout>}
{room.t !== 'd' && (
<Box pbe='x24' display='flex' justifyContent='center'>
<RoomAvatarEditor roomAvatar={roomAvatar} room={room} onChangeAvatar={handleRoomAvatar} />
@ -143,7 +186,7 @@ function EditRoom({ room, onChange }) {
<Field>
<Field.Label>{t('Name')}</Field.Label>
<Field.Row>
<TextInput disabled={deleted || !canViewName} value={roomName} onChange={handleRoomName} flexGrow={1} />
<TextInput disabled={deleting || !canViewName} value={roomName} onChange={handleRoomName} flexGrow={1} />
</Field.Row>
</Field>
{room.t !== 'd' && (
@ -158,7 +201,7 @@ function EditRoom({ room, onChange }) {
<Field>
<Field.Label>{t('Description')}</Field.Label>
<Field.Row>
<TextAreaInput rows={4} disabled={deleted} value={roomDescription} onChange={handleRoomDescription} flexGrow={1} />
<TextAreaInput rows={4} disabled={deleting} value={roomDescription} onChange={handleRoomDescription} flexGrow={1} />
</Field.Row>
</Field>
)}
@ -166,7 +209,7 @@ function EditRoom({ room, onChange }) {
<Field>
<Field.Label>{t('Announcement')}</Field.Label>
<Field.Row>
<TextAreaInput rows={4} disabled={deleted} value={roomAnnouncement} onChange={handleRoomAnnouncement} flexGrow={1} />
<TextAreaInput rows={4} disabled={deleting} value={roomAnnouncement} onChange={handleRoomAnnouncement} flexGrow={1} />
</Field.Row>
</Field>
)}
@ -174,7 +217,7 @@ function EditRoom({ room, onChange }) {
<Field>
<Field.Label>{t('Topic')}</Field.Label>
<Field.Row>
<TextAreaInput rows={4} disabled={deleted} value={roomTopic} onChange={handleRoomTopic} flexGrow={1} />
<TextAreaInput rows={4} disabled={deleting} value={roomTopic} onChange={handleRoomTopic} flexGrow={1} />
</Field.Row>
</Field>
)}
@ -182,7 +225,7 @@ function EditRoom({ room, onChange }) {
<Field>
<Field.Row>
<Field.Label>{t('Private')}</Field.Label>
<ToggleSwitch disabled={deleted} checked={roomType === 'p'} onChange={changeRoomType} />
<ToggleSwitch disabled={deleting} checked={roomType === 'p'} onChange={changeRoomType} />
</Field.Row>
<Field.Hint>{t('Just_invited_people_can_access_this_channel')}</Field.Hint>
</Field>
@ -192,7 +235,7 @@ function EditRoom({ room, onChange }) {
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
<Field.Label>{t('Read_only')}</Field.Label>
<ToggleSwitch disabled={deleted} checked={readOnly} onChange={handleReadOnly} />
<ToggleSwitch disabled={deleting} checked={readOnly} onChange={handleReadOnly} />
</Box>
</Field.Row>
<Field.Hint>{t('Only_authorized_users_can_write_new_messages')}</Field.Hint>
@ -203,7 +246,7 @@ function EditRoom({ room, onChange }) {
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
<Field.Label>{t('Room_archivation_state_true')}</Field.Label>
<ToggleSwitch disabled={deleted} checked={archived} onChange={handleArchived} />
<ToggleSwitch disabled={deleting} checked={archived} onChange={handleArchived} />
</Box>
</Field.Row>
</Field>
@ -214,7 +257,7 @@ function EditRoom({ room, onChange }) {
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
<Field.Label>{t('Default')}</Field.Label>
<ToggleSwitch disabled={deleted} checked={isDefault} onChange={handleIsDefault} />
<ToggleSwitch disabled={deleting} checked={isDefault} onChange={handleIsDefault} />
</Box>
</Field.Row>
</Field>
@ -222,7 +265,7 @@ function EditRoom({ room, onChange }) {
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
<Field.Label>{t('Favorite')}</Field.Label>
<ToggleSwitch disabled={deleted} checked={favorite} onChange={handleFavorite} />
<ToggleSwitch disabled={deleting} checked={favorite} onChange={handleFavorite} />
</Box>
</Field.Row>
</Field>
@ -230,7 +273,7 @@ function EditRoom({ room, onChange }) {
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
<Field.Label>{t('Featured')}</Field.Label>
<ToggleSwitch disabled={deleted} checked={featured} onChange={handleFeatured} />
<ToggleSwitch disabled={deleting} checked={featured} onChange={handleFeatured} />
</Box>
</Field.Row>
</Field>
@ -238,10 +281,10 @@ function EditRoom({ room, onChange }) {
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' w='full'>
<ButtonGroup stretch flexGrow={1}>
<Button type='reset' disabled={!hasUnsavedChanges || deleted} onClick={reset}>
<Button type='reset' disabled={!hasUnsavedChanges || deleting} onClick={reset}>
{t('Reset')}
</Button>
<Button flexGrow={1} disabled={!hasUnsavedChanges || deleted} onClick={handleSave}>
<Button flexGrow={1} disabled={!hasUnsavedChanges || deleting} onClick={handleSave}>
{t('Save')}
</Button>
</ButtonGroup>
@ -250,7 +293,7 @@ function EditRoom({ room, onChange }) {
</Field>
<Field>
<Field.Row>
<Button primary flexGrow={1} danger disabled={deleted || !canDelete} onClick={handleDelete}>
<Button primary flexGrow={1} danger disabled={deleting || !canDelete} onClick={handleDelete}>
<Icon name='trash' size='x16' />
{t('Delete')}
</Button>

View File

@ -4,9 +4,9 @@ import NotAuthorizedPage from '../../../components/NotAuthorizedPage';
import { usePermission } from '../../../contexts/AuthorizationContext';
import EditRoomWithData from './EditRoomWithData';
function EditRoomContextBar({ rid }) {
function EditRoomContextBar({ rid, onReload }) {
const canViewRoomAdministration = usePermission('view-room-administration');
return canViewRoomAdministration ? <EditRoomWithData rid={rid} /> : <NotAuthorizedPage />;
return canViewRoomAdministration ? <EditRoomWithData rid={rid} onReload={onReload} /> : <NotAuthorizedPage />;
}
export default EditRoomContextBar;

View File

@ -5,7 +5,7 @@ import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import EditRoom from './EditRoom';
function EditRoomWithData({ rid }) {
function EditRoomWithData({ rid, onReload }) {
const {
value: data = {},
phase: state,
@ -33,7 +33,16 @@ function EditRoomWithData({ rid }) {
return error.message;
}
return <EditRoom room={{ type: data.t, ...data }} onChange={reload} />;
const handleChange = () => {
reload();
onReload();
};
const handleDelete = () => {
onReload();
};
return <EditRoom room={{ type: data.t, ...data }} onChange={handleChange} onDelete={handleDelete} />;
}
export default EditRoomWithData;

View File

@ -1,12 +1,28 @@
import React from 'react';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { useState, useMemo } from 'react';
import Page from '../../../components/Page';
import VerticalBar from '../../../components/VerticalBar';
import { useRouteParameter, useRoute } from '../../../contexts/RouterContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpointData } from '../../../hooks/useEndpointData';
import EditRoomContextBar from './EditRoomContextBar';
import RoomsTable from './RoomsTable';
export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams'];
const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) =>
useMemo(
() => ({
filter: text || '',
types,
sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
...(itemsPerPage && { count: itemsPerPage }),
...(current && { offset: current }),
}),
[text, types, itemsPerPage, current, column, direction],
);
export function RoomsPage() {
const t = useTranslation();
@ -19,12 +35,27 @@ export function RoomsPage() {
roomsRoute.push({});
};
const [params, setParams] = useState({
text: '',
types: DEFAULT_TYPES,
current: 0,
itemsPerPage: 25,
});
const [sort, setSort] = useState(['name', 'asc']);
const debouncedParams = useDebouncedValue(params, 500);
const debouncedSort = useDebouncedValue(sort, 500);
const query = useQuery(debouncedParams, debouncedSort);
const endpointData = useEndpointData('rooms.adminRooms', query);
return (
<Page flexDirection='row'>
<Page>
<Page.Header title={t('Rooms')} />
<Page.Content>
<RoomsTable />
<RoomsTable endpointData={endpointData} params={params} onChangeParams={setParams} sort={sort} onChangeSort={setSort} />
</Page.Content>
</Page>
{context && (
@ -34,7 +65,7 @@ export function RoomsPage() {
<VerticalBar.Close onClick={handleVerticalBarCloseButtonClick} />
</VerticalBar.Header>
<EditRoomContextBar rid={id} />
<EditRoomContextBar rid={id} onReload={endpointData.reload} />
</VerticalBar>
)}
</Page>

View File

@ -1,20 +1,17 @@
import { Box, Table, Icon } from '@rocket.chat/fuselage';
import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useCallback, useState } from 'react';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useCallback } from 'react';
import GenericTable from '../../../components/GenericTable';
import RoomAvatar from '../../../components/avatar/RoomAvatar';
import { useRoute } from '../../../contexts/RouterContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpointData } from '../../../hooks/useEndpointData';
import { AsyncStatePhase } from '../../../lib/asyncState';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
import FilterByTypeAndText from './FilterByTypeAndText';
const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };
export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams'];
export const roomTypeI18nMap = {
l: 'Omnichannel',
c: 'Channel',
@ -30,18 +27,6 @@ const getRoomType = (room) => {
return roomTypeI18nMap[room.t];
};
const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) =>
useMemo(
() => ({
filter: text || '',
types,
sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
...(itemsPerPage && { count: itemsPerPage }),
...(current && { offset: current }),
}),
[text, types, itemsPerPage, current, column, direction],
);
const getRoomDisplayName = (room) => (room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, room));
const useDisplayData = (asyncState, sort) =>
@ -66,29 +51,14 @@ const useDisplayData = (asyncState, sort) =>
return value.rooms;
}, [asyncState, sort]);
function RoomsTable() {
function RoomsTable({ endpointData, params, onChangeParams, sort, onChangeSort }) {
const t = useTranslation();
const mediaQuery = useMediaQuery('(min-width: 1024px)');
const [params, setParams] = useState({
text: '',
types: DEFAULT_TYPES,
current: 0,
itemsPerPage: 25,
});
const [sort, setSort] = useState(['name', 'asc']);
const routeName = 'admin-rooms';
const debouncedParams = useDebouncedValue(params, 500);
const debouncedSort = useDebouncedValue(sort, 500);
const query = useQuery(debouncedParams, debouncedSort);
const asyncState = useEndpointData('rooms.adminRooms', query);
const { value: data = {} } = asyncState;
const { value: data = {} } = endpointData;
const router = useRoute(routeName);
@ -106,15 +76,15 @@ function RoomsTable() {
const [sortBy, sortDirection] = sort;
if (sortBy === id) {
setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']);
onChangeSort([id, sortDirection === 'asc' ? 'desc' : 'asc']);
return;
}
setSort([id, 'asc']);
onChangeSort([id, 'asc']);
},
[sort],
[sort, onChangeSort],
);
const displayData = useDisplayData(asyncState, sort);
const displayData = useDisplayData(endpointData, sort);
const header = useMemo(
() =>
@ -218,7 +188,7 @@ function RoomsTable() {
renderRow={renderRow}
results={displayData}
total={data.total}
setParams={setParams}
setParams={onChangeParams}
params={params}
renderFilter={({ onChange, ...props }) => <FilterByTypeAndText setFilter={onChange} {...props} />}
/>