From 95fb4ec8598c66ca23d8814318680c22b41b9472 Mon Sep 17 00:00:00 2001 From: Chris Deery <3932645+cdeery@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:40:12 -0400 Subject: [PATCH] feat: [AA-906] Front end for Number of Days goal setting Implement days per week buttons link buttons to api to set value Implement subscribe button link subscribe button to api tweak CSS Add new icons for flags Add new function for updating weekly goals --- src/course-home/data/api.js | 9 ++ src/course-home/data/index.js | 1 + src/course-home/data/thunks.js | 5 + src/course-home/outline-tab/OutlineTab.jsx | 8 ++ src/course-home/outline-tab/messages.js | 40 ++++++ .../outline-tab/widgets/FlagButton.jsx | 53 +++++++ .../widgets/WeeklyLearningGoal.jsx | 134 ++++++++++++++++++ .../outline-tab/widgets/flag_gray.svg | 18 +++ .../outline-tab/widgets/flag_outline.svg | 3 + 9 files changed, 271 insertions(+) create mode 100644 src/course-home/outline-tab/widgets/FlagButton.jsx create mode 100644 src/course-home/outline-tab/widgets/WeeklyLearningGoal.jsx create mode 100644 src/course-home/outline-tab/widgets/flag_gray.svg create mode 100644 src/course-home/outline-tab/widgets/flag_outline.svg 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 @@ + + +