Compare commits

...

33 Commits

Author SHA1 Message Date
Bilal Qamar
0f9e520481 refactor: updated package-lock 2022-12-29 12:08:41 +05:00
Bilal Qamar
9eeda23ceb Merge branch 'master' of https://github.com/edx/frontend-app-communications into bilalqamar95/frontend-build-upgrade 2022-12-29 12:01:43 +05:00
Mashal Malik
4231093347 Merge pull request #117 from Mashal-m/mashal-m/major-paragon-version-upgrade
build: major version upgrade of paragon
2022-12-28 11:43:57 +05:00
Muhammad Abdullah Waheed
b5b90272f8 refactor: updated renovate config to auto update minor and patch versions of edx dependencies (#74) 2022-12-20 13:19:32 +05:00
mashal-m
a08d30fbbb build: major version upgrade of paragon 2022-12-20 12:37:21 +05:00
mashal-m
ece65c83ad build: use shared browserslist configuration 2022-12-16 12:38:58 -05:00
renovate[bot]
2d9d195936 fix(deps): update dependency tinymce to v5.10.7 2022-12-12 12:22:24 +00:00
renovate[bot]
1082b27647 chore(deps): update dependency prettier to v2.8.1 2022-12-12 09:54:24 +00:00
Tim McCormack
9782cf108f build: Remove community-engineering CODEOWNERS (#112)
Team no longer exists. See <https://github.com/edx/edx-arch-experiments/issues/132>.
2022-12-09 19:41:48 +00:00
renovate[bot]
466fac7e9e fix(deps): update dependency @edx/frontend-component-header to v3.5.0 2022-12-05 11:02:31 +00:00
renovate[bot]
422632c582 chore(deps): update dependency prettier to v2.8.0 2022-11-28 09:19:27 +00:00
renovate[bot]
67b6512288 fix(deps): update dependency regenerator-runtime to v0.13.11 2022-11-21 10:26:48 +00:00
renovate[bot]
13ba06fd2a fix(deps): update dependency @edx/frontend-component-header to v3.4.1 2022-11-15 00:07:05 +00:00
renovate[bot]
61a2a4e8c9 fix(deps): update dependency core-js to v3.26.1 2022-11-14 08:47:19 +00:00
renovate[bot]
e112c3a6d1 fix(deps): update react-router monorepo to v5.3.4 2022-11-07 11:41:03 +00:00
renovate[bot]
97a21b9574 fix(deps): update dependency core-js to v3.26.0 2022-11-07 08:15:48 +00:00
Bilal Qamar
fe9d201bd5 refactor: pinned frontend-build version 2022-10-31 13:11:09 +05:00
Zubair Shakoor
22675fd17a fix: -t flag added in pull translation command (#100) 2022-10-28 14:54:27 +05:00
Bilal Qamar
3586fab6b1 refactor: updated frontend-build & resolved eslint issues 2022-10-25 15:43:19 +05:00
Bilal Qamar
8a6cd7937a Merge branch 'master' of https://github.com/edx/frontend-app-communications into bilalqamar95/frontend-build-upgrade 2022-10-25 15:20:58 +05:00
renovate[bot]
32327cde93 fix(deps): update dependency @edx/frontend-component-header to v3.3.0 2022-10-24 11:00:17 +00:00
renovate[bot]
dadbfed8e1 fix(deps): update dependency tinymce to v5.10.6 2022-10-24 07:14:55 +00:00
renovate[bot]
262ea5be0d fix(deps): update dependency redux to v4.2.0 2022-10-17 10:57:56 +00:00
renovate[bot]
fcb393d9e7 fix(deps): update dependency regenerator-runtime to v0.13.10 2022-10-17 07:58:39 +00:00
Bilal Qamar
c6563d8ef6 refactor: removed ignored rules from eslintrc & correct eslint issues 2022-09-12 16:38:42 +05:00
Bilal Qamar
f14e80099e refactor: pinned frontend-build version & ignored jsx-no-constructed-context-values for tests 2022-09-08 19:32:57 +05:00
Bilal Qamar
4977918e65 refactor: resolved merge conflicts with master branch 2022-09-01 12:33:47 +05:00
Bilal Qamar
4e2577191d refactor: resolved eslint issues after merge with master 2022-08-24 17:06:13 +05:00
Bilal Qamar
b849006b34 refactor: resolved merge conflicts with master 2022-08-24 17:01:24 +05:00
Bilal Qamar
c3088d986e refactor: resolved merge conflicts with master 2022-08-10 16:30:51 +05:00
Bilal Qamar
4b7068b49e refactor: updated eslintrc & resolved unstable nested component error 2022-08-05 16:34:29 +05:00
Bilal Qamar
bb28674c1b refactor: resolved eslint issues 2022-08-05 16:34:19 +05:00
Bilal Qamar
91e4e4b9df refactor: updated frontend-build to v12 2022-08-05 16:33:02 +05:00
36 changed files with 14410 additions and 16214 deletions

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');
module.exports = createConfig('eslint');

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @edx/community-engineering

View File

@@ -33,7 +33,5 @@ jobs:
run: npm run build
- name: i18n_extract
run: npm run i18n_extract
- name: is-es5
run: npm run is-es5
- name: Coverage
uses: codecov/codecov-action@v2

View File

@@ -46,7 +46,7 @@ push_translations:
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --languages=$(transifex_langs)
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:

30159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,13 +7,11 @@
"url": "git+https://github.com/edx/frontend-app-communications.git"
},
"browserslist": [
"last 2 versions",
"ie 11"
"extends @edx/browserslist-config"
],
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"is-es5": "es-check es5 ./dist/*.js",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
@@ -37,9 +35,9 @@
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "11.2.1",
"@edx/frontend-component-header": "3.2.1",
"@edx/frontend-component-header": "3.5.0",
"@edx/frontend-platform": "2.6.2",
"@edx/paragon": "19.25.3",
"@edx/paragon": "^20.20.0",
"@edx/tinymce-language-selector": "1.1.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
@@ -49,30 +47,30 @@
"@tinymce/tinymce-react": "3.14.0",
"axios": "0.27.2",
"classnames": "2.3.2",
"core-js": "3.25.5",
"core-js": "3.26.1",
"jquery": "3.6.1",
"popper.js": "1.16.1",
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.9",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"redux": "4.1.2",
"regenerator-runtime": "0.13.9",
"tinymce": "5.10.5"
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
"redux": "4.2.0",
"regenerator-runtime": "0.13.11",
"tinymce": "5.10.7"
},
"devDependencies": {
"@edx/frontend-build": "9.2.2",
"@edx/browserslist-config": "^1.1.0",
"@edx/frontend-build": "12.3.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"axios-mock-adapter": "1.21.2",
"codecov": "3.8.3",
"es-check": "6.2.1",
"glob": "7.2.3",
"husky": "7.0.4",
"jest": "27.5.1",
"prettier": "2.7.1",
"prettier": "2.8.1",
"reactifex": "1.1.1",
"rosie": "2.1.0"
}

View File

@@ -22,6 +22,11 @@
"pin"
],
"automerge": true
},
{
"matchPackagePatterns": ["@edx"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
],
"timezone": "America/New_York"

View File

@@ -12,7 +12,7 @@ import { CourseMetadataContext } from '../page-container/PageContainer';
import { BulkEmailProvider } from './bulk-email-context';
import BackToInstructor from '../navigation-tabs/BackToInstructor';
export default function BulkEmailTool() {
const BulkEmailTool = () => {
const { courseId } = useParams();
return (
@@ -46,4 +46,6 @@ export default function BulkEmailTool() {
))}
</CourseMetadataContext.Consumer>
);
}
};
export default BulkEmailTool;

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import useAsyncReducer, { combineReducers } from '../../../utils/useAsyncReducer';
import editor, { editorInitialState } from '../bulk-email-form/data/reducer';
@@ -8,7 +8,7 @@ import scheduledEmailsTable, {
export const BulkEmailContext = React.createContext();
export default function BulkEmailProvider({ children }) {
const BulkEmailProvider = ({ children }) => {
const initialState = {
editor: editorInitialState,
scheduledEmailsTable: scheduledEmailsTableInitialState,
@@ -17,9 +17,14 @@ export default function BulkEmailProvider({ children }) {
combineReducers({ editor, scheduledEmailsTable }),
initialState,
);
return <BulkEmailContext.Provider value={[state, dispatch]}>{children}</BulkEmailContext.Provider>;
}
const contextValue = useMemo(() => ([state, dispatch]), [dispatch, state]);
return <BulkEmailContext.Provider value={contextValue}>{children}</BulkEmailContext.Provider>;
};
BulkEmailProvider.propTypes = {
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
};
export default BulkEmailProvider;

View File

@@ -45,7 +45,52 @@ const FORM_ACTIONS = {
PATCH: 'PATCH',
};
function BulkEmailForm(props) {
const AlertMessage = (intl, editor, isScheduled) => (
<>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}</p>
<ul className="list-unstyled">
{editor.emailRecipients.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
{!isScheduled && (
<p>
<strong>{intl.formatMessage(messages.bulkEmailInstructionsCaution)}</strong>
{intl.formatMessage(messages.bulkEmailInstructionsCautionMessage)}
</p>
)}
</>
);
const EditMessage = (intl, editor, isScheduled) => (
<>
<p>
{intl.formatMessage(messages.bulkEmailTaskAlertEditingDate, {
dateTime: new Date(`${editor.scheduleDate} ${editor.scheduleTime}`).toLocaleString(),
})}
</p>
<p>
{intl.formatMessage(messages.bulkEmailTaskAlertEditingSubject, {
subject: editor.emailSubject,
})}
</p>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}</p>
<ul className="list-unstyled">
{editor.emailRecipients.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}</p>
{!isScheduled && (
<p>
<strong>{intl.formatMessage(messages.bulkEmailInstructionsCaution)}</strong>
{intl.formatMessage(messages.bulkEmailInstructionsCautionMessage)}
</p>
)}
</>
);
const BulkEmailForm = (props) => {
const { courseId, cohorts, intl } = props;
const [{ editor }, dispatch] = useContext(BulkEmailContext);
const [emailFormStatus, setEmailFormStatus] = useState(FORM_SUBMIT_STATES.DEFAULT);
@@ -205,58 +250,15 @@ function BulkEmailForm(props) {
} else {
setEmailFormStatus(FORM_SUBMIT_STATES.DEFAULT);
}
}, [isScheduled, editor.editMode, editor.isLoading, editor.errorRetrievingData, editor.formComplete]);
const AlertMessage = () => (
<>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}</p>
<ul className="list-unstyled">
{editor.emailRecipients.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
{!isScheduled && (
<p>
<strong>{intl.formatMessage(messages.bulkEmailInstructionsCaution)}</strong>
{intl.formatMessage(messages.bulkEmailInstructionsCautionMessage)}
</p>
)}
</>
);
const EditMessage = () => (
<>
<p>
{intl.formatMessage(messages.bulkEmailTaskAlertEditingDate, {
dateTime: new Date(`${editor.scheduleDate} ${editor.scheduleTime}`).toLocaleString(),
})}
</p>
<p>
{intl.formatMessage(messages.bulkEmailTaskAlertEditingSubject, {
subject: editor.emailSubject,
})}
</p>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}</p>
<ul className="list-unstyled">
{editor.emailRecipients.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}</p>
{!isScheduled && (
<p>
<strong>{intl.formatMessage(messages.bulkEmailInstructionsCaution)}</strong>
{intl.formatMessage(messages.bulkEmailInstructionsCautionMessage)}
</p>
)}
</>
);
}, [isScheduled, editor.editMode, editor.isLoading, editor.errorRetrievingData,
editor.formComplete, delayedEmailFormReset]);
return (
<div className={classNames('w-100 m-auto', !isMobile && 'p-4 border border-primary-200')}>
<TaskAlertModal
isOpen={isTaskAlertOpen}
alertMessage={editor.editMode ? EditMessage() : AlertMessage()}
alertMessage={editor.editMode
? EditMessage(intl, editor, isScheduled) : AlertMessage(intl, editor, isScheduled)}
close={(event) => {
closeTaskAlert();
if (event.target.name === 'continue') {
@@ -372,7 +374,7 @@ function BulkEmailForm(props) {
</Form>
</div>
);
}
};
BulkEmailForm.defaultProps = {
cohorts: [],

View File

@@ -5,7 +5,7 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import useMobileResponsive from '../../../utils/useMobileResponsive';
function ScheduleEmailForm(props) {
const ScheduleEmailForm = (props) => {
const isMobile = useMobileResponsive();
const { isValid, onDateTimeChange, dateTime } = props;
const { date, time } = dateTime;
@@ -68,7 +68,7 @@ function ScheduleEmailForm(props) {
)}
</Form.Group>
);
}
};
ScheduleEmailForm.defaultProps = {
dateTime: {

View File

@@ -13,7 +13,7 @@ const DEFAULT_GROUPS = {
AUDIT: 'track:audit',
};
export default function BulkEmailRecipient(props) {
const BulkEmailRecipient = (props) => {
const { handleCheckboxes, selectedGroups, additionalCohorts } = props;
return (
<Form.Group>
@@ -115,7 +115,7 @@ export default function BulkEmailRecipient(props) {
)}
</Form.Group>
);
}
};
BulkEmailRecipient.defaultProps = {
isValid: true,
@@ -128,3 +128,5 @@ BulkEmailRecipient.propTypes = {
isValid: PropTypes.bool,
additionalCohorts: PropTypes.arrayOf(PropTypes.string),
};
export default BulkEmailRecipient;

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailRecipient';

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailForm';

View File

@@ -32,7 +32,7 @@ function renderBulkEmailForm() {
function renderBulkEmailFormContext(value) {
return (
<BulkEmailContext.Provider value={[value, dispatchMock]}>
<BulkEmailContext.Provider value={value}>
<BulkEmailForm courseId="test" />
</BulkEmailContext.Provider>
);
@@ -144,7 +144,7 @@ describe('bulk-email-form', () => {
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onPatch().reply(200);
render(
renderBulkEmailFormContext({
renderBulkEmailFormContext([{
editor: {
editMode: true,
emailBody: 'test',
@@ -157,7 +157,7 @@ describe('bulk-email-form', () => {
isLoading: false,
errorRetrievingData: false,
},
}),
}, dispatchMock]),
);
const submitButton = screen.getByText('Reschedule Email');
fireEvent.click(submitButton);

View File

@@ -12,7 +12,7 @@ import { getSentEmailHistory } from './data/api';
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
import ViewEmailModal from './ViewEmailModal';
function BulkEmailContentHistory({ intl }) {
const BulkEmailContentHistory = ({ intl }) => {
const { courseId } = useParams();
const [emailHistoryData, setEmailHistoryData] = useState();
const [errorRetrievingData, setErrorRetrievingData] = useState(false);
@@ -24,7 +24,7 @@ function BulkEmailContentHistory({ intl }) {
* Async function that makes a REST API call to retrieve historical email message data sent by the bulk course email
* tool from edx-platform.
*/
async function fetchSentEmailHistoryData() {
const fetchSentEmailHistoryData = async () => {
setErrorRetrievingData(false);
setShowHistoricalEmailContentTable(false);
@@ -41,7 +41,7 @@ function BulkEmailContentHistory({ intl }) {
}
setShowHistoricalEmailContentTable(true);
}
};
/**
* This utility function transforms the data stored in `emailHistoryData` to make it easier to display in the Paragon
@@ -94,6 +94,13 @@ function BulkEmailContentHistory({ intl }) {
},
];
const getViewMessageCell = (tableData, row) => (
<Button variant="link" className="px-1" onClick={() => onViewMessageClick(tableData[row.index])}>
{intl.formatMessage(messages.buttonViewMessage)}
<span className="sr-only">&nbsp;{row.index}</span>
</Button>
);
/**
* Paragon's DataTable supports the ability to add extra columns that might not directly coincide with the data being
* represented in the table. We are using an additional column to embed a button that will open a Modal to display the
@@ -106,12 +113,7 @@ function BulkEmailContentHistory({ intl }) {
{
id: 'view_message',
Header: '',
Cell: ({ row }) => (
<Button variant="link" className="px-1" onClick={() => onViewMessageClick(tableData[row.index])}>
{intl.formatMessage(messages.buttonViewMessage)}
<span className="sr-only">&nbsp;{row.index}</span>
</Button>
),
Cell: ({ row }) => getViewMessageCell(tableData, row),
},
];
};
@@ -150,7 +152,7 @@ function BulkEmailContentHistory({ intl }) {
</div>
</div>
);
}
};
BulkEmailContentHistory.propTypes = {
intl: intlShape.isRequired,

View File

@@ -2,7 +2,7 @@ import { Alert, DataTable } from '@edx/paragon';
import PropTypes from 'prop-types';
import React from 'react';
export default function BulkEmailTaskManagerTable(props) {
const BulkEmailTaskManagerTable = (props) => {
const {
errorRetrievingData,
tableData,
@@ -67,16 +67,16 @@ export default function BulkEmailTaskManagerTable(props) {
{canRenderTable()}
</div>
);
}
};
BulkEmailTaskManagerTable.propTypes = {
errorRetrievingData: PropTypes.bool.isRequired,
tableData: PropTypes.arrayOf(PropTypes.object),
tableData: PropTypes.arrayOf(PropTypes.shape({})),
tableDescription: PropTypes.string,
alertWarningMessage: PropTypes.string.isRequired,
alertErrorMessage: PropTypes.string.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
additionalColumns: PropTypes.arrayOf(PropTypes.object),
columns: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
additionalColumns: PropTypes.arrayOf(PropTypes.shape({})),
};
BulkEmailTaskManagerTable.defaultProps = {
@@ -84,3 +84,5 @@ BulkEmailTaskManagerTable.defaultProps = {
tableDescription: '',
additionalColumns: [],
};
export default BulkEmailTaskManagerTable;

View File

@@ -7,7 +7,7 @@ import messages from './messages';
import useInterval from '../../../utils/useInterval';
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
function BulkEmailPendingTasks({ intl }) {
const BulkEmailPendingTasks = ({ intl }) => {
const { courseId } = useParams();
const [instructorTaskData, setInstructorTaskData] = useState();
@@ -87,7 +87,7 @@ function BulkEmailPendingTasks({ intl }) {
/>
</div>
);
}
};
BulkEmailPendingTasks.propTypes = {
intl: intlShape.isRequired,

View File

@@ -5,30 +5,28 @@ import { Hyperlink, Alert } from '@edx/paragon';
import { WarningFilled } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
export default function BulkEmailPendingTasksAlert() {
return (
<>
<Alert variant="warning" icon={WarningFilled}>
<FormattedMessage
id="bulk.email.pending.tasks.description.one"
defaultMessage="To view all pending tasks, including email, visit&nbsp;"
/>
<Hyperlink
destination={`${getConfig().LMS_BASE_URL}/courses/${window.location.pathname.split('/')[2]}/instructor#view-course-info`}
target="_blank"
isInline
showLaunchIcon={false}
>
<FormattedMessage
id="bulk.email.pending.tasks.link"
defaultMessage="Course Info"
/>
</Hyperlink>
<FormattedMessage
id="bulk.email.pending.tasks.description.two"
defaultMessage="&nbsp;in the Instructor Dashboard."
/>
</Alert>
</>
);
}
const BulkEmailPendingTasksAlert = () => (
<Alert variant="warning" icon={WarningFilled}>
<FormattedMessage
id="bulk.email.pending.tasks.description.one"
defaultMessage="To view all pending tasks, including email, visit&nbsp;"
/>
<Hyperlink
destination={`${getConfig().LMS_BASE_URL}/courses/${window.location.pathname.split('/')[2]}/instructor#view-course-info`}
target="_blank"
isInline
showLaunchIcon={false}
>
<FormattedMessage
id="bulk.email.pending.tasks.link"
defaultMessage="Course Info"
/>
</Hyperlink>
<FormattedMessage
id="bulk.email.pending.tasks.description.two"
defaultMessage="&nbsp;in the Instructor Dashboard."
/>
</Alert>
);
export default BulkEmailPendingTasksAlert;

View File

@@ -11,7 +11,7 @@ import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
import './bulkEmailTaskHistory.scss';
function BulkEmailTaskHistory({ intl }) {
const BulkEmailTaskHistory = ({ intl }) => {
const { courseId } = useParams();
const [emailTaskHistoryData, setEmailTaskHistoryData] = useState([]);
@@ -22,7 +22,7 @@ function BulkEmailTaskHistory({ intl }) {
* Async function that makes a REST API call to retrieve historical bulk email (Instructor) task data for display
* within this component.
*/
async function fetchEmailTaskHistoryData() {
const fetchEmailTaskHistoryData = async () => {
setErrorRetrievingData(false);
setShowHistoricalTaskContentTable(false);
@@ -39,7 +39,7 @@ function BulkEmailTaskHistory({ intl }) {
}
setShowHistoricalTaskContentTable(true);
}
};
const tableColumns = [
{
@@ -106,7 +106,7 @@ function BulkEmailTaskHistory({ intl }) {
</div>
</div>
);
}
};
BulkEmailTaskHistory.propTypes = {
intl: intlShape.isRequired,

View File

@@ -8,29 +8,27 @@ import messages from './messages';
import BulkEmailScheduledEmailsTable from './bulk-email-scheduled-emails-table';
import BulkEmailPendingTasksAlert from './BulkEmailPendingTasksAlert';
function BulkEmailTaskManager({ intl }) {
return (
<div className="w-100">
{getConfig().SCHEDULE_EMAIL_SECTION && (
<div>
<h2 className="h3 text-primary-500">{intl.formatMessage(messages.scheduledEmailsTableHeader)}</h2>
<BulkEmailScheduledEmailsTable />
</div>
)}
<div>
<h2 className="h3 text-primary-500">{intl.formatMessage(messages.emailTaskHistoryHeader)}</h2>
<BulkEmailContentHistory />
</div>
<div>
<BulkEmailTaskHistory />
</div>
<div className="border-top border-primary-500 pt-4.5">
<h2 className="h3 mb-4 text-primary-500">{intl.formatMessage(messages.pendingTasksHeader)}</h2>
<BulkEmailPendingTasksAlert />
</div>
const BulkEmailTaskManager = ({ intl }) => (
<div className="w-100">
{getConfig().SCHEDULE_EMAIL_SECTION && (
<div>
<h2 className="h3 text-primary-500">{intl.formatMessage(messages.scheduledEmailsTableHeader)}</h2>
<BulkEmailScheduledEmailsTable />
</div>
);
}
)}
<div>
<h2 className="h3 text-primary-500">{intl.formatMessage(messages.emailTaskHistoryHeader)}</h2>
<BulkEmailContentHistory />
</div>
<div>
<BulkEmailTaskHistory />
</div>
<div className="border-top border-primary-500 pt-4.5">
<h2 className="h3 mb-4 text-primary-500">{intl.formatMessage(messages.pendingTasksHeader)}</h2>
<BulkEmailPendingTasksAlert />
</div>
</div>
);
BulkEmailTaskManager.propTypes = {
intl: intlShape.isRequired,

View File

@@ -6,9 +6,9 @@ import messages from './messages';
import { BulkEmailContext } from '../bulk-email-context';
import { copyToEditor } from '../bulk-email-form/data/actions';
function ViewEmailModal({
const ViewEmailModal = ({
intl, messageContent, isOpen, setModalOpen,
}) {
}) => {
const [, dispatch] = useContext(BulkEmailContext);
return (
<div>
@@ -60,7 +60,7 @@ function ViewEmailModal({
/>
</div>
);
}
};
ViewEmailModal.propTypes = {
intl: intlShape.isRequired,

View File

@@ -29,7 +29,7 @@ function flattenScheduledEmailsArray(emails) {
}));
}
function BulkEmailScheduledEmailsTable({ intl }) {
const BulkEmailScheduledEmailsTable = ({ intl }) => {
const { courseId } = useParams();
const [{ scheduledEmailsTable }, dispatch] = useContext(BulkEmailContext);
const [tableData, setTableData] = useState([]);
@@ -46,6 +46,7 @@ function BulkEmailScheduledEmailsTable({ intl }) {
const fetchTableData = useCallback((args) => {
dispatch(getScheduledBulkEmailThunk(courseId, args.pageIndex + 1));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleViewEmail = (row) => {
@@ -108,6 +109,25 @@ function BulkEmailScheduledEmailsTable({ intl }) {
}),
);
};
const getCell = (row, state, page, previousPage) => (
<>
<IconButton src={Visibility} iconAs={Icon} alt="View" onClick={() => handleViewEmail(row)} />
<IconButton
src={Delete}
iconAs={Icon}
alt="Delete"
onClick={() => {
setCurrentTask({
row, pageIndex: state.pageIndex, page, previousPage,
});
openConfirmModal();
}}
/>
<IconButton src={Edit} iconAs={Icon} alt="Edit" onClick={() => handleEditEmail(row)} />
</>
);
return (
<>
<TaskAlertModal
@@ -167,30 +187,14 @@ function BulkEmailScheduledEmailsTable({ intl }) {
Header: 'Action',
Cell: ({
row, state, page, previousPage,
}) => (
<>
<IconButton src={Visibility} iconAs={Icon} alt="View" onClick={() => handleViewEmail(row)} />
<IconButton
src={Delete}
iconAs={Icon}
alt="Delete"
onClick={() => {
setCurrentTask({
row, pageIndex: state.pageIndex, page, previousPage,
});
openConfirmModal();
}}
/>
<IconButton src={Edit} iconAs={Icon} alt="Edit" onClick={() => handleEditEmail(row)} />
</>
),
}) => getCell(row, state, page, previousPage),
},
]}
/>
</div>
</>
);
}
};
BulkEmailScheduledEmailsTable.propTypes = {
intl: intlShape.isRequired,

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailScheduledEmailsTable';

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailTool';

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { ActionRow, AlertModal, Button } from '@edx/paragon';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
function TaskAlertModal(props) {
const TaskAlertModal = (props) => {
const {
isOpen, close, alertMessage, intl,
} = props;
@@ -40,6 +40,7 @@ function TaskAlertModal(props) {
// causing strange click event target issues in safari. To solve this, we want to
// wrap the string in a fragment instead of a span, so that the whole button considered
// a "button" target, and not a "span inside a button"
// eslint-disable-next-line react/jsx-no-useless-fragment
msg => <>{msg}</>
}
</FormattedMessage>
@@ -50,7 +51,7 @@ function TaskAlertModal(props) {
{alertMessage}
</AlertModal>
);
}
};
TaskAlertModal.propTypes = {
isOpen: PropTypes.bool.isRequired,

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './TaskAlertModal';

View File

@@ -20,7 +20,7 @@ import '@edx/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 TextEditor = (props) => {
const {
onChange, onKeyUp, onInit, disabled, value,
} = props;
@@ -51,7 +51,7 @@ export default function TextEditor(props) {
disabled={disabled}
/>
);
}
};
TextEditor.defaultProps = {
onChange: () => {},
@@ -68,3 +68,5 @@ TextEditor.propTypes = {
disabled: PropTypes.bool,
value: PropTypes.string,
};
export default TextEditor;

View File

@@ -6,18 +6,16 @@ import PropTypes from 'prop-types';
* with jest, so we need to mock it out. This is not ideal, but since the TextEditor
* component is really just a wrapper, we're not too concerned about unit testing.
*/
function MockTinyMCE({ onChange }) {
return <textarea data-testid="textEditor" onChange={onChange} />;
}
const MockTinyMCE = ({ onChange }) => <textarea data-testid="textEditor" onChange={onChange} />;
MockTinyMCE.propTypes = {
onChange: PropTypes.func.isRequired,
};
export default function TextEditor({ onChange }) {
return <MockTinyMCE onChange={onChange} />;
}
const TextEditor = ({ onChange }) => <MockTinyMCE onChange={onChange} />;
TextEditor.propTypes = {
onChange: PropTypes.func.isRequired,
};
export default TextEditor;

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './TextEditor';

View File

@@ -5,22 +5,22 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, Icon } from '@edx/paragon';
import { ArrowBack } from '@edx/paragon/icons';
export default function BackToInstructor() {
return (
<Button
variant="tertiary"
className="mb-4.5 ml-n4.5 text-primary-500"
href={`${getConfig().LMS_BASE_URL}/courses/${window.location.pathname.split('/')[2]}/instructor#view-course-info`}
>
<Icon
src={ArrowBack}
className="mr-2"
/>
<FormattedMessage
id="bulk.email.back.to.instructorDashboard"
defaultMessage="Back to Instructor Dashboard"
description="A link to take the user back to the instructor dashboard"
/>
</Button>
);
}
const BackToInstructor = () => (
<Button
variant="tertiary"
className="mb-4.5 ml-n4.5 text-primary-500"
href={`${getConfig().LMS_BASE_URL}/courses/${window.location.pathname.split('/')[2]}/instructor#view-course-info`}
>
<Icon
src={ArrowBack}
className="mr-2"
/>
<FormattedMessage
id="bulk.email.back.to.instructorDashboard"
defaultMessage="Back to Instructor Dashboard"
description="A link to take the user back to the instructor dashboard"
/>
</Button>
);
export default BackToInstructor;

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Nav } from '@edx/paragon';
export default function NavigationTabs(props) {
const NavigationTabs = (props) => {
const { tabData } = props;
return (
@@ -19,7 +19,7 @@ export default function NavigationTabs(props) {
</Nav>
</div>
);
}
};
NavigationTabs.propTypes = {
tabData: PropTypes.arrayOf(PropTypes.shape({
@@ -32,3 +32,5 @@ NavigationTabs.propTypes = {
NavigationTabs.defaultProps = {
tabData: [],
};
export default NavigationTabs;

View File

@@ -12,7 +12,7 @@ import './PageContainer.scss';
export const CourseMetadataContext = React.createContext();
export default function PageContainer(props) {
const PageContainer = (props) => {
const { children } = props;
const { courseId } = useParams();
@@ -53,6 +53,7 @@ export default function PageContainer(props) {
});
}
fetchCourseMetadata();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (courseMetadata) {
@@ -85,8 +86,10 @@ export default function PageContainer(props) {
/>
</div>
);
}
};
PageContainer.propTypes = {
children: PropTypes.node.isRequired,
};
export default PageContainer;

View File

@@ -9,7 +9,7 @@ import ReactDOM from 'react-dom';
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as paragonMessages } from '@edx/paragon';
import { Switch } from 'react-router-dom';
import appMessages from './i18n';
@@ -50,5 +50,5 @@ initialize({
);
},
},
messages: [appMessages, headerMessages, footerMessages],
messages: [appMessages, headerMessages, footerMessages, paragonMessages],
});

View File

@@ -52,14 +52,12 @@ export function initializeMockApp() {
function render(ui, options) {
// eslint-disable-next-line react/prop-types
function Wrapper({ children }) {
return (
// eslint-disable-next-line react/jsx-filename-extension
<IntlProvider locale="en">
<AppProvider>{children}</AppProvider>
</IntlProvider>
);
}
const Wrapper = ({ children }) => (
// eslint-disable-next-line react/jsx-filename-extension
<IntlProvider locale="en">
<AppProvider>{children}</AppProvider>
</IntlProvider>
);
return rtlRender(ui, { wrapper: Wrapper, ...options });
}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } 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)
@@ -29,14 +29,15 @@ const breakpoints = {
*/
export default function useMobileResponsive(breakpoint) {
const [isMobileWindow, setIsMobileWindow] = useState();
const checkForMobile = () => {
const checkForMobile = useCallback(() => {
setIsMobileWindow(window.matchMedia(`(max-width: ${breakpoint || breakpoints.small.maxWidth}px)`).matches);
};
}, [breakpoint]);
useEffect(() => {
checkForMobile();
window.addEventListener('resize', checkForMobile);
// return this function here to clean up the event listener
return () => window.removeEventListener('resize', checkForMobile);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return isMobileWindow;
}