Compare commits

...

14 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
Bilal Qamar
fe9d201bd5 refactor: pinned frontend-build version 2022-10-31 13:11:09 +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
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
31 changed files with 14027 additions and 15008 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');

28608
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.0",
"@edx/frontend-build": "9.2.2",
"@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",

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

@@ -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;
}