diff --git a/src/components/course/routes/CourseAbout.jsx b/src/components/course/routes/CourseAbout.jsx
index a32320135b..1f796cf436 100644
--- a/src/components/course/routes/CourseAbout.jsx
+++ b/src/components/course/routes/CourseAbout.jsx
@@ -13,7 +13,7 @@ import {
useIsAssignmentsOnlyLearner,
usePassLearnerCsodParams,
} from '../../app/data';
-import ExpiredSubscriptionModal from '../ExpiredSubscriptionModal';
+import ExpiredSubscriptionModal from '../../expired-subscription-modal';
const CourseAbout = () => {
const { data: canOnlyViewHighlightSets } = useCanOnlyViewHighlights();
diff --git a/src/components/dashboard/DashboardPage.jsx b/src/components/dashboard/DashboardPage.jsx
index 94ddb4e4e3..35174dd85a 100644
--- a/src/components/dashboard/DashboardPage.jsx
+++ b/src/components/dashboard/DashboardPage.jsx
@@ -16,6 +16,7 @@ import {
useSubscriptions,
} from '../app/data';
import BudgetExpiryNotification from '../budget-expiry-notification';
+import ExpiredSubscriptionModal from '../expired-subscription-modal';
const DashboardPage = () => {
const intl = useIntl();
@@ -95,6 +96,13 @@ const DashboardPage = () => {
{tabs.map((tab) => React.cloneElement(tab, { key: tab.props.eventKey }))}
+ {/* ExpiredSubscriptionModal is specifically tailored for learners with an expired license and is
+ triggered when the learner has hasCustomLicenseExpirationMessaging enabled.
+ Ideally, the existing SubscriptionExpirationModal should be extended or repurposed to incorporate
+ this logic and support the custom messaging.
+ This is noted as a TO-DO, and a ticket will be created to address this enhancement.
+ Ticket: https://2u-internal.atlassian.net/browse/ENT-9512 */}
+
{subscriptions.subscriptionPlan && subscriptions.showExpirationNotifications && }
);
diff --git a/src/components/course/ExpiredSubscriptionModal.jsx b/src/components/expired-subscription-modal/index.jsx
similarity index 100%
rename from src/components/course/ExpiredSubscriptionModal.jsx
rename to src/components/expired-subscription-modal/index.jsx
diff --git a/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx b/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx
new file mode 100644
index 0000000000..af6ab748af
--- /dev/null
+++ b/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx
@@ -0,0 +1,71 @@
+import { screen } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import ExpiredSubscriptionModal from '../index';
+import { useSubscriptions } from '../../app/data';
+import { renderWithRouter } from '../../../utils/tests';
+
+jest.mock('../../app/data', () => ({
+ ...jest.requireActual('../../app/data'),
+ useSubscriptions: jest.fn(),
+}));
+
+describe('', () => {
+ beforeEach(() => {
+ useSubscriptions.mockReturnValue({
+ data: {
+ customerAgreement: {
+ hasCustomLicenseExpirationMessaging: false,
+ expiredSubscriptionModalMessaging: null,
+ urlForExpiredModal: null,
+ hyperLinkTextForExpiredModal: null,
+ },
+ },
+ });
+ });
+
+ test('does not renderwithrouter if `hasCustomLicenseExpirationMessaging` is false', () => {
+ const { container } = renderWithRouter();
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ test('renderwithrouters modal with messaging when `hasCustomLicenseExpirationMessaging` is true', () => {
+ useSubscriptions.mockReturnValue({
+ data: {
+ customerAgreement: {
+ hasCustomLicenseExpirationMessaging: true,
+ expiredSubscriptionModalMessaging: 'Your subscription has expired.',
+ urlForExpiredModal: '/renew',
+ hyperLinkTextForExpiredModal: 'Click here to renew',
+ },
+ },
+ });
+
+ renderWithRouter();
+
+ expect(screen.getByText('Your subscription has expired.')).toBeInTheDocument();
+ expect(screen.getByText('Click here to renew')).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Click here to renew' })).toHaveAttribute('href', '/renew');
+ });
+
+ test('does not renderwithrouter modal if no customer agreement data is present', () => {
+ useSubscriptions.mockReturnValue({ data: { customerAgreement: null } });
+ const { container } = renderWithRouter();
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ test('renderwithrouters close button in modal', () => {
+ useSubscriptions.mockReturnValue({
+ data: {
+ customerAgreement: {
+ hasCustomLicenseExpirationMessaging: true,
+ expiredSubscriptionModalMessaging: 'Subscription expired',
+ urlForExpiredModal: '/renew',
+ hyperLinkTextForExpiredModal: 'Renew',
+ },
+ },
+ });
+
+ renderWithRouter();
+ expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();
+ });
+});
diff --git a/src/components/search/Search.jsx b/src/components/search/Search.jsx
index c5885c3ed6..cb2fd426df 100644
--- a/src/components/search/Search.jsx
+++ b/src/components/search/Search.jsx
@@ -32,6 +32,7 @@ import ContentTypeSearchResultsContainer from './ContentTypeSearchResultsContain
import SearchVideo from './SearchVideo';
import { hasActivatedAndCurrentSubscription } from './utils';
import VideoBanner from '../microlearning/VideoBanner';
+import ExpiredSubscriptionModal from '../expired-subscription-modal';
function useSearchPathwayModal() {
const [isLearnerPathwayModalOpen, openLearnerPathwayModal, close] = useToggle(false);
@@ -113,6 +114,7 @@ const Search = () => {
return (
<>
+