diff --git a/src/access-expiration-alert/AccessExpirationAlert.jsx b/src/access-expiration-alert/AccessExpirationAlert.jsx
new file mode 100644
index 00000000..0c9c0383
--- /dev/null
+++ b/src/access-expiration-alert/AccessExpirationAlert.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Alert from '../user-messages/Alert';
+
+function AccessExpirationAlert(props) {
+ const {
+ rawHtml,
+ } = props;
+ return rawHtml && (
+
+
+
+ );
+}
+
+AccessExpirationAlert.propTypes = {
+ rawHtml: PropTypes.string.isRequired,
+};
+
+export default AccessExpirationAlert;
diff --git a/src/access-expiration-alert/hooks.js b/src/access-expiration-alert/hooks.js
new file mode 100644
index 00000000..7bbe3259
--- /dev/null
+++ b/src/access-expiration-alert/hooks.js
@@ -0,0 +1,28 @@
+/* eslint-disable import/prefer-default-export */
+import { useContext, useState, useEffect } from 'react';
+import UserMessagesContext from '../user-messages/UserMessagesContext';
+import { useModel } from '../model-store';
+
+export function useAccessExpirationAlert(courseId) {
+ const course = useModel('courses', courseId);
+ const { add, remove } = useContext(UserMessagesContext);
+ const [alertId, setAlertId] = useState(null);
+ const expiredMessage = (course && course.courseExpiredMessage) || null;
+ useEffect(() => {
+ if (expiredMessage && alertId === null) {
+ setAlertId(add({
+ code: 'clientAccessExpirationAlert',
+ topic: 'course',
+ rawHtml: expiredMessage,
+ }));
+ } else if (alertId !== null) {
+ remove(alertId);
+ setAlertId(null);
+ }
+ return () => {
+ if (alertId !== null) {
+ remove(alertId);
+ }
+ };
+ }, [course, expiredMessage]);
+}
diff --git a/src/access-expiration-alert/index.js b/src/access-expiration-alert/index.js
new file mode 100644
index 00000000..d8b07c2f
--- /dev/null
+++ b/src/access-expiration-alert/index.js
@@ -0,0 +1,2 @@
+export { default as AccessExpirationAlert } from './AccessExpirationAlert';
+export { useAccessExpirationAlert } from './hooks';
diff --git a/src/courseware/course/Course.jsx b/src/courseware/course/Course.jsx
index 75af0273..634a2aab 100644
--- a/src/courseware/course/Course.jsx
+++ b/src/courseware/course/Course.jsx
@@ -4,6 +4,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import AlertList from '../../user-messages/AlertList';
+import { useAccessExpirationAlert } from '../../access-expiration-alert';
import { useLogistrationAlert } from '../../logistration-alert';
import { useEnrollmentAlert } from '../../enrollment-alert';
import PageLoading from '../../PageLoading';
@@ -22,6 +23,7 @@ import { useModel } from '../../model-store';
// This is because Reacy.lazy() requires that we import() from a file with a Component as it's
// default export.
// See React.lazy docs here: https://reactjs.org/docs/code-splitting.html#reactlazy
+const AccessExpirationAlert = React.lazy(() => import('../../access-expiration-alert/AccessExpirationAlert'));
const EnrollmentAlert = React.lazy(() => import('../../enrollment-alert/EnrollmentAlert'));
const StaffEnrollmentAlert = React.lazy(() => import('../../enrollment-alert/StaffEnrollmentAlert'));
const LogistrationAlert = React.lazy(() => import('../../logistration-alert'));
@@ -41,6 +43,7 @@ function Course({
useLogistrationAlert();
useEnrollmentAlert(courseId);
+ useAccessExpirationAlert(courseId);
const courseStatus = useSelector(state => state.courseware.courseStatus);
@@ -77,6 +80,7 @@ function Course({
clientEnrollmentAlert: EnrollmentAlert,
clientStaffEnrollmentAlert: StaffEnrollmentAlert,
clientLogistrationAlert: LogistrationAlert,
+ clientAccessExpirationAlert: AccessExpirationAlert,
}}
/>
remove(message.id)}
+ rawHtml={message.rawHtml}
>
{message.text}