diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx index 28b8b56a1df22..57aa18c7bb662 100644 --- a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx @@ -1,26 +1,48 @@ import type { SelectOption } from '@rocket.chat/fuselage'; -import { Field, Select, FieldGroup, FieldLabel, FieldRow } from '@rocket.chat/fuselage'; +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useState, useMemo } from 'react'; - -import { - ContextualbarHeader, - ContextualbarIcon, - ContextualbarTitle, - ContextualbarClose, - ContextualbarScrollableContent, -} from '../../../../components/Contextualbar'; +import React, { useMemo } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; + +import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../../components/Contextualbar'; +import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; import { useRoom } from '../../contexts/RoomContext'; import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import FileExport from './FileExport'; import MailExportForm from './MailExportForm'; +export type MailExportFormValues = { + type: 'email' | 'file'; + dateFrom: string; + dateTo: string; + format: 'html' | 'json'; + toUsers: string[]; + additionalEmails: string; + messagesCount: number; + subject: string; +}; + const ExportMessages = () => { const t = useTranslation(); const room = useRoom(); + const { closeTab } = useRoomToolbox(); - const [type, setType] = useState('email'); + const roomName = room?.t && roomCoordinator.getRoomName(room.t, room); + + const methods = useForm({ + mode: 'onBlur', + defaultValues: { + type: 'email', + dateFrom: '', + dateTo: '', + toUsers: [], + additionalEmails: '', + messagesCount: 0, + subject: t('Mail_Messages_Subject', roomName), + format: 'html', + }, + }); const exportOptions = useMemo( () => [ @@ -30,25 +52,23 @@ const ExportMessages = () => { [t], ); + const formId = useUniqueId(); + return ( <> - {t('Export_Messages')} + {t('Export_Messages')} - - - - {t('Method')} - - - - - - - - - + <> + +
+ + + {t('Method')} + + } + /> + + + +
+
+ + + + + + + ); }; diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx index c8ae5a96300d7..281bc7ed83f09 100644 --- a/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx @@ -1,54 +1,59 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; -import { Field, FieldLabel, FieldRow, TextInput, ButtonGroup, Button, Box, Icon, Callout, FieldGroup } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useUserRoom, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; -import type { FC, MouseEventHandler } from 'react'; -import React, { useState, useEffect, useContext } from 'react'; +import type { SelectOption } from '@rocket.chat/fuselage'; +import { + FieldError, + Field, + FieldLabel, + FieldRow, + TextAreaInput, + TextInput, + ButtonGroup, + Button, + Box, + Icon, + Callout, + FieldGroup, + Select, +} from '@rocket.chat/fuselage'; +import { useAutoFocus, useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useEffect, useContext } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; import { validateEmail } from '../../../../../lib/emailValidator'; +import { ContextualbarScrollableContent, ContextualbarFooter } from '../../../../components/Contextualbar'; import UserAutoCompleteMultiple from '../../../../components/UserAutoCompleteMultiple'; -import { useForm } from '../../../../hooks/useForm'; -import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; import { SelectedMessageContext, useCountSelected } from '../../MessageList/contexts/SelectedMessagesContext'; - -type MailExportFormValues = { - dateFrom: string; - dateTo: string; - toUsers: string[]; - additionalEmails: string; - subject: string; +import type { MailExportFormValues } from './ExportMessages'; +import { useRoomExportMutation } from './useRoomExportMutation'; + +type MailExportFormProps = { + formId: string; + rid: IRoom['_id']; + onCancel: () => void; + exportOptions: SelectOption[]; }; -type MailExportFormProps = { onCancel: MouseEventHandler; rid: IRoom['_id'] }; - -const clickable = css` - cursor: pointer; -`; - -const MailExportForm: FC = ({ onCancel, rid }) => { - const { selectedMessageStore } = useContext(SelectedMessageContext); - +const MailExportForm = ({ formId, rid, onCancel, exportOptions }: MailExportFormProps) => { const t = useTranslation(); - const room = useUserRoom(rid); - const roomName = room?.t && roomCoordinator.getRoomName(room.t, room); - - const [errorMessage, setErrorMessage] = useState(); + const formFocus = useAutoFocus(); + + const { + watch, + setValue, + control, + register, + formState: { errors, isDirty }, + handleSubmit, + clearErrors, + } = useFormContext(); + const roomExportMutation = useRoomExportMutation(); + const { selectedMessageStore } = useContext(SelectedMessageContext); const messages = selectedMessageStore.getSelectedMessages(); - const count = useCountSelected(); - const { values, handlers } = useForm({ - dateFrom: '', - dateTo: '', - toUsers: [], - additionalEmails: '', - subject: t('Mail_Messages_Subject', roomName), - }); - - const dispatchToastMessage = useToastMessageDispatch(); - - const { toUsers, additionalEmails, subject } = values as MailExportFormValues; + const count = useCountSelected(); const clearSelection = useMutableCallback(() => { selectedMessageStore.clearStore(); @@ -61,93 +66,155 @@ const MailExportForm: FC = ({ onCancel, rid }) => { }; }, [selectedMessageStore]); - const { handleToUsers, handleAdditionalEmails, handleSubject } = handlers; - - const roomsExport = useEndpoint('POST', '/v1/rooms.export'); - - const handleSubmit = async (): Promise => { - if (toUsers.length === 0 && additionalEmails === '') { - setErrorMessage(t('Mail_Message_Missing_to')); - return; - } - if (additionalEmails !== '' && !validateEmail(additionalEmails)) { - setErrorMessage(t('Mail_Message_Invalid_emails', additionalEmails)); - return; - } - if (messages.length === 0) { - setErrorMessage(t('Mail_Message_No_messages_selected_select_all')); - return; - } - setErrorMessage(undefined); - - try { - await roomsExport({ - rid, - type: 'email', - toUsers, - toEmails: additionalEmails.split(','), - subject, - messages, - }); - - dispatchToastMessage({ - type: 'success', - message: t('Your_email_has_been_queued_for_sending'), - }); - } catch (error) { - dispatchToastMessage({ - type: 'error', - message: error, - }); - } + const { toUsers } = watch(); + + useEffect(() => { + setValue('messagesCount', messages.length); + }, [setValue, messages.length]); + + const handleExport = async ({ type, toUsers, subject, additionalEmails }: MailExportFormValues) => { + roomExportMutation.mutateAsync({ + rid, + type, + toUsers, + toEmails: additionalEmails?.split(','), + subject, + messages, + }); }; + const clickable = css` + cursor: pointer; + `; + + const methodField = useUniqueId(); + const toUsersField = useUniqueId(); + const additionalEmailsField = useUniqueId(); + const subjectField = useUniqueId(); + return ( - - - 0 ? 'success' : 'info'}> -

{`${count} Messages selected`}

- {count > 0 && ( - - {t('Click_here_to_clear_the_selection')} - - )} - {count === 0 && {t('Click_the_messages_you_would_like_to_send_by_email')}} -
-
- - {t('To_users')} - - - - - - {t('To_additional_emails')} - - } - /> - - - - {t('Subject')} - - } /> - - - - {errorMessage && {errorMessage}} - - - - - -
+ <> + +
+ + + {t('Method')} + + (messagesCount > 0 ? undefined : t('Mail_Message_No_messages_selected_select_all')), + })} + /> + {errors.messagesCount && {errors.messagesCount.message}} + + + {t('To_users')} + + ( + { + onChange(value); + clearErrors('additionalEmails'); + }} + onBlur={onBlur} + name={name} + /> + )} + /> + + + + {t('To_additional_emails')} + + { + if (additionalEmails === '') { + return undefined; + } + + if (additionalEmails !== '' && validateEmail(additionalEmails)) { + return undefined; + } + + return t('Mail_Message_Invalid_emails', additionalEmails); + }, + validateToUsers: (additionalEmails) => { + if (additionalEmails !== '' || toUsers?.length > 0) { + return undefined; + } + + return t('Mail_Message_Missing_to'); + }, + }, + }} + render={({ field }) => ( + } + aria-describedby={`${additionalEmailsField}-error`} + aria-invalid={Boolean(errors?.additionalEmails?.message)} + error={errors?.additionalEmails?.message} + /> + )} + /> + + {errors?.additionalEmails && ( + + {errors.additionalEmails.message} + + )} + + + {t('Subject')} + + } />} + /> + + + +
+
+ + + + + + + ); }; diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/useRoomExportMutation.ts b/apps/meteor/client/views/room/contextualBar/ExportMessages/useRoomExportMutation.ts new file mode 100644 index 0000000000000..ef8d67e3af3d6 --- /dev/null +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/useRoomExportMutation.ts @@ -0,0 +1,24 @@ +import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import { useMutation } from '@tanstack/react-query'; + +export const useRoomExportMutation = () => { + const t = useTranslation(); + const roomsExport = useEndpoint('POST', '/v1/rooms.export'); + const dispatchToastMessage = useToastMessageDispatch(); + + return useMutation({ + mutationFn: roomsExport, + onSuccess: () => { + dispatchToastMessage({ + type: 'success', + message: t('Your_email_has_been_queued_for_sending'), + }); + }, + onError: (error) => { + dispatchToastMessage({ + type: 'error', + message: error, + }); + }, + }); +};