feat: [MICROBA-1664] Add copy email button (#20)
The old tool had a button in the modal used to view past emails that allowed you to copy the content into the editor. This replicates that functionality, but without the ability to fill in the subject field of the email. This was done to keep certain components more seperate.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -15,6 +15,13 @@ export default function BulkEmailTool() {
|
||||
|
||||
const [courseMetadata, setCourseMetadata] = useState();
|
||||
const isMobile = useMobileResponsive();
|
||||
const textEditorRef = useRef();
|
||||
|
||||
const copyTextToEditor = (body) => {
|
||||
if (textEditorRef?.current) {
|
||||
textEditorRef.current.setContent(body);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchTabData() {
|
||||
@@ -48,10 +55,10 @@ export default function BulkEmailTool() {
|
||||
<Navigationtabs courseId={courseId} tabData={courseMetadata.tabs} />
|
||||
<div className={classnames({ 'border border-primary-200': !isMobile })}>
|
||||
<div className="row">
|
||||
<BulkEmailForm courseId={courseId} cohorts={courseMetadata.cohorts} />
|
||||
<BulkEmailForm courseId={courseId} cohorts={courseMetadata.cohorts} editorRef={textEditorRef} />
|
||||
</div>
|
||||
<div className="row">
|
||||
<BulkEmailTaskManager courseId={courseId} />
|
||||
<BulkEmailTaskManager courseId={courseId} copyTextToEditor={copyTextToEditor} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Form, Icon, StatefulButton, useCheckboxSetValues, useToggle,
|
||||
@@ -20,7 +20,7 @@ export const FORM_SUBMIT_STATES = {
|
||||
};
|
||||
|
||||
export default function BulkEmailForm(props) {
|
||||
const { courseId, cohorts } = props;
|
||||
const { courseId, cohorts, editorRef } = props;
|
||||
const [subject, setSubject] = useState('');
|
||||
const [emailFormStatus, setEmailFormStatus] = useState(FORM_SUBMIT_STATES.DEFAULT);
|
||||
const [emailFormValidation, setEmailFormValidation] = useState({
|
||||
@@ -31,7 +31,6 @@ export default function BulkEmailForm(props) {
|
||||
});
|
||||
const [selectedRecipients, { add, remove }] = useCheckboxSetValues([]);
|
||||
const [isTaskAlertOpen, openTaskAlert, closeTaskAlert] = useToggle(false);
|
||||
const editorRef = useRef(null);
|
||||
const resetEmailForm = useTimeout(() => {
|
||||
setEmailFormStatus(FORM_SUBMIT_STATES.COMPLETED_DEFAULT);
|
||||
}, 3000);
|
||||
@@ -243,4 +242,6 @@ BulkEmailForm.defaultProps = {
|
||||
BulkEmailForm.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
cohorts: PropTypes.arrayOf(PropTypes.string),
|
||||
editorRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.instanceOf(Element) })])
|
||||
.isRequired,
|
||||
};
|
||||
|
||||
@@ -18,17 +18,17 @@ describe('bulk-email-form', () => {
|
||||
beforeEach(() => jest.resetModules());
|
||||
afterEach(cleanup);
|
||||
test('it renders', () => {
|
||||
render(<BulkEmailForm courseId="test-course-id" />);
|
||||
render(<BulkEmailForm courseId="test-course-id" editorRef={jest.fn()} />);
|
||||
expect(screen.getByText('Submit')).toBeTruthy();
|
||||
});
|
||||
test('it shows a warning when clicking submit', async () => {
|
||||
render(<BulkEmailForm courseId="test-course-id" />);
|
||||
render(<BulkEmailForm courseId="test-course-id" editorRef={jest.fn()} />);
|
||||
fireEvent.click(screen.getByText('Submit'));
|
||||
const warning = await screen.findByText('CAUTION!', { exact: false });
|
||||
expect(warning).toBeTruthy();
|
||||
});
|
||||
test('Prevent form POST if invalid', async () => {
|
||||
render(<BulkEmailForm courseId="test-course-id" />);
|
||||
render(<BulkEmailForm courseId="test-course-id" editorRef={jest.fn()} />);
|
||||
fireEvent.click(screen.getByText('Submit'));
|
||||
expect(await screen.findByRole('button', { name: /continue/i })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /continue/i }));
|
||||
@@ -36,7 +36,7 @@ describe('bulk-email-form', () => {
|
||||
expect(await screen.findByText('A subject is required')).toBeInTheDocument();
|
||||
});
|
||||
test('Shows complete message on completed POST', async () => {
|
||||
render(<BulkEmailForm courseId="test-course-id" />);
|
||||
render(<BulkEmailForm courseId="test-course-id" editorRef={jest.fn()} />);
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' }));
|
||||
expect(screen.getByRole('checkbox', { name: 'Myself' })).toBeChecked();
|
||||
fireEvent.change(screen.getByRole('textbox', { name: 'Subject:' }), { target: { value: 'test subject' } });
|
||||
@@ -51,7 +51,7 @@ describe('bulk-email-form', () => {
|
||||
throw Error('api-response-error');
|
||||
});
|
||||
await act(async () => {
|
||||
render(<BulkEmailForm courseId="test-course-id" />);
|
||||
render(<BulkEmailForm courseId="test-course-id" editorRef={jest.fn()} />);
|
||||
const subjectLine = screen.getByRole('textbox', { name: 'Subject:' });
|
||||
const recipient = screen.getByRole('checkbox', { name: 'Myself' });
|
||||
fireEvent.click(recipient);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
Button, Icon, Modal, StatefulButton,
|
||||
@@ -11,14 +11,13 @@ import messages from './messages';
|
||||
import { getSentEmailHistory } from './data/api';
|
||||
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
|
||||
export function BulkEmailContentHistory({ intl }) {
|
||||
export function BulkEmailContentHistory({ intl, copyTextToEditor }) {
|
||||
const { courseId } = useParams();
|
||||
const BUTTON_STATE = {
|
||||
DEFAULT: 'default',
|
||||
PENDING: 'pending',
|
||||
COMPLETE: 'complete',
|
||||
};
|
||||
|
||||
const [emailHistoryData, setEmailHistoryData] = useState();
|
||||
const [errorRetrievingData, setErrorRetrievingData] = useState(false);
|
||||
const [showHistoricalEmailContentTable, setShowHistoricalEmailContentTable] = useState(false);
|
||||
@@ -91,47 +90,42 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
body={(
|
||||
<div>
|
||||
<div className="d-flex flex-row">
|
||||
<p>
|
||||
{intl.formatMessage(messages.modalMessageSubject)}
|
||||
</p>
|
||||
<p className="pl-2">
|
||||
{messageContent.subject}
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages.modalMessageSubject)}</p>
|
||||
<p className="pl-2">{messageContent.subject}</p>
|
||||
</div>
|
||||
<div className="d-flex flex-row">
|
||||
<p>
|
||||
{intl.formatMessage(messages.modalMessageSentBy)}
|
||||
</p>
|
||||
<p className="pl-2">
|
||||
{messageContent.requester}
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages.modalMessageSentBy)}</p>
|
||||
<p className="pl-2">{messageContent.requester}</p>
|
||||
</div>
|
||||
<div className="d-flex flex-row">
|
||||
<p>
|
||||
{intl.formatMessage(messages.modalMessageTimeSent)}
|
||||
</p>
|
||||
<p className="pl-2">
|
||||
{messageContent.created}
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages.modalMessageTimeSent)}</p>
|
||||
<p className="pl-2">{messageContent.created}</p>
|
||||
</div>
|
||||
<div className="d-flex flex-row">
|
||||
<p>
|
||||
{intl.formatMessage(messages.modalMessageSentTo)}
|
||||
</p>
|
||||
<p className="pl-2">
|
||||
{messageContent.sent_to}
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages.modalMessageSentTo)}</p>
|
||||
<p className="pl-2">{messageContent.sent_to}</p>
|
||||
</div>
|
||||
<hr className="py-2" />
|
||||
<div>
|
||||
<p>
|
||||
{intl.formatMessage(messages.modalMessageBody)}
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages.modalMessageBody)}</p>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<div dangerouslySetInnerHTML={{ __html: messageContent.email.html_message }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
onClose={() => setIsMessageModalOpen(false)}
|
||||
buttons={[
|
||||
<Button onClick={() => {
|
||||
copyTextToEditor(messageContent.email.html_message);
|
||||
setIsMessageModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="bulk.email.tool.copy.message.button"
|
||||
defaultMessage="Copy to editor"
|
||||
/>
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -167,35 +161,31 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
const additionalColumns = () => {
|
||||
const tableData = transformDataForTable();
|
||||
|
||||
return (
|
||||
[
|
||||
{
|
||||
id: 'view_message',
|
||||
Header: '',
|
||||
Cell: ({ row }) => (
|
||||
<Button variant="link" className="px-1" onClick={() => onViewMessageClick(tableData[row.index])}>
|
||||
{intl.formatMessage(messages.buttonViewMessage)}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
);
|
||||
return [
|
||||
{
|
||||
id: 'view_message',
|
||||
Header: '',
|
||||
Cell: ({ row }) => (
|
||||
<Button variant="link" className="px-1" onClick={() => onViewMessageClick(tableData[row.index])}>
|
||||
{intl.formatMessage(messages.buttonViewMessage)}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{messageContent && renderMessageModal()}</div>
|
||||
<div>
|
||||
{messageContent && renderMessageModal()}
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
{intl.formatMessage(messages.emailHistoryTableSectionButtonHeader)}
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages.emailHistoryTableSectionButtonHeader)}</p>
|
||||
<StatefulButton
|
||||
className="btn btn-outline-primary mb-2"
|
||||
variant="outline-primary"
|
||||
type="submit"
|
||||
onClick={async () => { await fetchSentEmailHistoryData(); }}
|
||||
onClick={async () => {
|
||||
await fetchSentEmailHistoryData();
|
||||
}}
|
||||
labels={{
|
||||
default: `${intl.formatMessage(messages.emailHistoryTableSectionButton)}`,
|
||||
pending: `${intl.formatMessage(messages.emailHistoryTableSectionButton)}`,
|
||||
@@ -209,7 +199,7 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
>
|
||||
{intl.formatMessage(messages.emailHistoryTableSectionButton)}
|
||||
</StatefulButton>
|
||||
{ showHistoricalEmailContentTable && (
|
||||
{showHistoricalEmailContentTable && (
|
||||
<BulkEmailTaskManagerTable
|
||||
errorRetrievingData={errorRetrievingData}
|
||||
tableData={transformDataForTable()}
|
||||
@@ -230,6 +220,7 @@ BulkEmailContentHistory.propTypes = {
|
||||
row: PropTypes.shape({
|
||||
index: PropTypes.number,
|
||||
}),
|
||||
copyTextToEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
BulkEmailContentHistory.defaultProps = {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import BulkEmailContentHistory from './BulkEmailContentHistory';
|
||||
import BulkEmailPendingTasks from './BulkEmailPendingTasks';
|
||||
import BulkEmailTaskHistory from './BulkEmailTaskHistory';
|
||||
import messages from './messages';
|
||||
|
||||
export function BulkEmailTaskManager({ intl }) {
|
||||
export function BulkEmailTaskManager({ intl, copyTextToEditor }) {
|
||||
return (
|
||||
<div className="px-5">
|
||||
<div>
|
||||
@@ -19,7 +20,7 @@ export function BulkEmailTaskManager({ intl }) {
|
||||
<h2 className="h3">
|
||||
{intl.formatMessage(messages.emailTaskHistoryHeader)}
|
||||
</h2>
|
||||
<BulkEmailContentHistory />
|
||||
<BulkEmailContentHistory copyTextToEditor={copyTextToEditor} />
|
||||
</div>
|
||||
<div>
|
||||
<BulkEmailTaskHistory />
|
||||
@@ -30,6 +31,7 @@ export function BulkEmailTaskManager({ intl }) {
|
||||
|
||||
BulkEmailTaskManager.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
copyTextToEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailTaskManager);
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('BulkEmailContentHistory component', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test('renders correctly', async () => {
|
||||
render(<BulkEmailContentHistory />);
|
||||
render(<BulkEmailContentHistory copyTextToEditor={jest.fn()} />);
|
||||
const tableDescription = await screen.findByText(
|
||||
'To see the content of previously sent emails, click this button:',
|
||||
);
|
||||
@@ -33,7 +33,7 @@ describe('BulkEmailContentHistory component', () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
|
||||
render(<BulkEmailContentHistory />);
|
||||
render(<BulkEmailContentHistory copyTextToEditor={jest.fn()} />);
|
||||
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
@@ -68,7 +68,7 @@ describe('BulkEmailContentHistory component', () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
|
||||
render(<BulkEmailContentHistory />);
|
||||
render(<BulkEmailContentHistory copyTextToEditor={jest.fn()} />);
|
||||
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
@@ -101,7 +101,7 @@ describe('BulkEmailContentHistory component', () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(0);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
// render the component
|
||||
render(<BulkEmailContentHistory />);
|
||||
render(<BulkEmailContentHistory copyTextToEditor={jest.fn()} />);
|
||||
// press the `show sent email history` button to initiate data retrieval
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
@@ -117,7 +117,7 @@ describe('BulkEmailContentHistory component', () => {
|
||||
throw new Error();
|
||||
});
|
||||
// render the component
|
||||
render(<BulkEmailContentHistory />);
|
||||
render(<BulkEmailContentHistory copyTextToEditor={jest.fn()} />);
|
||||
// press the `show sent email history` button to initiate data retrieval
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
|
||||
Reference in New Issue
Block a user