diff --git a/src/course-home/data/__factories__/outlineTabData.factory.js b/src/course-home/data/__factories__/outlineTabData.factory.js index 21a9ecd0..0a740c3d 100644 --- a/src/course-home/data/__factories__/outlineTabData.factory.js +++ b/src/course-home/data/__factories__/outlineTabData.factory.js @@ -29,6 +29,7 @@ Factory.define('outlineTabData') upgrade_url: `${host}/dashboard`, })) .attrs({ + has_scheduled_content: null, access_expiration: null, can_show_upgrade_sock: false, cert_data: { diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap index 7a8a20b0..e2cabdaa 100644 --- a/src/course-home/data/__snapshots__/redux.test.js.snap +++ b/src/course-home/data/__snapshots__/redux.test.js.snap @@ -380,6 +380,7 @@ Object { "block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": Object { "effortActivities": undefined, "effortTime": undefined, + "hasScheduledContent": false, "id": "course-v1:edX+DemoX+Demo_Course_1", "sectionIds": Array [ "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2", @@ -448,6 +449,7 @@ Object { }, "handoutsHtml": "", "hasEnded": undefined, + "hasScheduledContent": null, "id": "course-v1:edX+DemoX+Demo_Course_1", "offer": null, "resumeCourse": Object { diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js index 73fa35aa..c3d3ba55 100644 --- a/src/course-home/data/api.js +++ b/src/course-home/data/api.js @@ -116,6 +116,7 @@ export function normalizeOutlineBlocks(courseId, blocks) { id: courseId, title: block.display_name, sectionIds: block.children || [], + hasScheduledContent: block.has_scheduled_content, }; break; @@ -316,6 +317,7 @@ export async function getOutlineTabData(courseId) { const datesWidget = camelCaseObject(data.dates_widget); const enrollAlert = camelCaseObject(data.enroll_alert); const handoutsHtml = data.handouts_html; + const hasScheduledContent = data.has_scheduled_content; const hasEnded = data.has_ended; const offer = camelCaseObject(data.offer); const resumeCourse = camelCaseObject(data.resume_course); @@ -334,6 +336,7 @@ export async function getOutlineTabData(courseId) { datesWidget, enrollAlert, handoutsHtml, + hasScheduledContent, hasEnded, offer, resumeCourse, diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx index d7d65715..a6183bfb 100644 --- a/src/course-home/outline-tab/OutlineTab.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -23,6 +23,7 @@ import useCertificateAvailableAlert from './alerts/certificate-status-alert'; import useCourseEndAlert from './alerts/course-end-alert'; import useCourseStartAlert from './alerts/course-start-alert'; import usePrivateCourseAlert from './alerts/private-course-alert'; +import useScheduledContentAlert from './alerts/scheduled-content-alert'; import { useModel } from '../../generic/model-store'; import WelcomeMessage from './widgets/WelcomeMessage'; import ProctoringInfoPanel from './widgets/ProctoringInfoPanel'; @@ -90,6 +91,7 @@ function OutlineTab({ intl }) { const courseEndAlert = useCourseEndAlert(courseId); const certificateAvailableAlert = useCertificateAvailableAlert(courseId); const privateCourseAlert = usePrivateCourseAlert(courseId); + const scheduledContentAlert = useScheduledContentAlert(courseId); const rootCourseId = courses && Object.keys(courses)[0]; @@ -152,6 +154,7 @@ function OutlineTab({ intl }) { ...certificateAvailableAlert, ...courseEndAlert, ...courseStartAlert, + ...scheduledContentAlert, }} /> )} diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx index a0b81b59..7a6f7bcc 100644 --- a/src/course-home/outline-tab/OutlineTab.test.jsx +++ b/src/course-home/outline-tab/OutlineTab.test.jsx @@ -694,6 +694,27 @@ describe('Outline Tab', () => { expect(screen.queryByText('Verify your identity to earn a certificate!')).toBeInTheDocument(); }); }); + + describe('Scheduled Content Alert', () => { + it('appears correctly', async () => { + const now = new Date(); + const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { hasScheduledContent: true }); + const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); + setMetadata({ is_enrolled: true }); + setTabData({ + course_blocks: { blocks: courseBlocks.blocks }, + date_blocks: [ + { + date_type: 'course-end-date', + date: tomorrow.toISOString(), + title: 'End', + }, + ], + }); + await fetchAndRender(); + expect(screen.queryByText('More content is coming soon!')).toBeInTheDocument(); + }); + }); }); describe('Certificate (web) Complete Alert', () => { diff --git a/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx b/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx new file mode 100644 index 00000000..68287fec --- /dev/null +++ b/src/course-home/outline-tab/alerts/scheduled-content-alert/ScheduledCotentAlert.jsx @@ -0,0 +1,33 @@ +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Alert, Button } from '@edx/paragon'; +import React from 'react'; + +const ScheduledContentAlert = () => ( + +
+
+ + + + + +
+
+ +
+
+
+); + +export default ScheduledContentAlert; diff --git a/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js b/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js new file mode 100644 index 00000000..6cb7e7b9 --- /dev/null +++ b/src/course-home/outline-tab/alerts/scheduled-content-alert/hooks.js @@ -0,0 +1,20 @@ +import React from 'react'; + +import { useAlert } from '../../../../generic/user-messages'; +import { useModel } from '../../../../generic/model-store'; + +const ScheduledContentAlert = React.lazy(() => import('./ScheduledCotentAlert')); + +const useScheduledContentAlert = (courseId) => { + const { courseBlocks: { courses } } = useModel('outline', courseId); + const hasScheduledContent = !!Object.values(courses).find(course => course.hasScheduledContent === true); + const { isEnrolled } = useModel('courseHomeMeta', courseId); + useAlert(hasScheduledContent && isEnrolled, { + code: 'ScheduledContentAlert', + topic: 'outline-course-alerts', + }); + + return { ScheduledContentAlert }; +}; + +export default useScheduledContentAlert; diff --git a/src/course-home/outline-tab/alerts/scheduled-content-alert/index.js b/src/course-home/outline-tab/alerts/scheduled-content-alert/index.js new file mode 100644 index 00000000..ed12eb0b --- /dev/null +++ b/src/course-home/outline-tab/alerts/scheduled-content-alert/index.js @@ -0,0 +1 @@ +export { default } from './hooks'; diff --git a/src/shared/data/__factories__/courseBlocks.factory.js b/src/shared/data/__factories__/courseBlocks.factory.js index 79a54931..24d25a3e 100644 --- a/src/shared/data/__factories__/courseBlocks.factory.js +++ b/src/shared/data/__factories__/courseBlocks.factory.js @@ -124,7 +124,12 @@ export function buildMinimalCourseBlocks(courseId, title, options = {}) { )]; const courseBlock = options.courseBlock || Factory.build( 'block', - { type: 'course', display_name: title, children: sectionBlocks.map(block => block.id) }, + { + type: 'course', + display_name: title, + has_scheduled_content: options.hasScheduledContent || false, + children: sectionBlocks.map(block => block.id), + }, { courseId }, ); return {