feat: [Microba-1620] Add email form to BulkEmailTool (#8)

* feat: [MB-1620] Add email body editor

Add email body and subject line to bulk email tool.
This commit is contained in:
Thomas Tracy
2022-01-12 15:26:43 -05:00
committed by GitHub
parent ab56f12087
commit ab274d1ea9
6 changed files with 180 additions and 5 deletions

19
package-lock.json generated
View File

@@ -7176,6 +7176,15 @@
"loader-utils": "^2.0.0"
}
},
"@tinymce/tinymce-react": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-3.13.0.tgz",
"integrity": "sha512-8+OHYIUP9W5D5z7gknausaG48ovQtEvjcK4c+zpha7QppRVVX0ltaINpo10V6Vb4qj9Jf7ZFfZpMRxxcFL2YvQ==",
"requires": {
"prop-types": "^15.6.2",
"tinymce": "^5.5.1"
}
},
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -22486,6 +22495,16 @@
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tinymce": {
"version": "5.10.2",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.10.2.tgz",
"integrity": "sha512-5QhnZ6c8F28fYucLLc00MM37fZoAZ4g7QCYzwIl38i5TwJR5xGqzOv6YMideyLM4tytCzLCRwJoQen2LI66p5A=="
},
"tinymce-language-selector": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tinymce-language-selector/-/tinymce-language-selector-1.0.4.tgz",
"integrity": "sha512-Ko2n2oRFGIZAtWqoG8O8B5XHxZEWZ/zRabPZ5rVeMzDTZhnbVX9oOgW+Ie18PRNMTNtOpI15OUhvrT07c0rVkw=="
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

View File

@@ -45,6 +45,8 @@
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.1.16",
"@tinymce/tinymce-react": "^3.13.0",
"classnames": "^2.3.1",
"core-js": "3.15.2",
"prop-types": "15.7.2",
"react": "16.14.0",
@@ -53,7 +55,9 @@
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"redux": "4.1.2",
"regenerator-runtime": "0.13.9"
"regenerator-runtime": "0.13.9",
"tinymce": "^5.10.2",
"tinymce-language-selector": "^1.0.4"
},
"devDependencies": {
"@edx/frontend-build": "8.1.6",

View File

@@ -1,5 +1,44 @@
import React from 'react';
import React, { useRef } from 'react';
import { Button, Form } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import TextEditor from './TextEditor';
export default function BulkEmailBody() {
return <div />;
const editorRef = useRef(null);
const onInit = (event, editor) => { editorRef.current = editor; };
return (
<div className="w-100 m-auto p-lg-4 p-2.5">
<Form>
<Form.Group controlId="emailSubject">
<Form.Label>
<FormattedMessage
id="bulk.email.subject.label"
defaultMessage="Subject:"
description="Email subject line input label. Meant to have colon or equivilant punctuation."
/>
</Form.Label>
<Form.Control className="w-lg-50" />
</Form.Group>
<Form.Group controlId="emailBody">
<Form.Label>
<FormattedMessage
id="bulk.email.body.label"
defaultMessage="Body:"
description="Email Body label. Meant to have colon or equivilant punctuation."
/>
</Form.Label>
<TextEditor onInit={onInit} />
</Form.Group>
<Button variant="primary" type="submit">
<FormattedMessage
id="bulk.email.submit.button"
defaultMessage="Submit"
description="Submit/Send email button"
/>
</Button>
</Form>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import { useParams } from 'react-router-dom';
import { Spinner } from '@edx/paragon';
@@ -8,15 +9,26 @@ import BulkEmailBody from './BulkEmailBody';
import BulkEmailTaskManager from './bulk-email-task-manager/BulkEmailTaskManager';
import Navigationtabs from '../navigation-tabs/NavigationTabs';
import { getCourseHomeCourseMetadata } from './api';
import useMobileResponsive from '../../utils/useMobileResponsive';
export default function BulkEmailTool() {
const { courseId } = useParams();
const [courseMetadata, setCourseMetadata] = useState();
const isMobile = useMobileResponsive();
useEffect(() => {
async function fetchTabData() {
const data = await getCourseHomeCourseMetadata(courseId);
let data;
try {
data = await getCourseHomeCourseMetadata(courseId);
} catch (e) {
setCourseMetadata({
isStaff: false,
tabs: [],
});
return;
}
const { tabs, is_staff: isStaff } = data;
setCourseMetadata({
isStaff,
@@ -31,7 +43,7 @@ export default function BulkEmailTool() {
courseMetadata.isStaff ? (
<div>
<Navigationtabs courseId={courseId} tabData={courseMetadata.tabs} />
<div className="border border-primary-200">
<div className={classnames({ 'border border-primary-200': !isMobile })}>
<div className="row">
<BulkEmailRecepient courseId={courseId} />
</div>

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { Editor } from '@tinymce/tinymce-react';
import PropTypes from 'prop-types';
import 'tinymce';
import 'tinymce/icons/default';
import 'tinymce/themes/silver';
import 'tinymce/skins/ui/oxide/skin.css';
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/code';
import 'tinymce/plugins/emoticons';
import 'tinymce/plugins/emoticons/js/emojis';
import 'tinymce/plugins/link';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/table';
import 'tinymce-language-selector';
import contentUiCss from 'tinymce/skins/ui/oxide/content.css';
import contentCss from 'tinymce/skins/content/default/content.css';
export default function TextEditor(props) {
const {
onChange, onKeyUp, onInit, disabled,
} = props;
return (
<Editor
initialValue=""
init={{
selector: 'textarea#editor',
height: 600,
plugins: 'advlist code emoticons link lists table',
toolbar: 'bold italic | bullist numlist | link emoticons',
skin: false,
content_css: false,
content_style: `${contentUiCss.toString()}\n${contentCss.toString()}`,
extended_valid_elements: 'span[lang|id] -span',
}}
onChange={onChange}
onKeyUp={onKeyUp}
onInit={onInit}
disabled={disabled}
/>
);
}
TextEditor.defaultProps = {
onChange: () => {},
onKeyUp: () => {},
onInit: () => {},
disabled: false,
};
TextEditor.propTypes = {
onChange: PropTypes.func,
onKeyUp: PropTypes.func,
onInit: PropTypes.func,
disabled: PropTypes.bool,
};

View File

@@ -0,0 +1,42 @@
import { useState, useEffect } from 'react';
// NOTE: These are the breakpoints used in Bootstrap v4.0.0 as seen in
// the documentation (https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints)
const breakpoints = {
extraSmall: {
maxWidth: 575.98,
},
small: {
minWidth: 576,
maxWidth: 767.98,
},
medium: {
minWidth: 768,
maxWidth: 991.98,
},
large: {
minWidth: 992,
maxWidth: 1199.98,
},
extraLarge: {
minWidth: 1200,
},
};
/**
* A react hook used to determine if the current window is mobile or not.
* returns true if the window is of mobile size.
*/
export default function useMobileResponsive(breakpoint) {
const [isMobileWindow, setIsMobileWindow] = useState();
const checkForMobile = () => {
setIsMobileWindow(window.matchMedia(`(max-width: ${breakpoint || breakpoints.small.maxWidth}px)`).matches);
};
useEffect(() => {
checkForMobile();
window.addEventListener('resize', checkForMobile);
// return this function here to clean up the event listener
return () => window.removeEventListener('resize', checkForMobile);
}, []);
return isMobileWindow;
}