Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { composeStories } from '@storybook/react';
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';

import * as stories from './ForwardChatModal.stories';

const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);

jest.mock('../../../../app/ui-utils/client', () => ({
LegacyRoomManager: {
close: jest.fn(),
},
}));

const appRoot = mockAppRoot().build();

test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
const { baseElement } = render(<Story />, { wrapper: appRoot });
expect(baseElement).toMatchSnapshot();
});

test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
const { container } = render(<Story />, { wrapper: appRoot });

const results = await axe(container);
expect(results).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta, StoryFn } from '@storybook/react';

import ForwardChatModal from './ForwardChatModal';
import { createFakeOmnichannelRoom } from '../../../../tests/mocks/data';

const mockedRoom = createFakeOmnichannelRoom();

export default {
component: ForwardChatModal,
parameters: {
layout: 'fullscreen',
actions: { argTypesRegex: '^on.*' },
},
args: {
room: mockedRoom,
},
} satisfies Meta<typeof ForwardChatModal>;

export const Default: StoryFn<typeof ForwardChatModal> = (args) => <ForwardChatModal {...args} />;
232 changes: 125 additions & 107 deletions apps/meteor/client/views/omnichannel/modals/ForwardChatModal.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import {
Field,
FieldGroup,
Button,
TextAreaInput,
Modal,
Box,
Divider,
FieldLabel,
FieldRow,
ModalHeader,
ModalIcon,
ModalTitle,
ModalClose,
ModalContent,
ModalFooter,
ModalFooterControllers,
} from '@rocket.chat/fuselage';
import { useEndpoint, useSetting } from '@rocket.chat/ui-contexts';
import { Field, FieldGroup, TextAreaInput, Box, Divider, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { GenericModal } from '@rocket.chat/ui-client';
import { useEndpoint, useRouter, useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import { useCallback, useEffect, useId } from 'react';
import { useForm } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import { LegacyRoomManager } from '../../../../app/ui-utils/client';
import AutoCompleteAgent from '../components/AutoCompleteAgent';
import AutoCompleteDepartment from '../components/AutoCompleteDepartment';

Expand All @@ -33,124 +18,157 @@ type ForwardChatModalFormData = {
};

type ForwardChatModalProps = {
onForward: (departmentId?: string, userId?: string, comment?: string) => Promise<void>;
onCancel: () => void;
room: IOmnichannelRoom;
onCancel: () => void;
};

const ForwardChatModal = ({ onForward, onCancel, room, ...props }: ForwardChatModalProps): ReactElement => {
const ForwardChatModal = ({ room, onCancel }: ForwardChatModalProps): ReactElement => {
const { t } = useTranslation();
const router = useRouter();
const dispatchToastMessage = useToastMessageDispatch();
const getUserData = useEndpoint('GET', '/v1/users.info');
const idleAgentsAllowedForForwarding = useSetting('Livechat_enabled_when_agent_idle', true);

const departmentFieldId = useId();
const userFieldId = useId();
const commentFieldId = useId();

const {
getValues,
handleSubmit,
register,
setFocus,
setValue,
control,
watch,
formState: { isSubmitting },
} = useForm<ForwardChatModalFormData>();

useEffect(() => {
setFocus('comment');
setFocus('department');
}, [setFocus]);

const department = watch('department');
const username = watch('username');

const onSubmit = useCallback(
const forwardChat = useEndpoint('POST', '/v1/livechat/room.forward');

const handleForwardChat = useCallback(
async ({ department: departmentId, username, comment }: ForwardChatModalFormData) => {
let uid;
try {
let userId;

if (username) {
const { user } = await getUserData({ username });
uid = user?._id;
}
if (username) {
const { user } = await getUserData({ username });
userId = user?._id;
}

if (departmentId && userId) {
return;
}

const payload: {
roomId: string;
departmentId?: string;
userId?: string;
comment?: string;
clientAction: boolean;
} = {
roomId: room._id,
comment,
clientAction: true,
};

await onForward(departmentId, uid, comment);
if (departmentId) {
payload.departmentId = departmentId;
}

if (userId) {
payload.userId = userId;
}

await forwardChat(payload);
dispatchToastMessage({ type: 'success', message: t('Transferred') });
router.navigate('/home');
LegacyRoomManager.close(room.t + room._id);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
onCancel();
}
},
[getUserData, onForward],
[room._id, room.t, getUserData, forwardChat, dispatchToastMessage, t, router, onCancel],
);

useEffect(() => {
register('department');
register('username');
}, [register]);

return (
<Modal
aria-label={t('Forward_chat')}
wrapperFunction={(props) => <Box is='form' onSubmit={handleSubmit(onSubmit)} {...props} />}
{...props}
<GenericModal
variant='warning'
icon={null}
title={t('Forward_chat')}
onCancel={onCancel}
onConfirm={handleSubmit(handleForwardChat)}
confirmText={t('Forward')}
confirmDisabled={!username && !department}
confirmLoading={isSubmitting}
>
<ModalHeader>
<ModalIcon name='baloon-arrow-top-right' />
<ModalTitle>{t('Forward_chat')}</ModalTitle>
<ModalClose onClick={onCancel} />
</ModalHeader>
<ModalContent fontScale='p2'>
<FieldGroup>
<Field>
<FieldLabel htmlFor={departmentFieldId}>{t('Forward_to_department')}</FieldLabel>
<FieldRow>
<AutoCompleteDepartment
id={departmentFieldId}
aria-label={t('Forward_to_department')}
withTitle={false}
maxWidth='100%'
flexGrow={1}
onChange={(value: string): void => {
setValue('department', value);
}}
/>
</FieldRow>
</Field>
<Divider p={0}>{t('or')}</Divider>
<Field>
<FieldLabel htmlFor={userFieldId}>{t('Forward_to_user')}</FieldLabel>
<FieldRow>
<AutoCompleteAgent
id={userFieldId}
aria-label={t('Forward_to_user')}
withTitle
onlyAvailable
value={getValues().username}
excludeId={room.servedBy?._id}
showIdleAgents={idleAgentsAllowedForForwarding}
placeholder={t('Username_name_email')}
onChange={(value) => {
setValue('username', value);
}}
/>
</FieldRow>
</Field>
<Field marginBlock={15}>
<FieldLabel>
{t('Leave_a_comment')}{' '}
<Box is='span' color='annotation'>
({t('Optional')})
</Box>
</FieldLabel>
<FieldRow>
<TextAreaInput {...register('comment')} rows={8} flexGrow={1} />
</FieldRow>
</Field>
</FieldGroup>
</ModalContent>
<ModalFooter>
<ModalFooterControllers>
<Button onClick={onCancel}>{t('Cancel')}</Button>
<Button type='submit' disabled={!username && !department} primary loading={isSubmitting}>
{t('Forward')}
</Button>
</ModalFooterControllers>
</ModalFooter>
</Modal>
<FieldGroup>
<Field>
<FieldLabel htmlFor={departmentFieldId}>{t('Forward_to_department')}</FieldLabel>
<FieldRow>
<Controller
name='department'
control={control}
render={({ field: { ref: _ref, ...field } }) => (
<AutoCompleteDepartment
{...field}
id={departmentFieldId}
aria-label={t('Forward_to_department')}
withTitle={false}
maxWidth='100%'
flexGrow={1}
disabled={!!username}
/>
)}
/>
</FieldRow>
</Field>
<Divider p={0}>{t('or')}</Divider>
<Field>
<FieldLabel htmlFor={userFieldId}>{t('Forward_to_user')}</FieldLabel>
<FieldRow>
<Controller
name='username'
control={control}
render={({ field: { ref: _ref, ...field } }) => (
<AutoCompleteAgent
id={userFieldId}
{...field}
aria-label={t('Forward_to_user')}
withTitle
onlyAvailable
excludeId={room.servedBy?._id}
showIdleAgents={idleAgentsAllowedForForwarding}
placeholder={t('Username_name_email')}
disabled={!!department}
/>
)}
/>
</FieldRow>
</Field>
<Field marginBlock={15}>
<FieldLabel htmlFor={commentFieldId}>
{t('Leave_a_comment')}
<Box mis={4} is='span' color='annotation'>
({t('Optional')})
</Box>
</FieldLabel>
<FieldRow>
<Controller
name='comment'
control={control}
render={({ field }) => <TextAreaInput {...field} id={commentFieldId} rows={8} flexGrow={1} />}
/>
</FieldRow>
</Field>
</FieldGroup>
</GenericModal>
);
};

Expand Down
Loading
Loading