Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Convert Admin -> Rooms to TS #25348

Merged
merged 13 commits into from
May 16, 2022
4 changes: 2 additions & 2 deletions apps/meteor/client/components/avatar/RoomAvatarEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IRoom } from '@rocket.chat/core-typings';
import { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
import { Box, Button, ButtonGroup, Icon } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
Expand All @@ -10,7 +10,7 @@ import { useFileInput } from '../../hooks/useFileInput';
import RoomAvatar from './RoomAvatar';

type RoomAvatarEditorProps = {
room: IRoom;
room: Pick<IRoom, RoomAdminFieldsType>;
roomAvatar?: string;
onChangeAvatar: (url: string | null) => void;
};
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/lib/rooms/roomCoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class RoomCoordinatorClient extends RoomCoordinator {
getAvatarPath(_room): string {
return '';
},
getIcon(_room: Partial<IRoom>): string | undefined {
getIcon(_room: Partial<IRoom>): IRoomTypeConfig['icon'] {
return this.config.icon;
},
getUserStatus(_roomId: string): string | undefined {
Expand Down Expand Up @@ -92,7 +92,7 @@ class RoomCoordinatorClient extends RoomCoordinator {
openRoom(type, name, render);
}

getIcon(room: Partial<IRoom>): string | undefined {
getIcon(room: Partial<IRoom>): IRoomTypeConfig['icon'] {
return room?.t && this.getRoomDirectives(room.t)?.getIcon(room);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings';
import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, TextAreaInput } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import {
Expand All @@ -9,7 +10,7 @@ import {
useMethod,
useTranslation,
} from '@rocket.chat/ui-contexts';
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, ReactElement } from 'react';

import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig';
import GenericModal from '../../../components/GenericModal';
Expand All @@ -18,10 +19,30 @@ import RoomAvatarEditor from '../../../components/avatar/RoomAvatarEditor';
import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionExperimental';
import { useForm } from '../../../hooks/useForm';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
import DeleteTeamModal from '../../teams/contextualBar/info/Delete/DeleteTeamModal';
import DeleteTeamModalWithRooms from '../../teams/contextualBar/info/Delete';

const getInitialValues = (room) => ({
roomName: room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, { type: room.t, ...room }),
type EditRoomProps = {
room: Pick<IRoom, RoomAdminFieldsType>;
onChange: () => void;
onDelete: () => void;
};

type EditRoomFormValues = {
roomName: IRoom['name'];
roomTopic: string;
roomType: IRoom['t'];
readOnly: boolean;
isDefault: boolean;
favorite: boolean;
featured: boolean;
roomDescription: string;
roomAnnouncement: string;
roomAvatar: IRoom['avatarETag'];
archived: boolean;
};

const getInitialValues = (room: Pick<IRoom, RoomAdminFieldsType>): EditRoomFormValues => ({
roomName: room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room),
roomType: room.t,
readOnly: !!room.ro,
archived: !!room.archived,
Expand All @@ -34,26 +55,27 @@ const getInitialValues = (room) => ({
roomAvatar: undefined,
});

function EditRoom({ room, onChange, onDelete }) {
const EditRoom = ({ room, onChange, onDelete }: EditRoomProps): ReactElement => {
const t = useTranslation();

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] =
useMemo(() => {
const isAllowed = roomCoordinator.getRoomDirectives(room.t)?.allowRoomSettingChange;
return [
isAllowed(room, RoomSettingsEnum.NAME),
isAllowed(room, RoomSettingsEnum.TOPIC),
isAllowed(room, RoomSettingsEnum.ANNOUNCEMENT),
isAllowed(room, RoomSettingsEnum.ARCHIVE_OR_UNARCHIVE),
isAllowed(room, RoomSettingsEnum.DESCRIPTION),
isAllowed(room, RoomSettingsEnum.TYPE),
isAllowed(room, RoomSettingsEnum.READ_ONLY),
isAllowed?.(room, RoomSettingsEnum.NAME),
isAllowed?.(room, RoomSettingsEnum.TOPIC),
isAllowed?.(room, RoomSettingsEnum.ANNOUNCEMENT),
isAllowed?.(room, RoomSettingsEnum.ARCHIVE_OR_UNARCHIVE),
isAllowed?.(room, RoomSettingsEnum.DESCRIPTION),
isAllowed?.(room, RoomSettingsEnum.TYPE),
isAllowed?.(room, RoomSettingsEnum.READ_ONLY),
];
}, [room]);

Expand All @@ -69,7 +91,7 @@ function EditRoom({ room, onChange, onDelete }) {
roomAvatar,
roomDescription,
roomAnnouncement,
} = values;
} = values as EditRoomFormValues;

const {
handleIsDefault,
Expand Down Expand Up @@ -98,7 +120,7 @@ function EditRoom({ room, onChange, onDelete }) {
const archiveAction = useEndpointActionExperimental('POST', 'rooms.changeArchivationState', t(archiveMessage));

const handleSave = useMutableCallback(async () => {
const save = () =>
const save = (): Promise<{ success: boolean; rid: string }> =>
saveAction({
rid: room._id,
roomName: roomType === 'd' ? undefined : roomName,
Expand All @@ -113,9 +135,12 @@ function EditRoom({ room, onChange, onDelete }) {
roomAvatar,
});

const archive = () => archiveAction({ rid: room._id, action: archiveSelector });
const archive = (): Promise<{ success: boolean }> => archiveAction({ rid: room._id, action: archiveSelector });

await Promise.all([hasUnsavedChanges && save(), changeArchivation && archive()].filter(Boolean));
const promises = [];
hasUnsavedChanges && promises.push(save());
changeArchivation && promises.push(archive());
await Promise.all(promises);
onChange();
});

Expand All @@ -129,25 +154,25 @@ function EditRoom({ room, onChange, onDelete }) {
const handleDelete = useMutableCallback(() => {
if (room.teamMain) {
setModal(
<DeleteTeamModal
onConfirm={async (deletedRooms) => {
const roomsToRemove = Array.isArray(deletedRooms) && deletedRooms.length > 0 ? deletedRooms : [];
<DeleteTeamModalWithRooms
onConfirm={async (deletedRooms: IRoom[]): Promise<void> => {
const roomsToRemove = Array.isArray(deletedRooms) && deletedRooms.length > 0 ? deletedRooms.map((room) => room._id) : [];

try {
setDeleting(true);
setModal(null);
await deleteTeam({ teamId: room.teamId, ...(roomsToRemove.length && { roomsToRemove }) });
await deleteTeam({ teamId: room.teamId as string, ...(roomsToRemove.length && { roomsToRemove }) });
dispatchToastMessage({ type: 'success', message: t('Team_has_been_deleted') });
roomsRoute.push({});
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
dispatchToastMessage({ type: 'error', message: String(error) });
setDeleting(false);
} finally {
onDelete();
}
}}
onCancel={() => setModal(null)}
teamId={room.teamId}
onCancel={(): void => setModal(null)}
teamId={room.teamId as string}
/>,
);

Expand All @@ -157,21 +182,22 @@ function EditRoom({ room, onChange, onDelete }) {
setModal(
<GenericModal
variant='danger'
onConfirm={async () => {
onConfirm={async (): Promise<void> => {
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 });
dispatchToastMessage({ type: 'error', message: String(error) });
setDeleting(false);
} finally {
onDelete();
}
}}
onCancel={() => setModal(null)}
onClose={(): void => setModal(null)}
onCancel={(): void => setModal(null)}
confirmText={t('Yes_delete_it')}
>
{t('Delete_Room_Warning')}
Expand Down Expand Up @@ -304,6 +330,6 @@ function EditRoom({ room, onChange, onDelete }) {
</Field>
</VerticalBar.ScrollableContent>
);
}
};

export default EditRoom;
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { usePermission } from '@rocket.chat/ui-contexts';
import React from 'react';
import React, { ReactElement } from 'react';

import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage';
import EditRoomWithData from './EditRoomWithData';

function EditRoomContextBar({ rid, onReload }) {
const EditRoomContextBar = ({ rid, onReload }: { rid: string | undefined; onReload: () => void }): ReactElement => {
const canViewRoomAdministration = usePermission('view-room-administration');
return canViewRoomAdministration ? <EditRoomWithData rid={rid} onReload={onReload} /> : <NotAuthorizedPage />;
}
};

export default EditRoomContextBar;
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Box, Skeleton } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';
import React, { useMemo, FC } from 'react';

import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import EditRoom from './EditRoom';

function EditRoomWithData({ rid, onReload }) {
const EditRoomWithData: FC<{ rid?: string; onReload: () => void }> = ({ rid, onReload }) => {
const {
value: data = {},
value: data,
phase: state,
error,
reload,
Expand All @@ -30,19 +30,19 @@ function EditRoomWithData({ rid, onReload }) {
}

if (state === AsyncStatePhase.REJECTED) {
return error.message;
return <>{error?.message}</>;
}

const handleChange = () => {
const handleChange = (): void => {
reload();
onReload();
};

const handleDelete = () => {
const handleDelete = (): void => {
onReload();
};

return <EditRoom room={{ type: data.t, ...data }} onChange={handleChange} onDelete={handleDelete} />;
}
return data ? <EditRoom room={data} onChange={handleChange} onDelete={handleDelete} /> : null;
};

export default EditRoomWithData;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, Icon, TextInput, Field, CheckBox, Margins } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useCallback, useState, useEffect } from 'react';
import React, { useCallback, useState, useEffect, ReactElement, Dispatch, SetStateAction } from 'react';

export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams'];

Expand All @@ -14,7 +14,7 @@ export const roomTypeI18nMap = {
team: 'Team',
};

const FilterByTypeAndText = ({ setFilter, ...props }) => {
const FilterByTypeAndText = ({ setFilter, ...props }: { setFilter?: Dispatch<SetStateAction<any>> }): ReactElement => {
const [text, setText] = useState('');
const [types, setTypes] = useState({
d: false,
Expand All @@ -28,16 +28,16 @@ const FilterByTypeAndText = ({ setFilter, ...props }) => {
const t = useTranslation();

const handleChange = useCallback((event) => setText(event.currentTarget.value), []);
const handleCheckBox = useCallback((type) => setTypes({ ...types, [type]: !types[type] }), [types]);
const handleCheckBox = useCallback((type: keyof typeof types) => setTypes({ ...types, [type]: !types[type] }), [types]);

useEffect(() => {
if (Object.values(types).filter(Boolean).length === 0) {
return setFilter({ text, types: DEFAULT_TYPES });
return setFilter?.({ text, types: DEFAULT_TYPES });
}
const _types = Object.entries(types)
.filter(([, value]) => Boolean(value))
.map(([key]) => key);
setFilter({ text, types: _types });
setFilter?.({ text, types: _types });
}, [setFilter, text, types]);

const idDirect = useUniqueId();
Expand All @@ -60,27 +60,27 @@ const FilterByTypeAndText = ({ setFilter, ...props }) => {
<Box display='flex' flexDirection='row' flexWrap='wrap' justifyContent='flex-start' mbs='x8' mi='neg-x8'>
<Margins inline='x8'>
<Field.Row>
<CheckBox checked={types.d} id={idDirect} onChange={() => handleCheckBox('d')} />
<CheckBox checked={types.d} id={idDirect} onChange={(): void => handleCheckBox('d')} />
<Field.Label htmlFor={idDirect}>{t('Direct')}</Field.Label>
</Field.Row>
<Field.Row>
<CheckBox checked={types.c} id={idDPublic} onChange={() => handleCheckBox('c')} />
<CheckBox checked={types.c} id={idDPublic} onChange={(): void => handleCheckBox('c')} />
<Field.Label htmlFor={idDPublic}>{t('Public')}</Field.Label>
</Field.Row>
<Field.Row>
<CheckBox checked={types.p} id={idPrivate} onChange={() => handleCheckBox('p')} />
<CheckBox checked={types.p} id={idPrivate} onChange={(): void => handleCheckBox('p')} />
<Field.Label htmlFor={idPrivate}>{t('Private')}</Field.Label>
</Field.Row>
<Field.Row>
<CheckBox checked={types.l} id={idOmnichannel} onChange={() => handleCheckBox('l')} />
<CheckBox checked={types.l} id={idOmnichannel} onChange={(): void => handleCheckBox('l')} />
<Field.Label htmlFor={idOmnichannel}>{t('Omnichannel')}</Field.Label>
</Field.Row>
<Field.Row>
<CheckBox checked={types.discussions} id={idDiscussions} onChange={() => handleCheckBox('discussions')} />
<CheckBox checked={types.discussions} id={idDiscussions} onChange={(): void => handleCheckBox('discussions')} />
<Field.Label htmlFor={idDiscussions}>{t('Discussions')}</Field.Label>
</Field.Row>
<Field.Row>
<CheckBox checked={types.teams} id={idTeam} onChange={() => handleCheckBox('teams')} />
<CheckBox checked={types.teams} id={idTeam} onChange={(): void => handleCheckBox('teams')} />
<Field.Label htmlFor={idTeam}>{t('Teams')}</Field.Label>
</Field.Row>
</Margins>
Expand Down
Loading