diff --git a/src/components/app/data/services/enterpriseCustomerUser.js b/src/components/app/data/services/enterpriseCustomerUser.js
index 70608ae4d3..10a294602e 100644
--- a/src/components/app/data/services/enterpriseCustomerUser.js
+++ b/src/components/app/data/services/enterpriseCustomerUser.js
@@ -204,3 +204,16 @@ export async function updateUserCsodParams({ data }) {
const url = `${getConfig().LMS_BASE_URL}/integrated_channels/api/v1/cornerstone/save-learner-information`;
return getAuthenticatedHttpClient().post(url, data);
}
+
+/**
+ * Helper function to uunlink enterprise customer user by making a POST API request.
+ * @param {Object} params - The parameters object.
+ * @param {Object} params.enterpriseCustomerUser - The enterprise customer user that should be unlinked.
+ * @returns {Promise} - A promise that resolves when the active enterprise customer user is unlinked.
+ */
+export async function postUnlinkUserFromEnterprise(enterpriseCustomerUserUUID, userEmail) {
+ const url = `${getConfig().LMS_BASE_URL}/enterprise/api/v1/enterprise-customer/${enterpriseCustomerUserUUID}/unlink_users/`;
+ return getAuthenticatedHttpClient().post(url, { user_emails: [userEmail], is_relinkable: true }).catch((error) => {
+ logError(error);
+ });
+}
diff --git a/src/components/expired-subscription-modal/index.jsx b/src/components/expired-subscription-modal/index.jsx
index 5e69f184f4..28751065c0 100644
--- a/src/components/expired-subscription-modal/index.jsx
+++ b/src/components/expired-subscription-modal/index.jsx
@@ -2,14 +2,29 @@ import {
useToggle, AlertModal, Button, ActionRow,
} from '@openedx/paragon';
import DOMPurify from 'dompurify';
-import { useSubscriptions } from '../app/data';
+import { useContext } from 'react';
+import { AppContext } from '@edx/frontend-platform/react';
+import { postUnlinkUserFromEnterprise, useEnterpriseCustomer, useSubscriptions } from '../app/data';
const ExpiredSubscriptionModal = () => {
const { data: { customerAgreement } } = useSubscriptions();
+ const {
+ authenticatedUser: { email: userEmail },
+ } = useContext(AppContext);
+ const { data: enterpriseCustomer } = useEnterpriseCustomer();
+
const [isOpen] = useToggle(true);
if (!customerAgreement?.hasCustomLicenseExpirationMessaging) {
return null;
}
+ const onClickHandler = (redirectUrl) => async (e) => {
+ e.preventDefault();
+
+ await postUnlinkUserFromEnterprise(enterpriseCustomer.uuid, userEmail);
+
+ // Redirect immediately
+ window.location.href = redirectUrl;
+ };
return (
{customerAgreement.modalHeaderText}}
@@ -17,7 +32,7 @@ const ExpiredSubscriptionModal = () => {
isBlocking
footerNode={(
-
@@ -31,6 +46,7 @@ const ExpiredSubscriptionModal = () => {
),
}}
/>
+ {userEmail}{enterpriseCustomer.uuid}
);
};
diff --git a/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx b/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx
index bc053adfa5..72bdb77218 100644
--- a/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx
+++ b/src/components/expired-subscription-modal/tests/ExpiredSubscriptionModal.test.jsx
@@ -1,14 +1,29 @@
import { screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import userEvent from '@testing-library/user-event';
+import { AppContext } from '@edx/frontend-platform/react';
import ExpiredSubscriptionModal from '../index';
-import { useSubscriptions } from '../../app/data';
+import { postUnlinkUserFromEnterprise, useEnterpriseCustomer, useSubscriptions } from '../../app/data';
import { renderWithRouter } from '../../../utils/tests';
+import { authenticatedUserFactory, enterpriseCustomerFactory } from '../../app/data/services/data/__factories__';
jest.mock('../../app/data', () => ({
...jest.requireActual('../../app/data'),
useSubscriptions: jest.fn(),
+ useEnterpriseCustomer: jest.fn(),
+ postUnlinkUserFromEnterprise: jest.fn(),
}));
+const mockAuthenticatedUser = authenticatedUserFactory();
+const mockEnterpriseCustomer = enterpriseCustomerFactory();
+
+const defaultAppContextValue = { authenticatedUser: mockAuthenticatedUser };
+const ExpirationModalWrapper = ({ children, appContextValue = defaultAppContextValue }) => (
+
+
+ {children}
+
+
+);
describe('', () => {
beforeEach(() => {
@@ -23,10 +38,11 @@ describe('', () => {
},
},
});
+ useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer });
});
test('does not renderwithrouter if `hasCustomLicenseExpirationMessaging` is false', () => {
- const { container } = renderWithRouter();
+ const { container } = renderWithRouter();
expect(container).toBeEmptyDOMElement();
});
@@ -43,7 +59,7 @@ describe('', () => {
},
});
- renderWithRouter();
+ renderWithRouter();
expect(screen.getByText('Expired Subscription')).toBeInTheDocument();
expect(screen.getByText('Continue Learning')).toBeInTheDocument();
@@ -51,7 +67,7 @@ describe('', () => {
test('does not renderwithrouter modal if no customer agreement data is present', () => {
useSubscriptions.mockReturnValue({ data: { customerAgreement: null } });
- const { container } = renderWithRouter();
+ const { container } = renderWithRouter();
expect(container).toBeEmptyDOMElement();
});
@@ -68,7 +84,7 @@ describe('', () => {
},
});
- renderWithRouter();
+ renderWithRouter();
expect(screen.queryByLabelText(/close/i)).not.toBeInTheDocument();
});
test('clicks on Continue Learning button', () => {
@@ -86,7 +102,7 @@ describe('', () => {
});
// Render the component
- renderWithRouter();
+ renderWithRouter();
// Find the Continue Learning button
const continueButton = screen.getByText('Continue Learning');
@@ -97,4 +113,26 @@ describe('', () => {
// Check that the button was rendered and clicked
expect(continueButton).toBeInTheDocument();
});
+ test('calls postUnlinkUserFromEnterprise and redirects on button click', async () => {
+ useSubscriptions.mockReturnValue({
+ data: {
+ customerAgreement: {
+ hasCustomLicenseExpirationMessaging: true,
+ modalHeaderText: 'Expired Subscription',
+ buttonLabelInModal: 'Continue Learning',
+ expiredSubscriptionModalMessaging: '
Your subscription has expired.
',
+ urlForButtonInModal: 'https://example.com',
+ },
+ },
+ });
+ postUnlinkUserFromEnterprise.mockResolvedValueOnce();
+
+ renderWithRouter();
+
+ const continueButton = screen.getByText('Continue Learning');
+
+ userEvent.click(continueButton);
+
+ expect(postUnlinkUserFromEnterprise).toHaveBeenCalledWith(mockEnterpriseCustomer.uuid, mockAuthenticatedUser.email);
+ });
});