Skip to content

Commit

Permalink
Emailer: preview HTML content before sending (#324)
Browse files Browse the repository at this point in the history
* initial preview

* touchup + translations

* better toggleView function name

* fix tests + style
  • Loading branch information
huang0h authored Feb 25, 2024
1 parent 9bdfe01 commit e82ef59
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 18 deletions.
5 changes: 5 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ module.exports = {
],
],
},
jest: {
configure: {
setupFiles: ['<rootDir>/setup.js'],
},
},
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"axios": "^0.21.2",
"craco-antd": "^2.0.0",
"i18next": "^23.5.1",
"isomorphic-dompurify": "1.4.0",
"lodash": "^4.17.20",
"moment": "^2.29.4",
"react": "^16.13.1",
Expand Down
3 changes: 3 additions & 0 deletions setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { TextEncoder, TextDecoder } = require('util');
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
83 changes: 71 additions & 12 deletions src/components/forms/sendEmailForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,112 @@
import React from 'react';
import { Form, Input, message } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import { Form, Input, Switch, message } from 'antd';
import { SubmitButton } from '../../../components/themedComponents';
import ProtectedApiClient from '../../../api/protectedApiClient';
import {
SendEmailFormValues,
SendEmailRequest,
} from '../../../components/forms/ducks/types';
import { requiredRule } from '../../../utils/formRules';
import { site } from '../../../constants';
import { useTranslation } from 'react-i18next';
import { n } from '../../../utils/stringFormat';
import DOMPurify from 'isomorphic-dompurify';

const PreviewSwitch = styled(Switch)`
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 10px 5px;
`;

const EmailPreview = styled.div`
min-height: 250px;
margin-bottom: 24px;
max-height: 800px;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 3px 11px;
resize: vertical;
overflow-y: scroll; // required for resizing
`;

interface SendEmailFormProps {
readonly emails: string[];
}

const SendEmailForm: React.FC<SendEmailFormProps> = ({ emails }) => {
const { t } = useTranslation(n(site, ['forms']), {
keyPrefix: 'volunteer_emailer',
nsMode: 'fallback',
});

const [sendEmailForm] = Form.useForm();

const [showPreview, setShowPreview] = useState<boolean>(false);
const [bodyContent, setBodyContent] = useState<string>('');
const [sanitizedBodyContent, setSanitizedBodyContent] = useState<string>('');

const togglePreview = (isShowPreview: boolean) => {
setShowPreview(isShowPreview);
if (isShowPreview) setSanitizedBodyContent(DOMPurify.sanitize(bodyContent));
};

const onFinishSendEmail = (values: SendEmailFormValues) => {
if (emails.length === 0) {
message.error(t('users_required'));
return;
}

const sendEmailRequest: SendEmailRequest = {
...values,
emails,
};

ProtectedApiClient.sendEmail(sendEmailRequest)
.then(() => {
message.success('Email sent!');
message.success(t('success'));
})
.catch((err) =>
message.error(`Email could not be sent: ${err.response.data}`),
message.error(t('response_error', { error: err.response.data })),
);
};

return (
<Form name="sendEmail" form={sendEmailForm} onFinish={onFinishSendEmail}>
<Form
name="sendEmail"
form={sendEmailForm}
onFinish={onFinishSendEmail}
onValuesChange={(changedValues, _) => {
if (changedValues.emailBody !== undefined)
setBodyContent(changedValues.emailBody);
}}
>
<Form.Item
name="emailSubject"
rules={requiredRule('The email subject is required')}
rules={requiredRule(t('subject_required'))}
>
<Input placeholder="Email Subject" />
<Input placeholder={t('subject_placeholder')} />
</Form.Item>

<PreviewSwitch
onChange={togglePreview}
checkedChildren={t('preview.preview')}
unCheckedChildren={t('preview.raw')}
/>
<Form.Item
name="emailBody"
rules={requiredRule('The email body is required')}
rules={requiredRule(t('body_required'))}
hidden={showPreview}
>
<Input.TextArea rows={6} placeholder="Email Body" />
<Input.TextArea rows={8} placeholder={t('body_placeholder')} />
</Form.Item>

{showPreview && (
<EmailPreview
dangerouslySetInnerHTML={{ __html: sanitizedBodyContent }}
/>
)}
<SubmitButton type="primary" htmlType="submit">
Send Email
{t('send')}
</SubmitButton>
</Form>
);
Expand Down
14 changes: 14 additions & 0 deletions src/i18n/en/forms.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,19 @@
},
"date_label": "Activity Date",
"activity_label": "Stewardship Activities"
},
"volunteer_emailer": {
"success": "Email sent!",
"users_required": "Please select users to email",
"response_error": "Email could not be sent: {{error}}",
"subject_required": "The email subject is required",
"subject_placeholder": "Email Subject",
"preview": {
"preview": "Preview",
"raw": "Raw"
},
"body_required": "The email body is required",
"body_placeholder": "Email Body",
"send": "Send Email"
}
}
Loading

0 comments on commit e82ef59

Please sign in to comment.