fix: update Tour components and product tour behavior (#794)
This commit is contained in:
@@ -20,5 +20,5 @@ Factory.define('courseHomeMetadata')
|
||||
},
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
user_timezone: 'UTC',
|
||||
username: 'testuser',
|
||||
username: 'MockUser',
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"proctoringPanelStatus": "loading",
|
||||
"targetUserId": undefined,
|
||||
"toastBodyLink": null,
|
||||
"toastBodyText": null,
|
||||
@@ -71,7 +72,7 @@ Object {
|
||||
],
|
||||
"title": "Demonstration Course",
|
||||
"userTimezone": "UTC",
|
||||
"username": "testuser",
|
||||
"username": "MockUser",
|
||||
"verifiedMode": Object {
|
||||
"currencySymbol": "$",
|
||||
"price": 10,
|
||||
@@ -317,6 +318,7 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"proctoringPanelStatus": "loading",
|
||||
"targetUserId": undefined,
|
||||
"toastBodyLink": null,
|
||||
"toastBodyText": null,
|
||||
@@ -383,7 +385,7 @@ Object {
|
||||
],
|
||||
"title": "Demonstration Course",
|
||||
"userTimezone": "UTC",
|
||||
"username": "testuser",
|
||||
"username": "MockUser",
|
||||
"verifiedMode": Object {
|
||||
"currencySymbol": "$",
|
||||
"price": 10,
|
||||
@@ -509,6 +511,7 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"proctoringPanelStatus": "loading",
|
||||
"targetUserId": undefined,
|
||||
"toastBodyLink": null,
|
||||
"toastBodyText": null,
|
||||
@@ -575,7 +578,7 @@ Object {
|
||||
],
|
||||
"title": "Demonstration Course",
|
||||
"userTimezone": "UTC",
|
||||
"username": "testuser",
|
||||
"username": "MockUser",
|
||||
"verifiedMode": Object {
|
||||
"currencySymbol": "$",
|
||||
"price": 10,
|
||||
|
||||
@@ -11,11 +11,15 @@ const slice = createSlice({
|
||||
initialState: {
|
||||
courseStatus: 'loading',
|
||||
courseId: null,
|
||||
proctoringPanelStatus: 'loading',
|
||||
toastBodyText: null,
|
||||
toastBodyLink: null,
|
||||
toastHeader: '',
|
||||
},
|
||||
reducers: {
|
||||
fetchProctoringInfoResolved: (state) => {
|
||||
state.proctoringPanelStatus = LOADED;
|
||||
},
|
||||
fetchTabDenied: (state, { payload }) => {
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = DENIED;
|
||||
@@ -47,6 +51,7 @@ const slice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
fetchProctoringInfoResolved,
|
||||
fetchTabDenied,
|
||||
fetchTabFailure,
|
||||
fetchTabRequest,
|
||||
|
||||
@@ -34,13 +34,13 @@ import { initHomeMMP2P, MMP2PFlyover } from '../../experiments/mm-p2p';
|
||||
function OutlineTab({ intl }) {
|
||||
const {
|
||||
courseId,
|
||||
proctoringPanelStatus,
|
||||
} = useSelector(state => state.courseHome);
|
||||
|
||||
const {
|
||||
isSelfPaced,
|
||||
org,
|
||||
title,
|
||||
username,
|
||||
userTimezone,
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
@@ -60,17 +60,11 @@ function OutlineTab({ intl }) {
|
||||
},
|
||||
enableProctoredExams,
|
||||
offer,
|
||||
resumeCourse: {
|
||||
url: resumeCourseUrl,
|
||||
},
|
||||
timeOffsetMillis,
|
||||
verifiedMode,
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
const [expandAll, setExpandAll] = useState(false);
|
||||
// Defer showing the goal widget until the ProctoringInfoPanel is either shown or determined as not showing
|
||||
// to avoid components bouncing around too much as screen is displayed
|
||||
const [proctorPanelResolved, setProctorPanelResolved] = useState(!enableProctoredExams);
|
||||
|
||||
const eventProperties = {
|
||||
org_key: org,
|
||||
@@ -150,9 +144,7 @@ function OutlineTab({ intl }) {
|
||||
<UpgradeToShiftDatesAlert model="outline" logUpgradeLinkClick={logUpgradeToShiftDatesLinkClick} />
|
||||
</>
|
||||
)}
|
||||
{resumeCourseUrl && (
|
||||
<StartOrResumeCourseCard />
|
||||
)}
|
||||
<WelcomeMessage courseId={courseId} />
|
||||
{rootCourseId && (
|
||||
<>
|
||||
@@ -179,20 +171,16 @@ function OutlineTab({ intl }) {
|
||||
</div>
|
||||
{rootCourseId && (
|
||||
<div className="col col-12 col-md-4">
|
||||
<ProctoringInfoPanel
|
||||
courseId={courseId}
|
||||
username={username}
|
||||
isResolved={() => setProctorPanelResolved(true)}
|
||||
/>
|
||||
{weeklyLearningGoalEnabled && proctorPanelResolved && (
|
||||
<ProctoringInfoPanel />
|
||||
{ /** Defer showing the goal widget until the ProctoringInfoPanel has resolved or has been determined as
|
||||
disabled to avoid components bouncing around too much as screen is rendered */ }
|
||||
{(!enableProctoredExams || proctoringPanelStatus === 'loaded') && weeklyLearningGoalEnabled && (
|
||||
<WeeklyLearningGoalCard
|
||||
daysPerWeek={selectedGoal && 'daysPerWeek' in selectedGoal ? selectedGoal.daysPerWeek : null}
|
||||
subscribedToReminders={selectedGoal && 'subscribedToReminders' in selectedGoal ? selectedGoal.subscribedToReminders : false}
|
||||
/>
|
||||
)}
|
||||
<CourseTools
|
||||
courseId={courseId}
|
||||
/>
|
||||
<CourseTools />
|
||||
{ /** [MM-P2P] Experiment (conditional) */ }
|
||||
{ MMP2P.state.isEnabled
|
||||
? <MMP2PFlyover isStatic options={MMP2P} />
|
||||
@@ -211,13 +199,10 @@ function OutlineTab({ intl }) {
|
||||
/>
|
||||
)}
|
||||
<CourseDates
|
||||
courseId={courseId}
|
||||
/** [MM-P2P] Experiment */
|
||||
mmp2p={MMP2P}
|
||||
/>
|
||||
<CourseHandouts
|
||||
courseId={courseId}
|
||||
/>
|
||||
<CourseHandouts />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('Outline Tab', () => {
|
||||
const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`;
|
||||
const masqueradeUrl = `${getConfig().LMS_BASE_URL}/courses/${courseId}/masquerade`;
|
||||
const outlineUrl = `${getConfig().LMS_BASE_URL}/api/course_home/outline/${courseId}`;
|
||||
const proctoringInfoUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/user_onboarding/status?is_learning_mfe=true&course_id=${encodeURIComponent(courseId)}&username=testuser`;
|
||||
const proctoringInfoUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/user_onboarding/status?is_learning_mfe=true&course_id=${encodeURIComponent(courseId)}&username=MockUser`;
|
||||
|
||||
const store = initializeStore();
|
||||
const defaultMetadata = Factory.build('courseHomeMetadata', { id: courseId });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -8,11 +9,13 @@ import messages from '../messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
function CourseDates({
|
||||
courseId,
|
||||
intl,
|
||||
/** [MM-P2P] Experiment */
|
||||
mmp2p,
|
||||
}) {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const {
|
||||
userTimezone,
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
@@ -51,14 +54,12 @@ function CourseDates({
|
||||
}
|
||||
|
||||
CourseDates.propTypes = {
|
||||
courseId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
/** [MM-P2P] Experiment */
|
||||
mmp2p: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
CourseDates.defaultProps = {
|
||||
courseId: null,
|
||||
/** [MM-P2P] Experiment */
|
||||
mmp2p: {},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
@@ -7,7 +7,10 @@ import LmsHtmlFragment from '../LmsHtmlFragment';
|
||||
import messages from '../messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
function CourseHandouts({ courseId, intl }) {
|
||||
function CourseHandouts({ intl }) {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const {
|
||||
handoutsHtml,
|
||||
} = useModel('outline', courseId);
|
||||
@@ -29,7 +32,6 @@ function CourseHandouts({ courseId, intl }) {
|
||||
}
|
||||
|
||||
CourseHandouts.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
@@ -14,7 +14,10 @@ import messages from '../messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import LaunchCourseHomeTourButton from '../../../product-tours/newUserCourseHomeTour/LaunchCourseHomeTourButton';
|
||||
|
||||
function CourseTools({ courseId, intl }) {
|
||||
function CourseTools({ intl }) {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const { org } = useModel('courseHomeMeta', courseId);
|
||||
const {
|
||||
courseTools,
|
||||
@@ -79,12 +82,7 @@ function CourseTools({ courseId, intl }) {
|
||||
}
|
||||
|
||||
CourseTools.propTypes = {
|
||||
courseId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
CourseTools.defaultProps = {
|
||||
courseId: null,
|
||||
};
|
||||
|
||||
export default injectIntl(CourseTools);
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import messages from '../messages';
|
||||
import { getProctoringInfoData } from '../../data/api';
|
||||
import { fetchProctoringInfoResolved } from '../../data/slice';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
function ProctoringInfoPanel({ intl }) {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const {
|
||||
username,
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
function ProctoringInfoPanel({
|
||||
courseId, username, intl, isResolved,
|
||||
}) {
|
||||
const [link, setLink] = useState('');
|
||||
const [onboardingPastDue, setOnboardingPastDue] = useState(false);
|
||||
const [showInfoPanel, setShowInfoPanel] = useState(false);
|
||||
@@ -102,7 +110,7 @@ function ProctoringInfoPanel({
|
||||
/* Do nothing. API throws 404 when class does not have proctoring */
|
||||
})
|
||||
.finally(() => {
|
||||
isResolved();
|
||||
dispatch(fetchProctoringInfoResolved());
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -191,14 +199,7 @@ function ProctoringInfoPanel({
|
||||
}
|
||||
|
||||
ProctoringInfoPanel.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
username: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
isResolved: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
ProctoringInfoPanel.defaultProps = {
|
||||
username: null,
|
||||
};
|
||||
|
||||
export default injectIntl(ProctoringInfoPanel);
|
||||
|
||||
@@ -26,9 +26,12 @@ function StartOrResumeCourseCard({ intl }) {
|
||||
hasVisitedCourse,
|
||||
url: resumeCourseUrl,
|
||||
},
|
||||
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
if (!resumeCourseUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const logResumeCourseClick = () => {
|
||||
sendTrackingLogEvent('edx.course.home.resume_course.clicked', {
|
||||
...eventProperties,
|
||||
|
||||
@@ -63,5 +63,5 @@ Factory.define('courseMetadata')
|
||||
related_programs: null,
|
||||
user_needs_integrity_signature: false,
|
||||
recommendations: null,
|
||||
username: 'testuser',
|
||||
username: 'MockUser',
|
||||
});
|
||||
|
||||
@@ -6,8 +6,6 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
|
||||
import Tour from '../tour/Tour';
|
||||
|
||||
import { useModel } from '../generic/model-store';
|
||||
|
||||
import abandonTour from './AbandonTour';
|
||||
import coursewareTour from './CoursewareTour';
|
||||
import existingUserCourseHomeTour from './ExistingUserCourseHomeTour';
|
||||
@@ -24,7 +22,6 @@ function ProductTours({
|
||||
activeTab,
|
||||
courseId,
|
||||
isStreakCelebrationOpen,
|
||||
metadataModel,
|
||||
org,
|
||||
}) {
|
||||
if (isStreakCelebrationOpen) {
|
||||
@@ -32,9 +29,8 @@ function ProductTours({
|
||||
}
|
||||
|
||||
const {
|
||||
username,
|
||||
verifiedMode,
|
||||
} = useModel(metadataModel, courseId);
|
||||
proctoringPanelStatus,
|
||||
} = useSelector(state => state.courseHome);
|
||||
|
||||
const {
|
||||
showCoursewareTour,
|
||||
@@ -49,41 +45,43 @@ function ProductTours({
|
||||
const [isNewUserCourseHomeTourEnabled, setIsNewUserCourseHomeTourEnabled] = useState(false);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const administrator = getAuthenticatedUser() && getAuthenticatedUser().administrator;
|
||||
const {
|
||||
administrator,
|
||||
username,
|
||||
} = getAuthenticatedUser() || {};
|
||||
const isCoursewareTab = activeTab === 'courseware';
|
||||
const isOutlineTab = activeTab === 'outline';
|
||||
|
||||
useEffect(() => {
|
||||
const isOutlineTabResolved = isOutlineTab && proctoringPanelStatus === 'loaded';
|
||||
const userIsAuthenticated = !!username;
|
||||
|
||||
// Tours currently only exist on the Outline Tab and within Courseware, so we'll avoid
|
||||
// calling the tour endpoint unnecessarily.
|
||||
if (username && (activeTab === 'outline' || metadataModel === 'coursewareMeta')) {
|
||||
if (userIsAuthenticated && (isCoursewareTab || isOutlineTabResolved)) {
|
||||
dispatch(fetchTourData(username));
|
||||
}
|
||||
}, []);
|
||||
}, [proctoringPanelStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metadataModel === 'coursewareMeta' && showCoursewareTour) {
|
||||
if (isCoursewareTab && showCoursewareTour) {
|
||||
setIsCoursewareTourEnabled(true);
|
||||
}
|
||||
}, [showCoursewareTour]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metadataModel === 'courseHomeMeta') {
|
||||
if (isOutlineTab) {
|
||||
setIsExistingUserCourseHomeTourEnabled(!!showExistingUserCourseHomeTour);
|
||||
}
|
||||
}, [showExistingUserCourseHomeTour]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metadataModel === 'courseHomeMeta' && showNewUserCourseHomeTour) {
|
||||
if (isOutlineTab && showNewUserCourseHomeTour) {
|
||||
setIsAbandonTourEnabled(false);
|
||||
setIsNewUserCourseHomeTourEnabled(true);
|
||||
}
|
||||
}, [showNewUserCourseHomeTour]);
|
||||
|
||||
const upgradeData = {
|
||||
courseId,
|
||||
org,
|
||||
upgradeUrl: verifiedMode && verifiedMode.upgradeUrl,
|
||||
};
|
||||
|
||||
// The <Tour /> component cannot handle rendering multiple enabled tours at once.
|
||||
// I.e. when adding new tours, beware that if multiple tours are enabled,
|
||||
// the first enabled tour in the following array will be the only one that renders.
|
||||
@@ -128,6 +126,7 @@ function ProductTours({
|
||||
is_staff: administrator,
|
||||
});
|
||||
dispatch(endCourseHomeTour(username));
|
||||
dispatch(endCoursewareTour(username));
|
||||
},
|
||||
onEnd: () => {
|
||||
setIsNewUserCourseHomeTourEnabled(false);
|
||||
@@ -138,7 +137,6 @@ function ProductTours({
|
||||
});
|
||||
dispatch(endCourseHomeTour(username));
|
||||
},
|
||||
upgradeData,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -148,7 +146,7 @@ function ProductTours({
|
||||
tours={tours}
|
||||
/>
|
||||
<NewUserCourseHomeTourModal
|
||||
isOpen={metadataModel === 'courseHomeMeta' && showNewUserCourseHomeModal}
|
||||
isOpen={isOutlineTab && showNewUserCourseHomeModal}
|
||||
onDismiss={() => {
|
||||
sendTrackEvent('edx.ui.lms.new_user_modal.dismissed', {
|
||||
org_key: org,
|
||||
@@ -177,7 +175,6 @@ ProductTours.propTypes = {
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
isStreakCelebrationOpen: PropTypes.bool.isRequired,
|
||||
metadataModel: PropTypes.string.isRequired,
|
||||
org: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ describe('Course Home Tours', () => {
|
||||
let courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseId}`;
|
||||
courseMetadataUrl = appendBrowserTimezoneToUrl(courseMetadataUrl);
|
||||
const outlineUrl = `${getConfig().LMS_BASE_URL}/api/course_home/outline/${courseId}`;
|
||||
const tourDataUrl = `${getConfig().LMS_BASE_URL}/api/user_tours/v1/testuser`;
|
||||
const tourDataUrl = `${getConfig().LMS_BASE_URL}/api/user_tours/v1/MockUser`;
|
||||
const proctoringUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/user_onboarding/status?is_learning_mfe=true&course_id=course-v1%3AedX%2BTest%2Brun&username=MockUser`;
|
||||
|
||||
const store = initializeStore();
|
||||
const defaultMetadata = Factory.build('courseHomeMetadata', { id: courseId });
|
||||
@@ -70,6 +71,7 @@ describe('Course Home Tours', () => {
|
||||
// Set defaults for network requests
|
||||
axiosMock.onGet(courseMetadataUrl).reply(200, defaultMetadata);
|
||||
axiosMock.onGet(outlineUrl).reply(200, defaultTabData);
|
||||
axiosMock.onGet(proctoringUrl).reply(404, {});
|
||||
axiosMock.onGet(tourDataUrl).reply(200, {
|
||||
course_home_tour_status: 'no-tour',
|
||||
show_courseware_tour: false,
|
||||
@@ -237,7 +239,7 @@ describe('Courseware Tour', () => {
|
||||
}
|
||||
|
||||
describe('when receiving successful course data', () => {
|
||||
const tourDataUrl = `${getConfig().LMS_BASE_URL}/api/user_tours/v1/testuser`;
|
||||
const tourDataUrl = `${getConfig().LMS_BASE_URL}/api/user_tours/v1/MockUser`;
|
||||
|
||||
beforeEach(async () => {
|
||||
// On page load, SequenceContext attempts to scroll to the top of the page.
|
||||
@@ -272,7 +274,7 @@ describe('Courseware Tour', () => {
|
||||
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${sequenceMetadata.item_id}`;
|
||||
axiosMock.onGet(sequenceMetadataUrl).reply(200, sequenceMetadata);
|
||||
const proctoredExamApiUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/proctored_exam/attempt/course_id/${courseId}/content_id/${sequenceMetadata.item_id}?is_learning_mfe=true`;
|
||||
axiosMock.onGet(proctoredExamApiUrl).reply(200, { exam: {}, active_attempt: {} });
|
||||
axiosMock.onGet(proctoredExamApiUrl).reply(404);
|
||||
});
|
||||
|
||||
axiosMock.onPost(`${courseId}/xblock/${defaultSequenceBlock.id}/handler/get_completion`).reply(200, {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
DismissButtonFormattedMessage,
|
||||
@@ -12,7 +11,7 @@ const datesCheckpoint = {
|
||||
id="tours.datesCheckpoint.body"
|
||||
defaultMessage="Important dates can help you stay on track."
|
||||
/>,
|
||||
placement: 'left-start',
|
||||
placement: 'left',
|
||||
target: '#courseHome-dates',
|
||||
title: <FormattedMessage
|
||||
id="tours.datesCheckpoint.title"
|
||||
@@ -46,28 +45,18 @@ const tabNavigationCheckpoint = {
|
||||
/>,
|
||||
};
|
||||
|
||||
const upgradeCheckpoint = (logUpgradeClick, upgradeLink) => ({
|
||||
const upgradeCheckpoint = {
|
||||
body: <FormattedMessage
|
||||
id="tours.upgradeCheckpoint.body"
|
||||
defaultMessage="Work towards a certificate and gain full access to course materials. {upgradeLink}"
|
||||
values={{
|
||||
upgradeLink: (
|
||||
<a href={upgradeLink} onClick={logUpgradeClick}>
|
||||
<FormattedMessage
|
||||
id="tours.upgradeCheckpoint.upgradeLink"
|
||||
defaultMessage="Upgrade now!"
|
||||
/>
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
defaultMessage="Work towards a certificate and gain full access to course materials. Upgrade now!"
|
||||
/>,
|
||||
placement: 'left-start',
|
||||
placement: 'left',
|
||||
target: '#courseHome-upgradeNotification',
|
||||
title: <FormattedMessage
|
||||
id="tours.upgradeCheckpoint.title"
|
||||
defaultMessage="Unlock your course"
|
||||
/>,
|
||||
});
|
||||
};
|
||||
|
||||
const weeklyGoalsCheckpoint = {
|
||||
body: <FormattedMessage
|
||||
@@ -86,35 +75,22 @@ const newUserCourseHomeTour = ({
|
||||
enabled,
|
||||
onDismiss,
|
||||
onEnd,
|
||||
upgradeData,
|
||||
}) => {
|
||||
const logUpgradeClick = () => {
|
||||
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
|
||||
org_key: upgradeData.org,
|
||||
courserun_key: upgradeData.courseId,
|
||||
linkCategory: '(none)',
|
||||
linkName: 'course_home_upgrade_product_tour',
|
||||
linkType: 'link',
|
||||
pageName: 'course_home',
|
||||
});
|
||||
};
|
||||
return ({
|
||||
advanceButtonText: <NextButtonFormattedMessage />,
|
||||
checkpoints: [
|
||||
outlineCheckpoint,
|
||||
datesCheckpoint,
|
||||
tabNavigationCheckpoint,
|
||||
upgradeCheckpoint(logUpgradeClick, upgradeData.upgradeUrl),
|
||||
weeklyGoalsCheckpoint,
|
||||
],
|
||||
dismissButtonText: <DismissButtonFormattedMessage />,
|
||||
enabled,
|
||||
endButtonText: <OkayButtonFormattedMessage />,
|
||||
onDismiss,
|
||||
onEnd,
|
||||
onEscape: onDismiss,
|
||||
tourId: 'newUserCourseHomeTour',
|
||||
});
|
||||
};
|
||||
}) => ({
|
||||
advanceButtonText: <NextButtonFormattedMessage />,
|
||||
checkpoints: [
|
||||
outlineCheckpoint,
|
||||
datesCheckpoint,
|
||||
tabNavigationCheckpoint,
|
||||
upgradeCheckpoint,
|
||||
weeklyGoalsCheckpoint,
|
||||
],
|
||||
dismissButtonText: <DismissButtonFormattedMessage />,
|
||||
enabled,
|
||||
endButtonText: <OkayButtonFormattedMessage />,
|
||||
onDismiss,
|
||||
onEnd,
|
||||
onEscape: onDismiss,
|
||||
tourId: 'newUserCourseHomeTour',
|
||||
});
|
||||
|
||||
export default newUserCourseHomeTour;
|
||||
|
||||
@@ -66,7 +66,7 @@ Object.defineProperty(window, 'matchMedia', {
|
||||
|
||||
export const authenticatedUser = {
|
||||
userId: 'abc123',
|
||||
username: 'Mock User',
|
||||
username: 'MockUser',
|
||||
roles: [],
|
||||
administrator: false,
|
||||
};
|
||||
@@ -79,7 +79,7 @@ export function initializeMockApp() {
|
||||
TWITTER_URL: process.env.TWITTER_URL || null,
|
||||
authenticatedUser: {
|
||||
userId: 'abc123',
|
||||
username: 'Mock User',
|
||||
username: 'MockUser',
|
||||
roles: [],
|
||||
administrator: false,
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Loaded Tab Page', () => {
|
||||
let mockData;
|
||||
let testStore;
|
||||
let axiosMock;
|
||||
const calculateUrl = `${getConfig().ECOMMERCE_BASE_URL}/api/v2/baskets/calculate/?code=ZGY11119949&sku=8CF08E5&username=testuser`;
|
||||
const calculateUrl = `${getConfig().ECOMMERCE_BASE_URL}/api/v2/baskets/calculate/?code=ZGY11119949&sku=8CF08E5&username=MockUser`;
|
||||
const courseMetadata = Factory.build('courseMetadata', { celebrations: { streak_length_to_celebrate: 3 } });
|
||||
|
||||
function setDiscount(percent) {
|
||||
|
||||
@@ -49,7 +49,6 @@ function LoadedTabPage({
|
||||
activeTab={activeTabSlug}
|
||||
courseId={courseId}
|
||||
isStreakCelebrationOpen={isStreakCelebrationOpen}
|
||||
metadataModel={metadataModel}
|
||||
org={org}
|
||||
/>
|
||||
<Helmet>
|
||||
|
||||
Reference in New Issue
Block a user