RocketChat/Rocket.Chat

View on GitHub
apps/meteor/client/views/admin/emailInbox/EmailInboxForm.tsx

Summary

Maintainability
F
2 wks
Test Coverage
import type { IEmailInboxPayload } from '@rocket.chat/core-typings';
import {
    Accordion,
    Button,
    ButtonGroup,
    TextInput,
    TextAreaInput,
    Field,
    ToggleSwitch,
    FieldGroup,
    Box,
    Margins,
    NumberInput,
    PasswordInput,
    FieldLabel,
    FieldRow,
    FieldError,
    FieldHint,
} from '@rocket.chat/fuselage';
import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useCallback } from 'react';
import { useForm, Controller } from 'react-hook-form';

import { validateEmail } from '../../../../lib/emailValidator';
import AutoCompleteDepartment from '../../../components/AutoCompleteDepartment';
import GenericModal from '../../../components/GenericModal';
import { PageScrollableContentWithShadow } from '../../../components/Page';

const EmailInboxForm = ({ inboxData }: { inboxData?: IEmailInboxPayload }): ReactElement => {
    const t = useTranslation();
    const dispatchToastMessage = useToastMessageDispatch();
    const setModal = useSetModal();
    const router = useRoute('admin-email-inboxes');

    const handleBack = useCallback(() => router.push({}), [router]);

    const saveEmailInbox = useEndpoint('POST', '/v1/email-inbox');
    const deleteInboxAction = useEndpoint('DELETE', '/v1/email-inbox/:_id', { _id: inboxData?._id ?? '' });
    const emailAlreadyExistsAction = useEndpoint('GET', '/v1/email-inbox.search');

    const {
        control,
        handleSubmit,
        formState: { errors, isDirty },
    } = useForm({
        defaultValues: {
            active: inboxData?.active ?? true,
            name: inboxData?.name,
            email: inboxData?.email,
            description: inboxData?.description,
            senderInfo: inboxData?.senderInfo,
            department: inboxData?.department || '',
            // SMTP
            smtpServer: inboxData?.smtp.server,
            smtpPort: inboxData?.smtp.port ?? 587,
            smtpUsername: inboxData?.smtp.username,
            smtpPassword: inboxData?.smtp.password,
            smtpSecure: inboxData?.smtp.secure ?? false,
            // IMAP
            imapServer: inboxData?.imap.server,
            imapPort: inboxData?.imap.port ?? 993,
            imapUsername: inboxData?.imap.username,
            imapPassword: inboxData?.imap.password,
            imapSecure: inboxData?.imap.secure ?? false,
            imapRetries: inboxData?.imap.maxRetries ?? 10,
        },
    });

    const handleDelete = useMutableCallback(() => {
        const deleteInbox = async (): Promise<void> => {
            try {
                await deleteInboxAction();
                dispatchToastMessage({ type: 'success', message: t('Email_Inbox_has_been_removed') });
                handleBack();
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            } finally {
                setModal(null);
            }
        };

        setModal(
            <GenericModal variant='danger' onConfirm={deleteInbox} onCancel={(): void => setModal(null)} confirmText={t('Delete')}>
                {t('You_will_not_be_able_to_recover_email_inbox')}
            </GenericModal>,
        );
    });

    const handleSave = useMutableCallback(
        async ({
            active,
            name,
            email,
            description,
            senderInfo,
            department,
            smtpServer,
            smtpPort,
            smtpUsername,
            smtpPassword,
            smtpSecure,
            imapServer,
            imapPort,
            imapUsername,
            imapPassword,
            imapSecure,
            imapRetries,
        }) => {
            const smtp = {
                server: smtpServer,
                port: parseInt(smtpPort),
                username: smtpUsername,
                password: smtpPassword,
                secure: smtpSecure,
            };

            const imap = {
                server: imapServer,
                port: parseInt(imapPort),
                username: imapUsername,
                password: imapPassword,
                secure: imapSecure,
                maxRetries: parseInt(imapRetries),
            };

            const payload = {
                ...(inboxData?._id && { _id: inboxData?._id }),
                active,
                name,
                email,
                description,
                senderInfo,
                ...(department && { department: typeof department === 'string' ? department : department.value }),
                smtp,
                imap,
            };

            try {
                await saveEmailInbox(payload);
                dispatchToastMessage({ type: 'success', message: t('Email_Inbox_has_been_added') });
                handleBack();
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            }
        },
    );

    const checkEmailExists = useMutableCallback(async (email) => {
        if (!email) {
            return;
        }

        if (!validateEmail(email)) {
            return t('Validate_email_address');
        }

        const { emailInbox } = await emailAlreadyExistsAction({ email });

        if (!emailInbox || (inboxData?._id && emailInbox._id === inboxData?._id)) {
            return;
        }

        return t('Email_already_exists');
    });

    const activeField = useUniqueId();
    const nameField = useUniqueId();
    const emailField = useUniqueId();
    const descriptionField = useUniqueId();
    const senderInfoField = useUniqueId();
    const departmentField = useUniqueId();

    const smtpServerField = useUniqueId();
    const smtpPortField = useUniqueId();
    const smtpUsernameField = useUniqueId();
    const smtpPasswordField = useUniqueId();
    const smtpSecureField = useUniqueId();

    const imapServerField = useUniqueId();
    const imapPortField = useUniqueId();
    const imapUsernameField = useUniqueId();
    const imapPasswordField = useUniqueId();
    const imapRetriesField = useUniqueId();
    const imapSecureField = useUniqueId();

    return (
        <PageScrollableContentWithShadow>
            <Box maxWidth='x600' w='full' alignSelf='center'>
                <Accordion>
                    <Accordion.Item defaultExpanded title={t('Inbox_Info')}>
                        <FieldGroup>
                            <Field>
                                <FieldRow>
                                    <FieldLabel htmlFor={activeField}>{t('Active')}</FieldLabel>
                                    <Controller
                                        control={control}
                                        name='active'
                                        render={({ field: { onChange, value, ref } }): ReactElement => (
                                            <ToggleSwitch id={activeField} ref={ref} checked={value} onChange={onChange} />
                                        )}
                                    />
                                </FieldRow>
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={nameField} required>
                                    {t('Name')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='name'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Name') }) }}
                                        render={({ field }) => (
                                            <TextInput
                                                id={nameField}
                                                {...field}
                                                error={errors.name?.message}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.name)}
                                                aria-describedby={`${nameField}-error`}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.name && (
                                    <FieldError aria-live='assertive' id={`${nameField}-error`}>
                                        {errors.name?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={emailField} required>
                                    {t('Email')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='email'
                                        control={control}
                                        rules={{
                                            required: t('error-the-field-is-required', { field: t('Email') }),
                                            validate: (value) => checkEmailExists(value),
                                        }}
                                        render={({ field }) => (
                                            <TextInput
                                                {...field}
                                                id={emailField}
                                                error={errors.email?.message}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                                aria-describedby={`${emailField}-error`}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.email && (
                                    <FieldError aria-live='assertive' id={`${emailField}-error`}>
                                        {errors.email?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={descriptionField}>{t('Description')}</FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='description'
                                        control={control}
                                        render={({ field }) => <TextAreaInput id={descriptionField} {...field} rows={4} />}
                                    />
                                </FieldRow>
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={senderInfoField}>{t('Sender_Info')}</FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='senderInfo'
                                        control={control}
                                        render={({ field }) => <TextInput id={senderInfoField} {...field} aria-describedby={`${senderInfoField}-hint`} />}
                                    />
                                </FieldRow>
                                <FieldHint id={`${senderInfoField}-hint`}>{t('Will_Appear_In_From')}</FieldHint>
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={departmentField}>{t('Department')}</FieldLabel>
                                <FieldRow>
                                    <Controller
                                        control={control}
                                        name='department'
                                        render={({ field: { onChange, onBlur, name, value } }) => (
                                            <AutoCompleteDepartment
                                                id={departmentField}
                                                name={name}
                                                onBlur={onBlur}
                                                value={value}
                                                onChange={onChange}
                                                aria-describedby={`${departmentField}-hint`}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                <FieldHint id={`${departmentField}-hint`}>{t('Only_Members_Selected_Department_Can_View_Channel')}</FieldHint>
                            </Field>
                        </FieldGroup>
                    </Accordion.Item>
                    <Accordion.Item defaultExpanded={!inboxData?._id} title={t('Configure_Outgoing_Mail_SMTP')}>
                        <FieldGroup>
                            <Field>
                                <FieldLabel htmlFor={smtpServerField} required>
                                    {t('Server')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='smtpServer'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Server') }) }}
                                        render={({ field }) => (
                                            <TextInput
                                                id={smtpServerField}
                                                {...field}
                                                error={errors.smtpServer?.message}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                                aria-describedby={`${smtpServerField}-error`}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.smtpServer && (
                                    <FieldError aria-live='assertive' id={`${smtpServerField}-error`}>
                                        {errors.smtpServer?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={smtpPortField} required>
                                    {t('Port')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='smtpPort'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Port') }) }}
                                        render={({ field }) => (
                                            <NumberInput
                                                id={smtpPortField}
                                                {...field}
                                                error={errors.smtpPort?.message}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                                aria-describedby={`${smtpPortField}-error`}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.smtpPort && (
                                    <FieldError aria-live='assertive' id={`${smtpPortField}-error`}>
                                        {errors.smtpPort?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={smtpUsernameField} required>
                                    {t('Username')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='smtpUsername'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Username') }) }}
                                        render={({ field }) => (
                                            <TextInput
                                                id={smtpUsernameField}
                                                {...field}
                                                error={errors.smtpUsername?.message}
                                                aria-describedby={`${smtpUsernameField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.smtpUsername && (
                                    <FieldError aria-live='assertive' id={`${smtpUsernameField}-error`}>
                                        {errors.smtpUsername?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={smtpPasswordField} required>
                                    {t('Password')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='smtpPassword'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Password') }) }}
                                        render={({ field }) => (
                                            <PasswordInput
                                                id={smtpPasswordField}
                                                {...field}
                                                error={errors.smtpPassword?.message}
                                                aria-describedby={`${smtpPasswordField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.smtpPassword && (
                                    <FieldError aria-live='assertive' id={`${smtpPasswordField}-error`}>
                                        {errors.smtpPassword?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldRow>
                                    <FieldLabel htmlFor={smtpSecureField}>{t('Connect_SSL_TLS')}</FieldLabel>
                                    <Controller
                                        control={control}
                                        name='smtpSecure'
                                        render={({ field: { value, ...field } }) => <ToggleSwitch id={smtpSecureField} {...field} checked={value} />}
                                    />
                                </FieldRow>
                            </Field>
                        </FieldGroup>
                    </Accordion.Item>
                    <Accordion.Item defaultExpanded={!inboxData?._id} title={t('Configure_Incoming_Mail_IMAP')}>
                        <FieldGroup>
                            <Field>
                                <FieldLabel htmlFor={imapServerField} required>
                                    {t('Server')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='imapServer'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Server') }) }}
                                        render={({ field }) => (
                                            <TextInput
                                                id={imapServerField}
                                                {...field}
                                                error={errors.imapServer?.message}
                                                aria-describedby={`${imapServerField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.imapServer && (
                                    <FieldError aria-live='assertive' id={`${imapServerField}-error`}>
                                        {errors.imapServer?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={imapPortField} required>
                                    {t('Port')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='imapPort'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Port') }) }}
                                        render={({ field }) => (
                                            <NumberInput
                                                id={imapPortField}
                                                {...field}
                                                error={errors.imapPort?.message}
                                                aria-describedby={`${imapPortField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.imapPort && (
                                    <FieldError aria-live='assertive' id={`${imapPortField}-error`}>
                                        {errors.imapPort?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={imapUsernameField} required>
                                    {t('Username')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='imapUsername'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Username') }) }}
                                        render={({ field }) => (
                                            <TextInput
                                                id={imapUsernameField}
                                                {...field}
                                                error={errors.imapUsername?.message}
                                                aria-describedby={`${imapUsernameField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.imapUsername && (
                                    <FieldError aria-live='assertive' id={`${imapUsernameField}-error`}>
                                        {errors.imapUsername?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={imapPasswordField} required>
                                    {t('Password')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='imapPassword'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Password') }) }}
                                        render={({ field }) => (
                                            <PasswordInput
                                                id={imapPasswordField}
                                                {...field}
                                                error={errors.imapPassword?.message}
                                                aria-describedby={`${imapPasswordField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.imapPassword && (
                                    <FieldError aria-live='assertive' id={`${imapPasswordField}-error`}>
                                        {errors.imapPassword?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldLabel htmlFor={imapRetriesField} required>
                                    {t('Max_Retry')}
                                </FieldLabel>
                                <FieldRow>
                                    <Controller
                                        name='imapRetries'
                                        control={control}
                                        rules={{ required: t('error-the-field-is-required', { field: t('Max_Retry') }) }}
                                        render={({ field }) => (
                                            <NumberInput
                                                id={imapRetriesField}
                                                {...field}
                                                error={errors.imapRetries?.message}
                                                aria-describedby={`${imapRetriesField}-error`}
                                                aria-required={true}
                                                aria-invalid={Boolean(errors.email)}
                                            />
                                        )}
                                    />
                                </FieldRow>
                                {errors.imapRetries && (
                                    <FieldError aria-live='assertive' id={`${imapRetriesField}-error`}>
                                        {errors.imapRetries?.message}
                                    </FieldError>
                                )}
                            </Field>
                            <Field>
                                <FieldRow>
                                    <FieldLabel htmlFor={imapSecureField}>{t('Connect_SSL_TLS')}</FieldLabel>
                                    <Controller
                                        control={control}
                                        name='imapSecure'
                                        render={({ field: { value, ...field } }) => <ToggleSwitch id={imapSecureField} {...field} checked={value} />}
                                    />
                                </FieldRow>
                            </Field>
                        </FieldGroup>
                    </Accordion.Item>
                    <Field>
                        <FieldRow>
                            <ButtonGroup stretch>
                                <Button onClick={handleBack}>{t('Cancel')}</Button>
                                <Button disabled={!isDirty} primary onClick={handleSubmit(handleSave)}>
                                    {t('Save')}
                                </Button>
                            </ButtonGroup>
                        </FieldRow>
                        <FieldRow>
                            <Margins blockStart={16}>
                                <ButtonGroup stretch>
                                    {inboxData?._id && (
                                        <Button danger onClick={handleDelete}>
                                            {t('Delete')}
                                        </Button>
                                    )}
                                </ButtonGroup>
                            </Margins>
                        </FieldRow>
                    </Field>
                </Accordion>
            </Box>
        </PageScrollableContentWithShadow>
    );
};

export default EmailInboxForm;