diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js
index 6680b421..42b0abe4 100644
--- a/src/course-home/data/api.js
+++ b/src/course-home/data/api.js
@@ -391,6 +391,15 @@ export async function postCourseGoals(courseId, goalKey) {
return getAuthenticatedHttpClient().post(url.href, { course_id: courseId, goal_key: goalKey });
}
+export async function postWeeklyCourseGoals(courseId, daysPerWeek, subscribedToReminders) {
+ const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`);
+ return getAuthenticatedHttpClient().post(url.href, {
+ course_id: courseId,
+ days_per_week: daysPerWeek,
+ subscribed_to_reminders: subscribedToReminders,
+ });
+}
+
export async function postDismissWelcomeMessage(courseId) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/dismiss_welcome_message`);
await getAuthenticatedHttpClient().post(url.href, { course_id: courseId });
diff --git a/src/course-home/data/index.js b/src/course-home/data/index.js
index c5f79fa6..2cf66ce0 100644
--- a/src/course-home/data/index.js
+++ b/src/course-home/data/index.js
@@ -4,6 +4,7 @@ export {
fetchProgressTab,
resetDeadlines,
saveCourseGoal,
+ saveWeeklyCourseGoal,
} from './thunks';
export { reducer } from './slice';
diff --git a/src/course-home/data/thunks.js b/src/course-home/data/thunks.js
index 99fdc6fd..3da89867 100644
--- a/src/course-home/data/thunks.js
+++ b/src/course-home/data/thunks.js
@@ -8,6 +8,7 @@ import {
getProgressTabData,
postCourseDeadlines,
postCourseGoals,
+ postWeeklyCourseGoals,
postDismissWelcomeMessage,
postRequestCert,
} from './api';
@@ -113,6 +114,10 @@ export async function saveCourseGoal(courseId, goalKey) {
return postCourseGoals(courseId, goalKey);
}
+export async function saveWeeklyCourseGoal(courseId, daysPerWeek, subscribedToReminders) {
+ return postWeeklyCourseGoals(courseId, daysPerWeek, subscribedToReminders);
+}
+
export function processEvent(eventData, getTabData) {
return async (dispatch) => {
// Pulling this out early so the data doesn't get camelCased and is easier
diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx
index 51d08d50..5a845028 100644
--- a/src/course-home/outline-tab/OutlineTab.jsx
+++ b/src/course-home/outline-tab/OutlineTab.jsx
@@ -32,6 +32,7 @@ import AccountActivationAlert from '../../alerts/logistration-alert/AccountActiv
/** [MM-P2P] Experiment */
import { initHomeMMP2P, MMP2PFlyover } from '../../experiments/mm-p2p';
+import WeeklyLearningGoal from './widgets/WeeklyLearningGoal';
function OutlineTab({ intl }) {
const {
@@ -229,6 +230,13 @@ function OutlineTab({ intl }) {
setGoalToastHeader={(newHeader) => { setGoalToastHeader(newHeader); }}
/>
)}
+ {numberOfDaysGoalsEnabled && (
+
+ )}
diff --git a/src/course-home/outline-tab/messages.js b/src/course-home/outline-tab/messages.js
index c8d7ce7b..1f646e89 100644
--- a/src/course-home/outline-tab/messages.js
+++ b/src/course-home/outline-tab/messages.js
@@ -82,6 +82,14 @@ const messages = defineMessages({
id: 'learning.outline.setGoal',
defaultMessage: 'To start, set a course goal by selecting the option below that best describes your learning plan.',
},
+ setWeeklyGoal: {
+ id: 'learning.outline.setWeeklyGoal',
+ defaultMessage: 'Set a weekly learning goal',
+ },
+ setWeeklyGoalDetail: {
+ id: 'learning.outline.setWeeklyGoalDetail',
+ defaultMessage: 'Setting a goal motivates you to finish the course. You can always change it later',
+ },
start: {
id: 'learning.outline.start',
defaultMessage: 'Start Course',
@@ -120,6 +128,38 @@ const messages = defineMessages({
defaultMessage: 'Welcome to',
description: 'This precedes the title of the course',
},
+ goalButtonTitleCasual: {
+ id: 'learning.outline.goalButtonTitleCasual',
+ defaultMessage: 'Casual',
+ },
+ goalButtonTextCasual: {
+ id: 'learning.outline.goalButtonTitleCasual',
+ defaultMessage: '1 day a week',
+ },
+ goalButtonTitleRegular: {
+ id: 'learning.outline.goalButtonTitleRegular',
+ defaultMessage: 'Regular',
+ },
+ goalButtonTextRegular: {
+ id: 'learning.outline.goalButtonTitleRegular',
+ defaultMessage: '3 days a week',
+ },
+ goalButtonTitleIntense: {
+ id: 'learning.outline.goalButtonTitleIntense',
+ defaultMessage: 'Intense',
+ },
+ goalButtonTextIntense: {
+ id: 'learning.outline.goalButtonTitleIntense',
+ defaultMessage: '5 days a week',
+ },
+ setGoalReminder: {
+ id: 'learning.outline.setGoalReminder',
+ defaultMessage: 'Set a goal reminder',
+ },
+ goalReminderDetail: {
+ id: 'learning.outline.goalReminderDetail',
+ defaultMessage: 'If we notice you\'re not quite at your goal, we\'ll send you an email reminder.',
+ },
proctoringInfoPanel: {
id: 'learning.proctoringPanel.header',
defaultMessage: 'This course contains proctored exams',
diff --git a/src/course-home/outline-tab/widgets/FlagButton.jsx b/src/course-home/outline-tab/widgets/FlagButton.jsx
new file mode 100644
index 00000000..3eafb820
--- /dev/null
+++ b/src/course-home/outline-tab/widgets/FlagButton.jsx
@@ -0,0 +1,53 @@
+import React, { useState } from 'react';
+
+import PropTypes from 'prop-types';
+
+function FlagButton({
+ icon,
+ title,
+ text,
+ isEnabled,
+ handleSelect,
+}) {
+ const baseOutlineStyle = 'col flex-grow-1 p-3 border border-light rounded bg-white';
+ const selectedOutlineStyle = 'col flex-grow-1 p-3 border border-dark rounded bg-white';
+ const [isHighlight, setHighlight] = useState(false);
+
+ function getOutlineStyle() {
+ return isEnabled || isHighlight ? selectedOutlineStyle : baseOutlineStyle;
+ }
+
+ return (
+
+ );
+}
+
+FlagButton.propTypes = {
+ icon: PropTypes.node.isRequired,
+ title: PropTypes.string.isRequired,
+ text: PropTypes.string,
+ isEnabled: PropTypes.bool,
+ handleSelect: PropTypes.func.isRequired,
+};
+FlagButton.defaultProps = {
+ isEnabled: false,
+ text: '',
+};
+
+export default FlagButton;
diff --git a/src/course-home/outline-tab/widgets/WeeklyLearningGoal.jsx b/src/course-home/outline-tab/widgets/WeeklyLearningGoal.jsx
new file mode 100644
index 00000000..c4ffe0c9
--- /dev/null
+++ b/src/course-home/outline-tab/widgets/WeeklyLearningGoal.jsx
@@ -0,0 +1,134 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+
+import { Form, Card, Icon } from '@edx/paragon';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+
+import { Email } from '@edx/paragon/icons';
+import { ReactComponent as FlagIntenseIcon } from '@edx/paragon/icons/svg/flag.svg';
+import { ReactComponent as FlagCasualIcon } from './flag_outline.svg';
+import { ReactComponent as FlagRegularIcon } from './flag_gray.svg';
+import messages from '../messages';
+import FlagButton from './FlagButton';
+
+import { saveWeeklyCourseGoal } from '../../data';
+
+function WeeklyLearningGoal({
+ selectedGoal,
+ courseId,
+ intl,
+}) {
+ // eslint-disable-next-line react/prop-types
+ const [daysPerWeekGoal, setDaysPerWeekGoal] = useState('daysPerWeek' in selectedGoal ? selectedGoal.daysPerWeek : 0);
+ // eslint-disable-next-line react/prop-types
+ const [isGetReminderChecked, setGetReminderChecked] = useState('subscribedToReminders' in selectedGoal ? selectedGoal.subscribedToReminders : false);
+ const LevelToDays = {
+ CASUAL: 3,
+ REGULAR: 4,
+ INTENSE: 5,
+ };
+ Object.freeze(LevelToDays);
+
+ function handleSelect(days) {
+ setDaysPerWeekGoal(days);
+ saveWeeklyCourseGoal(courseId, days, isGetReminderChecked).then((response) => {
+ const { data } = response;
+ const {
+ header,
+ message,
+ } = data;
+ // TODO: add Toast?, remove console.log
+ console.log(header, ':', message);
+ });
+ }
+
+ function handleSubscribeToReminders(event) {
+ const isGetReminders = event.target.checked;
+ setGetReminderChecked(isGetReminders);
+ saveWeeklyCourseGoal(courseId, daysPerWeekGoal, isGetReminders).then((response) => {
+ const { data } = response;
+ const {
+ header,
+ message,
+ } = data;
+ // TODO: add Toast?, remove console.log
+ console.log(header, ':', message);
+ });
+ }
+ const buttonRowStyle = 'row w-100 m-0 p-0 justify-content-around'; // 'row w-100 m-0 flex-grow-1 p-0 justify content-end';
+ const flagButtonStyle = 'col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0'; // 'col-auto flex-grow-1 p-0';
+
+ return (
+
+
+
+
+ {intl.formatMessage(messages.setWeeklyGoal)}
+
+
+ {intl.formatMessage(messages.setWeeklyGoalDetail)}
+
+
+
+ }
+ title={intl.formatMessage(messages.goalButtonTitleCasual)}
+ text={intl.formatMessage(messages.goalButtonTextCasual)}
+ isEnabled={daysPerWeekGoal === LevelToDays.CASUAL}
+ handleSelect={() => { handleSelect(LevelToDays.CASUAL); }}
+ />
+
+
+ }
+ title={intl.formatMessage(messages.goalButtonTitleRegular)}
+ text={intl.formatMessage(messages.goalButtonTextRegular)}
+ isEnabled={daysPerWeekGoal === LevelToDays.REGULAR}
+ handleSelect={() => { handleSelect(LevelToDays.REGULAR); }}
+ />
+
+
+ }
+ title={intl.formatMessage(messages.goalButtonTitleIntense)}
+ text={intl.formatMessage(messages.goalButtonTextIntense)}
+ isEnabled={daysPerWeekGoal === LevelToDays.INTENSE}
+ handleSelect={() => { handleSelect(LevelToDays.INTENSE); }}
+ />
+
+
+
+
handleSubscribeToReminders(event)}
+ >
+ {intl.formatMessage(messages.setGoalReminder)}
+
+
+
+ {/* This is supposed to fill with gray in the bottom of the card */}
+ {isGetReminderChecked && (
+
+
+
+
+
+
+ {intl.formatMessage(messages.goalReminderDetail)}
+
+
+
+ )}
+
+
+
+ );
+}
+
+WeeklyLearningGoal.propTypes = {
+ selectedGoal: PropTypes.shape({}).isRequired,
+ courseId: PropTypes.string.isRequired,
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(WeeklyLearningGoal);
diff --git a/src/course-home/outline-tab/widgets/flag_gray.svg b/src/course-home/outline-tab/widgets/flag_gray.svg
new file mode 100644
index 00000000..4ae7e147
--- /dev/null
+++ b/src/course-home/outline-tab/widgets/flag_gray.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/src/course-home/outline-tab/widgets/flag_outline.svg b/src/course-home/outline-tab/widgets/flag_outline.svg
new file mode 100644
index 00000000..78d1af24
--- /dev/null
+++ b/src/course-home/outline-tab/widgets/flag_outline.svg
@@ -0,0 +1,3 @@
+