diff --git a/.env b/.env
index 8864ad88..912f29cb 100644
--- a/.env
+++ b/.env
@@ -38,3 +38,4 @@ TWITTER_URL=''
USER_INFO_COOKIE_NAME=''
SESSION_COOKIE_DOMAIN=''
ENABLE_JUMPNAV='true'
+ENABLE_NOTICES=''
diff --git a/.env.development b/.env.development
index 61164b00..16d6764d 100644
--- a/.env.development
+++ b/.env.development
@@ -38,3 +38,4 @@ TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'
SESSION_COOKIE_DOMAIN='localhost'
ENABLE_JUMPNAV='true'
+ENABLE_NOTICES=''
diff --git a/.env.test b/.env.test
index 3b844f3c..24a5c93e 100644
--- a/.env.test
+++ b/.env.test
@@ -37,3 +37,4 @@ TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'
ENABLE_JUMPNAV='true'
+ENABLE_NOTICES=''
diff --git a/src/generic/notices/NoticesProvider.jsx b/src/generic/notices/NoticesProvider.jsx
new file mode 100644
index 00000000..4cbd5f95
--- /dev/null
+++ b/src/generic/notices/NoticesProvider.jsx
@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import { getConfig } from '@edx/frontend-platform';
+import PropTypes from 'prop-types';
+import { getNotices } from './api';
+/**
+ * This component uses the platform-plugin-notices plugin to function.
+ * If the user has an unacknowledged notice, they will be rerouted off
+ * course home and onto a full-screen notice page. If the plugin is not
+ * installed, or there are no notices, we just passthrough this component.
+ */
+const NoticesProvider = ({ children }) => {
+ const [isRedirected, setIsRedirected] = useState();
+ useEffect(async () => {
+ if (getConfig().ENABLE_NOTICES) {
+ const data = await getNotices();
+ if (data && data.results) {
+ const { results } = data;
+ setIsRedirected(true);
+ window.location.replace(`${results[0]}?next=${window.location.href}`);
+ }
+ }
+ }, []);
+
+ return (
+
+ {isRedirected === true ? null : children}
+
+ );
+};
+
+NoticesProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export default NoticesProvider;
diff --git a/src/generic/notices/NoticesProvider.test.jsx b/src/generic/notices/NoticesProvider.test.jsx
new file mode 100644
index 00000000..1da5cb3c
--- /dev/null
+++ b/src/generic/notices/NoticesProvider.test.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import { getConfig } from '@edx/frontend-platform';
+import {
+ initializeMockApp,
+ render,
+ act,
+} from '../../setupTest';
+import NoticesProvider from './NoticesProvider';
+import { getNotices } from './api';
+
+jest.mock('./api', () => ({
+ getNotices: jest.fn(),
+}));
+
+jest.mock('@edx/frontend-platform', () => ({
+ getConfig: jest.fn(),
+}));
+
+describe('NoticesProvider', () => {
+ beforeAll(async () => {
+ jest.resetModules();
+ await initializeMockApp();
+ });
+
+ function buildAndRender() {
+ render(
+
+
+ ,
+ );
+ }
+
+ it('does not call api if ENABLE_NOTICES is false', () => {
+ getConfig.mockImplementation(() => ({ ENABLE_NOTICES: false }));
+ buildAndRender();
+ expect(getNotices).toHaveBeenCalledTimes(0);
+ });
+
+ it('redirects user on notice returned from API', async () => {
+ const redirectUrl = 'http://example.com/test_route';
+ getConfig.mockImplementation(() => ({ ENABLE_NOTICES: true }));
+ getNotices.mockImplementation(() => ({ results: [redirectUrl] }));
+ delete window.location;
+ window.location = { replace: jest.fn() };
+ process.env.ENABLE_NOTICES = true;
+ await act(() => buildAndRender());
+ expect(window.location.replace).toHaveBeenCalledWith(`${redirectUrl}?next=${window.location.href}`);
+ });
+
+ it('does not redirect on no data', async () => {
+ getNotices.mockImplementation(() => ({}));
+ getConfig.mockImplementation(() => ({ ENABLE_NOTICES: true }));
+ delete window.location;
+ window.location = { replace: jest.fn() };
+ process.env.ENABLE_NOTICES = true;
+ await act(() => buildAndRender());
+ expect(window.location.replace).toHaveBeenCalledTimes(0);
+ expect(window.location.toString() === 'http://localhost/');
+ });
+});
diff --git a/src/generic/notices/api.js b/src/generic/notices/api.js
new file mode 100644
index 00000000..a3b4fb4e
--- /dev/null
+++ b/src/generic/notices/api.js
@@ -0,0 +1,25 @@
+/* eslint-disable import/prefer-default-export */
+import { getConfig } from '@edx/frontend-platform';
+import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
+import { logError, logInfo } from '@edx/frontend-platform/logging';
+
+export const getNotices = async () => {
+ const authenticatedUser = getAuthenticatedUser();
+ const url = new URL(`${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`);
+ if (authenticatedUser) {
+ try {
+ const { data } = await getAuthenticatedHttpClient().get(url.href, {});
+ return data;
+ } catch (e) {
+ // we will just swallow error, as that probably means the notices app is not installed.
+ // Notices are not necessary for the rest of courseware to function.
+ const { customAttributes: { httpErrorStatus } } = e;
+ if (httpErrorStatus === 404) {
+ logInfo(`${e}. This probably happened because the notices plugin is not installed on platform.`);
+ } else {
+ logError(e);
+ }
+ }
+ }
+ return null;
+};
diff --git a/src/generic/notices/index.js b/src/generic/notices/index.js
new file mode 100644
index 00000000..436d09a9
--- /dev/null
+++ b/src/generic/notices/index.js
@@ -0,0 +1 @@
+export { default } from './NoticesProvider';
diff --git a/src/index.jsx b/src/index.jsx
index 0378fde7..fd6d8c35 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -28,54 +28,57 @@ import { TabContainer } from './tab-page';
import { fetchDatesTab, fetchOutlineTab, fetchProgressTab } from './course-home/data';
import { fetchCourse } from './courseware/data';
import initializeStore from './store';
+import NoticesProvider from './generic/notices';
subscribe(APP_READY, () => {
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (
- fetchProgressTab(courseId, match.params.targetUserId)}
- slice="courseHome"
- >
-
+
+
+
+
+
+
+
+
- )}
- />
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ (
+ fetchProgressTab(courseId, match.params.targetUserId)}
+ slice="courseHome"
+ >
+
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
,
document.getElementById('root'),
);
@@ -93,6 +96,7 @@ initialize({
CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL || null,
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null,
ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null,
+ ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
SOCIAL_UTM_MILESTONE_CAMPAIGN: process.env.SOCIAL_UTM_MILESTONE_CAMPAIGN || null,