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 @@ + + +