Skip to content

Commit 4d67eb7

Browse files
refactor: standardize ForwardChatModal using GenericModal (#38424)
Co-authored-by: Martin Schoeler <20868078+MartinSchoeler@users.noreply.github.com>
1 parent 7d89aae commit 4d67eb7

File tree

8 files changed

+433
-147
lines changed

8 files changed

+433
-147
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { mockAppRoot } from '@rocket.chat/mock-providers';
2+
import { composeStories } from '@storybook/react';
3+
import { render } from '@testing-library/react';
4+
import { axe } from 'jest-axe';
5+
6+
import * as stories from './ForwardChatModal.stories';
7+
8+
const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);
9+
10+
jest.mock('../../../../app/ui-utils/client', () => ({
11+
LegacyRoomManager: {
12+
close: jest.fn(),
13+
},
14+
}));
15+
16+
const appRoot = mockAppRoot().build();
17+
18+
test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
19+
const { baseElement } = render(<Story />, { wrapper: appRoot });
20+
expect(baseElement).toMatchSnapshot();
21+
});
22+
23+
test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
24+
const { container } = render(<Story />, { wrapper: appRoot });
25+
26+
const results = await axe(container);
27+
expect(results).toHaveNoViolations();
28+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Meta, StoryFn } from '@storybook/react';
2+
3+
import ForwardChatModal from './ForwardChatModal';
4+
import { createFakeOmnichannelRoom } from '../../../../tests/mocks/data';
5+
6+
const mockedRoom = createFakeOmnichannelRoom();
7+
8+
export default {
9+
component: ForwardChatModal,
10+
parameters: {
11+
layout: 'fullscreen',
12+
actions: { argTypesRegex: '^on.*' },
13+
},
14+
args: {
15+
room: mockedRoom,
16+
},
17+
} satisfies Meta<typeof ForwardChatModal>;
18+
19+
export const Default: StoryFn<typeof ForwardChatModal> = (args) => <ForwardChatModal {...args} />;

apps/meteor/client/views/omnichannel/modals/ForwardChatModal.tsx

Lines changed: 125 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,13 @@
11
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
2-
import {
3-
Field,
4-
FieldGroup,
5-
Button,
6-
TextAreaInput,
7-
Modal,
8-
Box,
9-
Divider,
10-
FieldLabel,
11-
FieldRow,
12-
ModalHeader,
13-
ModalIcon,
14-
ModalTitle,
15-
ModalClose,
16-
ModalContent,
17-
ModalFooter,
18-
ModalFooterControllers,
19-
} from '@rocket.chat/fuselage';
20-
import { useEndpoint, useSetting } from '@rocket.chat/ui-contexts';
2+
import { Field, FieldGroup, TextAreaInput, Box, Divider, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
3+
import { GenericModal } from '@rocket.chat/ui-client';
4+
import { useEndpoint, useRouter, useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
215
import type { ReactElement } from 'react';
226
import { useCallback, useEffect, useId } from 'react';
23-
import { useForm } from 'react-hook-form';
7+
import { Controller, useForm } from 'react-hook-form';
248
import { useTranslation } from 'react-i18next';
259

10+
import { LegacyRoomManager } from '../../../../app/ui-utils/client';
2611
import AutoCompleteAgent from '../components/AutoCompleteAgent';
2712
import AutoCompleteDepartment from '../components/AutoCompleteDepartment';
2813

@@ -33,124 +18,157 @@ type ForwardChatModalFormData = {
3318
};
3419

3520
type ForwardChatModalProps = {
36-
onForward: (departmentId?: string, userId?: string, comment?: string) => Promise<void>;
37-
onCancel: () => void;
3821
room: IOmnichannelRoom;
22+
onCancel: () => void;
3923
};
4024

41-
const ForwardChatModal = ({ onForward, onCancel, room, ...props }: ForwardChatModalProps): ReactElement => {
25+
const ForwardChatModal = ({ room, onCancel }: ForwardChatModalProps): ReactElement => {
4226
const { t } = useTranslation();
27+
const router = useRouter();
28+
const dispatchToastMessage = useToastMessageDispatch();
4329
const getUserData = useEndpoint('GET', '/v1/users.info');
4430
const idleAgentsAllowedForForwarding = useSetting('Livechat_enabled_when_agent_idle', true);
4531

4632
const departmentFieldId = useId();
4733
const userFieldId = useId();
34+
const commentFieldId = useId();
4835

4936
const {
50-
getValues,
5137
handleSubmit,
52-
register,
5338
setFocus,
54-
setValue,
39+
control,
5540
watch,
5641
formState: { isSubmitting },
5742
} = useForm<ForwardChatModalFormData>();
5843

5944
useEffect(() => {
60-
setFocus('comment');
45+
setFocus('department');
6146
}, [setFocus]);
6247

6348
const department = watch('department');
6449
const username = watch('username');
6550

66-
const onSubmit = useCallback(
51+
const forwardChat = useEndpoint('POST', '/v1/livechat/room.forward');
52+
53+
const handleForwardChat = useCallback(
6754
async ({ department: departmentId, username, comment }: ForwardChatModalFormData) => {
68-
let uid;
55+
try {
56+
let userId;
6957

70-
if (username) {
71-
const { user } = await getUserData({ username });
72-
uid = user?._id;
73-
}
58+
if (username) {
59+
const { user } = await getUserData({ username });
60+
userId = user?._id;
61+
}
62+
63+
if (departmentId && userId) {
64+
return;
65+
}
66+
67+
const payload: {
68+
roomId: string;
69+
departmentId?: string;
70+
userId?: string;
71+
comment?: string;
72+
clientAction: boolean;
73+
} = {
74+
roomId: room._id,
75+
comment,
76+
clientAction: true,
77+
};
7478

75-
await onForward(departmentId, uid, comment);
79+
if (departmentId) {
80+
payload.departmentId = departmentId;
81+
}
82+
83+
if (userId) {
84+
payload.userId = userId;
85+
}
86+
87+
await forwardChat(payload);
88+
dispatchToastMessage({ type: 'success', message: t('Transferred') });
89+
router.navigate('/home');
90+
LegacyRoomManager.close(room.t + room._id);
91+
} catch (error) {
92+
dispatchToastMessage({ type: 'error', message: error });
93+
} finally {
94+
onCancel();
95+
}
7696
},
77-
[getUserData, onForward],
97+
[room._id, room.t, getUserData, forwardChat, dispatchToastMessage, t, router, onCancel],
7898
);
7999

80-
useEffect(() => {
81-
register('department');
82-
register('username');
83-
}, [register]);
84-
85100
return (
86-
<Modal
87-
aria-label={t('Forward_chat')}
88-
wrapperFunction={(props) => <Box is='form' onSubmit={handleSubmit(onSubmit)} {...props} />}
89-
{...props}
101+
<GenericModal
102+
variant='warning'
103+
icon={null}
104+
title={t('Forward_chat')}
105+
onCancel={onCancel}
106+
onConfirm={handleSubmit(handleForwardChat)}
107+
confirmText={t('Forward')}
108+
confirmDisabled={!username && !department}
109+
confirmLoading={isSubmitting}
90110
>
91-
<ModalHeader>
92-
<ModalIcon name='baloon-arrow-top-right' />
93-
<ModalTitle>{t('Forward_chat')}</ModalTitle>
94-
<ModalClose onClick={onCancel} />
95-
</ModalHeader>
96-
<ModalContent fontScale='p2'>
97-
<FieldGroup>
98-
<Field>
99-
<FieldLabel htmlFor={departmentFieldId}>{t('Forward_to_department')}</FieldLabel>
100-
<FieldRow>
101-
<AutoCompleteDepartment
102-
id={departmentFieldId}
103-
aria-label={t('Forward_to_department')}
104-
withTitle={false}
105-
maxWidth='100%'
106-
flexGrow={1}
107-
onChange={(value: string): void => {
108-
setValue('department', value);
109-
}}
110-
/>
111-
</FieldRow>
112-
</Field>
113-
<Divider p={0}>{t('or')}</Divider>
114-
<Field>
115-
<FieldLabel htmlFor={userFieldId}>{t('Forward_to_user')}</FieldLabel>
116-
<FieldRow>
117-
<AutoCompleteAgent
118-
id={userFieldId}
119-
aria-label={t('Forward_to_user')}
120-
withTitle
121-
onlyAvailable
122-
value={getValues().username}
123-
excludeId={room.servedBy?._id}
124-
showIdleAgents={idleAgentsAllowedForForwarding}
125-
placeholder={t('Username_name_email')}
126-
onChange={(value) => {
127-
setValue('username', value);
128-
}}
129-
/>
130-
</FieldRow>
131-
</Field>
132-
<Field marginBlock={15}>
133-
<FieldLabel>
134-
{t('Leave_a_comment')}{' '}
135-
<Box is='span' color='annotation'>
136-
({t('Optional')})
137-
</Box>
138-
</FieldLabel>
139-
<FieldRow>
140-
<TextAreaInput {...register('comment')} rows={8} flexGrow={1} />
141-
</FieldRow>
142-
</Field>
143-
</FieldGroup>
144-
</ModalContent>
145-
<ModalFooter>
146-
<ModalFooterControllers>
147-
<Button onClick={onCancel}>{t('Cancel')}</Button>
148-
<Button type='submit' disabled={!username && !department} primary loading={isSubmitting}>
149-
{t('Forward')}
150-
</Button>
151-
</ModalFooterControllers>
152-
</ModalFooter>
153-
</Modal>
111+
<FieldGroup>
112+
<Field>
113+
<FieldLabel htmlFor={departmentFieldId}>{t('Forward_to_department')}</FieldLabel>
114+
<FieldRow>
115+
<Controller
116+
name='department'
117+
control={control}
118+
render={({ field: { ref: _ref, ...field } }) => (
119+
<AutoCompleteDepartment
120+
{...field}
121+
id={departmentFieldId}
122+
aria-label={t('Forward_to_department')}
123+
withTitle={false}
124+
maxWidth='100%'
125+
flexGrow={1}
126+
disabled={!!username}
127+
/>
128+
)}
129+
/>
130+
</FieldRow>
131+
</Field>
132+
<Divider p={0}>{t('or')}</Divider>
133+
<Field>
134+
<FieldLabel htmlFor={userFieldId}>{t('Forward_to_user')}</FieldLabel>
135+
<FieldRow>
136+
<Controller
137+
name='username'
138+
control={control}
139+
render={({ field: { ref: _ref, ...field } }) => (
140+
<AutoCompleteAgent
141+
id={userFieldId}
142+
{...field}
143+
aria-label={t('Forward_to_user')}
144+
withTitle
145+
onlyAvailable
146+
excludeId={room.servedBy?._id}
147+
showIdleAgents={idleAgentsAllowedForForwarding}
148+
placeholder={t('Username_name_email')}
149+
disabled={!!department}
150+
/>
151+
)}
152+
/>
153+
</FieldRow>
154+
</Field>
155+
<Field marginBlock={15}>
156+
<FieldLabel htmlFor={commentFieldId}>
157+
{t('Leave_a_comment')}
158+
<Box mis={4} is='span' color='annotation'>
159+
({t('Optional')})
160+
</Box>
161+
</FieldLabel>
162+
<FieldRow>
163+
<Controller
164+
name='comment'
165+
control={control}
166+
render={({ field }) => <TextAreaInput {...field} id={commentFieldId} rows={8} flexGrow={1} />}
167+
/>
168+
</FieldRow>
169+
</Field>
170+
</FieldGroup>
171+
</GenericModal>
154172
);
155173
};
156174

0 commit comments

Comments
 (0)