diff --git a/src/courseware/course/course-exit/CourseExit.jsx b/src/courseware/course/course-exit/CourseExit.jsx
index 37b28cf1..3c95f5b6 100644
--- a/src/courseware/course/course-exit/CourseExit.jsx
+++ b/src/courseware/course/course-exit/CourseExit.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -11,6 +11,7 @@ import CourseInProgress from './CourseInProgress';
import CourseNonPassing from './CourseNonPassing';
import { COURSE_EXIT_MODES, getCourseExitMode } from './utils';
import messages from './messages';
+import { unsubscribeFromGoalReminders } from './data/thunks';
import { useModel } from '../../../generic/model-store';
@@ -18,10 +19,13 @@ function CourseExit({ intl }) {
const { courseId } = useSelector(state => state.courseware);
const {
certificateData,
+ courseExitPageIsActive,
+ courseGoals,
+ enrollmentMode,
hasScheduledContent,
isEnrolled,
+ isMasquerading,
userHasPassingGrade,
- courseExitPageIsActive,
} = useModel('coursewareMeta', courseId);
const mode = getCourseExitMode(
@@ -32,6 +36,15 @@ function CourseExit({ intl }) {
courseExitPageIsActive,
);
+ // Audit users cannot fully complete a course, so we will
+ // unsubscribe them from goal reminders once they reach the course exit page
+ // to avoid spamming them with goal reminder emails
+ if (courseGoals && enrollmentMode === 'audit' && !isMasquerading) {
+ useEffect(() => {
+ unsubscribeFromGoalReminders(courseId);
+ }, []);
+ }
+
let body = null;
if (mode === COURSE_EXIT_MODES.nonPassing) {
body = ();
diff --git a/src/courseware/course/course-exit/CourseExit.test.jsx b/src/courseware/course/course-exit/CourseExit.test.jsx
index 0a1e2c2e..55f0d107 100644
--- a/src/courseware/course/course-exit/CourseExit.test.jsx
+++ b/src/courseware/course/course-exit/CourseExit.test.jsx
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import { waitFor } from '@testing-library/react';
import { fetchCourse } from '../../data';
import { buildSimpleCourseBlocks } from '../../../shared/data/__factories__/courseBlocks.factory';
@@ -377,4 +378,25 @@ describe('Course Exit Pages', () => {
expect(screen.getByRole('link', { name: 'View course schedule' })).toBeInTheDocument();
});
});
+
+ it('unsubscribes the user when loading the course exit page', async () => {
+ setMetadata({
+ enrollment: {
+ mode: 'audit',
+ courseGoals: {
+ goal_options: [],
+ selected_goal: {
+ days_per_week: 1,
+ subscribed_to_reminders: true,
+ },
+ },
+ },
+ });
+ await fetchAndRender();
+ const url = `${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`;
+ await waitFor(() => {
+ expect(axiosMock.history.post[0].url).toMatch(url);
+ expect(axiosMock.history.post[0].data).toMatch(`{"course_id":"${defaultMetadata.id}","subscribed_to_reminders":false}`);
+ });
+ });
});
diff --git a/src/courseware/course/course-exit/data/api.js b/src/courseware/course/course-exit/data/api.js
index 000f9684..d819db72 100644
--- a/src/courseware/course/course-exit/data/api.js
+++ b/src/courseware/course/course-exit/data/api.js
@@ -23,7 +23,7 @@ function filterRecommendationsList(
));
}
-export default async function getCourseRecommendations(courseKey) {
+export async function getCourseRecommendations(courseKey) {
const discoveryApiUrl = getConfig().DISCOVERY_API_BASE_URL;
if (!discoveryApiUrl) {
return [];
@@ -36,3 +36,11 @@ export default async function getCourseRecommendations(courseKey) {
]);
return filterRecommendationsList(camelCaseObject(recommendationsResponse), camelCaseObject(enrollmentsResponse));
}
+
+export async function postUnsubscribeFromGoalReminders(courseId) {
+ const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`);
+ return getAuthenticatedHttpClient().post(url.href, {
+ course_id: courseId,
+ subscribed_to_reminders: false,
+ });
+}
diff --git a/src/courseware/course/course-exit/data/thunks.js b/src/courseware/course/course-exit/data/thunks.js
index f355afb0..56d66961 100644
--- a/src/courseware/course/course-exit/data/thunks.js
+++ b/src/courseware/course/course-exit/data/thunks.js
@@ -5,7 +5,10 @@ import {
fetchCourseRecommendationsRequest,
fetchCourseRecommendationsSuccess,
} from './slice';
-import getCourseRecommendations from './api';
+import {
+ getCourseRecommendations,
+ postUnsubscribeFromGoalReminders,
+} from './api';
import { updateModel } from '../../../../generic/model-store';
export default function fetchCourseRecommendations(courseKey, courseId) {
@@ -27,3 +30,7 @@ export default function fetchCourseRecommendations(courseKey, courseId) {
}
};
}
+
+export async function unsubscribeFromGoalReminders(courseId, daysPerWeek, subscribedToReminders) {
+ return postUnsubscribeFromGoalReminders(courseId, daysPerWeek, subscribedToReminders);
+}
diff --git a/src/courseware/data/__factories__/courseMetadata.factory.js b/src/courseware/data/__factories__/courseMetadata.factory.js
index 10b180b6..a8ca74a1 100644
--- a/src/courseware/data/__factories__/courseMetadata.factory.js
+++ b/src/courseware/data/__factories__/courseMetadata.factory.js
@@ -8,6 +8,13 @@ Factory.define('courseMetadata')
.attrs({
content_type_gating_enabled: false,
course_expired_message: null,
+ course_goals: {
+ goal_options: [],
+ selected_goal: {
+ days_per_week: 1,
+ subscribed_to_reminders: true,
+ },
+ },
end: null,
enrollment_start: null,
enrollment_end: null,
diff --git a/src/courseware/data/api.js b/src/courseware/data/api.js
index e9df4f40..941426ef 100644
--- a/src/courseware/data/api.js
+++ b/src/courseware/data/api.js
@@ -187,6 +187,7 @@ function normalizeMetadata(metadata) {
accessExpiration: camelCaseObject(data.access_expiration),
canShowUpgradeSock: data.can_show_upgrade_sock,
contentTypeGatingEnabled: data.content_type_gating_enabled,
+ courseGoals: data.course_goals,
id: data.id,
title: data.name,
number: data.number,