Skip to content

Commit

Permalink
Chore: Convert Admin -> Rooms to TS (#25348)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
  • Loading branch information
yash-rajpal and ggazzo committed May 16, 2022
1 parent 394b4aa commit bdeb54a
Show file tree
Hide file tree
Showing 19 changed files with 306 additions and 165 deletions.
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

0 comments on commit bdeb54a

Please sign in to comment.