Updated frontend-build to v12 (#962)

* feat: rebase previous frontend-build upgrade

* chore: make welcome message to default to empty
This commit is contained in:
Bilal Qamar
2023-01-30 22:20:07 +05:00
committed by GitHub
parent b500546e8d
commit b3d33667d4
164 changed files with 847 additions and 798 deletions

View File

@@ -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',
},
});

View File

@@ -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}
</Alert>
);
}
};
AccessExpirationAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
/>
</PageBanner>
);
}
};
AccessExpirationMasqueradeBanner.propTypes = {
payload: PropTypes.shape({

View File

@@ -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',
});

View File

@@ -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 = (
<Hyperlink
@@ -35,7 +35,7 @@ function ActiveEnterpriseAlert({ intl, payload }) {
/>
</Alert>
);
}
};
ActiveEnterpriseAlert.propTypes = {
intl: intlShape.isRequired,

View File

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

View File

@@ -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 }) {
/>
</Alert>
);
}
};
CourseStartAlert.propTypes = {
payload: PropTypes.shape({

View File

@@ -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 }) {
/>
</PageBanner>
);
}
};
CourseStartMasqueradeBanner.propTypes = {
payload: PropTypes.shape({

View File

@@ -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',
});

View File

@@ -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 }) {
</div>
</Alert>
);
}
};
EnrollmentAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -27,7 +27,7 @@ function useEnrollClickHandler(courseId, orgId, successText) {
});
global.location.reload();
});
}, [courseId]);
}, [addFlash, courseId, orgId, successText]);
return { enrollClickHandler, loading };
}

View File

@@ -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',
});

View File

@@ -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()}
</AlertModal>
);
}
};
AccountActivationAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -7,7 +7,7 @@ import { WarningFilled } from '@edx/paragon/icons';
import genericMessages from '../../generic/messages';
function LogistrationAlert({ intl }) {
const LogistrationAlert = ({ intl }) => {
const signIn = (
<Hyperlink
style={{ textDecoration: 'underline' }}
@@ -41,7 +41,7 @@ function LogistrationAlert({ intl }) {
/>
</Alert>
);
}
};
LogistrationAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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,

View File

@@ -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 }) {
<Timeline />
</>
);
}
};
DatesTab.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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({
</div>
</li>
);
}
};
Day.propTypes = {
date: PropTypes.objectOf(Date).isRequired,

View File

@@ -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() {
))}
</ul>
);
}
};
export default Timeline;

View File

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

View File

@@ -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 }) {
</main>
</>
);
}
};
GoalUnsubscribe.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 = (
<FormattedMessage
id="learning.goals.unsubscribe.errorDescription"
@@ -44,7 +44,7 @@ function ResultPage({ courseTitle, error, intl }) {
</Button>
</>
);
}
};
ResultPage.defaultProps = {
courseTitle: null,

View File

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

View File

@@ -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({
</div>
</li>
);
}
};
DateSummary.propTypes = {
dateBlock: PropTypes.shape({
@@ -93,3 +93,5 @@ DateSummary.propTypes = {
DateSummary.defaultProps = {
userTimezone: null,
};
export default DateSummary;

View File

@@ -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 = `
<html>
<head>
@@ -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;

View File

@@ -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 }) {
</div>
</>
);
}
};
OutlineTab.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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({
</Collapsible>
</li>
);
}
};
Section.propTypes = {
courseId: PropTypes.string.isRequired,

View File

@@ -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({
</div>
</li>
);
}
};
SequenceLink.propTypes = {
id: PropTypes.string.isRequired,

View File

@@ -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 }) {
)}
</AlertWrapper>
);
}
};
CertificateStatusAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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',
});

View File

@@ -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}
</Alert>
);
}
};
CourseEndAlert.propTypes = {
payload: PropTypes.shape({

View File

@@ -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',
});

View File

@@ -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 }) {
)}
</Alert>
);
}
};
PrivateCourseAlert.propTypes = {
intl: intlShape.isRequired,

View File

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

View File

@@ -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 }) {
</div>
</Alert>
);
}
};
ScheduledContentAlert.propTypes = {
payload: PropTypes.shape({

View File

@@ -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',
});

View File

@@ -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({
</div>
</section>
);
}
};
CourseDates.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
/>
</section>
);
}
};
CourseHandouts.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</ul>
</section>
);
}
};
CourseTools.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 (
<button
type="button"
className={classnames(
'flag-button row w-100 align-content-between m-1.5 py-3.5',
isSelected ? 'flag-button-selected' : '',
)}
aria-checked={isSelected}
role="radio"
onClick={() => handleSelect()}
data-testid={`weekly-learning-goal-input-${title}`}
>
<div className="row w-100 m-0 justify-content-center pb-1">
{buttonIcon}
</div>
<div className={classnames('row w-100 m-0 justify-content-center small text-gray-700 pb-1', isSelected ? 'font-weight-bold' : '')}>
{title}
</div>
<div className={classnames('row w-100 m-0 justify-content-center micro text-gray-500', isSelected ? 'font-weight-bold' : '')}>
{text}
</div>
</button>
);
}
}) => (
<button
type="button"
className={classnames(
'flag-button row w-100 align-content-between m-1.5 py-3.5',
isSelected ? 'flag-button-selected' : '',
)}
aria-checked={isSelected}
role="radio"
onClick={() => handleSelect()}
data-testid={`weekly-learning-goal-input-${title}`}
>
<div className="row w-100 m-0 justify-content-center pb-1">
{buttonIcon}
</div>
<div className={classnames('row w-100 m-0 justify-content-center small text-gray-700 pb-1', isSelected ? 'font-weight-bold' : '')}>
{title}
</div>
<div className={classnames('row w-100 m-0 justify-content-center micro text-gray-500', isSelected ? 'font-weight-bold' : '')}>
{text}
</div>
</button>
);
FlagButton.propTypes = {
buttonIcon: PropTypes.element.isRequired,

View File

@@ -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,

View File

@@ -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 && (
<section className={`mb-4 p-3 outline-sidebar-proctoring-panel ${getBorderClass()}`}>
@@ -212,7 +214,7 @@ function ProctoringInfoPanel({ intl }) {
)}
</>
);
}
};
ProctoringInfoPanel.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 */}
<Card.Footer><></></Card.Footer>
</Card>
);
}
};
StartOrResumeCourseCard.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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({
)}
</Card>
);
}
};
WeeklyLearningGoalCard.propTypes = {
daysPerWeek: PropTypes.number,

View File

@@ -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 (
<Alert
data-testid="alert-container-welcome"
@@ -69,7 +70,7 @@ function WelcomeMessage({ courseId, intl }) {
</TransitionReplace>
</Alert>
);
}
};
WelcomeMessage.propTypes = {
courseId: PropTypes.string.isRequired,

View File

@@ -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 }) {
)}
</div>
);
}
};
ProgressHeader.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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() {
</div>
</>
);
}
};
export default ProgressTab;

View File

@@ -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 }) {
</Card>
</section>
);
}
};
CertificateStatus.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
)}
</g>
);
}
};
CompleteDonutSegment.propTypes = {
completePercentage: PropTypes.number.isRequired,

View File

@@ -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 }) {
</div>
</>
);
}
};
CompletionDonutChart.propTypes = {
intl: intlShape.isRequired,

View File

@@ -4,23 +4,21 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import CompletionDonutChart from './CompletionDonutChart';
import messages from './messages';
function CourseCompletion({ intl }) {
return (
<section className="text-dark-700 mb-4 rounded raised-card p-4">
<div className="row w-100 m-0">
<div className="col-12 col-sm-6 col-md-7 p-0">
<h2>{intl.formatMessage(messages.courseCompletion)}</h2>
<p className="small">
{intl.formatMessage(messages.completionBody)}
</p>
</div>
<div className="col-12 col-sm-6 col-md-5 mt-sm-n3 p-0 text-center">
<CompletionDonutChart />
</div>
const CourseCompletion = ({ intl }) => (
<section className="text-dark-700 mb-4 rounded raised-card p-4">
<div className="row w-100 m-0">
<div className="col-12 col-sm-6 col-md-7 p-0">
<h2>{intl.formatMessage(messages.courseCompletion)}</h2>
<p className="small">
{intl.formatMessage(messages.completionBody)}
</p>
</div>
</section>
);
}
<div className="col-12 col-sm-6 col-md-5 mt-sm-n3 p-0 text-center">
<CompletionDonutChart />
</div>
</div>
</section>
);
CourseCompletion.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</OverlayTrigger>
</g>
);
}
};
IncompleteDonutSegment.propTypes = {
incompletePercentage: PropTypes.number.isRequired,

View File

@@ -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 }) {
</OverlayTrigger>
</g>
);
}
};
LockedDonutSegment.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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,

View File

@@ -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 }) {
</div>
</section>
);
}
};
CourseGrade.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</div>
</div>
);
}
};
CourseGradeFooter.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
)}
</div>
);
}
};
CourseGradeHeader.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</text>
</>
);
}
};
CurrentGradeTooltip.defaultProps = {
tooltipClassName: '',

View File

@@ -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 }) {
</svg>
</div>
);
}
};
GradeBar.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
/>
</OverlayTrigger>
);
}
};
GradeRangeTooltip.defaultProps = {
iconButtonClassName: '',

View File

@@ -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 }) {
</text>
</>
);
}
};
PassingGradeTooltip.defaultProps = {
tooltipClassName: '',

View File

@@ -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 }) {
)}
</section>
);
}
};
DetailedGrades.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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,

View File

@@ -8,7 +8,7 @@ import {
import messages from '../messages';
function ProblemScoreDrawer({ intl, problemScores, subsection }) {
const ProblemScoreDrawer = ({ intl, problemScores, subsection }) => {
const isLocaleRtl = isRtl(getLocale());
return (
<span className="row w-100 m-0 x-small ml-4 pt-2 pl-1 text-gray-700 flex-nowrap">
@@ -22,7 +22,7 @@ function ProblemScoreDrawer({ intl, problemScores, subsection }) {
</div>
</span>
);
}
};
ProblemScoreDrawer.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</Collapsible.Body>
</Collapsible.Advanced>
);
}
};
SubsectionTitleCell.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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({
</div>
</div>
);
}
};
AssignmentTypeCell.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</ul>
</>
);
}
};
DroppableAssignmentFootnote.propTypes = {
footnotes: PropTypes.arrayOf(PropTypes.shape({

View File

@@ -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() {
<GradeSummaryTable setAllOfSomeAssignmentTypeIsLocked={setAllOfSomeAssignmentTypeIsLocked} />
</section>
);
}
};
export default GradeSummary;

View File

@@ -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 }) {
)}
</div>
);
}
};
GradeSummaryHeader.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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) => (
<AssignmentTypeCell
assignmentType={value.type} // eslint-disable-line react/prop-types
footnoteId={value.footnoteId} // eslint-disable-line react/prop-types
footnoteMarker={value.footnoteMarker} // eslint-disable-line react/prop-types
locked={value.locked} // eslint-disable-line react/prop-types
/>
);
const getCell = (locked, value) => <span className={locked ? 'greyed-out' : ''}>{value}</span>;
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 }) => (
<AssignmentTypeCell
assignmentType={value.type} // eslint-disable-line react/prop-types
footnoteId={value.footnoteId} // eslint-disable-line react/prop-types
footnoteMarker={value.footnoteMarker} // eslint-disable-line react/prop-types
locked={value.locked} // eslint-disable-line react/prop-types
/>
),
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 }) => (
<span className={value.locked ? 'greyed-out' : ''}>{value.weight}</span> // 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 }) => (
<span className={value.locked ? 'greyed-out' : ''}>{value.grade}</span> // 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 }) => (
<span className={value.locked ? 'greyed-out' : ''}>{value.weightedGrade}</span> // 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,

View File

@@ -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 }) {
</div>
</DataTable.TableFooter>
);
}
};
GradeSummaryTableFooter.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</ul>
</section>
);
}
};
RelatedLinks.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 (
<Alert variant="warning">
<Row className="w-100 m-0">
@@ -55,7 +55,7 @@ function ShiftDatesAlert({ fetch, intl, model }) {
</Row>
</Alert>
);
}
};
ShiftDatesAlert.propTypes = {
fetch: PropTypes.func.isRequired,

View File

@@ -3,13 +3,11 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
function SuggestedScheduleHeader({ intl }) {
return (
<p className="large">
{intl.formatMessage(messages.suggestedSchedule)}
</p>
);
}
const SuggestedScheduleHeader = ({ intl }) => (
<p className="large">
{intl.formatMessage(messages.suggestedSchedule)}
</p>
);
SuggestedScheduleHeader.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</Row>
</Alert>
);
}
};
UpgradeToCompleteAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</Row>
</Alert>
);
}
};
UpgradeToShiftDatesAlert.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 (
<div id="courseTabsNavigation" className={classNames('course-tabs-navigation', className)}>
<div className="container-xl">
<Tabs
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
{tabs.map(({ url, title, slug }) => (
<a
key={slug}
className={classNames('nav-item flex-shrink-0 nav-link', { active: slug === activeTabSlug })}
href={url}
>
{title}
</a>
))}
</Tabs>
</div>
}) => (
<div id="courseTabsNavigation" className={classNames('course-tabs-navigation', className)}>
<div className="container-xl">
<Tabs
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
{tabs.map(({ url, title, slug }) => (
<a
key={slug}
className={classNames('nav-item flex-shrink-0 nav-link', { active: slug === activeTabSlug })}
href={url}
>
{title}
</a>
))}
</Tabs>
</div>
);
}
</div>
);
CourseTabsNavigation.propTypes = {
activeTabSlug: PropTypes.string,

View File

@@ -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 (
<div className="fake-unit">Unit Contents {courseId} {id}</div>
);
}
jest.mock(
'./course/sequence/Unit',
() => MockUnit,
// eslint-disable-next-line react/prop-types
() => function ({ courseId, id }) {
return <div className="fake-unit">Unit Contents {courseId} {id}</div>;
},
);
jest.mock('@edx/frontend-platform/analytics');

View File

@@ -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 (
<div className="flex-grow-1">
@@ -50,3 +50,5 @@ export default () => {
</div>
);
};
export default CoursewareRedirectLandingPage;

View File

@@ -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({
<ContentTools course={course} />
</SidebarProvider>
);
}
};
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 <Course> 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 <Course {...props} windowWidth={windowWidth} />;
}
};
export default CourseWrapper;

View File

@@ -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({
</li>
</>
);
}
};
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({
</ol>
</nav>
);
}
};
CourseBreadcrumbs.propTypes = {
courseId: PropTypes.string.isRequired,
@@ -167,3 +167,5 @@ CourseBreadcrumbs.defaultProps = {
unitId: null,
isStaff: null,
};
export default CourseBreadcrumbs;

View File

@@ -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}
</MenuItem>
);
}
};
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;

View File

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

View File

@@ -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 <FontAwesomeIcon icon={faBookmark} {...props} />;
}
const BookmarkFilledIcon = (props) => <FontAwesomeIcon icon={faBookmark} {...props} />;
export default BookmarkFilledIcon;

View File

@@ -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 <FontAwesomeIcon icon={faBookmark} {...props} />;
}
const BookmarkOutlineIcon = (props) => <FontAwesomeIcon icon={faBookmark} {...props} />;
export default BookmarkOutlineIcon;

View File

@@ -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({
</>
</StandardModal>
);
}
};
CelebrationModal.propTypes = {
courseId: PropTypes.string.isRequired,

View File

@@ -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({
</>
</StandardModal>
);
}
};
WeeklyGoalCelebrationModal.propTypes = {
courseId: PropTypes.string.isRequired,

View File

@@ -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 (
<div className="content-tools">
<div className="d-flex justify-content-end align-items-end m-0">
{course.showCalculator && (
<Calculator />
)}
{course.notes.enabled && (
<NotesVisibility course={course} />
)}
</div>
}) => (
<div className="content-tools">
<div className="d-flex justify-content-end align-items-end m-0">
{course.showCalculator && (
<Calculator />
)}
{course.notes.enabled && (
<NotesVisibility course={course} />
)}
</div>
);
}
</div>
);
ContentTools.propTypes = {
course: PropTypes.shape({
@@ -29,3 +27,5 @@ ContentTools.propTypes = {
showCalculator: PropTypes.bool,
}).isRequired,
};
export default ContentTools;

View File

@@ -2,8 +2,12 @@ import React from 'react';
import { initializeTestStore, render, screen } from '../../../setupTest';
import ContentTools from './ContentTools';
jest.mock('./calculator/Calculator', () => () => <div data-testid="Calculator" />);
jest.mock('./notes-visibility/NotesVisibility', () => () => <div data-testid="NotesVisibility" />);
jest.mock('./calculator/Calculator', () => function () {
return <div data-testid="Calculator" />;
});
jest.mock('./notes-visibility/NotesVisibility', () => function () {
return <div data-testid="NotesVisibility" />;
});
describe('Content Tools', () => {
const mockData = {

View File

@@ -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 }) {
</div>
</div>
);
}
};
CatalogSuggestion.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</div>
</>
);
}
};
CourseCelebration.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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,

View File

@@ -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 }) {
</div>
</>
);
}
};
CourseInProgress.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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 }) {
</div>
</>
);
}
};
CourseNonPassing.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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({
</Hyperlink>
</div>
);
}
};
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 }) {
</Hyperlink>
</div>
);
}
};
CourseRecommendations.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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,

View File

@@ -2,16 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function Footnote({ icon, text }) {
return (
<div className="row w-100 mx-0 my-4 justify-content-center">
<p className="text-gray-700">
<FontAwesomeIcon icon={icon} style={{ width: '20px' }} />&nbsp;
{text}
</p>
</div>
);
}
const Footnote = ({ icon, text }) => (
<div className="row w-100 mx-0 my-4 justify-content-center">
<p className="text-gray-700">
<FontAwesomeIcon icon={icon} style={{ width: '20px' }} />&nbsp;
{text}
</p>
</div>
);
Footnote.propTypes = {
icon: PropTypes.shape({}).isRequired,

View File

@@ -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({
</div>
</Alert>
);
}
};
ProgramCompletion.propTypes = {
intl: intlShape.isRequired,

View File

@@ -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,

View File

@@ -1,4 +1,4 @@
import CourseExit from './CourseExit';
import { getCourseExitNavigation } from './utils';
import { GetCourseExitNavigation } from './utils';
export { CourseExit, getCourseExitNavigation };
export { CourseExit, GetCourseExitNavigation };

View File

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

Some files were not shown because too many files have changed in this diff Show More