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); + }); });