Skip to content

Commit

Permalink
fix: Users in role list not working properly
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfabris committed May 10, 2024
1 parent e26397a commit 5f0ebe0
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { IRole, IRoom } from '@rocket.chat/core-typings';
import { Box, Field, FieldLabel, FieldRow, Margins, ButtonGroup, Button, Callout } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { Box, Field, FieldLabel, FieldRow, Margins, ButtonGroup, Button, Callout, FieldError } from '@rocket.chat/fuselage';
import { useEffectEvent, useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useEndpoint, useTranslation, useRouter } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import React, { useRef } from 'react';
import React from 'react';
import { useForm, Controller } from 'react-hook-form';

import { Page, PageHeader, PageContent } from '../../../../components/Page';
Expand All @@ -18,42 +19,35 @@ type UsersInRolePayload = {

const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
const t = useTranslation();
const reload = useRef<() => void>(() => undefined);
const dispatchToastMessage = useToastMessageDispatch();
const queryClient = useQueryClient();

const {
control,
handleSubmit,
formState: { isDirty },
reset,
getValues,
formState: { errors, isDirty },
watch,
} = useForm<UsersInRolePayload>({ defaultValues: { users: [] } });

const { _id, name, description } = role;
const router = useRoute('admin-permissions');
const addUser = useEndpoint('POST', '/v1/roles.addUserToRole');
const router = useRouter();
const addUserToRoleEndpoint = useEndpoint('POST', '/v1/roles.addUserToRole');

const rid = getValues('rid');
const { rid } = watch();
const roomFieldId = useUniqueId();
const usersFieldId = useUniqueId();

const handleReturn = useMutableCallback(() => {
router.push({
context: 'edit',
_id,
});
});

const handleAdd = useMutableCallback(async ({ users, rid }: UsersInRolePayload) => {
const handleAdd = useEffectEvent(async ({ users, rid }: UsersInRolePayload) => {
try {
await Promise.all(
users.map(async (user) => {
if (user) {
await addUser({ roleName: _id, username: user, roomId: rid });
await addUserToRoleEndpoint({ roleName: _id, username: user, roomId: rid });
}
}),
);
dispatchToastMessage({ type: 'success', message: t('Users_added') });
reload.current();
reset();
queryClient.invalidateQueries(['getUsersInRole']);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
Expand All @@ -63,48 +57,76 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
<Page>
<PageHeader title={`${t('Users_in_role')} "${description || name}"`}>
<ButtonGroup>
<Button onClick={handleReturn}>{t('Back')}</Button>
<Button onClick={() => router.navigate(`/admin/permissions/users-in-role/${_id}`)}>{t('Back')}</Button>
</ButtonGroup>
</PageHeader>
<PageContent>
<Box display='flex' flexDirection='column' w='full' mi='neg-x4'>
<Margins inline={4}>
{role.scope !== 'Users' && (
<Field mbe={4}>
<FieldLabel>{t('Choose_a_room')}</FieldLabel>
<FieldLabel htmlFor={roomFieldId}>{t('Choose_a_room')}</FieldLabel>
<FieldRow>
<Controller
control={control}
name='rid'
render={({ field: { onChange, value } }): ReactElement => (
<RoomAutoComplete value={value} onChange={onChange} placeholder={t('User')} />
rules={{ required: t('error-the-field-is-required', { field: t('Room') }) }}
render={({ field: { onChange, value } }) => (
<RoomAutoComplete
id={roomFieldId}
aria-required='true'
aria-invalid={Boolean(errors.rid)}
aria-describedby={`${roomFieldId}-error`}
scope='admin'
value={value}
onChange={onChange}
placeholder={t('Room')}
/>
)}
/>
</FieldRow>
{errors.rid && (
<FieldError aria-live='assertive' id={`${roomFieldId}-error`}>
{errors.rid.message}
</FieldError>
)}
</Field>
)}
<Field>
<FieldLabel>{t('Add_users')}</FieldLabel>
<FieldLabel htmlFor={usersFieldId}>{t('Add_users')}</FieldLabel>
<FieldRow>
<Controller
control={control}
name='users'
render={({ field: { onChange, value } }): ReactElement => (
<UserAutoCompleteMultiple value={value} placeholder={t('User')} onChange={onChange} />
rules={{ required: t('error-the-field-is-required', { field: t('Users') }) }}
render={({ field: { onChange, value } }) => (
<UserAutoCompleteMultiple
id={usersFieldId}
aria-required='true'
aria-invalid={Boolean(errors.users)}
aria-describedby={`${usersFieldId}-error`}
value={value}
placeholder={t('Users')}
onChange={onChange}
/>
)}
/>

<Button mis={8} primary onClick={handleSubmit(handleAdd)} disabled={!isDirty}>
{t('Add')}
</Button>
</FieldRow>
{errors.users && (
<FieldRow>
<FieldError aria-live='assertive' id={`${usersFieldId}-error`}>
{errors.users.message}
</FieldError>
</FieldRow>
)}
</Field>
</Margins>
</Box>
<Margins blockStart={8}>
{(role.scope === 'Users' || rid) && (
<UsersInRoleTable reloadRef={reload} rid={rid} roleId={_id} roleName={name} description={description} />
)}
{(role.scope === 'Users' || rid) && <UsersInRoleTable rid={rid} roleId={_id} roleName={name} description={description} />}
{role.scope !== 'Users' && !rid && <Callout type='info'>{t('Select_a_room')}</Callout>}
</Margins>
</PageContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,79 +1,106 @@
import type { IRole, IRoom, IUserInRole } from '@rocket.chat/core-typings';
import type { IRole, IRoom } from '@rocket.chat/core-typings';
import { Pagination } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import React from 'react';
import React, { useMemo } from 'react';

import GenericError from '../../../../../components/GenericError';
import GenericModal from '../../../../../components/GenericModal';
import GenericNoResults from '../../../../../components/GenericNoResults';
import { GenericTable, GenericTableHeader, GenericTableHeaderCell, GenericTableBody } from '../../../../../components/GenericTable';
import type { usePagination } from '../../../../../components/GenericTable/hooks/usePagination';
import {
GenericTable,
GenericTableHeader,
GenericTableHeaderCell,
GenericTableBody,
GenericTableLoadingTable,
} from '../../../../../components/GenericTable';
import { usePagination } from '../../../../../components/GenericTable/hooks/usePagination';
import UsersInRoleTableRow from './UsersInRoleTableRow';

type UsersInRoleTableProps = {
users: IUserInRole[];
reload: () => void;
roleName: IRole['name'];
roleId: IRole['_id'];
description: IRole['description'];
total: number;
rid?: IRoom['_id'];
paginationData: ReturnType<typeof usePagination>;
};

// TODO: Missing error state
const UsersInRoleTable = ({
users,
reload,
roleName,
roleId,
description,
total,
rid,
paginationData,
}: UsersInRoleTableProps): ReactElement => {
const UsersInRoleTable = ({ rid, roleId, roleName, description }: UsersInRoleTableProps): ReactElement => {
const t = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const removeUser = useEndpoint('POST', '/v1/roles.removeUserFromRole');
const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = paginationData;
const queryClient = useQueryClient();

const closeModal = (): void => setModal();
const getUsersInRoleEndpoint = useEndpoint('GET', '/v1/roles.getUsersInRole');
const removeUserFromRoleEndpoint = useEndpoint('POST', '/v1/roles.removeUserFromRole');

const handleRemove = useMutableCallback((username) => {
const remove = async (): Promise<void> => {
const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination();

const query = useMemo(
() => ({
role: roleId,
...(rid && { roomId: rid }),
...(itemsPerPage && { count: itemsPerPage }),
...(current && { offset: current }),
}),
[itemsPerPage, current, rid, roleId],
);

const { data, isLoading, isSuccess, refetch, isError } = useQuery(['getUsersInRole', roleId, query], async () =>
getUsersInRoleEndpoint(query),
);

const users = data?.users?.map((user) => ({
...user,
createdAt: new Date(user.createdAt),
_updatedAt: new Date(user._updatedAt),
}));

const handleRemove = useEffectEvent((username) => {
const remove = async () => {
try {
await removeUser({ roleId, username, scope: rid });
await removeUserFromRoleEndpoint({ roleId, username, scope: rid });
dispatchToastMessage({ type: 'success', message: t('User_removed') });
} catch (error: unknown) {
queryClient.invalidateQueries(['getUsersInRole']);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
closeModal();
reload();
setModal(null);
}
};

setModal(
<GenericModal variant='danger' onConfirm={remove} onClose={closeModal} onCancel={closeModal} confirmText={t('Delete')}>
<GenericModal variant='danger' onConfirm={remove} onCancel={() => setModal(null)} confirmText={t('Delete')}>
{t('The_user_s_will_be_removed_from_role_s', username, description || roleName)}
</GenericModal>,
);
});

const headers = (
<>
<GenericTableHeaderCell>{t('Name')}</GenericTableHeaderCell>
<GenericTableHeaderCell>{t('Email')}</GenericTableHeaderCell>
<GenericTableHeaderCell w='x80'></GenericTableHeaderCell>
</>
);

return (
<>
{users.length === 0 && <GenericNoResults />}
{users.length > 0 && (
{isLoading && (
<GenericTable>
<GenericTableHeader>{headers}</GenericTableHeader>
<GenericTableBody>
<GenericTableLoadingTable headerCells={2} />
</GenericTableBody>
</GenericTable>
)}
{isSuccess && users && users.length > 0 && (
<>
<GenericTable>
<GenericTableHeader>
<GenericTableHeaderCell>{t('Name')}</GenericTableHeaderCell>
<GenericTableHeaderCell>{t('Email')}</GenericTableHeaderCell>
<GenericTableHeaderCell w='x80'></GenericTableHeaderCell>
</GenericTableHeader>
<GenericTableHeader>{headers}</GenericTableHeader>
<GenericTableBody>
{users.map((user) => (
{users?.map((user) => (
<UsersInRoleTableRow onRemove={handleRemove} key={user?._id} user={user} />
))}
</GenericTableBody>
Expand All @@ -82,13 +109,15 @@ const UsersInRoleTable = ({
divider
current={current}
itemsPerPage={itemsPerPage}
count={total}
count={users.length || 0}
onSetItemsPerPage={onSetItemsPerPage}
onSetCurrent={onSetCurrent}
{...paginationProps}
/>
</>
)}
{users && users.length === 0 && <GenericNoResults />}
{isError && <GenericError buttonAction={refetch} />}
</>
);
};
Expand Down
Loading

0 comments on commit 5f0ebe0

Please sign in to comment.