Compare commits
29 Commits
julianajlk
...
cdeery/AA-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c9899be5e | ||
|
|
93873cd845 | ||
|
|
3def37d28c | ||
|
|
d7e7b8c873 | ||
|
|
b401f7bb34 | ||
|
|
952d5d89e7 | ||
|
|
15d9c9f1bd | ||
|
|
b5e22c983d | ||
|
|
4d3b68d287 | ||
|
|
2607cec626 | ||
|
|
ec18af1ec0 | ||
|
|
ee62aab0e7 | ||
|
|
3cb55b320a | ||
|
|
d2e26bf8cf | ||
|
|
95fb4ec859 | ||
|
|
03f0ec4aec | ||
|
|
9dc447fa85 | ||
|
|
0286304c39 | ||
|
|
d88c4ade78 | ||
|
|
e3b8143677 | ||
|
|
09384b317e | ||
|
|
2172ea4041 | ||
|
|
7e3aee7147 | ||
|
|
4f187390a0 | ||
|
|
5b3053d3bb | ||
|
|
dae1e03e73 | ||
|
|
a32c79984d | ||
|
|
8d0596a32e | ||
|
|
91b1229a29 |
@@ -9,10 +9,13 @@ import {
|
||||
Icon,
|
||||
} from '@edx/paragon';
|
||||
import { Check, ArrowForward } from '@edx/paragon/icons';
|
||||
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { sendActivationEmail } from '../../courseware/data';
|
||||
import messages from './messages';
|
||||
|
||||
function AccountActivationAlert() {
|
||||
function AccountActivationAlert({
|
||||
intl,
|
||||
}) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showSpinner, setShowSpinner] = useState(false);
|
||||
const [showCheck, setShowCheck] = useState(false);
|
||||
@@ -29,22 +32,12 @@ function AccountActivationAlert() {
|
||||
if (showAccountActivationAlert !== undefined) {
|
||||
Cookies.remove('show-account-activation-popup', { path: '/', domain: process.env.SESSION_COOKIE_DOMAIN });
|
||||
// extra check to make sure cookie was removed before updating the state. Updating the state without removal
|
||||
// of cookie would make it infinit rendering
|
||||
// of cookie would make it infinite rendering
|
||||
if (Cookies.get('show-account-activation-popup') === undefined) {
|
||||
setShowModal(true);
|
||||
}
|
||||
}
|
||||
|
||||
const title = (
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="account-activation.alert.title"
|
||||
defaultMessage="Activate your account so you can log back in"
|
||||
description="Title for account activation alert which is shown after the registration"
|
||||
/>
|
||||
</h3>
|
||||
);
|
||||
|
||||
const button = (
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -64,7 +57,7 @@ function AccountActivationAlert() {
|
||||
);
|
||||
|
||||
const children = () => {
|
||||
let bodyContent = null;
|
||||
let bodyContent;
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id="account-activation.alert.message"
|
||||
@@ -123,7 +116,7 @@ function AccountActivationAlert() {
|
||||
return (
|
||||
<AlertModal
|
||||
isOpen={showModal}
|
||||
title={title}
|
||||
title={intl.formatMessage(messages.accountActivationAlertTitle)}
|
||||
footerNode={button}
|
||||
onClose={() => ({})}
|
||||
>
|
||||
@@ -132,4 +125,8 @@ function AccountActivationAlert() {
|
||||
);
|
||||
}
|
||||
|
||||
AccountActivationAlert.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AccountActivationAlert);
|
||||
|
||||
11
src/alerts/logistration-alert/messages.js
Normal file
11
src/alerts/logistration-alert/messages.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
accountActivationAlertTitle: {
|
||||
id: 'account-activation.alert.title',
|
||||
defaultMessage: 'Activate your account so you can log back in',
|
||||
description: 'Title for account activation alert which is shown after the registration',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -40,6 +40,9 @@ Factory.define('outlineTabData')
|
||||
course_goals: {
|
||||
goal_options: [],
|
||||
selected_goal: null,
|
||||
number_of_days_goals_enabled: false,
|
||||
days_per_week: null,
|
||||
subscribed_to_reminders: null,
|
||||
},
|
||||
course_tools: [
|
||||
{
|
||||
|
||||
@@ -432,8 +432,11 @@ Object {
|
||||
},
|
||||
},
|
||||
"courseGoals": Object {
|
||||
"daysPerWeek": null,
|
||||
"goalOptions": Array [],
|
||||
"numberOfDaysGoalsEnabled": false,
|
||||
"selectedGoal": null,
|
||||
"subscribedToReminders": null,
|
||||
},
|
||||
"courseTools": Array [
|
||||
Object {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -4,6 +4,7 @@ export {
|
||||
fetchProgressTab,
|
||||
resetDeadlines,
|
||||
saveCourseGoal,
|
||||
saveWeeklyCourseGoal,
|
||||
} from './thunks';
|
||||
|
||||
export { reducer } from './slice';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
@@ -10,6 +10,8 @@ import { AlertList } from '../../generic/user-messages';
|
||||
import CourseDates from './widgets/CourseDates';
|
||||
import CourseGoalCard from './widgets/CourseGoalCard';
|
||||
import CourseHandouts from './widgets/CourseHandouts';
|
||||
import StartOrResumeCourseCard from './widgets/StartOrResumeCourseCard';
|
||||
import WeeklyLearningGoalCard from './widgets/WeeklyLearningGoalCard';
|
||||
import CourseTools from './widgets/CourseTools';
|
||||
import { fetchOutlineTab } from '../data';
|
||||
import genericMessages from '../../generic/messages';
|
||||
@@ -54,13 +56,13 @@ function OutlineTab({ intl }) {
|
||||
courseGoals: {
|
||||
goalOptions,
|
||||
selectedGoal,
|
||||
weeklyLearningGoalEnabled,
|
||||
} = {},
|
||||
datesBannerInfo,
|
||||
datesWidget: {
|
||||
courseDateBlocks,
|
||||
},
|
||||
resumeCourse: {
|
||||
hasVisitedCourse,
|
||||
url: resumeCourseUrl,
|
||||
},
|
||||
offer,
|
||||
@@ -68,7 +70,7 @@ function OutlineTab({ intl }) {
|
||||
verifiedMode,
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
const [courseGoalToDisplay, setCourseGoalToDisplay] = useState(selectedGoal);
|
||||
const [deprecatedCourseGoalToDisplay, setDeprecatedCourseGoalToDisplay] = useState(selectedGoal);
|
||||
const [goalToastHeader, setGoalToastHeader] = useState('');
|
||||
const [expandAll, setExpandAll] = useState(false);
|
||||
|
||||
@@ -77,14 +79,6 @@ function OutlineTab({ intl }) {
|
||||
courserun_key: courseId,
|
||||
};
|
||||
|
||||
const logResumeCourseClick = () => {
|
||||
sendTrackingLogEvent('edx.course.home.resume_course.clicked', {
|
||||
...eventProperties,
|
||||
event_type: hasVisitedCourse ? 'resume' : 'start',
|
||||
url: resumeCourseUrl,
|
||||
});
|
||||
};
|
||||
|
||||
// Below the course title alerts (appearing in the order listed here)
|
||||
const courseStartAlert = useCourseStartAlert(courseId);
|
||||
const courseEndAlert = useCourseEndAlert(courseId);
|
||||
@@ -132,13 +126,6 @@ function OutlineTab({ intl }) {
|
||||
<div className="col-12 col-sm-auto p-0">
|
||||
<div role="heading" aria-level="1" className="h2">{title}</div>
|
||||
</div>
|
||||
{resumeCourseUrl && (
|
||||
<div className="col-12 col-sm-auto p-0">
|
||||
<Button variant="brand" block href={resumeCourseUrl} onClick={() => logResumeCourseClick()}>
|
||||
{hasVisitedCourse ? intl.formatMessage(messages.resume) : intl.formatMessage(messages.start)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/** [MM-P2P] Experiment (className for optimizely trigger) */}
|
||||
<div className="row course-outline-tab">
|
||||
@@ -172,15 +159,18 @@ function OutlineTab({ intl }) {
|
||||
<UpgradeToShiftDatesAlert model="outline" logUpgradeLinkClick={logUpgradeToShiftDatesLinkClick} />
|
||||
</>
|
||||
)}
|
||||
{!courseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
|
||||
{!deprecatedCourseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
|
||||
<CourseGoalCard
|
||||
courseId={courseId}
|
||||
goalOptions={goalOptions}
|
||||
title={title}
|
||||
setGoalToDisplay={(newGoal) => { setCourseGoalToDisplay(newGoal); }}
|
||||
setGoalToDisplay={(newGoal) => { setDeprecatedCourseGoalToDisplay(newGoal); }}
|
||||
setGoalToastHeader={(newHeader) => { setGoalToastHeader(newHeader); }}
|
||||
/>
|
||||
)}
|
||||
{resumeCourseUrl && (
|
||||
<StartOrResumeCourseCard />
|
||||
)}
|
||||
<WelcomeMessage courseId={courseId} />
|
||||
{rootCourseId && (
|
||||
<>
|
||||
@@ -211,15 +201,22 @@ function OutlineTab({ intl }) {
|
||||
courseId={courseId}
|
||||
username={username}
|
||||
/>
|
||||
{courseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
|
||||
{deprecatedCourseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
|
||||
<UpdateGoalSelector
|
||||
courseId={courseId}
|
||||
goalOptions={goalOptions}
|
||||
selectedGoal={courseGoalToDisplay}
|
||||
setGoalToDisplay={(newGoal) => { setCourseGoalToDisplay(newGoal); }}
|
||||
selectedGoal={deprecatedCourseGoalToDisplay}
|
||||
setGoalToDisplay={(newGoal) => { setDeprecatedCourseGoalToDisplay(newGoal); }}
|
||||
setGoalToastHeader={(newHeader) => { setGoalToastHeader(newHeader); }}
|
||||
/>
|
||||
)}
|
||||
{weeklyLearningGoalEnabled && (
|
||||
<WeeklyLearningGoalCard
|
||||
daysPerWeek={selectedGoal && 'daysPerWeek' in selectedGoal ? selectedGoal.daysPerWeek : null}
|
||||
subscribedToReminders={selectedGoal && 'subscribedToReminders' in selectedGoal ? selectedGoal.subscribedToReminders : false}
|
||||
courseId={courseId}
|
||||
/>
|
||||
)}
|
||||
<CourseTools
|
||||
courseId={courseId}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Cookies from 'js-cookie';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import messages from './messages';
|
||||
|
||||
import { buildMinimalCourseBlocks } from '../../shared/data/__factories__/courseBlocks.factory';
|
||||
import {
|
||||
@@ -413,6 +414,68 @@ describe('Outline Tab', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Weekly Learning Goals', () => {
|
||||
it('does not render weekly learning goal if weeklyLearningGoalEnabled is false', async () => {
|
||||
await fetchAndRender();
|
||||
expect(screen.queryByTestId('weekly-learning-goal-card')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('weekly learning goal is not set', () => {
|
||||
beforeEach(async () => {
|
||||
setTabData({
|
||||
course_goals: {
|
||||
weekly_learning_goal_enabled: true,
|
||||
},
|
||||
});
|
||||
await fetchAndRender();
|
||||
});
|
||||
|
||||
it('renders weekly learning goal card', async () => {
|
||||
expect(screen.queryByTestId('weekly-learning-goal-card')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders startOrResumeCourseCard', async () => {
|
||||
expect(screen.queryByTestId('start-resume-card')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables the subscribe button if no goal is set', async () => {
|
||||
expect(screen.getByLabelText(messages.setGoalReminder.defaultMessage)).toBeDisabled();
|
||||
});
|
||||
|
||||
it('calls the API when a button is clicked', async () => {
|
||||
expect(screen.queryByText(messages.casualGoalButtonText.defaultMessage)).toBeInTheDocument();
|
||||
expect(screen.getByText(messages.casualGoalButtonText.defaultMessage).closest('button')).toBeInTheDocument();
|
||||
|
||||
// click on Casual goal
|
||||
const button = await screen.getByText(messages.casualGoalButtonText.defaultMessage).closest('button');
|
||||
fireEvent.click(button);
|
||||
// Verify the request was made
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post[0].url).toMatch(goalUrl);
|
||||
// subscribe is turned on automatically
|
||||
expect(axiosMock.history.post[0].data).toMatch(`{"course_id":"${courseId}","days_per_week":3,"subscribed_to_reminders":true}`);
|
||||
// verify that the additional info about subscriptions shows up
|
||||
expect(screen.queryByText(messages.goalReminderDetail.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByLabelText(messages.setGoalReminder.defaultMessage)).toBeEnabled();
|
||||
|
||||
// Click on subscribe to reminders
|
||||
const subscriptionSwitch = await screen.getByRole('switch', { name: messages.setGoalReminder.defaultMessage });
|
||||
expect(subscriptionSwitch).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(subscriptionSwitch);
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post[1].url).toMatch(goalUrl);
|
||||
expect(axiosMock.history.post[1].data)
|
||||
.toMatch(`{"course_id":"${courseId}","days_per_week":3,"subscribed_to_reminders":false}`);
|
||||
});
|
||||
|
||||
// verify that the additional info about subscriptions gets hidden
|
||||
expect(screen.queryByText(messages.goalReminderDetail.defaultMessage)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Course Handouts', () => {
|
||||
it('renders title when handouts are available', async () => {
|
||||
await fetchAndRender();
|
||||
|
||||
@@ -66,6 +66,15 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Open',
|
||||
description: 'A button to open the given section of the course outline',
|
||||
},
|
||||
startBlurb: {
|
||||
id: 'learning.outline.startBlurb',
|
||||
defaultMessage: 'Begin your course today',
|
||||
},
|
||||
resumeBlurb: {
|
||||
id: 'learning.outline.resumeBlurb',
|
||||
defaultMessage: 'Pick up where you left off',
|
||||
description: 'Text describing to the learner that they can return to the last section they visited within the course.',
|
||||
},
|
||||
resume: {
|
||||
id: 'learning.outline.resume',
|
||||
defaultMessage: 'Resume course',
|
||||
@@ -74,6 +83,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',
|
||||
@@ -112,6 +129,46 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Welcome to',
|
||||
description: 'This precedes the title of the course',
|
||||
},
|
||||
setLearningGoalButtonScreenReaderText: {
|
||||
id: 'learning.outline.goalButton.casual.title',
|
||||
defaultMessage: 'Set a learning goal style.',
|
||||
description: 'screen reader text informing learner they can select an intensity of learning goal',
|
||||
},
|
||||
casualGoalButtonTitle: {
|
||||
id: 'learning.outline.goalButton.screenReader.text',
|
||||
defaultMessage: 'Casual',
|
||||
description: 'A very short description of the least intense of three learning goals',
|
||||
},
|
||||
casualGoalButtonText: {
|
||||
id: 'learning.outline.goalButton.casual.text',
|
||||
defaultMessage: '1 day a week',
|
||||
},
|
||||
regularGoalButtonTitle: {
|
||||
id: 'learning.outline.goalButton.regular.title',
|
||||
defaultMessage: 'Regular',
|
||||
description: 'A very short description of the middle option of three learning goals, Casual, Regular and Intense',
|
||||
},
|
||||
regularGoalButtonText: {
|
||||
id: 'learning.outline.goalButton.regular.text',
|
||||
defaultMessage: '3 days a week',
|
||||
},
|
||||
intenseGoalButtonTitle: {
|
||||
id: 'learning.outline.goalButton.intense.title',
|
||||
defaultMessage: 'Intense',
|
||||
description: 'A very short description of the most intensive option of three learning goals, Casual, Regular and Intense',
|
||||
},
|
||||
intenseGoalButtonText: {
|
||||
id: 'learning.outline.goalButton.intense.text',
|
||||
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',
|
||||
@@ -220,6 +277,11 @@ const messages = defineMessages({
|
||||
id: 'learning.proctoringPanel.onboardingButtonPastDue',
|
||||
defaultMessage: 'Onboarding Past Due',
|
||||
},
|
||||
accountActivationAlertTitle: {
|
||||
id: 'account-activation.alert.title',
|
||||
defaultMessage: 'Activate your account so you can log back in',
|
||||
description: 'Title for account activation alert which is shown after the registration',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
51
src/course-home/outline-tab/widgets/FlagButton.jsx
Normal file
51
src/course-home/outline-tab/widgets/FlagButton.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function FlagButton({
|
||||
ButtonIcon,
|
||||
srText,
|
||||
title,
|
||||
text,
|
||||
isEnabled,
|
||||
handleSelect,
|
||||
}) {
|
||||
const [isSelected, setIsSelected] = useState(false);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames('col flex-grow-1 p-3 border border-light rounded bg-white', { 'border-dark': isEnabled || isSelected })}
|
||||
onMouseEnter={() => setIsSelected(true)}
|
||||
onMouseLeave={() => setIsSelected(false)}
|
||||
onClick={() => handleSelect()}
|
||||
>
|
||||
<div className=" justify-content-center">
|
||||
{ButtonIcon}
|
||||
</div>
|
||||
<span className="sr-only sr-only-focusable">{srText}</span>
|
||||
<div className="text-center small">
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-center micro">
|
||||
{text}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
FlagButton.propTypes = {
|
||||
ButtonIcon: PropTypes.element.isRequired,
|
||||
srText: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
text: PropTypes.string,
|
||||
isEnabled: PropTypes.bool,
|
||||
handleSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
FlagButton.defaultProps = {
|
||||
isEnabled: false,
|
||||
text: '',
|
||||
};
|
||||
|
||||
export default FlagButton;
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Button, Card } from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
|
||||
import messages from '../messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
function StartOrResumeCourseCard({ intl }) {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
|
||||
const {
|
||||
org,
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
const eventProperties = {
|
||||
org_key: org,
|
||||
courserun_key: courseId,
|
||||
};
|
||||
|
||||
const {
|
||||
resumeCourse: {
|
||||
hasVisitedCourse,
|
||||
url: resumeCourseUrl,
|
||||
},
|
||||
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
const logResumeCourseClick = () => {
|
||||
sendTrackingLogEvent('edx.course.home.resume_course.clicked', {
|
||||
...eventProperties,
|
||||
event_type: hasVisitedCourse ? 'resume' : 'start',
|
||||
url: resumeCourseUrl,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="mb-3" data-testid="start-resume-card">
|
||||
<Card.Body>
|
||||
<div className="row w-100 m-0 ">
|
||||
<h2 className="h4 col-auto flex-grow-1">{intl.formatMessage(messages.startBlurb)}</h2>
|
||||
<div className="col col-auto p-0 justify-content-end">
|
||||
<Button
|
||||
variant="brand"
|
||||
block
|
||||
href={resumeCourseUrl}
|
||||
onClick={() => logResumeCourseClick()}
|
||||
>
|
||||
{hasVisitedCourse ? intl.formatMessage(messages.resume) : intl.formatMessage(messages.start)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
StartOrResumeCourseCard.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(StartOrResumeCourseCard);
|
||||
173
src/course-home/outline-tab/widgets/WeeklyLearningGoalCard.jsx
Normal file
173
src/course-home/outline-tab/widgets/WeeklyLearningGoalCard.jsx
Normal file
@@ -0,0 +1,173 @@
|
||||
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 WeeklyLearningGoalCard({
|
||||
daysPerWeek,
|
||||
subscribedToReminders,
|
||||
courseId,
|
||||
intl,
|
||||
}) {
|
||||
const [daysPerWeekGoal, setDaysPerWeekGoal] = useState(daysPerWeek);
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const [isGetReminderSelected, setGetReminderSelected] = useState(subscribedToReminders);
|
||||
const weeklyLearningGoalLevels = {
|
||||
CASUAL: 3,
|
||||
REGULAR: 4,
|
||||
INTENSE: 5,
|
||||
};
|
||||
Object.freeze(weeklyLearningGoalLevels);
|
||||
|
||||
function handleSelect(days) {
|
||||
// Set the subscription button if this is the first time selecting a goal
|
||||
const selectReminders = daysPerWeekGoal === null ? true : isGetReminderSelected;
|
||||
setGetReminderSelected(selectReminders);
|
||||
setDaysPerWeekGoal(days);
|
||||
saveWeeklyCourseGoal(courseId, days, selectReminders);
|
||||
}
|
||||
|
||||
function handleSubscribeToReminders(event) {
|
||||
const isGetReminderChecked = event.target.checked;
|
||||
setGetReminderSelected(isGetReminderChecked);
|
||||
saveWeeklyCourseGoal(courseId, daysPerWeekGoal, isGetReminderChecked);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row w-100 m-0 p-0">
|
||||
<Card className="mb-3" data-testid="weekly-learning-goal-card">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<h4 className="m-0">{intl.formatMessage(messages.setWeeklyGoal)}</h4>
|
||||
</Card.Title>
|
||||
<Card.Text>
|
||||
{intl.formatMessage(messages.setWeeklyGoalDetail)}
|
||||
</Card.Text>
|
||||
<div
|
||||
className="row w-100 m-0 p-0 justify-content-around"
|
||||
>
|
||||
<label
|
||||
htmlFor={weeklyLearningGoalLevels.CASUAL}
|
||||
className="col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id={weeklyLearningGoalLevels.CASUAL}
|
||||
name="learningGoal"
|
||||
radioGroup="learningGoal"
|
||||
value={weeklyLearningGoalLevels.CASUAL}
|
||||
onChange={() => handleSelect(weeklyLearningGoalLevels.CASUAL)}
|
||||
tabIndex="0"
|
||||
checked={weeklyLearningGoalLevels.CASUAL === daysPerWeekGoal}
|
||||
className="position-absolute invisible"
|
||||
/>
|
||||
<FlagButton
|
||||
ButtonIcon={<FlagCasualIcon />}
|
||||
srText={intl.formatMessage(messages.setLearningGoalButtonScreenReaderText)}
|
||||
title={intl.formatMessage(messages.casualGoalButtonTitle)}
|
||||
text={intl.formatMessage(messages.casualGoalButtonText)}
|
||||
isEnabled={weeklyLearningGoalLevels.CASUAL === daysPerWeekGoal}
|
||||
handleSelect={() => { handleSelect(weeklyLearningGoalLevels.CASUAL); }}
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
htmlFor={weeklyLearningGoalLevels.REGULAR}
|
||||
className="col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id={weeklyLearningGoalLevels.REGULAR}
|
||||
name="learningGoal"
|
||||
radioGroup="learningGoal"
|
||||
value={weeklyLearningGoalLevels.REGULAR}
|
||||
onChange={() => handleSelect(weeklyLearningGoalLevels.REGULAR)}
|
||||
tabIndex="-1"
|
||||
checked={weeklyLearningGoalLevels.REGULAR === daysPerWeekGoal}
|
||||
className="position-absolute invisible"
|
||||
/>
|
||||
|
||||
<FlagButton
|
||||
ButtonIcon={<FlagRegularIcon />}
|
||||
srText={intl.formatMessage(messages.setLearningGoalButtonScreenReaderText)}
|
||||
title={intl.formatMessage(messages.regularGoalButtonTitle)}
|
||||
text={intl.formatMessage(messages.regularGoalButtonText)}
|
||||
isEnabled={weeklyLearningGoalLevels.REGULAR === daysPerWeekGoal}
|
||||
handleSelect={() => { handleSelect(weeklyLearningGoalLevels.REGULAR); }}
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
htmlFor={weeklyLearningGoalLevels.INTENSE}
|
||||
className="col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id={weeklyLearningGoalLevels.INTENSE}
|
||||
name="learningGoal"
|
||||
radioGroup="learningGoal"
|
||||
value={weeklyLearningGoalLevels.INTENSE}
|
||||
onChange={() => handleSelect(weeklyLearningGoalLevels.INTENSE)}
|
||||
tabIndex="-1"
|
||||
checked={weeklyLearningGoalLevels.INTENSE === daysPerWeekGoal}
|
||||
className="position-absolute invisible"
|
||||
/>
|
||||
|
||||
<FlagButton
|
||||
ButtonIcon={<FlagIntenseIcon />}
|
||||
srText={intl.formatMessage(messages.setLearningGoalButtonScreenReaderText)}
|
||||
title={intl.formatMessage(messages.intenseGoalButtonTitle)}
|
||||
text={intl.formatMessage(messages.intenseGoalButtonText)}
|
||||
isEnabled={weeklyLearningGoalLevels.INTENSE === daysPerWeekGoal}
|
||||
handleSelect={() => { handleSelect(weeklyLearningGoalLevels.INTENSE); }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="row p-3">
|
||||
<Form.Switch
|
||||
checked={isGetReminderSelected}
|
||||
onChange={(event) => handleSubscribeToReminders(event)}
|
||||
disabled={!daysPerWeekGoal}
|
||||
>
|
||||
{intl.formatMessage(messages.setGoalReminder)}
|
||||
</Form.Switch>
|
||||
</div>
|
||||
</Card.Body>
|
||||
{isGetReminderSelected && (
|
||||
<Card.Footer className="border-0 px-2.5">
|
||||
<div className="row w-100 m-0 small align-center">
|
||||
<div className="d-flex align-items-center pr-1.5">
|
||||
<Icon src={Email} />
|
||||
</div>
|
||||
<div className="col align-center">
|
||||
{intl.formatMessage(messages.goalReminderDetail)}
|
||||
</div>
|
||||
</div>
|
||||
</Card.Footer>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
WeeklyLearningGoalCard.propTypes = {
|
||||
daysPerWeek: PropTypes.number,
|
||||
subscribedToReminders: PropTypes.bool,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
WeeklyLearningGoalCard.defaultProps = {
|
||||
daysPerWeek: null,
|
||||
subscribedToReminders: false,
|
||||
};
|
||||
export default injectIntl(WeeklyLearningGoalCard);
|
||||
18
src/course-home/outline-tab/widgets/flag_gray.svg
Normal file
18
src/course-home/outline-tab/widgets/flag_gray.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="15"
|
||||
height="17"
|
||||
viewBox="0 0 15 17"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg11"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9.4 2L9 0H0V17H2V10H7.6L8 12H15V2H9.4ZM13 10H9.64L9.24 8H2V2H7.36L7.76 4H13V10Z"
|
||||
fill="#002B2B"
|
||||
id="path9" />
|
||||
<path
|
||||
style="fill:#808080;fill-rule:evenodd;stroke-width:0.0150977"
|
||||
d="M 9.6594698,9.9871226 C 9.6577909,9.9829707 9.5654776,9.5311723 9.4543296,8.9831261 L 9.2522415,7.9866785 5.6376662,7.9790074 2.0230906,7.9713362 V 4.9970494 2.0227625 l 2.6636151,0.00771 2.6636151,0.00771 0.1968204,0.9888987 0.1968205,0.9888988 h 2.6200263 2.620026 v 2.9893428 2.9893428 h -1.660746 c -0.91341,0 -1.6621194,-0.0034 -1.6637982,-0.00755 z"
|
||||
id="path302" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 801 B |
3
src/course-home/outline-tab/widgets/flag_outline.svg
Normal file
3
src/course-home/outline-tab/widgets/flag_outline.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="15" height="17" viewBox="0 0 15 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.4 2L9 0H0V17H2V10H7.6L8 12H15V2H9.4ZM13 10H9.64L9.24 8H2V2H7.36L7.76 4H13V10Z" fill="#002B2B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 211 B |
Reference in New Issue
Block a user