Compare commits
7 Commits
master
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41bd035132 | ||
|
|
eae2037d20 | ||
|
|
39c18ac4fa | ||
|
|
a4b7b5f28d | ||
|
|
42797240df | ||
|
|
ce1e7d040e | ||
|
|
3e7ea6a3e5 |
@@ -1,8 +0,0 @@
|
|||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
|
|
||||||
[o:open-edx:p:edx-platform:r:frontend-app-communications]
|
|
||||||
file_filter = src/i18n/messages/<lang>.json
|
|
||||||
source_file = src/i18n/transifex_input.json
|
|
||||||
source_lang = en
|
|
||||||
type = KEYVALUEJSON
|
|
||||||
31
Makefile
31
Makefile
@@ -1,6 +1,3 @@
|
|||||||
export TRANSIFEX_RESOURCE = frontend-app-communications
|
|
||||||
transifex_langs = "ar,fr,es_419,zh_CN"
|
|
||||||
|
|
||||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||||
i18n = ./src/i18n
|
i18n = ./src/i18n
|
||||||
@@ -32,35 +29,17 @@ detect_changed_source_translations:
|
|||||||
# Checking for changed translations...
|
# Checking for changed translations...
|
||||||
git diff --exit-code $(i18n)
|
git diff --exit-code $(i18n)
|
||||||
|
|
||||||
# Pushes translations to Transifex. You must run make extract_translations first.
|
|
||||||
push_translations:
|
|
||||||
# Pushing strings to Transifex...
|
|
||||||
tx push -s
|
|
||||||
# Fetching hashes from Transifex...
|
|
||||||
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
|
||||||
# Writing out comments to file...
|
|
||||||
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
|
||||||
# Pushing comments to Transifex...
|
|
||||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
|
||||||
|
|
||||||
ifeq ($(OPENEDX_ATLAS_PULL),)
|
|
||||||
# Pulls translations from Transifex.
|
|
||||||
pull_translations:
|
|
||||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
|
||||||
else
|
|
||||||
# Experimental: OEP-58 Pulls translations using atlas
|
|
||||||
pull_translations:
|
pull_translations:
|
||||||
rm -rf src/i18n/messages
|
rm -rf src/i18n/messages
|
||||||
mkdir src/i18n/messages
|
mkdir src/i18n/messages
|
||||||
cd src/i18n/messages \
|
cd src/i18n/messages \
|
||||||
&& atlas pull --filter=$(transifex_langs) \
|
&& atlas pull \
|
||||||
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
||||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
||||||
translations/paragon/src/i18n/messages:paragon \
|
translations/paragon/src/i18n/messages:paragon \
|
||||||
translations/frontend-app-communications/src/i18n/messages:frontend-app-communications
|
translations/frontend-app-communications/src/i18n/messages:frontend-app-communications
|
||||||
|
|
||||||
$(intl_imports) frontend-component-header frontend-component-footer paragon frontend-app-communications
|
$(intl_imports) frontend-component-header frontend-component-footer paragon frontend-app-communications
|
||||||
endif
|
|
||||||
|
|
||||||
# This target is used by Travis.
|
# This target is used by Travis.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -12,7 +12,8 @@
|
|||||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||||
"@edx/frontend-component-footer": "12.2.0",
|
"@edx/frontend-component-footer": "12.2.0",
|
||||||
"@edx/frontend-component-header": "4.6.0",
|
"@edx/frontend-component-header": "4.6.0",
|
||||||
"@edx/frontend-platform": "5.0.0",
|
"@edx/frontend-platform": "5.5.2",
|
||||||
|
"@edx/openedx-atlas": "^0.5.0",
|
||||||
"@edx/paragon": "^20.44.0",
|
"@edx/paragon": "^20.44.0",
|
||||||
"@edx/tinymce-language-selector": "1.1.0",
|
"@edx/tinymce-language-selector": "1.1.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
@@ -3020,8 +3021,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-platform": {
|
"node_modules/@edx/frontend-platform": {
|
||||||
"version": "5.0.0",
|
"version": "5.5.2",
|
||||||
"license": "AGPL-3.0",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-5.5.2.tgz",
|
||||||
|
"integrity": "sha512-cbUvWcFL/mTc7eypBS/BnCojgWDcJCe3h3ffb3GD7F+Y4ysrFBJYf031qPcgmWNUrN30452dR7r1+sqE7uVvYA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cospired/i18n-iso-languages": "4.1.0",
|
"@cospired/i18n-iso-languages": "4.1.0",
|
||||||
"@formatjs/intl-pluralrules": "4.3.3",
|
"@formatjs/intl-pluralrules": "4.3.3",
|
||||||
@@ -3049,7 +3051,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edx/frontend-build": ">= 8.1.0 || ^12.9.0-alpha.1",
|
"@edx/frontend-build": ">= 8.1.0 || ^12.9.0-alpha.1",
|
||||||
"@edx/paragon": ">= 10.0.0 < 21.0.0",
|
"@edx/paragon": ">= 10.0.0 < 22.0.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.9.0 || ^17.0.0",
|
"react": "^16.9.0 || ^17.0.0",
|
||||||
"react-dom": "^16.9.0 || ^17.0.0",
|
"react-dom": "^16.9.0 || ^17.0.0",
|
||||||
@@ -3065,6 +3067,14 @@
|
|||||||
"@newrelic/publish-sourcemap": "^5.0.1"
|
"@newrelic/publish-sourcemap": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@edx/openedx-atlas": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-wgcquAVj2BVw1rS7m1LZzUg6wVku37gs/Ks14LPWefpEIQ0HcTFV4nkg8dWfGX5dP3GiEp8s1Up7XwXzlErQhQ==",
|
||||||
|
"bin": {
|
||||||
|
"atlas": "atlas"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@edx/paragon": {
|
"node_modules/@edx/paragon": {
|
||||||
"version": "20.46.2",
|
"version": "20.46.2",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.46.2.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.46.2.tgz",
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||||
"@edx/frontend-component-footer": "12.2.0",
|
"@edx/frontend-component-footer": "12.2.0",
|
||||||
"@edx/frontend-component-header": "4.6.0",
|
"@edx/frontend-component-header": "4.6.0",
|
||||||
"@edx/frontend-platform": "5.0.0",
|
"@edx/frontend-platform": "5.5.2",
|
||||||
|
"@edx/openedx-atlas": "^0.5.0",
|
||||||
"@edx/paragon": "^20.44.0",
|
"@edx/paragon": "^20.44.0",
|
||||||
"@edx/tinymce-language-selector": "1.1.0",
|
"@edx/tinymce-language-selector": "1.1.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<title>Communications | <%= process.env.SITE_NAME %></title>
|
<title>Communications | <%= process.env.SITE_NAME %></title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ export default function BulkEmailTool() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<BulkEmailForm courseId={courseId} cohorts={courseMetadata.cohorts} />
|
<BulkEmailForm
|
||||||
|
courseId={courseId}
|
||||||
|
cohorts={courseMetadata.cohorts}
|
||||||
|
courseModes={courseMetadata.courseModes}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="row py-5">
|
<div className="row py-5">
|
||||||
<BulkEmailTaskManager courseId={courseId} />
|
<BulkEmailTaskManager courseId={courseId} />
|
||||||
|
|||||||
@@ -47,7 +47,12 @@ const FORM_ACTIONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function BulkEmailForm(props) {
|
function BulkEmailForm(props) {
|
||||||
const { courseId, cohorts, intl } = props;
|
const {
|
||||||
|
courseId,
|
||||||
|
cohorts,
|
||||||
|
courseModes,
|
||||||
|
intl,
|
||||||
|
} = props;
|
||||||
const [{ editor }, dispatch] = useContext(BulkEmailContext);
|
const [{ editor }, dispatch] = useContext(BulkEmailContext);
|
||||||
const [emailFormStatus, setEmailFormStatus] = useState(FORM_SUBMIT_STATES.DEFAULT);
|
const [emailFormStatus, setEmailFormStatus] = useState(FORM_SUBMIT_STATES.DEFAULT);
|
||||||
const [emailFormValidation, setEmailFormValidation] = useState({
|
const [emailFormValidation, setEmailFormValidation] = useState({
|
||||||
@@ -272,10 +277,14 @@ function BulkEmailForm(props) {
|
|||||||
handleCheckboxes={onRecipientChange}
|
handleCheckboxes={onRecipientChange}
|
||||||
additionalCohorts={cohorts}
|
additionalCohorts={cohorts}
|
||||||
isValid={emailFormValidation.recipients}
|
isValid={emailFormValidation.recipients}
|
||||||
|
courseModes={courseModes}
|
||||||
/>
|
/>
|
||||||
<Form.Group controlId="emailSubject">
|
<Form.Group controlId="emailSubject">
|
||||||
<Form.Label className="h3 text-primary-500">{intl.formatMessage(messages.bulkEmailSubjectLabel)}</Form.Label>
|
<Form.Label className="h3 text-primary-500">{intl.formatMessage(messages.bulkEmailSubjectLabel)}</Form.Label>
|
||||||
<Form.Control name="emailSubject" className="w-lg-50" onChange={onFormChange} value={editor.emailSubject} />
|
<Form.Control name="emailSubject" className="w-lg-50" onChange={onFormChange} value={editor.emailSubject} maxLength={128} />
|
||||||
|
<Form.Control.Feedback className="px-3" type="default">
|
||||||
|
{intl.formatMessage(messages.bulkEmailFormSubjectTip)}
|
||||||
|
</Form.Control.Feedback>
|
||||||
{!emailFormValidation.subject && (
|
{!emailFormValidation.subject && (
|
||||||
<Form.Control.Feedback className="px-3" hasIcon type="invalid">
|
<Form.Control.Feedback className="px-3" hasIcon type="invalid">
|
||||||
{intl.formatMessage(messages.bulkEmailFormSubjectError)}
|
{intl.formatMessage(messages.bulkEmailFormSubjectError)}
|
||||||
@@ -384,6 +393,12 @@ BulkEmailForm.propTypes = {
|
|||||||
courseId: PropTypes.string.isRequired,
|
courseId: PropTypes.string.isRequired,
|
||||||
cohorts: PropTypes.arrayOf(PropTypes.string),
|
cohorts: PropTypes.arrayOf(PropTypes.string),
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
|
courseModes: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
slug: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(BulkEmailForm);
|
export default injectIntl(BulkEmailForm);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ function ScheduleEmailForm(props) {
|
|||||||
const isMobile = useMobileResponsive();
|
const isMobile = useMobileResponsive();
|
||||||
const { isValid, onDateTimeChange, dateTime } = props;
|
const { isValid, onDateTimeChange, dateTime } = props;
|
||||||
const { date, time } = dateTime;
|
const { date, time } = dateTime;
|
||||||
|
const descriptionDate = new Date();
|
||||||
|
descriptionDate.setDate(new Date().getDate() + 1);
|
||||||
return (
|
return (
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<div className={classNames('d-flex', isMobile ? 'flex-column' : 'flex-row', 'my-3')}>
|
<div className={classNames('d-flex', isMobile ? 'flex-column' : 'flex-row', 'my-3')}>
|
||||||
@@ -30,7 +32,10 @@ function ScheduleEmailForm(props) {
|
|||||||
<small className="text-gray-500 x-small">
|
<small className="text-gray-500 x-small">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="bulk.email.form.schedule.date.description"
|
id="bulk.email.form.schedule.date.description"
|
||||||
defaultMessage="Enter a start date, e.g. 11/27/2023"
|
defaultMessage="Enter a start date, e.g. {date}"
|
||||||
|
values={{
|
||||||
|
date: descriptionDate.toLocaleDateString(),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,7 +57,10 @@ function ScheduleEmailForm(props) {
|
|||||||
<small className="text-gray-500 x-small">
|
<small className="text-gray-500 x-small">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="bulk.email.form.schedule.time.description"
|
id="bulk.email.form.schedule.time.description"
|
||||||
defaultMessage="Enter a start time, e.g. 09:00 AM"
|
defaultMessage="Enter a start time, e.g. {time}"
|
||||||
|
values={{
|
||||||
|
time: descriptionDate.toLocaleTimeString([], { timeStyle: 'short' }),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ const DEFAULT_GROUPS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function BulkEmailRecipient(props) {
|
export default function BulkEmailRecipient(props) {
|
||||||
const { handleCheckboxes, selectedGroups, additionalCohorts } = props;
|
const {
|
||||||
|
handleCheckboxes,
|
||||||
|
selectedGroups,
|
||||||
|
additionalCohorts,
|
||||||
|
courseModes,
|
||||||
|
} = props;
|
||||||
|
const hasCourseModes = courseModes && courseModes.length > 1;
|
||||||
return (
|
return (
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Label>
|
<Form.Label>
|
||||||
@@ -50,18 +56,24 @@ export default function BulkEmailRecipient(props) {
|
|||||||
description="A selectable choice from a list of potential email recipients"
|
description="A selectable choice from a list of potential email recipients"
|
||||||
/>
|
/>
|
||||||
</Form.Checkbox>
|
</Form.Checkbox>
|
||||||
<Form.Checkbox
|
{
|
||||||
key="track:verified"
|
// additional modes
|
||||||
value="track:verified"
|
hasCourseModes
|
||||||
disabled={selectedGroups.find((group) => group === DEFAULT_GROUPS.ALL_LEARNERS)}
|
&& courseModes.map((courseMode) => (
|
||||||
className="col col-lg-4 col-sm-6 col-12"
|
<Form.Checkbox
|
||||||
>
|
key={`track:${courseMode.slug}`}
|
||||||
<FormattedMessage
|
value={`track:${courseMode.slug}`}
|
||||||
id="bulk.email.form.recipients.verified"
|
disabled={selectedGroups.find((group) => group === DEFAULT_GROUPS.ALL_LEARNERS)}
|
||||||
defaultMessage="Learners in the verified certificate track"
|
className="col col-lg-4 col-sm-6 col-12"
|
||||||
description="A selectable choice from a list of potential email recipients"
|
>
|
||||||
/>
|
<FormattedMessage
|
||||||
</Form.Checkbox>
|
id="bulk.email.form.mode.label"
|
||||||
|
defaultMessage="Learners in the {courseModeName} Track"
|
||||||
|
values={{ courseModeName: courseMode.name }}
|
||||||
|
/>
|
||||||
|
</Form.Checkbox>
|
||||||
|
))
|
||||||
|
}
|
||||||
{
|
{
|
||||||
// additional cohorts
|
// additional cohorts
|
||||||
additionalCohorts
|
additionalCohorts
|
||||||
@@ -80,18 +92,6 @@ export default function BulkEmailRecipient(props) {
|
|||||||
</Form.Checkbox>
|
</Form.Checkbox>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<Form.Checkbox
|
|
||||||
key="track:audit"
|
|
||||||
value="track:audit"
|
|
||||||
disabled={selectedGroups.find((group) => group === DEFAULT_GROUPS.ALL_LEARNERS)}
|
|
||||||
className="col col-lg-4 col-sm-6 col-12"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="bulk.email.form.recipients.audit"
|
|
||||||
defaultMessage="Learners in the audit track"
|
|
||||||
description="A selectable choice from a list of potential email recipients"
|
|
||||||
/>
|
|
||||||
</Form.Checkbox>
|
|
||||||
<Form.Checkbox
|
<Form.Checkbox
|
||||||
key="learners"
|
key="learners"
|
||||||
value="learners"
|
value="learners"
|
||||||
@@ -127,4 +127,10 @@ BulkEmailRecipient.propTypes = {
|
|||||||
handleCheckboxes: PropTypes.func.isRequired,
|
handleCheckboxes: PropTypes.func.isRequired,
|
||||||
isValid: PropTypes.bool,
|
isValid: PropTypes.bool,
|
||||||
additionalCohorts: PropTypes.arrayOf(PropTypes.string),
|
additionalCohorts: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
courseModes: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
slug: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
).isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of course mode objects using Rosie Factory.
|
||||||
|
* @returns {Array<Object>} An array of course mode objects with attributes 'slug' and 'name'.
|
||||||
|
*/
|
||||||
|
const courseModeFactory = () => {
|
||||||
|
const AuditModeFactory = Factory.define('AuditModeFactory')
|
||||||
|
.attr('slug', 'audit')
|
||||||
|
.attr('name', 'Audit');
|
||||||
|
|
||||||
|
const VerifiedModeFactory = Factory.define('VerifiedModeFactory')
|
||||||
|
.attr('slug', 'verified')
|
||||||
|
.attr('name', 'Verified Certificate');
|
||||||
|
|
||||||
|
return [
|
||||||
|
AuditModeFactory.build(),
|
||||||
|
VerifiedModeFactory.build(),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default courseModeFactory;
|
||||||
@@ -41,6 +41,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Subject',
|
defaultMessage: 'Subject',
|
||||||
description: 'Email subject line input label. Meant to have colon or equivilant punctuation.',
|
description: 'Email subject line input label. Meant to have colon or equivilant punctuation.',
|
||||||
},
|
},
|
||||||
|
bulkEmailFormSubjectTip: {
|
||||||
|
id: 'bulk.email.form.subject.tip',
|
||||||
|
defaultMessage: '(Maximum 128 characters)',
|
||||||
|
description: 'Default Subject tip',
|
||||||
|
},
|
||||||
bulkEmailFormSubjectError: {
|
bulkEmailFormSubjectError: {
|
||||||
id: 'bulk.email.form.subject.error',
|
id: 'bulk.email.form.subject.error',
|
||||||
defaultMessage: 'A subject is required',
|
defaultMessage: 'A subject is required',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import * as bulkEmailFormApi from '../data/api';
|
|||||||
import { BulkEmailContext, BulkEmailProvider } from '../../bulk-email-context';
|
import { BulkEmailContext, BulkEmailProvider } from '../../bulk-email-context';
|
||||||
import { formatDate } from '../../../../utils/formatDateAndTime';
|
import { formatDate } from '../../../../utils/formatDateAndTime';
|
||||||
import cohortFactory from '../data/__factories__/bulkEmailFormCohort.factory';
|
import cohortFactory from '../data/__factories__/bulkEmailFormCohort.factory';
|
||||||
|
import courseModeFactory from '../data/__factories__/bulkEmailFormCourseMode.factory';
|
||||||
|
|
||||||
jest.mock('../../text-editor/TextEditor');
|
jest.mock('../../text-editor/TextEditor');
|
||||||
|
|
||||||
@@ -20,12 +21,17 @@ const dispatchMock = jest.fn();
|
|||||||
|
|
||||||
const tomorrow = new Date();
|
const tomorrow = new Date();
|
||||||
tomorrow.setDate(new Date().getDate() + 1);
|
tomorrow.setDate(new Date().getDate() + 1);
|
||||||
|
const courseMode = courseModeFactory();
|
||||||
|
|
||||||
function renderBulkEmailForm() {
|
function renderBulkEmailForm() {
|
||||||
const { cohorts } = cohortFactory.build();
|
const { cohorts } = cohortFactory.build();
|
||||||
return (
|
return (
|
||||||
<BulkEmailProvider>
|
<BulkEmailProvider>
|
||||||
<BulkEmailForm courseId="test" cohorts={cohorts} />
|
<BulkEmailForm
|
||||||
|
courseId="test"
|
||||||
|
cohorts={cohorts}
|
||||||
|
courseModes={courseMode}
|
||||||
|
/>
|
||||||
</BulkEmailProvider>
|
</BulkEmailProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -33,7 +39,7 @@ function renderBulkEmailForm() {
|
|||||||
function renderBulkEmailFormContext(value) {
|
function renderBulkEmailFormContext(value) {
|
||||||
return (
|
return (
|
||||||
<BulkEmailContext.Provider value={[value, dispatchMock]}>
|
<BulkEmailContext.Provider value={[value, dispatchMock]}>
|
||||||
<BulkEmailForm courseId="test" />
|
<BulkEmailForm courseId="test" courseMode={courseMode} />
|
||||||
</BulkEmailContext.Provider>
|
</BulkEmailContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -96,8 +102,8 @@ describe('bulk-email-form', () => {
|
|||||||
test('Checking "All Learners" disables each learner group', async () => {
|
test('Checking "All Learners" disables each learner group', async () => {
|
||||||
render(renderBulkEmailForm());
|
render(renderBulkEmailForm());
|
||||||
fireEvent.click(screen.getByRole('checkbox', { name: 'All Learners' }));
|
fireEvent.click(screen.getByRole('checkbox', { name: 'All Learners' }));
|
||||||
const verifiedLearners = screen.getByRole('checkbox', { name: 'Learners in the verified certificate track' });
|
const verifiedLearners = screen.getByRole('checkbox', { name: 'Learners in the Verified Certificate Track' });
|
||||||
const auditLearners = screen.getByRole('checkbox', { name: 'Learners in the audit track' });
|
const auditLearners = screen.getByRole('checkbox', { name: 'Learners in the Audit Track' });
|
||||||
const { cohorts } = cohortFactory.build();
|
const { cohorts } = cohortFactory.build();
|
||||||
cohorts.forEach(cohort => expect(screen.getByRole('checkbox', { name: `Cohort: ${cohort}` })).toBeDisabled());
|
cohorts.forEach(cohort => expect(screen.getByRole('checkbox', { name: `Cohort: ${cohort}` })).toBeDisabled());
|
||||||
expect(verifiedLearners).toBeDisabled();
|
expect(verifiedLearners).toBeDisabled();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function flattenScheduledEmailsArray(emails) {
|
|||||||
emailId: email.courseEmail.id,
|
emailId: email.courseEmail.id,
|
||||||
task: email.task,
|
task: email.task,
|
||||||
taskDue: new Date(email.taskDue).toLocaleString(),
|
taskDue: new Date(email.taskDue).toLocaleString(),
|
||||||
|
taskDueUTC: email.taskDue,
|
||||||
...email.courseEmail,
|
...email.courseEmail,
|
||||||
targets: email.courseEmail.targets.join(', '),
|
targets: email.courseEmail.targets.join(', '),
|
||||||
}));
|
}));
|
||||||
@@ -91,10 +92,10 @@ function BulkEmailScheduledEmailsTable({ intl }) {
|
|||||||
const handleEditEmail = (row) => {
|
const handleEditEmail = (row) => {
|
||||||
const {
|
const {
|
||||||
original: {
|
original: {
|
||||||
htmlMessage: emailBody, subject: emailSubject, taskDue, targets, schedulingId, emailId,
|
htmlMessage: emailBody, subject: emailSubject, taskDueUTC, targets, schedulingId, emailId,
|
||||||
},
|
},
|
||||||
} = row;
|
} = row;
|
||||||
const dateTime = new Date(taskDue);
|
const dateTime = new Date(taskDueUTC);
|
||||||
const emailRecipients = targets.replaceAll('-', ':').split(', ');
|
const emailRecipients = targets.replaceAll('-', ':').split(', ');
|
||||||
const scheduleDate = formatDate(dateTime);
|
const scheduleDate = formatDate(dateTime);
|
||||||
const scheduleTime = formatTime(dateTime);
|
const scheduleTime = formatTime(dateTime);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function PageContainer(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
org, number, title, tabs, originalUserIsStaff,
|
org, number, title, tabs, originalUserIsStaff, courseModes,
|
||||||
} = metadataResponse;
|
} = metadataResponse;
|
||||||
const { cohorts } = cohortsResponse;
|
const { cohorts } = cohortsResponse;
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ export default function PageContainer(props) {
|
|||||||
number,
|
number,
|
||||||
title,
|
title,
|
||||||
originalUserIsStaff,
|
originalUserIsStaff,
|
||||||
|
courseModes,
|
||||||
tabs: [...tabs],
|
tabs: [...tabs],
|
||||||
cohorts: cohorts.map(({ name }) => name),
|
cohorts: cohorts.map(({ name }) => name),
|
||||||
});
|
});
|
||||||
@@ -66,9 +67,11 @@ export default function PageContainer(props) {
|
|||||||
courseNumber={courseMetadata.number}
|
courseNumber={courseMetadata.number}
|
||||||
courseTitle={courseMetadata.title}
|
courseTitle={courseMetadata.title}
|
||||||
/>
|
/>
|
||||||
<main>
|
<div className="pb-3 container">
|
||||||
{children}
|
<main>
|
||||||
</main>
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
</CourseMetadataContext.Provider>
|
</CourseMetadataContext.Provider>
|
||||||
|
|||||||
@@ -21,20 +21,18 @@ subscribe(APP_READY, () => {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="pb-3 container">
|
<Routes>
|
||||||
<Routes>
|
<Route
|
||||||
<Route
|
path="/courses/:courseId/bulk_email"
|
||||||
path="/courses/:courseId/bulk_email"
|
element={(
|
||||||
element={(
|
<AuthenticatedPageRoute>
|
||||||
<AuthenticatedPageRoute>
|
<PageContainer>
|
||||||
<PageContainer>
|
<BulkEmailTool />
|
||||||
<BulkEmailTool />
|
</PageContainer>
|
||||||
</PageContainer>
|
</AuthenticatedPageRoute>
|
||||||
</AuthenticatedPageRoute>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
</Routes>
|
||||||
</Routes>
|
|
||||||
</div>
|
|
||||||
</AppProvider>,
|
</AppProvider>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user