diff --git a/.eslintrc.js b/.eslintrc.js index 8b13869843..18330d5157 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/no-extraneous-dependencies const { createConfig } = require('@edx/frontend-build'); const config = createConfig('eslint', { @@ -5,14 +6,11 @@ const config = createConfig('eslint', { // TODO: all these rules should be renabled/addressed. temporarily turned off to unblock a release. 'react-hooks/rules-of-hooks': 'off', 'react-hooks/exhaustive-deps': 'off', - 'react/function-component-definition': 'off', 'import/no-extraneous-dependencies': 'off', 'no-restricted-exports': 'off', 'react/jsx-no-useless-fragment': 'off', - 'react/jsx-no-bind': 'off', 'react/no-unknown-property': 'off', - 'react/no-unstable-nested-components': 'off', - 'react/jsx-no-constructed-context-values': 'off', + 'func-names': 'off', }, }); diff --git a/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx b/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx index 0f47df9154..9f71b6c823 100644 --- a/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx +++ b/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx @@ -8,7 +8,7 @@ import { Info } from '@edx/paragon/icons'; import messages from './messages'; -function AccessExpirationAlert({ intl, payload }) { +const AccessExpirationAlert = ({ intl, payload }) => { const { accessExpiration, courseId, @@ -116,7 +116,7 @@ function AccessExpirationAlert({ intl, payload }) { {deadlineMessage} ); -} +}; AccessExpirationAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/alerts/access-expiration-alert/AccessExpirationMasqueradeBanner.jsx b/src/alerts/access-expiration-alert/AccessExpirationMasqueradeBanner.jsx index 2ad9b6d89e..c4266159a0 100644 --- a/src/alerts/access-expiration-alert/AccessExpirationMasqueradeBanner.jsx +++ b/src/alerts/access-expiration-alert/AccessExpirationMasqueradeBanner.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n'; import { PageBanner } from '@edx/paragon'; -function AccessExpirationMasqueradeBanner({ payload }) { +const AccessExpirationMasqueradeBanner = ({ payload }) => { const { expirationDate, userTimezone, @@ -27,7 +27,7 @@ function AccessExpirationMasqueradeBanner({ payload }) { /> ); -} +}; AccessExpirationMasqueradeBanner.propTypes = { payload: PropTypes.shape({ diff --git a/src/alerts/access-expiration-alert/hooks.js b/src/alerts/access-expiration-alert/hooks.js index 3dfebc6968..2afdddd8fd 100644 --- a/src/alerts/access-expiration-alert/hooks.js +++ b/src/alerts/access-expiration-alert/hooks.js @@ -7,17 +7,17 @@ const AccessExpirationMasqueradeBanner = React.lazy(() => import('./AccessExpira function useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, topic, analyticsPageName) { const isVisible = accessExpiration && !accessExpiration.masqueradingExpiredCourse; // If it exists, show it. - const payload = { + const payload = useMemo(() => ({ accessExpiration, courseId, org, userTimezone, analyticsPageName, - }; + }), [accessExpiration, analyticsPageName, courseId, org, userTimezone]); useAlert(isVisible, { code: 'clientAccessExpirationAlert', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic, }); @@ -34,14 +34,14 @@ export function useAccessExpirationMasqueradeBanner(courseId, tab) { const isVisible = accessExpiration && accessExpiration.masqueradingExpiredCourse; const expirationDate = accessExpiration && accessExpiration.expirationDate; - const payload = { + const payload = useMemo(() => ({ expirationDate, userTimezone, - }; + }), [expirationDate, userTimezone]); useAlert(isVisible, { code: 'clientAccessExpirationMasqueradeBanner', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'instructor-toolbar-alerts', }); diff --git a/src/alerts/active-enteprise-alert/ActiveEnterpriseAlert.jsx b/src/alerts/active-enteprise-alert/ActiveEnterpriseAlert.jsx index cb64f097b6..5e142e932f 100644 --- a/src/alerts/active-enteprise-alert/ActiveEnterpriseAlert.jsx +++ b/src/alerts/active-enteprise-alert/ActiveEnterpriseAlert.jsx @@ -7,7 +7,7 @@ import { WarningFilled } from '@edx/paragon/icons'; import { getConfig } from '@edx/frontend-platform'; import genericMessages from './messages'; -function ActiveEnterpriseAlert({ intl, payload }) { +const ActiveEnterpriseAlert = ({ intl, payload }) => { const { text, courseId } = payload; const changeActiveEnterprise = ( ); -} +}; ActiveEnterpriseAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/alerts/active-enteprise-alert/hooks.js b/src/alerts/active-enteprise-alert/hooks.js index 7fcaf67320..5bf07ff604 100644 --- a/src/alerts/active-enteprise-alert/hooks.js +++ b/src/alerts/active-enteprise-alert/hooks.js @@ -12,16 +12,16 @@ export default function useActiveEnterpriseAlert(courseId) { */ const isVisible = courseAccess && !courseAccess.hasAccess && courseAccess.errorCode === 'incorrect_active_enterprise'; - const payload = { + const payload = useMemo(() => ({ text: courseAccess && courseAccess.userMessage, courseId, - }; + }), [courseAccess, courseId]); useAlert(isVisible, { code: 'clientActiveEnterpriseAlert', topic: 'outline', dismissible: false, type: ALERT_TYPES.ERROR, - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, }); return { clientActiveEnterpriseAlert: ActiveEnterpriseAlert }; diff --git a/src/alerts/course-start-alert/CourseStartAlert.jsx b/src/alerts/course-start-alert/CourseStartAlert.jsx index bcd683671d..f24e328273 100644 --- a/src/alerts/course-start-alert/CourseStartAlert.jsx +++ b/src/alerts/course-start-alert/CourseStartAlert.jsx @@ -15,7 +15,7 @@ const DAY_SEC = 24 * 60 * 60; // in seconds const DAY_MS = DAY_SEC * 1000; // in ms const YEAR_SEC = 365 * DAY_SEC; // in seconds -function CourseStartAlert({ payload }) { +const CourseStartAlert = ({ payload }) => { const { courseId, } = payload; @@ -94,7 +94,7 @@ function CourseStartAlert({ payload }) { /> ); -} +}; CourseStartAlert.propTypes = { payload: PropTypes.shape({ diff --git a/src/alerts/course-start-alert/CourseStartMasqueradeBanner.jsx b/src/alerts/course-start-alert/CourseStartMasqueradeBanner.jsx index ad85a07ecd..8df0d69503 100644 --- a/src/alerts/course-start-alert/CourseStartMasqueradeBanner.jsx +++ b/src/alerts/course-start-alert/CourseStartMasqueradeBanner.jsx @@ -5,7 +5,7 @@ import { PageBanner } from '@edx/paragon'; import { useModel } from '../../generic/model-store'; -function CourseStartMasqueradeBanner({ payload }) { +const CourseStartMasqueradeBanner = ({ payload }) => { const { courseId, } = payload; @@ -33,7 +33,7 @@ function CourseStartMasqueradeBanner({ payload }) { /> ); -} +}; CourseStartMasqueradeBanner.propTypes = { payload: PropTypes.shape({ diff --git a/src/alerts/course-start-alert/hooks.js b/src/alerts/course-start-alert/hooks.js index f8ab5676f4..0389aac86b 100644 --- a/src/alerts/course-start-alert/hooks.js +++ b/src/alerts/course-start-alert/hooks.js @@ -5,7 +5,7 @@ import { useModel } from '../../generic/model-store'; const CourseStartAlert = React.lazy(() => import('./CourseStartAlert')); const CourseStartMasqueradeBanner = React.lazy(() => import('./CourseStartMasqueradeBanner')); -function isStartDateInFuture(courseId) { +function IsStartDateInFuture(courseId) { const { start, } = useModel('courseHomeMeta', courseId); @@ -20,15 +20,15 @@ function useCourseStartAlert(courseId) { isEnrolled, } = useModel('courseHomeMeta', courseId); - const isVisible = isEnrolled && isStartDateInFuture(courseId); + const isVisible = isEnrolled && IsStartDateInFuture(courseId); - const payload = { + const payload = useMemo(() => ({ courseId, - }; + }), [courseId]); useAlert(isVisible, { code: 'clientCourseStartAlert', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'outline-course-alerts', }); @@ -42,15 +42,15 @@ export function useCourseStartMasqueradeBanner(courseId, tab) { isMasquerading, } = useModel('courseHomeMeta', courseId); - const isVisible = isMasquerading && tab === 'progress' && isStartDateInFuture(courseId); + const isVisible = isMasquerading && tab === 'progress' && IsStartDateInFuture(courseId); - const payload = { + const payload = useMemo(() => ({ courseId, - }; + }), [courseId]); useAlert(isVisible, { code: 'clientCourseStartMasqueradeBanner', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'instructor-toolbar-alerts', }); diff --git a/src/alerts/enrollment-alert/EnrollmentAlert.jsx b/src/alerts/enrollment-alert/EnrollmentAlert.jsx index de659d728b..e8489e5fc5 100644 --- a/src/alerts/enrollment-alert/EnrollmentAlert.jsx +++ b/src/alerts/enrollment-alert/EnrollmentAlert.jsx @@ -11,7 +11,7 @@ import { useModel } from '../../generic/model-store'; import messages from './messages'; import useEnrollClickHandler from './clickHook'; -function EnrollmentAlert({ intl, payload }) { +const EnrollmentAlert = ({ intl, payload }) => { const { canEnroll, courseId, @@ -55,7 +55,7 @@ function EnrollmentAlert({ intl, payload }) { ); -} +}; EnrollmentAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/alerts/enrollment-alert/clickHook.js b/src/alerts/enrollment-alert/clickHook.js index 225e4fafdf..76ab52fc0f 100644 --- a/src/alerts/enrollment-alert/clickHook.js +++ b/src/alerts/enrollment-alert/clickHook.js @@ -27,7 +27,7 @@ function useEnrollClickHandler(courseId, orgId, successText) { }); global.location.reload(); }); - }, [courseId]); + }, [addFlash, courseId, orgId, successText]); return { enrollClickHandler, loading }; } diff --git a/src/alerts/enrollment-alert/hooks.js b/src/alerts/enrollment-alert/hooks.js index 03df8b616d..f3da187777 100644 --- a/src/alerts/enrollment-alert/hooks.js +++ b/src/alerts/enrollment-alert/hooks.js @@ -22,16 +22,16 @@ export function useEnrollmentAlert(courseId) { * 3. the course is private. */ const isVisible = !enrolledUser && authenticatedUser !== null && privateOutline; - const payload = { + const payload = useMemo(() => ({ canEnroll: outline && outline.enrollAlert ? outline.enrollAlert.canEnroll : false, courseId, extraText: outline && outline.enrollAlert ? outline.enrollAlert.extraText : '', isStaff: course && course.isStaff, - }; + }), [course, courseId, outline]); useAlert(isVisible, { code: 'clientEnrollmentAlert', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'outline', }); diff --git a/src/alerts/logistration-alert/AccountActivationAlert.jsx b/src/alerts/logistration-alert/AccountActivationAlert.jsx index dcdd06d7cb..8455e2dc3f 100644 --- a/src/alerts/logistration-alert/AccountActivationAlert.jsx +++ b/src/alerts/logistration-alert/AccountActivationAlert.jsx @@ -13,9 +13,9 @@ import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/ import { sendActivationEmail } from '../../courseware/data'; import messages from './messages'; -function AccountActivationAlert({ +const AccountActivationAlert = ({ intl, -}) { +}) => { const [showModal, setShowModal] = useState(false); const [showSpinner, setShowSpinner] = useState(false); const [showCheck, setShowCheck] = useState(false); @@ -123,7 +123,7 @@ function AccountActivationAlert({ {children()} ); -} +}; AccountActivationAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/alerts/logistration-alert/LogistrationAlert.jsx b/src/alerts/logistration-alert/LogistrationAlert.jsx index be72180e53..5ab8ff6d31 100644 --- a/src/alerts/logistration-alert/LogistrationAlert.jsx +++ b/src/alerts/logistration-alert/LogistrationAlert.jsx @@ -7,7 +7,7 @@ import { WarningFilled } from '@edx/paragon/icons'; import genericMessages from '../../generic/messages'; -function LogistrationAlert({ intl }) { +const LogistrationAlert = ({ intl }) => { const signIn = ( ); -} +}; LogistrationAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js index eac84f1a98..3265d8a1c4 100644 --- a/src/course-home/data/api.js +++ b/src/course-home/data/api.js @@ -349,7 +349,7 @@ export async function getOutlineTabData(courseId) { const timeOffsetMillis = getTimeOffsetMillis(headers && headers.date, requestTime, responseTime); const userHasPassingGrade = data.user_has_passing_grade; const verifiedMode = camelCaseObject(data.verified_mode); - const welcomeMessageHtml = data.welcome_message_html; + const welcomeMessageHtml = data.welcome_message_html || ''; return { accessExpiration, diff --git a/src/course-home/dates-tab/DatesTab.jsx b/src/course-home/dates-tab/DatesTab.jsx index 23e157cd6f..71b4389bc9 100644 --- a/src/course-home/dates-tab/DatesTab.jsx +++ b/src/course-home/dates-tab/DatesTab.jsx @@ -14,7 +14,7 @@ import ShiftDatesAlert from '../suggested-schedule-messaging/ShiftDatesAlert'; import UpgradeToCompleteAlert from '../suggested-schedule-messaging/UpgradeToCompleteAlert'; import UpgradeToShiftDatesAlert from '../suggested-schedule-messaging/UpgradeToShiftDatesAlert'; -function DatesTab({ intl }) { +const DatesTab = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -57,7 +57,7 @@ function DatesTab({ intl }) { ); -} +}; DatesTab.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/dates-tab/timeline/Day.jsx b/src/course-home/dates-tab/timeline/Day.jsx index ad89316594..125ce1ef0f 100644 --- a/src/course-home/dates-tab/timeline/Day.jsx +++ b/src/course-home/dates-tab/timeline/Day.jsx @@ -17,13 +17,13 @@ import { useModel } from '../../../generic/model-store'; import { getBadgeListAndColor } from './badgelist'; import { isLearnerAssignment } from '../utils'; -function Day({ +const Day = ({ date, first, intl, items, last, -}) { +}) => { const { courseId, } = useSelector(state => state.courseHome); @@ -103,7 +103,7 @@ function Day({ ); -} +}; Day.propTypes = { date: PropTypes.objectOf(Date).isRequired, diff --git a/src/course-home/dates-tab/timeline/Timeline.jsx b/src/course-home/dates-tab/timeline/Timeline.jsx index e9f6868a97..09073d6fb7 100644 --- a/src/course-home/dates-tab/timeline/Timeline.jsx +++ b/src/course-home/dates-tab/timeline/Timeline.jsx @@ -6,7 +6,7 @@ import { useModel } from '../../../generic/model-store'; import Day from './Day'; import { daycmp, isLearnerAssignment } from '../utils'; -export default function Timeline() { +const Timeline = () => { const { courseId, } = useSelector(state => state.courseHome); @@ -67,4 +67,6 @@ export default function Timeline() { ))} ); -} +}; + +export default Timeline; diff --git a/src/course-home/discussion-tab/DiscussionTab.jsx b/src/course-home/discussion-tab/DiscussionTab.jsx index d4d2d6e9a2..0bbae8be3c 100644 --- a/src/course-home/discussion-tab/DiscussionTab.jsx +++ b/src/course-home/discussion-tab/DiscussionTab.jsx @@ -6,7 +6,7 @@ import { generatePath, useHistory } from 'react-router'; import { useParams } from 'react-router-dom'; import { useIFrameHeight, useIFramePluginEvents } from '../../generic/hooks'; -function DiscussionTab() { +const DiscussionTab = () => { const { courseId } = useSelector(state => state.courseHome); const { path } = useParams(); const [originalPath] = useState(path); @@ -29,7 +29,7 @@ function DiscussionTab() { title="discussion" /> ); -} +}; DiscussionTab.propTypes = {}; diff --git a/src/course-home/goal-unsubscribe/GoalUnsubscribe.jsx b/src/course-home/goal-unsubscribe/GoalUnsubscribe.jsx index 24fdddee7e..3a6922eed6 100644 --- a/src/course-home/goal-unsubscribe/GoalUnsubscribe.jsx +++ b/src/course-home/goal-unsubscribe/GoalUnsubscribe.jsx @@ -10,7 +10,7 @@ import { unsubscribeFromCourseGoal } from '../data/api'; import messages from './messages'; import ResultPage from './ResultPage'; -function GoalUnsubscribe({ intl }) { +const GoalUnsubscribe = ({ intl }) => { const { token } = useParams(); const [error, setError] = useState(false); const [isLoading, setIsLoading] = useState(true); @@ -33,6 +33,7 @@ function GoalUnsubscribe({ intl }) { // as visiting this page is allowed to be done anonymously and without the context of the course. // The token can be used to connect a user and course, it will just require some post-processing sendTrackEvent('edx.ui.lms.goal.unsubscribe', { token }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // deps=[] to only run once return ( @@ -48,7 +49,7 @@ function GoalUnsubscribe({ intl }) { ); -} +}; GoalUnsubscribe.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/goal-unsubscribe/ResultPage.jsx b/src/course-home/goal-unsubscribe/ResultPage.jsx index edeadc05e1..ba6b36f627 100644 --- a/src/course-home/goal-unsubscribe/ResultPage.jsx +++ b/src/course-home/goal-unsubscribe/ResultPage.jsx @@ -6,7 +6,7 @@ import { Button, Hyperlink } from '@edx/paragon'; import messages from './messages'; import { ReactComponent as UnsubscribeIcon } from './unsubscribe.svg'; -function ResultPage({ courseTitle, error, intl }) { +const ResultPage = ({ courseTitle, error, intl }) => { const errorDescription = ( ); -} +}; ResultPage.defaultProps = { courseTitle: null, diff --git a/src/course-home/live-tab/LiveTab.jsx b/src/course-home/live-tab/LiveTab.jsx index 960d66e38e..05a470e038 100644 --- a/src/course-home/live-tab/LiveTab.jsx +++ b/src/course-home/live-tab/LiveTab.jsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; -function LiveTab() { +const LiveTab = () => { const { courseId } = useSelector(state => state.courseHome); const liveModel = useSelector(state => state.models.live); useEffect(() => { @@ -17,6 +17,6 @@ function LiveTab() { dangerouslySetInnerHTML={{ __html: liveModel[courseId]?.iframe }} /> ); -} +}; export default LiveTab; diff --git a/src/course-home/outline-tab/DateSummary.jsx b/src/course-home/outline-tab/DateSummary.jsx index abf5be05ed..edff43a7d2 100644 --- a/src/course-home/outline-tab/DateSummary.jsx +++ b/src/course-home/outline-tab/DateSummary.jsx @@ -9,10 +9,10 @@ import { useModel } from '../../generic/model-store'; import { isLearnerAssignment } from '../dates-tab/utils'; import './DateSummary.scss'; -export default function DateSummary({ +const DateSummary = ({ dateBlock, userTimezone, -}) { +}) => { const { courseId, } = useSelector(state => state.courseHome); @@ -75,7 +75,7 @@ export default function DateSummary({ ); -} +}; DateSummary.propTypes = { dateBlock: PropTypes.shape({ @@ -93,3 +93,5 @@ DateSummary.propTypes = { DateSummary.defaultProps = { userTimezone: null, }; + +export default DateSummary; diff --git a/src/course-home/outline-tab/LmsHtmlFragment.jsx b/src/course-home/outline-tab/LmsHtmlFragment.jsx index 0a525f25fa..191663554c 100644 --- a/src/course-home/outline-tab/LmsHtmlFragment.jsx +++ b/src/course-home/outline-tab/LmsHtmlFragment.jsx @@ -3,12 +3,12 @@ import PropTypes from 'prop-types'; import { getConfig } from '@edx/frontend-platform'; -export default function LmsHtmlFragment({ +const LmsHtmlFragment = ({ className, html, title, ...rest -}) { +}) => { const wholePage = ` @@ -55,7 +55,7 @@ export default function LmsHtmlFragment({ {...rest} /> ); -} +}; LmsHtmlFragment.defaultProps = { className: '', @@ -66,3 +66,5 @@ LmsHtmlFragment.propTypes = { html: PropTypes.string.isRequired, title: PropTypes.string.isRequired, }; + +export default LmsHtmlFragment; diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx index 1c75f73d20..072f0d04c6 100644 --- a/src/course-home/outline-tab/OutlineTab.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -29,7 +29,7 @@ import WelcomeMessage from './widgets/WelcomeMessage'; import ProctoringInfoPanel from './widgets/ProctoringInfoPanel'; import AccountActivationAlert from '../../alerts/logistration-alert/AccountActivationAlert'; -function OutlineTab({ intl }) { +const OutlineTab = ({ intl }) => { const { courseId, proctoringPanelStatus, @@ -212,7 +212,7 @@ function OutlineTab({ intl }) { ); -} +}; OutlineTab.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/Section.jsx b/src/course-home/outline-tab/Section.jsx index b50781267f..3de888a89a 100644 --- a/src/course-home/outline-tab/Section.jsx +++ b/src/course-home/outline-tab/Section.jsx @@ -12,13 +12,13 @@ import { useModel } from '../../generic/model-store'; import genericMessages from '../../generic/messages'; import messages from './messages'; -function Section({ +const Section = ({ courseId, defaultOpen, expand, intl, section, -}) { +}) => { const { complete, sequenceIds, @@ -38,6 +38,7 @@ function Section({ useEffect(() => { setOpen(defaultOpen); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const sectionTitle = ( @@ -109,7 +110,7 @@ function Section({ ); -} +}; Section.propTypes = { courseId: PropTypes.string.isRequired, diff --git a/src/course-home/outline-tab/SequenceLink.jsx b/src/course-home/outline-tab/SequenceLink.jsx index 794cdc2538..5a02c7f511 100644 --- a/src/course-home/outline-tab/SequenceLink.jsx +++ b/src/course-home/outline-tab/SequenceLink.jsx @@ -16,13 +16,13 @@ import EffortEstimate from '../../shared/effort-estimate'; import { useModel } from '../../generic/model-store'; import messages from './messages'; -function SequenceLink({ +const SequenceLink = ({ id, intl, courseId, first, sequence, -}) { +}) => { const { complete, description, @@ -98,7 +98,7 @@ function SequenceLink({ ); -} +}; SequenceLink.propTypes = { id: PropTypes.string.isRequired, diff --git a/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx b/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx index b650b36fc6..5b191cffcd 100644 --- a/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx +++ b/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx @@ -25,7 +25,7 @@ export const CERT_STATUS_TYPE = { UNVERIFIED: 'unverified', }; -function CertificateStatusAlert({ intl, payload }) { +const CertificateStatusAlert = ({ intl, payload }) => { const dispatch = useDispatch(); const { certificateAvailableDate, @@ -189,7 +189,7 @@ function CertificateStatusAlert({ intl, payload }) { )} ); -} +}; CertificateStatusAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js b/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js index 86445e3621..9cae4442f5 100644 --- a/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js +++ b/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js @@ -75,7 +75,7 @@ function useCertificateStatusAlert(courseId) { && hasEnded && !userHasPassingGrade ); - const payload = { + const payload = useMemo(() => ({ certificateAvailableDate, certURL, certStatus, @@ -85,11 +85,12 @@ function useCertificateStatusAlert(courseId) { org, notPassingCourseEnded, tabs, - }; + }), [certStatus, certURL, certificateAvailableDate, courseId, + endBlock, notPassingCourseEnded, org, tabs, userTimezone]); useAlert(isVisible || notPassingCourseEnded, { code: 'clientCertificateStatusAlert', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'outline-course-alerts', }); diff --git a/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx b/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx index 52287fb10b..f08e738055 100644 --- a/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx +++ b/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx @@ -13,7 +13,7 @@ const DAY_SEC = 24 * 60 * 60; // in seconds const DAY_MS = DAY_SEC * 1000; // in ms const YEAR_SEC = 365 * DAY_SEC; // in seconds -function CourseEndAlert({ payload }) { +const CourseEndAlert = ({ payload }) => { const { description, endDate, @@ -88,7 +88,7 @@ function CourseEndAlert({ payload }) { {description} ); -} +}; CourseEndAlert.propTypes = { payload: PropTypes.shape({ diff --git a/src/course-home/outline-tab/alerts/course-end-alert/hooks.js b/src/course-home/outline-tab/alerts/course-end-alert/hooks.js index c39e6dadc6..147649ef44 100644 --- a/src/course-home/outline-tab/alerts/course-end-alert/hooks.js +++ b/src/course-home/outline-tab/alerts/course-end-alert/hooks.js @@ -23,15 +23,15 @@ export function useCourseEndAlert(courseId) { const endDate = endBlock ? new Date(endBlock.date) : null; const delta = endBlock ? endDate - new Date() : 0; const isVisible = isEnrolled && endBlock && delta > 0 && delta < WARNING_PERIOD_MS; - const payload = { + const payload = useMemo(() => ({ description: endBlock && endBlock.description, endDate: endBlock && endBlock.date, userTimezone, - }; + }), [endBlock, userTimezone]); useAlert(isVisible, { code: 'clientCourseEndAlert', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'outline-course-alerts', }); diff --git a/src/course-home/outline-tab/alerts/private-course-alert/PrivateCourseAlert.jsx b/src/course-home/outline-tab/alerts/private-course-alert/PrivateCourseAlert.jsx index 19e07bea55..5c520d631e 100644 --- a/src/course-home/outline-tab/alerts/private-course-alert/PrivateCourseAlert.jsx +++ b/src/course-home/outline-tab/alerts/private-course-alert/PrivateCourseAlert.jsx @@ -14,7 +14,7 @@ import outlineMessages from '../../messages'; import useEnrollClickHandler from '../../../../alerts/enrollment-alert/clickHook'; import { useModel } from '../../../../generic/model-store'; -function PrivateCourseAlert({ intl, payload }) { +const PrivateCourseAlert = ({ intl, payload }) => { const { anonymousUser, canEnroll, @@ -100,7 +100,7 @@ function PrivateCourseAlert({ intl, payload }) { )} ); -} +}; PrivateCourseAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/alerts/private-course-alert/hooks.js b/src/course-home/outline-tab/alerts/private-course-alert/hooks.js index ec5efc5c4a..c3e2b377dd 100644 --- a/src/course-home/outline-tab/alerts/private-course-alert/hooks.js +++ b/src/course-home/outline-tab/alerts/private-course-alert/hooks.js @@ -18,16 +18,16 @@ export function usePrivateCourseAlert(courseId) { * 2. the user is authenticated. * */ const isVisible = !enrolledUser && (privateOutline || authenticatedUser !== null); - const payload = { + const payload = useMemo(() => ({ anonymousUser: authenticatedUser === null, canEnroll: outline && outline.enrollAlert ? outline.enrollAlert.canEnroll : false, courseId, - }; + }), [authenticatedUser, courseId, outline]); useAlert(isVisible, { code: 'clientPrivateCourseAlert', dismissible: false, - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'outline-private-alerts', type: ALERT_TYPES.WELCOME, }); diff --git a/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx b/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx index 4f0dddbc1a..fadb0a3e57 100644 --- a/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx +++ b/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx @@ -3,7 +3,7 @@ import { Alert, Button } from '@edx/paragon'; import React from 'react'; import PropTypes from 'prop-types'; -function ScheduledContentAlert({ payload }) { +const ScheduledContentAlert = ({ payload }) => { const { datesTabLink, } = payload; @@ -38,7 +38,7 @@ function ScheduledContentAlert({ payload }) { ); -} +}; ScheduledContentAlert.propTypes = { payload: PropTypes.shape({ diff --git a/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js b/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js index fe29a22015..58e7d72501 100644 --- a/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js +++ b/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js @@ -20,12 +20,12 @@ const useScheduledContentAlert = (courseId) => { && !!Object.values(courses).find(course => course.hasScheduledContent === true) ); const { isEnrolled } = useModel('courseHomeMeta', courseId); - const payload = { + const payload = useMemo(() => ({ datesTabLink, - }; + }), [datesTabLink]); useAlert(hasScheduledContent && isEnrolled, { code: 'ScheduledContentAlert', - payload: useMemo(() => payload, Object.values(payload).sort()), + payload, topic: 'outline-course-alerts', }); diff --git a/src/course-home/outline-tab/widgets/CourseDates.jsx b/src/course-home/outline-tab/widgets/CourseDates.jsx index 5604e00a75..0db9e2e5a6 100644 --- a/src/course-home/outline-tab/widgets/CourseDates.jsx +++ b/src/course-home/outline-tab/widgets/CourseDates.jsx @@ -7,9 +7,9 @@ import DateSummary from '../DateSummary'; import messages from '../messages'; import { useModel } from '../../../generic/model-store'; -function CourseDates({ +const CourseDates = ({ intl, -}) { +}) => { const { courseId, } = useSelector(state => state.courseHome); @@ -46,7 +46,7 @@ function CourseDates({ ); -} +}; CourseDates.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/widgets/CourseHandouts.jsx b/src/course-home/outline-tab/widgets/CourseHandouts.jsx index 7a07175c24..ac90adad8b 100644 --- a/src/course-home/outline-tab/widgets/CourseHandouts.jsx +++ b/src/course-home/outline-tab/widgets/CourseHandouts.jsx @@ -7,7 +7,7 @@ import LmsHtmlFragment from '../LmsHtmlFragment'; import messages from '../messages'; import { useModel } from '../../../generic/model-store'; -function CourseHandouts({ intl }) { +const CourseHandouts = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -29,7 +29,7 @@ function CourseHandouts({ intl }) { /> ); -} +}; CourseHandouts.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/widgets/CourseTools.jsx b/src/course-home/outline-tab/widgets/CourseTools.jsx index 0847deac37..62ef499000 100644 --- a/src/course-home/outline-tab/widgets/CourseTools.jsx +++ b/src/course-home/outline-tab/widgets/CourseTools.jsx @@ -14,7 +14,7 @@ import messages from '../messages'; import { useModel } from '../../../generic/model-store'; import LaunchCourseHomeTourButton from '../../../product-tours/newUserCourseHomeTour/LaunchCourseHomeTourButton'; -function CourseTools({ intl }) { +const CourseTools = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -79,7 +79,7 @@ function CourseTools({ intl }) { ); -} +}; CourseTools.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/widgets/FlagButton.jsx b/src/course-home/outline-tab/widgets/FlagButton.jsx index 48375cb212..40848a2c40 100644 --- a/src/course-home/outline-tab/widgets/FlagButton.jsx +++ b/src/course-home/outline-tab/widgets/FlagButton.jsx @@ -2,37 +2,35 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -function FlagButton({ +const FlagButton = ({ buttonIcon, title, text, handleSelect, isSelected, -}) { - return ( - - ); -} +}) => ( + +); FlagButton.propTypes = { buttonIcon: PropTypes.element.isRequired, diff --git a/src/course-home/outline-tab/widgets/LearningGoalButton.jsx b/src/course-home/outline-tab/widgets/LearningGoalButton.jsx index 35f87ee024..c477ea9028 100644 --- a/src/course-home/outline-tab/widgets/LearningGoalButton.jsx +++ b/src/course-home/outline-tab/widgets/LearningGoalButton.jsx @@ -9,12 +9,12 @@ import { ReactComponent as FlagRegularIcon } from './flag_gray.svg'; import FlagButton from './FlagButton'; import messages from '../messages'; -function LearningGoalButton({ +const LearningGoalButton = ({ level, isSelected, handleSelect, intl, -}) { +}) => { const buttonDetails = { casual: { daysPerWeek: 1, @@ -47,7 +47,7 @@ function LearningGoalButton({ isSelected={isSelected} /> ); -} +}; LearningGoalButton.propTypes = { level: PropTypes.string.isRequired, diff --git a/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx b/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx index 37d90bfe07..0159d66076 100644 --- a/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx +++ b/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx @@ -10,7 +10,7 @@ import { getProctoringInfoData } from '../../data/api'; import { fetchProctoringInfoResolved } from '../../data/slice'; import { useModel } from '../../../generic/model-store'; -function ProctoringInfoPanel({ intl }) { +const ProctoringInfoPanel = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -128,6 +128,7 @@ function ProctoringInfoPanel({ intl }) { .finally(() => { dispatch(fetchProctoringInfoResolved()); }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); let onboardingExamButton = null; @@ -170,6 +171,7 @@ function ProctoringInfoPanel({ intl }) { } return ( + // eslint-disable-next-line react/jsx-no-useless-fragment <> { showInfoPanel && (
@@ -212,7 +214,7 @@ function ProctoringInfoPanel({ intl }) { )} ); -} +}; ProctoringInfoPanel.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/widgets/StartOrResumeCourseCard.jsx b/src/course-home/outline-tab/widgets/StartOrResumeCourseCard.jsx index a668cff427..5b1f033486 100644 --- a/src/course-home/outline-tab/widgets/StartOrResumeCourseCard.jsx +++ b/src/course-home/outline-tab/widgets/StartOrResumeCourseCard.jsx @@ -7,7 +7,7 @@ import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; import messages from '../messages'; import { useModel } from '../../../generic/model-store'; -function StartOrResumeCourseCard({ intl }) { +const StartOrResumeCourseCard = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -56,10 +56,11 @@ function StartOrResumeCourseCard({ intl }) { )} /> {/* Footer is needed for internal vertical spacing to work out. If you can remove, be my guest */} + {/* eslint-disable-next-line react/jsx-no-useless-fragment */} <> ); -} +}; StartOrResumeCourseCard.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/outline-tab/widgets/WeeklyLearningGoalCard.jsx b/src/course-home/outline-tab/widgets/WeeklyLearningGoalCard.jsx index 1d8a0adfca..00def35b0e 100644 --- a/src/course-home/outline-tab/widgets/WeeklyLearningGoalCard.jsx +++ b/src/course-home/outline-tab/widgets/WeeklyLearningGoalCard.jsx @@ -15,11 +15,11 @@ import { saveWeeklyLearningGoal } from '../../data'; import { useModel } from '../../../generic/model-store'; import './FlagButton.scss'; -function WeeklyLearningGoalCard({ +const WeeklyLearningGoalCard = ({ daysPerWeek, subscribedToReminders, intl, -}) { +}) => { const { courseId, } = useSelector(state => state.courseHome); @@ -36,7 +36,7 @@ function WeeklyLearningGoalCard({ const [isGetReminderSelected, setGetReminderSelected] = useState(subscribedToReminders); const location = useLocation(); - function handleSelect(days, triggeredFromEmail = false) { + const handleSelect = (days, triggeredFromEmail = false) => { // Set the subscription button if this is the first time selecting a goal const selectReminders = daysPerWeekGoal === null ? true : isGetReminderSelected; setGetReminderSelected(selectReminders); @@ -54,7 +54,7 @@ function WeeklyLearningGoalCard({ sendTrackEvent('enrollment.email.clicked.setgoal', {}); } } - } + }; function handleSubscribeToReminders(event) { const isGetReminderChecked = event.target.checked; @@ -84,6 +84,7 @@ function WeeklyLearningGoalCard({ search: currentParams.toString(), }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.search]); return ( @@ -146,7 +147,7 @@ function WeeklyLearningGoalCard({ )} ); -} +}; WeeklyLearningGoalCard.propTypes = { daysPerWeek: PropTypes.number, diff --git a/src/course-home/outline-tab/widgets/WelcomeMessage.jsx b/src/course-home/outline-tab/widgets/WelcomeMessage.jsx index b2f93cab82..ed64b1b547 100644 --- a/src/course-home/outline-tab/widgets/WelcomeMessage.jsx +++ b/src/course-home/outline-tab/widgets/WelcomeMessage.jsx @@ -11,21 +11,22 @@ import messages from '../messages'; import { useModel } from '../../../generic/model-store'; import { dismissWelcomeMessage } from '../../data/thunks'; -function WelcomeMessage({ courseId, intl }) { +const WelcomeMessage = ({ courseId, intl }) => { const { welcomeMessageHtml, } = useModel('outline', courseId); - if (!welcomeMessageHtml) { - return null; - } - const [display, setDisplay] = useState(true); const shortWelcomeMessageHtml = truncate(welcomeMessageHtml, 100, { byWords: true, keepWhitespaces: true }); const messageCanBeShortened = shortWelcomeMessageHtml.length < welcomeMessageHtml.length; const [showShortMessage, setShowShortMessage] = useState(messageCanBeShortened); const dispatch = useDispatch(); + + if (!welcomeMessageHtml) { + return null; + } + return ( ); -} +}; WelcomeMessage.propTypes = { courseId: PropTypes.string.isRequired, diff --git a/src/course-home/progress-tab/ProgressHeader.jsx b/src/course-home/progress-tab/ProgressHeader.jsx index ece2c81b46..40b31d56c7 100644 --- a/src/course-home/progress-tab/ProgressHeader.jsx +++ b/src/course-home/progress-tab/ProgressHeader.jsx @@ -9,7 +9,7 @@ import { useModel } from '../../generic/model-store'; import messages from './messages'; -function ProgressHeader({ intl }) { +const ProgressHeader = ({ intl }) => { const { courseId, targetUserId, @@ -35,7 +35,7 @@ function ProgressHeader({ intl }) { )} ); -} +}; ProgressHeader.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/ProgressTab.jsx b/src/course-home/progress-tab/ProgressTab.jsx index 7954927aab..3413d3836f 100644 --- a/src/course-home/progress-tab/ProgressTab.jsx +++ b/src/course-home/progress-tab/ProgressTab.jsx @@ -12,7 +12,7 @@ import RelatedLinks from './related-links/RelatedLinks'; import { useModel } from '../../generic/model-store'; -function ProgressTab() { +const ProgressTab = () => { const { courseId, } = useSelector(state => state.courseHome); @@ -55,6 +55,6 @@ function ProgressTab() { ); -} +}; export default ProgressTab; diff --git a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx index 5e187d433b..496feb4c62 100644 --- a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx +++ b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx @@ -14,7 +14,7 @@ import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../ import { requestCert } from '../../data/thunks'; import messages from './messages'; -function CertificateStatus({ intl }) { +const CertificateStatus = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -206,6 +206,7 @@ function CertificateStatus({ intl }) { grade_variant: gradeEventName, certificate_status_variant: certEventName, }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (!certCase) { @@ -257,7 +258,7 @@ function CertificateStatus({ intl }) {
); -} +}; CertificateStatus.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/course-completion/CompleteDonutSegment.jsx b/src/course-home/progress-tab/course-completion/CompleteDonutSegment.jsx index 2615b270d7..b604ddb87e 100644 --- a/src/course-home/progress-tab/course-completion/CompleteDonutSegment.jsx +++ b/src/course-home/progress-tab/course-completion/CompleteDonutSegment.jsx @@ -6,13 +6,13 @@ import { OverlayTrigger, Popover } from '@edx/paragon'; import messages from './messages'; -function CompleteDonutSegment({ completePercentage, intl, lockedPercentage }) { +const CompleteDonutSegment = ({ completePercentage, intl, lockedPercentage }) => { + const [showCompletePopover, setShowCompletePopover] = useState(false); + if (!completePercentage) { return null; } - const [showCompletePopover, setShowCompletePopover] = useState(false); - const completeSegmentOffset = (3.6 * completePercentage) / 8; let completeTooltipDegree = completePercentage < 100 ? -completeSegmentOffset : 0; @@ -78,7 +78,7 @@ function CompleteDonutSegment({ completePercentage, intl, lockedPercentage }) { )} ); -} +}; CompleteDonutSegment.propTypes = { completePercentage: PropTypes.number.isRequired, diff --git a/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx b/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx index fe62189f44..54b6caa9c6 100644 --- a/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx +++ b/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx @@ -10,7 +10,7 @@ import IncompleteDonutSegment from './IncompleteDonutSegment'; import LockedDonutSegment from './LockedDonutSegment'; import messages from './messages'; -function CompletionDonutChart({ intl }) { +const CompletionDonutChart = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -60,7 +60,7 @@ function CompletionDonutChart({ intl }) { ); -} +}; CompletionDonutChart.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/course-completion/CourseCompletion.jsx b/src/course-home/progress-tab/course-completion/CourseCompletion.jsx index 5b4abc227d..1fc7dfa946 100644 --- a/src/course-home/progress-tab/course-completion/CourseCompletion.jsx +++ b/src/course-home/progress-tab/course-completion/CourseCompletion.jsx @@ -4,23 +4,21 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import CompletionDonutChart from './CompletionDonutChart'; import messages from './messages'; -function CourseCompletion({ intl }) { - return ( -
-
-
-

{intl.formatMessage(messages.courseCompletion)}

-

- {intl.formatMessage(messages.completionBody)} -

-
-
- -
+const CourseCompletion = ({ intl }) => ( +
+
+
+

{intl.formatMessage(messages.courseCompletion)}

+

+ {intl.formatMessage(messages.completionBody)} +

-
- ); -} +
+ +
+
+
+); CourseCompletion.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/course-completion/IncompleteDonutSegment.jsx b/src/course-home/progress-tab/course-completion/IncompleteDonutSegment.jsx index e1bcc3d7f1..9f2d16eb87 100644 --- a/src/course-home/progress-tab/course-completion/IncompleteDonutSegment.jsx +++ b/src/course-home/progress-tab/course-completion/IncompleteDonutSegment.jsx @@ -6,13 +6,13 @@ import { OverlayTrigger, Popover } from '@edx/paragon'; import messages from './messages'; -function IncompleteDonutSegment({ incompletePercentage, intl }) { +const IncompleteDonutSegment = ({ incompletePercentage, intl }) => { + const [showIncompletePopover, setShowIncompletePopover] = useState(false); + if (!incompletePercentage) { return null; } - const [showIncompletePopover, setShowIncompletePopover] = useState(false); - const incompleteSegmentOffset = (3.6 * incompletePercentage) / 16; const incompleteTooltipDegree = incompletePercentage < 100 ? incompleteSegmentOffset : 0; @@ -49,7 +49,7 @@ function IncompleteDonutSegment({ incompletePercentage, intl }) { ); -} +}; IncompleteDonutSegment.propTypes = { incompletePercentage: PropTypes.number.isRequired, diff --git a/src/course-home/progress-tab/course-completion/LockedDonutSegment.jsx b/src/course-home/progress-tab/course-completion/LockedDonutSegment.jsx index 0ad8b02d04..9ab5b4de71 100644 --- a/src/course-home/progress-tab/course-completion/LockedDonutSegment.jsx +++ b/src/course-home/progress-tab/course-completion/LockedDonutSegment.jsx @@ -6,7 +6,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import messages from './messages'; -function LockedDonutSegment({ intl, lockedPercentage }) { +const LockedDonutSegment = ({ intl, lockedPercentage }) => { const [showLockedPopover, setShowLockedPopover] = useState(false); if (!lockedPercentage) { @@ -62,7 +62,7 @@ function LockedDonutSegment({ intl, lockedPercentage }) { ); -} +}; LockedDonutSegment.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/credit-information/CreditInformation.jsx b/src/course-home/progress-tab/credit-information/CreditInformation.jsx index 8683fb2c6f..24198be975 100644 --- a/src/course-home/progress-tab/credit-information/CreditInformation.jsx +++ b/src/course-home/progress-tab/credit-information/CreditInformation.jsx @@ -10,7 +10,7 @@ import { DashboardLink } from '../../../shared/links'; import messages from './messages'; -function CreditInformation({ intl }) { +const CreditInformation = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -106,7 +106,7 @@ function CreditInformation({ intl }) { {requirements} ); -} +}; CreditInformation.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx index 2d2677e3a8..6aabdc08e6 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx @@ -11,7 +11,7 @@ import CreditInformation from '../../credit-information/CreditInformation'; import messages from '../messages'; -function CourseGrade({ intl }) { +const CourseGrade = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -52,7 +52,7 @@ function CourseGrade({ intl }) { ); -} +}; CourseGrade.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx index 39443a0e50..7710de35fb 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx @@ -10,7 +10,7 @@ import { useModel } from '../../../../generic/model-store'; import GradeRangeTooltip from './GradeRangeTooltip'; import messages from '../messages'; -function CourseGradeFooter({ intl, passingGrade }) { +const CourseGradeFooter = ({ intl, passingGrade }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -83,7 +83,7 @@ function CourseGradeFooter({ intl, passingGrade }) { ); -} +}; CourseGradeFooter.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx index 4a485b11ef..498788eea6 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx @@ -10,7 +10,7 @@ import { Button, Icon } from '@edx/paragon'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; -function CourseGradeHeader({ intl }) { +const CourseGradeHeader = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -81,7 +81,7 @@ function CourseGradeHeader({ intl }) { )} ); -} +}; CourseGradeHeader.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx index c778bb3778..2941dc662e 100644 --- a/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx @@ -11,7 +11,7 @@ import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; -function CurrentGradeTooltip({ intl, tooltipClassName }) { +const CurrentGradeTooltip = ({ intl, tooltipClassName }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -62,7 +62,7 @@ function CurrentGradeTooltip({ intl, tooltipClassName }) { ); -} +}; CurrentGradeTooltip.defaultProps = { tooltipClassName: '', diff --git a/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx b/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx index a0f72eb3c0..3cbbe5b1c0 100644 --- a/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx +++ b/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx @@ -11,7 +11,7 @@ import PassingGradeTooltip from './PassingGradeTooltip'; import messages from '../messages'; -function GradeBar({ intl, passingGrade }) { +const GradeBar = ({ intl, passingGrade }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -49,7 +49,7 @@ function GradeBar({ intl, passingGrade }) { ); -} +}; GradeBar.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx index e983d96232..760c6e3da4 100644 --- a/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx @@ -11,7 +11,7 @@ import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; -function GradeRangeTooltip({ intl, iconButtonClassName, passingGrade }) { +const GradeRangeTooltip = ({ intl, iconButtonClassName, passingGrade }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -72,7 +72,7 @@ function GradeRangeTooltip({ intl, iconButtonClassName, passingGrade }) { /> ); -} +}; GradeRangeTooltip.defaultProps = { iconButtonClassName: '', diff --git a/src/course-home/progress-tab/grades/course-grade/PassingGradeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/PassingGradeTooltip.jsx index 3b5cabe076..0c4eb0f740 100644 --- a/src/course-home/progress-tab/grades/course-grade/PassingGradeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/PassingGradeTooltip.jsx @@ -8,7 +8,7 @@ import { OverlayTrigger, Popover } from '@edx/paragon'; import messages from '../messages'; -function PassingGradeTooltip({ intl, passingGrade, tooltipClassName }) { +const PassingGradeTooltip = ({ intl, passingGrade, tooltipClassName }) => { const isLocaleRtl = isRtl(getLocale()); let passingGradeDirection = passingGrade < 50 ? '' : '-'; @@ -47,7 +47,7 @@ function PassingGradeTooltip({ intl, passingGrade, tooltipClassName }) { ); -} +}; PassingGradeTooltip.defaultProps = { tooltipClassName: '', diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx index 62c0e1d5da..c7f2b1138a 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx @@ -12,7 +12,7 @@ import DetailedGradesTable from './DetailedGradesTable'; import messages from '../messages'; -function DetailedGrades({ intl }) { +const DetailedGrades = ({ intl }) => { const { administrator } = getAuthenticatedUser(); const { courseId, @@ -79,7 +79,7 @@ function DetailedGrades({ intl }) { )} ); -} +}; DetailedGrades.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx index 0e34907098..8f84423a1e 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx @@ -10,7 +10,7 @@ import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; import SubsectionTitleCell from './SubsectionTitleCell'; -function DetailedGradesTable({ intl }) { +const DetailedGradesTable = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -64,7 +64,7 @@ function DetailedGradesTable({ intl }) { ); }) ); -} +}; DetailedGradesTable.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/detailed-grades/ProblemScoreDrawer.jsx b/src/course-home/progress-tab/grades/detailed-grades/ProblemScoreDrawer.jsx index 7f64aa8f8c..855f3dfe46 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/ProblemScoreDrawer.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/ProblemScoreDrawer.jsx @@ -8,7 +8,7 @@ import { import messages from '../messages'; -function ProblemScoreDrawer({ intl, problemScores, subsection }) { +const ProblemScoreDrawer = ({ intl, problemScores, subsection }) => { const isLocaleRtl = isRtl(getLocale()); return ( @@ -22,7 +22,7 @@ function ProblemScoreDrawer({ intl, problemScores, subsection }) { ); -} +}; ProblemScoreDrawer.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx b/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx index 0b086e30a9..e60d2be421 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx @@ -14,7 +14,7 @@ import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; import ProblemScoreDrawer from './ProblemScoreDrawer'; -function SubsectionTitleCell({ intl, subsection }) { +const SubsectionTitleCell = ({ intl, subsection }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -99,7 +99,7 @@ function SubsectionTitleCell({ intl, subsection }) { ); -} +}; SubsectionTitleCell.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx index 11633e2a8c..0a1218af5c 100644 --- a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx @@ -7,9 +7,9 @@ import { Icon } from '@edx/paragon'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; -function AssignmentTypeCell({ +const AssignmentTypeCell = ({ intl, assignmentType, footnoteMarker, footnoteId, locked, -}) { +}) => { const { courseId, } = useSelector(state => state.courseHome); @@ -42,7 +42,7 @@ function AssignmentTypeCell({ ); -} +}; AssignmentTypeCell.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx index 56d1a73944..14f6b2c337 100644 --- a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx @@ -7,7 +7,7 @@ import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/ import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; -function DroppableAssignmentFootnote({ footnotes, intl }) { +const DroppableAssignmentFootnote = ({ footnotes, intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -37,7 +37,7 @@ function DroppableAssignmentFootnote({ footnotes, intl }) { ); -} +}; DroppableAssignmentFootnote.propTypes = { footnotes: PropTypes.arrayOf(PropTypes.shape({ diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx index b937652cb4..e6c6b9ad02 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx @@ -5,7 +5,7 @@ import { useModel } from '../../../../generic/model-store'; import GradeSummaryHeader from './GradeSummaryHeader'; import GradeSummaryTable from './GradeSummaryTable'; -function GradeSummary() { +const GradeSummary = () => { const { courseId, } = useSelector(state => state.courseHome); @@ -28,6 +28,6 @@ function GradeSummary() { ); -} +}; export default GradeSummary; diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx index 230d205930..cff58e2f1c 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx @@ -11,7 +11,7 @@ import { Blocked, InfoOutline } from '@edx/paragon/icons'; import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; -function GradeSummaryHeader({ intl, allOfSomeAssignmentTypeIsLocked }) { +const GradeSummaryHeader = ({ intl, allOfSomeAssignmentTypeIsLocked }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -54,7 +54,7 @@ function GradeSummaryHeader({ intl, allOfSomeAssignmentTypeIsLocked }) { )} ); -} +}; GradeSummaryHeader.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx index e9cf260c62..ae44d155a1 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx @@ -14,7 +14,7 @@ import GradeSummaryTableFooter from './GradeSummaryTableFooter'; import messages from '../messages'; -function GradeSummaryTable({ intl, setAllOfSomeAssignmentTypeIsLocked }) { +const GradeSummaryTable = ({ intl, setAllOfSomeAssignmentTypeIsLocked }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -79,6 +79,16 @@ function GradeSummaryTable({ intl, setAllOfSomeAssignmentTypeIsLocked }) { weightedGrade: { weightedGrade: `${(assignment.weightedGrade * 100).toFixed(0)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, }; }); + const getAssignmentTypeCell = (value) => ( + + ); + + const getCell = (locked, value) => {value}; return ( <> @@ -89,45 +99,28 @@ function GradeSummaryTable({ intl, setAllOfSomeAssignmentTypeIsLocked }) { { Header: `${intl.formatMessage(messages.assignmentType)}`, accessor: 'type', - // eslint-disable-next-line react/prop-types - Cell: ({ value }) => ( - - ), + Cell: ({ value }) => getAssignmentTypeCell(value), headerClassName: 'h5 mb-0', }, { Header: `${intl.formatMessage(messages.weight)}`, accessor: 'weight', headerClassName: 'justify-content-end h5 mb-0', - // eslint-disable-next-line react/prop-types - Cell: ({ value }) => ( - {value.weight} // eslint-disable-line react/prop-types - ), + Cell: ({ value }) => getCell(value.locked, value.weight), cellClassName: 'text-right small', }, { Header: `${intl.formatMessage(messages.grade)}`, accessor: 'grade', headerClassName: 'justify-content-end h5 mb-0', - // eslint-disable-next-line react/prop-types - Cell: ({ value }) => ( - {value.grade} // eslint-disable-line react/prop-types - ), + Cell: ({ value }) => getCell(value.locked, value.grade), cellClassName: 'text-right small', }, { Header: `${intl.formatMessage(messages.weightedGrade)}`, accessor: 'weightedGrade', headerClassName: 'justify-content-end h5 mb-0 text-right', - // eslint-disable-next-line react/prop-types - Cell: ({ value }) => ( - {value.weightedGrade} // eslint-disable-line react/prop-types - ), + Cell: ({ value }) => getCell(value.locked, value.weightedGrade), cellClassName: 'text-right font-weight-bold small', }, ]} @@ -141,7 +134,7 @@ function GradeSummaryTable({ intl, setAllOfSomeAssignmentTypeIsLocked }) { )} ); -} +}; GradeSummaryTable.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx index d30dde0e1d..a3d380e26c 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx @@ -9,7 +9,7 @@ import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; -function GradeSummaryTableFooter({ intl }) { +const GradeSummaryTableFooter = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -34,7 +34,7 @@ function GradeSummaryTableFooter({ intl }) { ); -} +}; GradeSummaryTableFooter.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/progress-tab/related-links/RelatedLinks.jsx b/src/course-home/progress-tab/related-links/RelatedLinks.jsx index 2392e9e138..fe5e34181d 100644 --- a/src/course-home/progress-tab/related-links/RelatedLinks.jsx +++ b/src/course-home/progress-tab/related-links/RelatedLinks.jsx @@ -9,7 +9,7 @@ import { Hyperlink } from '@edx/paragon'; import messages from './messages'; import { useModel } from '../../../generic/model-store'; -function RelatedLinks({ intl }) { +const RelatedLinks = ({ intl }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -56,7 +56,7 @@ function RelatedLinks({ intl }) { ); -} +}; RelatedLinks.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/suggested-schedule-messaging/ShiftDatesAlert.jsx b/src/course-home/suggested-schedule-messaging/ShiftDatesAlert.jsx index 37b918ffa9..e3e4450884 100644 --- a/src/course-home/suggested-schedule-messaging/ShiftDatesAlert.jsx +++ b/src/course-home/suggested-schedule-messaging/ShiftDatesAlert.jsx @@ -14,7 +14,7 @@ import { resetDeadlines } from '../data'; import { useModel } from '../../generic/model-store'; import messages from './messages'; -function ShiftDatesAlert({ fetch, intl, model }) { +const ShiftDatesAlert = ({ fetch, intl, model }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -29,12 +29,12 @@ function ShiftDatesAlert({ fetch, intl, model }) { missedGatedContent, } = datesBannerInfo; + const dispatch = useDispatch(); + if (!missedDeadlines || missedGatedContent || hasEnded) { return null; } - const dispatch = useDispatch(); - return ( @@ -55,7 +55,7 @@ function ShiftDatesAlert({ fetch, intl, model }) { ); -} +}; ShiftDatesAlert.propTypes = { fetch: PropTypes.func.isRequired, diff --git a/src/course-home/suggested-schedule-messaging/SuggestedScheduleHeader.jsx b/src/course-home/suggested-schedule-messaging/SuggestedScheduleHeader.jsx index 2e9b1a7421..7953929c98 100644 --- a/src/course-home/suggested-schedule-messaging/SuggestedScheduleHeader.jsx +++ b/src/course-home/suggested-schedule-messaging/SuggestedScheduleHeader.jsx @@ -3,13 +3,11 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import messages from './messages'; -function SuggestedScheduleHeader({ intl }) { - return ( -

- {intl.formatMessage(messages.suggestedSchedule)} -

- ); -} +const SuggestedScheduleHeader = ({ intl }) => ( +

+ {intl.formatMessage(messages.suggestedSchedule)} +

+); SuggestedScheduleHeader.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/suggested-schedule-messaging/UpgradeToCompleteAlert.jsx b/src/course-home/suggested-schedule-messaging/UpgradeToCompleteAlert.jsx index d01c21d177..e2e73539b7 100644 --- a/src/course-home/suggested-schedule-messaging/UpgradeToCompleteAlert.jsx +++ b/src/course-home/suggested-schedule-messaging/UpgradeToCompleteAlert.jsx @@ -12,7 +12,7 @@ import { import { useModel } from '../../generic/model-store'; import messages from './messages'; -function UpgradeToCompleteAlert({ intl, logUpgradeLinkClick }) { +const UpgradeToCompleteAlert = ({ intl, logUpgradeLinkClick }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -55,7 +55,7 @@ function UpgradeToCompleteAlert({ intl, logUpgradeLinkClick }) { ); -} +}; UpgradeToCompleteAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-home/suggested-schedule-messaging/UpgradeToShiftDatesAlert.jsx b/src/course-home/suggested-schedule-messaging/UpgradeToShiftDatesAlert.jsx index 70f818e503..1592d80125 100644 --- a/src/course-home/suggested-schedule-messaging/UpgradeToShiftDatesAlert.jsx +++ b/src/course-home/suggested-schedule-messaging/UpgradeToShiftDatesAlert.jsx @@ -13,7 +13,7 @@ import { import { useModel } from '../../generic/model-store'; import messages from './messages'; -function UpgradeToShiftDatesAlert({ intl, logUpgradeLinkClick, model }) { +const UpgradeToShiftDatesAlert = ({ intl, logUpgradeLinkClick, model }) => { const { courseId, } = useSelector(state => state.courseHome); @@ -57,7 +57,7 @@ function UpgradeToShiftDatesAlert({ intl, logUpgradeLinkClick, model }) { ); -} +}; UpgradeToShiftDatesAlert.propTypes = { intl: intlShape.isRequired, diff --git a/src/course-tabs/CourseTabsNavigation.jsx b/src/course-tabs/CourseTabsNavigation.jsx index ec84ce0744..838a558ad6 100644 --- a/src/course-tabs/CourseTabsNavigation.jsx +++ b/src/course-tabs/CourseTabsNavigation.jsx @@ -6,30 +6,28 @@ import classNames from 'classnames'; import messages from './messages'; import Tabs from '../generic/tabs/Tabs'; -function CourseTabsNavigation({ +const CourseTabsNavigation = ({ activeTabSlug, className, tabs, intl, -}) { - return ( -
-
- - {tabs.map(({ url, title, slug }) => ( - - {title} - - ))} - -
+}) => ( +
+
+ + {tabs.map(({ url, title, slug }) => ( + + {title} + + ))} +
- ); -} +
+); CourseTabsNavigation.propTypes = { activeTabSlug: PropTypes.string, diff --git a/src/courseware/CoursewareContainer.test.jsx b/src/courseware/CoursewareContainer.test.jsx index 83e59262e6..7b02f611e3 100644 --- a/src/courseware/CoursewareContainer.test.jsx +++ b/src/courseware/CoursewareContainer.test.jsx @@ -24,15 +24,13 @@ import { buildOutlineFromBlocks } from './data/__factories__/learningSequencesOu // to have been passed into the component. Separate tests can handle unit rendering, but this // proves that the component is rendered and receives the correct props. We probably COULD render // Unit.jsx and its iframe in this test, but it's already complex enough. -function MockUnit({ courseId, id }) { // eslint-disable-line react/prop-types - return ( -
Unit Contents {courseId} {id}
- ); -} jest.mock( './course/sequence/Unit', - () => MockUnit, + // eslint-disable-next-line react/prop-types + () => function ({ courseId, id }) { + return
Unit Contents {courseId} {id}
; + }, ); jest.mock('@edx/frontend-platform/analytics'); diff --git a/src/courseware/CoursewareRedirectLandingPage.jsx b/src/courseware/CoursewareRedirectLandingPage.jsx index 5be31f90f3..0597a7b22b 100644 --- a/src/courseware/CoursewareRedirectLandingPage.jsx +++ b/src/courseware/CoursewareRedirectLandingPage.jsx @@ -7,7 +7,7 @@ import { PageRoute } from '@edx/frontend-platform/react'; import queryString from 'query-string'; import PageLoading from '../generic/PageLoading'; -export default () => { +const CoursewareRedirectLandingPage = () => { const { path } = useRouteMatch(); return (
@@ -50,3 +50,5 @@ export default () => {
); }; + +export default CoursewareRedirectLandingPage; diff --git a/src/courseware/course/Course.jsx b/src/courseware/course/Course.jsx index 480bf8a5cb..4bc56f1430 100644 --- a/src/courseware/course/Course.jsx +++ b/src/courseware/course/Course.jsx @@ -18,7 +18,7 @@ import SidebarTriggers from './sidebar/SidebarTriggers'; import { useModel } from '../../generic/model-store'; import { getSessionStorage, setSessionStorage } from '../../data/sessionStorage'; -function Course({ +const Course = ({ courseId, sequenceId, unitId, @@ -26,7 +26,7 @@ function Course({ previousSequenceHandler, unitNavigationHandler, windowWidth, -}) { +}) => { const course = useModel('coursewareMeta', courseId); const { celebrations, @@ -109,7 +109,7 @@ function Course({ ); -} +}; Course.propTypes = { courseId: PropTypes.string, @@ -127,7 +127,7 @@ Course.defaultProps = { unitId: null, }; -function CourseWrapper(props) { +const CourseWrapper = (props) => { // useWindowSize initially returns an undefined width intentionally at first. // See https://www.joshwcomeau.com/react/the-perils-of-rehydration/ for why. // But has some tricky window-size-dependent, session-storage-setting logic and React would yell at us if @@ -139,6 +139,6 @@ function CourseWrapper(props) { } return ; -} +}; export default CourseWrapper; diff --git a/src/courseware/course/CourseBreadcrumbs.jsx b/src/courseware/course/CourseBreadcrumbs.jsx index a90aff5f8c..0a1e73493d 100644 --- a/src/courseware/course/CourseBreadcrumbs.jsx +++ b/src/courseware/course/CourseBreadcrumbs.jsx @@ -10,9 +10,9 @@ import { Link } from 'react-router-dom'; import { useModel, useModels } from '../../generic/model-store'; import JumpNavMenuItem from './JumpNavMenuItem'; -function CourseBreadcrumb({ +const CourseBreadcrumb = ({ content, withSeparator, courseId, sequenceId, unitId, isStaff, -}) { +}) => { const defaultContent = content.filter(destination => destination.default)[0] || { id: courseId, label: '', sequences: [] }; return ( <> @@ -55,7 +55,7 @@ function CourseBreadcrumb({ ); -} +}; CourseBreadcrumb.propTypes = { content: PropTypes.arrayOf( PropTypes.shape({ @@ -79,13 +79,13 @@ CourseBreadcrumb.defaultProps = { isStaff: null, }; -export default function CourseBreadcrumbs({ +const CourseBreadcrumbs = ({ courseId, sectionId, sequenceId, unitId, isStaff, -}) { +}) => { const course = useModel('coursewareMeta', courseId); const courseStatus = useSelector(state => state.courseware.courseStatus); const sequenceStatus = useSelector(state => state.courseware.sequenceStatus); @@ -151,7 +151,7 @@ export default function CourseBreadcrumbs({ ); -} +}; CourseBreadcrumbs.propTypes = { courseId: PropTypes.string.isRequired, @@ -167,3 +167,5 @@ CourseBreadcrumbs.defaultProps = { unitId: null, isStaff: null, }; + +export default CourseBreadcrumbs; diff --git a/src/courseware/course/JumpNavMenuItem.jsx b/src/courseware/course/JumpNavMenuItem.jsx index 37077f29c8..4bfe74b3c4 100644 --- a/src/courseware/course/JumpNavMenuItem.jsx +++ b/src/courseware/course/JumpNavMenuItem.jsx @@ -8,14 +8,14 @@ import { sendTrackEvent, } from '@edx/frontend-platform/analytics'; -export default function JumpNavMenuItem({ +const JumpNavMenuItem = ({ title, courseId, currentSequence, currentUnit, sequences, isDefault, -}) { +}) => { function logEvent(targetUrl) { const eventName = 'edx.ui.lms.jump_nav.selected'; const payload = { @@ -48,7 +48,7 @@ export default function JumpNavMenuItem({ {title} ); -} +}; const sequenceShape = PropTypes.shape({ id: PropTypes.string.isRequired, @@ -62,3 +62,5 @@ JumpNavMenuItem.propTypes = { currentSequence: PropTypes.string.isRequired, currentUnit: PropTypes.string.isRequired, }; + +export default JumpNavMenuItem; diff --git a/src/courseware/course/bookmark/BookmarkButton.jsx b/src/courseware/course/bookmark/BookmarkButton.jsx index 29e9d9ba8c..0f105a9160 100644 --- a/src/courseware/course/bookmark/BookmarkButton.jsx +++ b/src/courseware/course/bookmark/BookmarkButton.jsx @@ -23,9 +23,9 @@ const hasBookmarkLabel = ( /> ); -export default function BookmarkButton({ +const BookmarkButton = ({ isBookmarked, isProcessing, unitId, -}) { +}) => { const bookmarkState = isBookmarked ? 'bookmarked' : 'default'; const state = isProcessing ? `${bookmarkState}Processing` : bookmarkState; @@ -36,6 +36,7 @@ export default function BookmarkButton({ } else { dispatch(addBookmark(unitId)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isBookmarked, unitId]); return ( @@ -59,7 +60,7 @@ export default function BookmarkButton({ }} /> ); -} +}; BookmarkButton.propTypes = { unitId: PropTypes.string.isRequired, @@ -70,3 +71,5 @@ BookmarkButton.propTypes = { BookmarkButton.defaultProps = { isBookmarked: false, }; + +export default BookmarkButton; diff --git a/src/courseware/course/bookmark/BookmarkFilledIcon.jsx b/src/courseware/course/bookmark/BookmarkFilledIcon.jsx index cb2a6614ee..af6efe730b 100644 --- a/src/courseware/course/bookmark/BookmarkFilledIcon.jsx +++ b/src/courseware/course/bookmark/BookmarkFilledIcon.jsx @@ -2,6 +2,6 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBookmark } from '@fortawesome/free-solid-svg-icons'; -export default function BookmarkFilledIcon(props) { - return ; -} +const BookmarkFilledIcon = (props) => ; + +export default BookmarkFilledIcon; diff --git a/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx b/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx index eed786a69f..2153625754 100644 --- a/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx +++ b/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx @@ -2,6 +2,6 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBookmark } from '@fortawesome/free-regular-svg-icons'; -export default function BookmarkOutlineIcon(props) { - return ; -} +const BookmarkOutlineIcon = (props) => ; + +export default BookmarkOutlineIcon; diff --git a/src/courseware/course/celebration/CelebrationModal.jsx b/src/courseware/course/celebration/CelebrationModal.jsx index 768bc1fe38..6b0f2e7e5d 100644 --- a/src/courseware/course/celebration/CelebrationModal.jsx +++ b/src/courseware/course/celebration/CelebrationModal.jsx @@ -16,9 +16,9 @@ import SocialIcons from '../../social-share/SocialIcons'; import { recordFirstSectionCelebration } from './utils'; import { useModel } from '../../../generic/model-store'; -function CelebrationModal({ +const CelebrationModal = ({ courseId, intl, isOpen, onClose, ...rest -}) { +}) => { const { org } = useModel('courseHomeMeta', courseId); const wideScreen = useWindowSize().width >= breakpoints.small.minWidth; @@ -26,6 +26,7 @@ function CelebrationModal({ if (isOpen) { recordFirstSectionCelebration(org, courseId); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); return ( @@ -59,7 +60,7 @@ function CelebrationModal({ ); -} +}; CelebrationModal.propTypes = { courseId: PropTypes.string.isRequired, diff --git a/src/courseware/course/celebration/WeeklyGoalCelebrationModal.jsx b/src/courseware/course/celebration/WeeklyGoalCelebrationModal.jsx index 4d04f16149..19dafc964d 100644 --- a/src/courseware/course/celebration/WeeklyGoalCelebrationModal.jsx +++ b/src/courseware/course/celebration/WeeklyGoalCelebrationModal.jsx @@ -11,15 +11,16 @@ import messages from './messages'; import { recordWeeklyGoalCelebration } from './utils'; import { useModel } from '../../../generic/model-store'; -function WeeklyGoalCelebrationModal({ +const WeeklyGoalCelebrationModal = ({ courseId, daysPerWeek, intl, isOpen, onClose, ...rest -}) { +}) => { const { org } = useModel('courseHomeMeta', courseId); useEffect(() => { if (isOpen) { recordWeeklyGoalCelebration(org, courseId); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); return ( @@ -71,7 +72,7 @@ function WeeklyGoalCelebrationModal({ ); -} +}; WeeklyGoalCelebrationModal.propTypes = { courseId: PropTypes.string.isRequired, diff --git a/src/courseware/course/content-tools/ContentTools.jsx b/src/courseware/course/content-tools/ContentTools.jsx index 095dc2ed6e..871bf62971 100644 --- a/src/courseware/course/content-tools/ContentTools.jsx +++ b/src/courseware/course/content-tools/ContentTools.jsx @@ -4,22 +4,20 @@ import PropTypes from 'prop-types'; import Calculator from './calculator'; import NotesVisibility from './notes-visibility'; -export default function ContentTools({ +const ContentTools = ({ course, -}) { - return ( -
-
- {course.showCalculator && ( - - )} - {course.notes.enabled && ( - - )} -
+}) => ( +
+
+ {course.showCalculator && ( + + )} + {course.notes.enabled && ( + + )}
- ); -} +
+); ContentTools.propTypes = { course: PropTypes.shape({ @@ -29,3 +27,5 @@ ContentTools.propTypes = { showCalculator: PropTypes.bool, }).isRequired, }; + +export default ContentTools; diff --git a/src/courseware/course/content-tools/ContentTools.test.jsx b/src/courseware/course/content-tools/ContentTools.test.jsx index 08cadff99f..152acac862 100644 --- a/src/courseware/course/content-tools/ContentTools.test.jsx +++ b/src/courseware/course/content-tools/ContentTools.test.jsx @@ -2,8 +2,12 @@ import React from 'react'; import { initializeTestStore, render, screen } from '../../../setupTest'; import ContentTools from './ContentTools'; -jest.mock('./calculator/Calculator', () => () =>
); -jest.mock('./notes-visibility/NotesVisibility', () => () =>
); +jest.mock('./calculator/Calculator', () => function () { + return
; +}); +jest.mock('./notes-visibility/NotesVisibility', () => function () { + return
; +}); describe('Content Tools', () => { const mockData = { diff --git a/src/courseware/course/course-exit/CatalogSuggestion.jsx b/src/courseware/course/course-exit/CatalogSuggestion.jsx index ededa14676..f0287d97bb 100644 --- a/src/courseware/course/course-exit/CatalogSuggestion.jsx +++ b/src/courseware/course/course-exit/CatalogSuggestion.jsx @@ -16,7 +16,7 @@ import { useModel } from '../../../generic/model-store'; import messages from './messages'; import { logClick } from './utils'; -function CatalogSuggestion({ intl, variant }) { +const CatalogSuggestion = ({ intl, variant }) => { const { courseId } = useSelector(state => state.courseware); const { org } = useModel('courseHomeMeta', courseId); const { administrator } = getAuthenticatedUser(); @@ -45,7 +45,7 @@ function CatalogSuggestion({ intl, variant }) {
); -} +}; CatalogSuggestion.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/CourseCelebration.jsx b/src/courseware/course/course-exit/CourseCelebration.jsx index c9c5a2ba39..fd18b35259 100644 --- a/src/courseware/course/course-exit/CourseCelebration.jsx +++ b/src/courseware/course/course-exit/CourseCelebration.jsx @@ -36,7 +36,7 @@ import CourseRecommendations from './CourseRecommendations'; const LINKEDIN_BLUE = '#2867B2'; -function CourseCelebration({ intl }) { +const CourseCelebration = ({ intl }) => { const wideScreen = useWindowSize().width >= breakpoints.medium.minWidth; const { courseId } = useSelector(state => state.courseware); const dispatch = useDispatch(); @@ -362,7 +362,7 @@ function CourseCelebration({ intl }) {
); -} +}; CourseCelebration.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/CourseExit.jsx b/src/courseware/course/course-exit/CourseExit.jsx index 6bd80b857e..d6bff4084a 100644 --- a/src/courseware/course/course-exit/CourseExit.jsx +++ b/src/courseware/course/course-exit/CourseExit.jsx @@ -15,7 +15,7 @@ import { unsubscribeFromGoalReminders } from './data/thunks'; import { useModel } from '../../../generic/model-store'; -function CourseExit({ intl }) { +const CourseExit = ({ intl }) => { const { courseId } = useSelector(state => state.courseware); const { certificateData, @@ -74,7 +74,7 @@ function CourseExit({ intl }) { {body} ); -} +}; CourseExit.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/CourseInProgress.jsx b/src/courseware/course/course-exit/CourseInProgress.jsx index 7c230d4f9e..a6b29096bd 100644 --- a/src/courseware/course/course-exit/CourseInProgress.jsx +++ b/src/courseware/course/course-exit/CourseInProgress.jsx @@ -14,7 +14,7 @@ import DashboardFootnote from './DashboardFootnote'; import messages from './messages'; import { logClick, logVisit } from './utils'; -function CourseInProgress({ intl }) { +const CourseInProgress = ({ intl }) => { const { courseId } = useSelector(state => state.courseware); const { org, @@ -58,7 +58,7 @@ function CourseInProgress({ intl }) {
); -} +}; CourseInProgress.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/CourseNonPassing.jsx b/src/courseware/course/course-exit/CourseNonPassing.jsx index a39d35bb8c..e63b9a704e 100644 --- a/src/courseware/course/course-exit/CourseNonPassing.jsx +++ b/src/courseware/course/course-exit/CourseNonPassing.jsx @@ -14,7 +14,7 @@ import DashboardFootnote from './DashboardFootnote'; import messages from './messages'; import { logClick, logVisit } from './utils'; -function CourseNonPassing({ intl }) { +const CourseNonPassing = ({ intl }) => { const { courseId } = useSelector(state => state.courseware); const { org, @@ -58,7 +58,7 @@ function CourseNonPassing({ intl }) {
); -} +}; CourseNonPassing.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/CourseRecommendations.jsx b/src/courseware/course/course-exit/CourseRecommendations.jsx index 53dff1b17c..d4cc344599 100644 --- a/src/courseware/course/course-exit/CourseRecommendations.jsx +++ b/src/courseware/course/course-exit/CourseRecommendations.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-no-useless-fragment */ import React, { useEffect } from 'react'; import { getConfig } from '@edx/frontend-platform'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; @@ -55,7 +56,7 @@ const ListStyles = { conjunction: 'conjunction', }; -function CourseCard({ +const CourseCard = ({ original: { title, image, @@ -64,7 +65,7 @@ function CourseCard({ onClick, }, intl, -}) { +}) => { const formatList = (items, style) => ( items.join(intl.formatMessage( messages.listJoin, @@ -112,7 +113,7 @@ function CourseCard({
); -} +}; CourseCard.propTypes = { original: PropTypes.shape({ @@ -131,7 +132,7 @@ CourseCard.propTypes = { const IntlCard = injectIntl(CourseCard); -function CourseRecommendations({ intl, variant }) { +const CourseRecommendations = ({ intl, variant }) => { const { courseId, recommendationsStatus } = useSelector(state => ({ ...state.recommendations, ...state.courseware })); const { recommendations } = useModel('coursewareMeta', courseId); const { org, number } = useModel('courseHomeMeta', courseId); @@ -142,6 +143,7 @@ function CourseRecommendations({ intl, variant }) { useEffect(() => { dispatch(fetchCourseRecommendations(courseKey, courseId)); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [dispatch]); const recommendationsLength = recommendations ? recommendations.length : 0; @@ -200,7 +202,7 @@ function CourseRecommendations({ intl, variant }) {
); -} +}; CourseRecommendations.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/DashboardFootnote.jsx b/src/courseware/course/course-exit/DashboardFootnote.jsx index 5a52542d0b..132d46fff7 100644 --- a/src/courseware/course/course-exit/DashboardFootnote.jsx +++ b/src/courseware/course/course-exit/DashboardFootnote.jsx @@ -16,7 +16,7 @@ import Footnote from './Footnote'; import messages from './messages'; import { logClick } from './utils'; -function DashboardFootnote({ intl, variant }) { +const DashboardFootnote = ({ intl, variant }) => { const { courseId } = useSelector(state => state.courseware); const { org } = useModel('courseHomeMeta', courseId); const { administrator } = getAuthenticatedUser(); @@ -45,7 +45,7 @@ function DashboardFootnote({ intl, variant }) { )} /> ); -} +}; DashboardFootnote.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/Footnote.jsx b/src/courseware/course/course-exit/Footnote.jsx index b405edbe83..85317548ac 100644 --- a/src/courseware/course/course-exit/Footnote.jsx +++ b/src/courseware/course/course-exit/Footnote.jsx @@ -2,16 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -function Footnote({ icon, text }) { - return ( -
-

-   - {text} -

-
- ); -} +const Footnote = ({ icon, text }) => ( +
+

+   + {text} +

+
+); Footnote.propTypes = { icon: PropTypes.shape({}).isRequired, diff --git a/src/courseware/course/course-exit/ProgramCompletion.jsx b/src/courseware/course/course-exit/ProgramCompletion.jsx index 0114f65e89..f9e5635dad 100644 --- a/src/courseware/course/course-exit/ProgramCompletion.jsx +++ b/src/courseware/course/course-exit/ProgramCompletion.jsx @@ -19,13 +19,13 @@ import messages from './messages'; const programTypes = ['microbachelors', 'micromasters', 'professional-certificate', 'xseries']; -function ProgramCompletion({ +const ProgramCompletion = ({ intl, progress, title, type, url, -}) { +}) => { if (!programTypes.includes(type) || progress.notStarted !== 0 || progress.inProgress !== 0) { return null; } @@ -95,7 +95,7 @@ function ProgramCompletion({ ); -} +}; ProgramCompletion.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/course-exit/UpgradeFootnote.jsx b/src/courseware/course/course-exit/UpgradeFootnote.jsx index d4173346cb..0f2cf72887 100644 --- a/src/courseware/course/course-exit/UpgradeFootnote.jsx +++ b/src/courseware/course/course-exit/UpgradeFootnote.jsx @@ -14,7 +14,7 @@ import { logClick } from './utils'; import messages from './messages'; import { useModel } from '../../../generic/model-store'; -function UpgradeFootnote({ deadline, href, intl }) { +const UpgradeFootnote = ({ deadline, href, intl }) => { const { courseId } = useSelector(state => state.courseware); const { org } = useModel('courseHomeMeta', courseId); const { administrator } = getAuthenticatedUser(); @@ -55,7 +55,7 @@ function UpgradeFootnote({ deadline, href, intl }) { )} /> ); -} +}; UpgradeFootnote.propTypes = { deadline: PropTypes.instanceOf(Date).isRequired, diff --git a/src/courseware/course/course-exit/index.js b/src/courseware/course/course-exit/index.js index 2252238bbc..2f74d88a21 100644 --- a/src/courseware/course/course-exit/index.js +++ b/src/courseware/course/course-exit/index.js @@ -1,4 +1,4 @@ import CourseExit from './CourseExit'; -import { getCourseExitNavigation } from './utils'; +import { GetCourseExitNavigation } from './utils'; -export { CourseExit, getCourseExitNavigation }; +export { CourseExit, GetCourseExitNavigation }; diff --git a/src/courseware/course/course-exit/utils.js b/src/courseware/course/course-exit/utils.js index 5da8356b4e..da1d353094 100644 --- a/src/courseware/course/course-exit/utils.js +++ b/src/courseware/course/course-exit/utils.js @@ -66,7 +66,7 @@ function getCourseExitMode( } // Returns null in order to render the default navigation text -function getCourseExitNavigation(courseId, intl) { +function GetCourseExitNavigation(courseId, intl) { const { certificateData, hasScheduledContent, @@ -133,7 +133,7 @@ const logVisit = (org, courseId, administrator, variant) => { export { COURSE_EXIT_MODES, getCourseExitMode, - getCourseExitNavigation, + GetCourseExitNavigation, logClick, logVisit, }; diff --git a/src/courseware/course/course-license/CourseLicense.jsx b/src/courseware/course/course-license/CourseLicense.jsx index 3d3cf5128a..870d7be48c 100644 --- a/src/courseware/course/course-license/CourseLicense.jsx +++ b/src/courseware/course/course-license/CourseLicense.jsx @@ -103,10 +103,10 @@ function parseLicense(license) { return [licenseType, options, version]; } -function CourseLicense({ +const CourseLicense = ({ license, intl, -}) { +}) => { const renderAllRightsReservedLicense = () => (
); -} +}; CourseLicense.propTypes = { license: PropTypes.string, diff --git a/src/courseware/course/sequence/Sequence.jsx b/src/courseware/course/sequence/Sequence.jsx index 73738256e5..faa58c90c4 100644 --- a/src/courseware/course/sequence/Sequence.jsx +++ b/src/courseware/course/sequence/Sequence.jsx @@ -26,7 +26,7 @@ import HiddenAfterDue from './hidden-after-due'; import { SequenceNavigation, UnitNavigation } from './sequence-navigation'; import SequenceContent from './SequenceContent'; -function Sequence({ +const Sequence = ({ unitId, sequenceId, courseId, @@ -34,7 +34,7 @@ function Sequence({ nextSequenceHandler, previousSequenceHandler, intl, -}) { +}) => { const course = useModel('coursewareMeta', courseId); const { isStaff, @@ -218,7 +218,7 @@ function Sequence({ {intl.formatMessage(messages.loadFailure)}

); -} +}; Sequence.propTypes = { unitId: PropTypes.string, diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx index 55d6513e0a..d771d97da5 100644 --- a/src/courseware/course/sequence/Sequence.test.jsx +++ b/src/courseware/course/sequence/Sequence.test.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Factory } from 'rosie'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { breakpoints } from '@edx/paragon'; @@ -381,27 +382,25 @@ describe('Sequence', () => { }); }); + const SidebarWrapper = ({ contextValue }) => ( + + + + ); + + SidebarWrapper.propTypes = { + contextValue: PropTypes.shape({}).isRequired, + }; + describe('notification feature', () => { it('renders notification tray in sequence', async () => { - render( - null }} - > - - , - ); + render( null }} />); expect(await screen.findByText('Notifications')).toBeInTheDocument(); }); it('handles click on notification tray close button', async () => { const toggleNotificationTray = jest.fn(); - render( - - - , - ); + render(); const notificationCloseIconButton = await screen.findByRole('button', { name: /Close notification tray/i }); fireEvent.click(notificationCloseIconButton); expect(toggleNotificationTray).toHaveBeenCalledTimes(1); diff --git a/src/courseware/course/sequence/SequenceContent.jsx b/src/courseware/course/sequence/SequenceContent.jsx index 9f78d59b49..35a9a27f9d 100644 --- a/src/courseware/course/sequence/SequenceContent.jsx +++ b/src/courseware/course/sequence/SequenceContent.jsx @@ -9,14 +9,14 @@ import Unit from './Unit'; const ContentLock = React.lazy(() => import('./content-lock')); -function SequenceContent({ +const SequenceContent = ({ gated, intl, courseId, sequenceId, unitId, unitLoadedHandler, -}) { +}) => { const sequence = useModel('sequences', sequenceId); // Go back to the top of the page whenever the unit or sequence changes. @@ -61,7 +61,7 @@ function SequenceContent({ onLoaded={unitLoadedHandler} /> ); -} +}; SequenceContent.propTypes = { gated: PropTypes.bool.isRequired, diff --git a/src/courseware/course/sequence/Unit.jsx b/src/courseware/course/sequence/Unit.jsx index 41fe9d85d2..274bc68143 100644 --- a/src/courseware/course/sequence/Unit.jsx +++ b/src/courseware/course/sequence/Unit.jsx @@ -74,13 +74,13 @@ export function sendUrlHashToFrame(frame) { } } -function Unit({ +const Unit = ({ courseId, format, onLoaded, id, intl, -}) { +}) => { const { authenticatedUser } = useContext(AppContext); const view = authenticatedUser ? 'student_view' : 'public_view'; let iframeUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}?show_title=0&show_bookmark_button=0&recheck_access=1&view=${view}`; @@ -239,7 +239,7 @@ function Unit({ )} ); -} +}; Unit.propTypes = { courseId: PropTypes.string.isRequired, diff --git a/src/courseware/course/sequence/content-lock/ContentLock.jsx b/src/courseware/course/sequence/content-lock/ContentLock.jsx index 2ec0f00b69..26393fec0c 100644 --- a/src/courseware/course/sequence/content-lock/ContentLock.jsx +++ b/src/courseware/course/sequence/content-lock/ContentLock.jsx @@ -8,12 +8,12 @@ import { Button } from '@edx/paragon'; import messages from './messages'; -function ContentLock({ +const ContentLock = ({ intl, courseId, prereqSectionName, prereqId, sequenceTitle, -}) { +}) => { const handleClick = useCallback(() => { history.push(`/course/${courseId}/${prereqId}`); - }); + }, [courseId, prereqId]); return ( <> @@ -33,7 +33,7 @@ function ContentLock({

); -} +}; ContentLock.propTypes = { intl: intlShape.isRequired, courseId: PropTypes.string.isRequired, diff --git a/src/courseware/course/sequence/hidden-after-due/HiddenAfterDue.jsx b/src/courseware/course/sequence/hidden-after-due/HiddenAfterDue.jsx index 01f50b57bf..13f6d896c2 100644 --- a/src/courseware/course/sequence/hidden-after-due/HiddenAfterDue.jsx +++ b/src/courseware/course/sequence/hidden-after-due/HiddenAfterDue.jsx @@ -8,7 +8,7 @@ import { useModel } from '../../../../generic/model-store'; import messages from './messages'; -function HiddenAfterDue({ courseId, intl }) { +const HiddenAfterDue = ({ courseId, intl }) => { const { tabs } = useModel('courseHomeMeta', courseId); const progressTab = tabs.find(tab => tab.slug === 'progress'); @@ -42,7 +42,7 @@ function HiddenAfterDue({ courseId, intl }) {

); -} +}; HiddenAfterDue.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sequence/honor-code/HonorCode.jsx b/src/courseware/course/sequence/honor-code/HonorCode.jsx index 4525482212..11e7d59468 100644 --- a/src/courseware/course/sequence/honor-code/HonorCode.jsx +++ b/src/courseware/course/sequence/honor-code/HonorCode.jsx @@ -10,7 +10,7 @@ import { useModel } from '../../../../generic/model-store'; import { saveIntegritySignature } from '../../../data'; import messages from './messages'; -function HonorCode({ intl, courseId }) { +const HonorCode = ({ intl, courseId }) => { const dispatch = useDispatch(); const { isMasquerading, @@ -63,7 +63,7 @@ function HonorCode({ intl, courseId }) { ); -} +}; HonorCode.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx index b128eab3a2..90af888e20 100644 --- a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx +++ b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx @@ -19,10 +19,10 @@ import { SupportMissionBullet, } from '../../../../generic/upsell-bullets/UpsellBullets'; -function LockPaywall({ +const LockPaywall = ({ intl, courseId, -}) { +}) => { const { notificationTrayVisible } = useContext(SidebarContext); const course = useModel('coursewareMeta', courseId); const { @@ -141,7 +141,7 @@ function LockPaywall({ ); -} +}; LockPaywall.propTypes = { intl: intlShape.isRequired, courseId: PropTypes.string.isRequired, diff --git a/src/courseware/course/sequence/sequence-navigation/CompleteIcon.jsx b/src/courseware/course/sequence/sequence-navigation/CompleteIcon.jsx index bfddccdf0a..31dea65ef1 100644 --- a/src/courseware/course/sequence/sequence-navigation/CompleteIcon.jsx +++ b/src/courseware/course/sequence/sequence-navigation/CompleteIcon.jsx @@ -2,6 +2,6 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; -export default function CompleteIcon(props) { - return ; -} +const CompleteIcon = (props) => ; + +export default CompleteIcon; diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx index 0cc975c35d..76bbaf3ff6 100644 --- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx +++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx @@ -11,7 +11,7 @@ import { } from '@edx/frontend-platform/i18n'; import { useSelector } from 'react-redux'; -import { getCourseExitNavigation } from '../../course-exit'; +import { GetCourseExitNavigation } from '../../course-exit'; import UnitButton from './UnitButton'; import SequenceNavigationTabs from './SequenceNavigationTabs'; import { useSequenceNavigationMetadata } from './hooks'; @@ -20,7 +20,7 @@ import { LOADED } from '../../../data/slice'; import messages from './messages'; -function SequenceNavigation({ +const SequenceNavigation = ({ intl, unitId, sequenceId, @@ -29,7 +29,7 @@ function SequenceNavigation({ nextSequenceHandler, previousSequenceHandler, goToCourseExitPage, -}) { +}) => { const sequence = useModel('sequences', sequenceId); const { isFirstUnit, isLastUnit } = useSequenceNavigationMetadata(sequenceId, unitId); const { @@ -64,7 +64,7 @@ function SequenceNavigation({ }; const renderNextButton = () => { - const { exitActive, exitText } = getCourseExitNavigation(courseId, intl); + const { exitActive, exitText } = GetCourseExitNavigation(courseId, intl); const buttonOnClick = isLastUnit ? goToCourseExitPage : nextSequenceHandler; const buttonText = (isLastUnit && exitText) ? exitText : intl.formatMessage(messages.nextButton); const disabled = isLastUnit && !exitActive; @@ -89,7 +89,7 @@ function SequenceNavigation({ ); -} +}; SequenceNavigation.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.jsx index e88017326f..8898ff3d8e 100644 --- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.jsx +++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.jsx @@ -5,42 +5,40 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n'; import UnitButton from './UnitButton'; -export default function SequenceNavigationDropdown({ +const SequenceNavigationDropdown = ({ unitId, onNavigate, showCompletion, unitIds, -}) { - return ( - - - ( + + + + + + {unitIds.map(buttonUnitId => ( + - - - {unitIds.map(buttonUnitId => ( - - ))} - - - ); -} + ))} + + +); SequenceNavigationDropdown.propTypes = { unitId: PropTypes.string.isRequired, @@ -48,3 +46,5 @@ SequenceNavigationDropdown.propTypes = { showCompletion: PropTypes.bool.isRequired, unitIds: PropTypes.arrayOf(PropTypes.string).isRequired, }; + +export default SequenceNavigationDropdown; diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.jsx index 6275548408..6e1e84d6c5 100644 --- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.jsx +++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.jsx @@ -5,9 +5,9 @@ import UnitButton from './UnitButton'; import SequenceNavigationDropdown from './SequenceNavigationDropdown'; import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastVisibleChild'; -export default function SequenceNavigationTabs({ +const SequenceNavigationTabs = ({ unitIds, unitId, showCompletion, onNavigate, -}) { +}) => { const [ indexOfLastVisibleChild, containerRef, @@ -43,7 +43,7 @@ export default function SequenceNavigationTabs({ )} ); -} +}; SequenceNavigationTabs.propTypes = { unitId: PropTypes.string.isRequired, @@ -51,3 +51,5 @@ SequenceNavigationTabs.propTypes = { showCompletion: PropTypes.bool.isRequired, unitIds: PropTypes.arrayOf(PropTypes.string).isRequired, }; + +export default SequenceNavigationTabs; diff --git a/src/courseware/course/sequence/sequence-navigation/UnitButton.jsx b/src/courseware/course/sequence/sequence-navigation/UnitButton.jsx index ed55e1a499..393a299bbe 100644 --- a/src/courseware/course/sequence/sequence-navigation/UnitButton.jsx +++ b/src/courseware/course/sequence/sequence-navigation/UnitButton.jsx @@ -8,7 +8,7 @@ import UnitIcon from './UnitIcon'; import CompleteIcon from './CompleteIcon'; import BookmarkFilledIcon from '../../bookmark/BookmarkFilledIcon'; -function UnitButton({ +const UnitButton = ({ onClick, title, contentType, @@ -19,10 +19,10 @@ function UnitButton({ unitId, className, showTitle, -}) { +}) => { const handleClick = useCallback(() => { onClick(unitId); - }); + }, [onClick, unitId]); return ( - ); -} +}) => ( + +); SidebarTriggerBase.propTypes = { onClick: PropTypes.func.isRequired, diff --git a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.jsx b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.jsx index 918a3a914c..c3dc90b1b2 100644 --- a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.jsx +++ b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.jsx @@ -10,7 +10,7 @@ import messages from './messages'; ensureConfig(['DISCUSSIONS_MFE_BASE_URL']); -function DiscussionsSidebar({ intl }) { +const DiscussionsSidebar = ({ intl }) => { const { unitId, courseId, @@ -37,7 +37,7 @@ function DiscussionsSidebar({ intl }) { /> ); -} +}; DiscussionsSidebar.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.test.jsx b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.test.jsx index 40c7032039..59c31b1cf7 100644 --- a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.test.jsx +++ b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsSidebar.test.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-no-constructed-context-values */ import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import MockAdapter from 'axios-mock-adapter'; diff --git a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.jsx b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.jsx index 5f8dc4ab56..ef4b9944d7 100644 --- a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.jsx +++ b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.jsx @@ -14,10 +14,10 @@ import messages from './messages'; ensureConfig(['DISCUSSIONS_MFE_BASE_URL']); export const ID = 'DISCUSSIONS'; -function DiscussionsTrigger({ +const DiscussionsTrigger = ({ intl, onClick, -}) { +}) => { const { unitId, courseId, @@ -31,6 +31,7 @@ function DiscussionsTrigger({ if (baseUrl) { dispatch(getCourseDiscussionTopics(courseId)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [courseId, baseUrl]); if (!topic?.id || !topic?.enabledInContext) { @@ -42,7 +43,7 @@ function DiscussionsTrigger({ ); -} +}; DiscussionsTrigger.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.test.jsx b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.test.jsx index 18d4677d8d..f907cff99d 100644 --- a/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.test.jsx +++ b/src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger.test.jsx @@ -2,6 +2,7 @@ import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import MockAdapter from 'axios-mock-adapter'; import React from 'react'; +import PropTypes from 'prop-types'; import { fireEvent, initializeMockApp, initializeTestStore, render, screen, } from '../../../../../setupTest'; @@ -42,12 +43,19 @@ describe('Discussions Trigger', () => { .reply(200, buildTopicsFromUnits(state.models.units)); }); + const SidebarWrapper = ({ contextValue, onClick }) => ( + + + + ); + + SidebarWrapper.propTypes = { + contextValue: PropTypes.shape({}).isRequired, + onClick: PropTypes.func.isRequired, + }; + function renderWithProvider(testData = {}, onClick = () => null) { - const { container } = render( - - - , - ); + const { container } = render(); return container; } diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationIcon.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationIcon.jsx index d8ea403038..d7fbc7d995 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationIcon.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationIcon.jsx @@ -7,29 +7,27 @@ import React from 'react'; import messages from '../../../messages'; -function NotificationIcon({ +const NotificationIcon = ({ intl, status, notificationColor, -}) { - return ( - <> - - {status === 'active' - ? ( - - ) - : null} - - ); -} +}) => ( + <> + + {status === 'active' + ? ( + + ) + : null} + +); NotificationIcon.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx index bce9c899f8..3cef46a0ca 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx @@ -9,7 +9,7 @@ import SidebarBase from '../../common/SidebarBase'; import SidebarContext from '../../SidebarContext'; import NotificationTrigger, { ID } from './NotificationTrigger'; -function NotificationTray({ intl }) { +const NotificationTray = ({ intl }) => { const { courseId, onNotificationSeen, @@ -34,6 +34,7 @@ function NotificationTray({ intl }) { } = useModel('courseHomeMeta', courseId); // After three seconds, update notificationSeen (to hide red dot) + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { setTimeout(onNotificationSeen, 3000); }, []); return ( @@ -66,7 +67,7 @@ function NotificationTray({ intl }) { ); -} +}; NotificationTray.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx index 9028bdfb19..d7ba582e0c 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-no-constructed-context-values */ import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { breakpoints } from '@edx/paragon'; diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.jsx index 73c667ba92..6819e06acc 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.jsx @@ -11,10 +11,10 @@ import NotificationIcon from './NotificationIcon'; export const ID = 'NOTIFICATIONS'; -function NotificationTrigger({ +const NotificationTrigger = ({ intl, onClick, -}) { +}) => { const { courseId, notificationStatus, @@ -61,7 +61,7 @@ function NotificationTrigger({ ); -} +}; NotificationTrigger.propTypes = { intl: intlShape.isRequired, diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.test.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.test.jsx index 92b86843e2..c6c264bec3 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.test.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTrigger.test.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Factory } from 'rosie'; import { fireEvent, initializeTestStore, render, screen, @@ -36,13 +37,20 @@ describe('Notification Trigger', () => { setItemSpy.mockRestore(); }); + const SidebarWrapper = ({ contextValue, onClick }) => ( + + + + ); + + SidebarWrapper.propTypes = { + contextValue: PropTypes.shape({}).isRequired, + onClick: PropTypes.func.isRequired, + }; + function renderWithProvider(data, onClick = () => { }) { - const { container } = render( - - - , - ); + const { container } = render(); return container; } diff --git a/src/courseware/social-share/SocialIcons.jsx b/src/courseware/social-share/SocialIcons.jsx index 1752166c9f..6b6bfd29e7 100644 --- a/src/courseware/social-share/SocialIcons.jsx +++ b/src/courseware/social-share/SocialIcons.jsx @@ -19,7 +19,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import messages from './messages'; import { useModel } from '../../generic/model-store'; -function SocialIcons({ +const SocialIcons = ({ analyticsId, className, courseId, @@ -28,7 +28,7 @@ function SocialIcons({ hashtags, intl, socialMessage, -}) { +}) => { const { marketingUrl } = useModel('coursewareMeta', courseId); const { @@ -104,7 +104,7 @@ function SocialIcons({ ); -} +}; SocialIcons.defaultProps = { analyticsId: '', diff --git a/src/generic/CourseAccessErrorPage.jsx b/src/generic/CourseAccessErrorPage.jsx index a88f72322d..3440382ff4 100644 --- a/src/generic/CourseAccessErrorPage.jsx +++ b/src/generic/CourseAccessErrorPage.jsx @@ -12,13 +12,14 @@ import { LOADED, LOADING } from '../course-home/data/slice'; import PageLoading from './PageLoading'; import messages from '../tab-page/messages'; -function CourseAccessErrorPage({ intl }) { +const CourseAccessErrorPage = ({ intl }) => { const { courseId } = useParams(); const dispatch = useDispatch(); const activeEnterpriseAlert = useActiveEnterpriseAlert(courseId); useEffect(() => { dispatch(fetchDiscussionTab(courseId)); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [courseId]); const { @@ -54,7 +55,7 @@ function CourseAccessErrorPage({ intl }) {