diff --git a/src/alerts/course-end-alert/CourseEndAlert.jsx b/src/alerts/course-end-alert/CourseEndAlert.jsx new file mode 100644 index 00000000..1328ff73 --- /dev/null +++ b/src/alerts/course-end-alert/CourseEndAlert.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + FormattedDate, + FormattedMessage, + FormattedRelative, + FormattedTime, +} from '@edx/frontend-platform/i18n'; + +import { Alert, ALERT_TYPES } from '../../generic/user-messages'; + +const DAY_MS = 24 * 60 * 60 * 1000; // in ms + +function CourseEndAlert({ payload }) { + const { + delta, + description, + endDate, + userTimezone, + } = payload; + + const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {}; + + const timeRemaining = ( + + ); + + let msg; + if (delta < DAY_MS) { + const courseEndTime = ( + + ); + msg = ( + + ); + } else { + const courseEndDate = ( + + ); + msg = ( + + ); + } + + return ( + + {msg}
+ {description} +
+ ); +} + +CourseEndAlert.propTypes = { + payload: PropTypes.shape({ + delta: PropTypes.number, + description: PropTypes.string, + endDate: PropTypes.string, + userTimezone: PropTypes.string, + }).isRequired, +}; + +export default CourseEndAlert; diff --git a/src/alerts/course-end-alert/hooks.js b/src/alerts/course-end-alert/hooks.js new file mode 100644 index 00000000..101efeed --- /dev/null +++ b/src/alerts/course-end-alert/hooks.js @@ -0,0 +1,41 @@ +/* eslint-disable import/prefer-default-export */ +import React from 'react'; +import { useAlert } from '../../generic/user-messages'; +import { useModel } from '../../generic/model-store'; + +const CourseEndAlert = React.lazy(() => import('./CourseEndAlert')); + +// period of time (in ms) before end of course during which we alert +const WARNING_PERIOD_MS = 14 * 24 * 60 * 60 * 1000; // 14 days + +export function useCourseEndAlert(courseId) { + const { + isEnrolled, + } = useModel('courses', courseId); + const { + datesWidget: { + courseDateBlocks, + userTimezone, + }, + } = useModel('outline', courseId); + + const endBlock = courseDateBlocks.find(b => b.dateType === 'course-end-date'); + 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; + + useAlert(isVisible, { + code: 'clientCourseEndAlert', + payload: { + delta, + description: endBlock && endBlock.description, + endDate: endBlock && endBlock.date, + userTimezone, + }, + topic: 'outline-course-alerts', + }); + + return { + clientCourseEndAlert: CourseEndAlert, + }; +} diff --git a/src/alerts/course-end-alert/index.js b/src/alerts/course-end-alert/index.js new file mode 100644 index 00000000..eb25c278 --- /dev/null +++ b/src/alerts/course-end-alert/index.js @@ -0,0 +1 @@ +export { useCourseEndAlert as default } from './hooks'; diff --git a/src/alerts/enrollment-alert/hooks.js b/src/alerts/enrollment-alert/hooks.js index ccc23066..0f1e44a9 100644 --- a/src/alerts/enrollment-alert/hooks.js +++ b/src/alerts/enrollment-alert/hooks.js @@ -26,7 +26,7 @@ export function useEnrollmentAlert(courseId) { topic: 'outline', }); - return EnrollmentAlert; + return { clientEnrollmentAlert: EnrollmentAlert }; } export function useEnrollClickHandler(courseId, successText) { diff --git a/src/alerts/logistration-alert/hooks.js b/src/alerts/logistration-alert/hooks.js index a2370a48..be804b1b 100644 --- a/src/alerts/logistration-alert/hooks.js +++ b/src/alerts/logistration-alert/hooks.js @@ -1,8 +1,10 @@ /* eslint-disable import/prefer-default-export */ -import { useContext } from 'react'; +import React, { useContext } from 'react'; import { AppContext } from '@edx/frontend-platform/react'; import { ALERT_TYPES, useAlert } from '../../generic/user-messages'; +const LogistrationAlert = React.lazy(() => import('./LogistrationAlert')); + export function useLogistrationAlert() { const { authenticatedUser } = useContext(AppContext); const isVisible = authenticatedUser === null; @@ -13,4 +15,6 @@ export function useLogistrationAlert() { dismissible: false, type: ALERT_TYPES.ERROR, }); + + return { clientLogistrationAlert: LogistrationAlert }; } diff --git a/src/alerts/logistration-alert/index.js b/src/alerts/logistration-alert/index.js index 7da16700..bd6ec9f8 100644 --- a/src/alerts/logistration-alert/index.js +++ b/src/alerts/logistration-alert/index.js @@ -1,2 +1 @@ -export { default } from './LogistrationAlert'; -export { useLogistrationAlert } from './hooks'; +export { useLogistrationAlert as default } from './hooks'; diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx index d171e956..af250a52 100644 --- a/src/course-home/outline-tab/OutlineTab.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -10,13 +10,12 @@ import CourseHandouts from './widgets/CourseHandouts'; import CourseTools from './widgets/CourseTools'; import messages from './messages'; import Section from './Section'; +import useCourseEndAlert from '../../alerts/course-end-alert'; import useEnrollmentAlert from '../../alerts/enrollment-alert'; -import { useLogistrationAlert } from '../../alerts/logistration-alert'; +import useLogistrationAlert from '../../alerts/logistration-alert'; import { useModel } from '../../generic/model-store'; import WelcomeMessage from './widgets/WelcomeMessage'; -const LogistrationAlert = React.lazy(() => import('../../alerts/logistration-alert')); - function OutlineTab({ intl }) { const { courseId, @@ -39,8 +38,9 @@ function OutlineTab({ intl }) { }, } = useModel('outline', courseId); - const clientEnrollmentAlert = useEnrollmentAlert(courseId); - useLogistrationAlert(); + const courseEndAlert = useCourseEndAlert(courseId); + const enrollmentAlert = useEnrollmentAlert(courseId); + const logistrationAlert = useLogistrationAlert(); const rootCourseId = Object.keys(courses)[0]; const { sectionIds } = courses[rootCourseId]; @@ -51,8 +51,8 @@ function OutlineTab({ intl }) { topic="outline" className="mb-3" customAlerts={{ - clientEnrollmentAlert, - clientLogistrationAlert: LogistrationAlert, + ...enrollmentAlert, + ...logistrationAlert, }} />
@@ -62,6 +62,13 @@ function OutlineTab({ intl }) {
+ {sectionIds.map((sectionId) => (