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:
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -27,7 +27,7 @@ function useEnrollClickHandler(courseId, orgId, successText) {
|
||||
});
|
||||
global.location.reload();
|
||||
});
|
||||
}, [courseId]);
|
||||
}, [addFlash, courseId, orgId, successText]);
|
||||
|
||||
return { enrollClickHandler, loading };
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' }} />
|
||||
{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' }} />
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
Footnote.propTypes = {
|
||||
icon: PropTypes.shape({}).isRequired,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import CourseExit from './CourseExit';
|
||||
import { getCourseExitNavigation } from './utils';
|
||||
import { GetCourseExitNavigation } from './utils';
|
||||
|
||||
export { CourseExit, getCourseExitNavigation };
|
||||
export { CourseExit, GetCourseExitNavigation };
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user