Compare commits
3 Commits
open-relea
...
ddumesnil/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48ae83084b | ||
|
|
6d524f535d | ||
|
|
cb1ea7ad9f |
7
.env
7
.env
@@ -15,12 +15,7 @@ ORDER_HISTORY_URL=null
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT=null
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME=null
|
||||
SOCIAL_UTM_MILESTONE_CAMPAIGN=null
|
||||
TWITTER_URL=null
|
||||
STUDIO_BASE_URL=
|
||||
SUPPORT_URL=null
|
||||
SUPPORT_URL_CALCULATOR_MATH=null
|
||||
SUPPORT_URL_ID_VERIFICATION=null
|
||||
SUPPORT_URL_VERIFIED_CERTIFICATE=null
|
||||
TWITTER_HASHTAG=null
|
||||
TWITTER_URL=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
|
||||
@@ -15,12 +15,7 @@ PORT=2000
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone'
|
||||
TWITTER_URL='https://twitter.com/edXOnline'
|
||||
STUDIO_BASE_URL='http://localhost:18010'
|
||||
SUPPORT_URL='https://support.edx.org'
|
||||
SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator'
|
||||
SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity'
|
||||
SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate'
|
||||
TWITTER_HASHTAG='myedxjourney'
|
||||
TWITTER_URL='https://twitter.com/edXOnline'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
|
||||
@@ -14,12 +14,7 @@ PORT=2000
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone'
|
||||
TWITTER_URL='https://twitter.com/edXOnline'
|
||||
STUDIO_BASE_URL='http://localhost:18010'
|
||||
SUPPORT_URL='https://support.edx.org'
|
||||
SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator'
|
||||
SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity'
|
||||
SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate'
|
||||
TWITTER_HASHTAG='myedxjourney'
|
||||
TWITTER_URL='https://twitter.com/edXOnline'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
|
||||
@@ -18,7 +18,7 @@ const messages = defineMessages({
|
||||
},
|
||||
success: {
|
||||
id: 'learning.enrollment.success',
|
||||
defaultMessage: "You've successfully enrolled in this course!",
|
||||
defaultMessage: 'You’ve successfully enrolled in this course!',
|
||||
description: 'A message telling the user that their course enrollment was successful.',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
const messages = defineMessages({
|
||||
'datesBanner.datesTabInfoBanner.header': {
|
||||
id: 'datesBanner.datesTabInfoBanner.header',
|
||||
defaultMessage: "We've built a suggested schedule to help you stay on track. ",
|
||||
defaultMessage: 'We’ve built a suggested schedule to help you stay on track. ',
|
||||
description: 'Strong text in Dates Tab Info Banner',
|
||||
},
|
||||
'datesBanner.datesTabInfoBanner.body': {
|
||||
@@ -35,8 +35,8 @@ const messages = defineMessages({
|
||||
},
|
||||
'datesBanner.upgradeToResetBanner.body': {
|
||||
id: 'datesBanner.upgradeToResetBanner.body',
|
||||
defaultMessage: `which means that you are unable to participate in graded assignments. It looks like you missed
|
||||
some important deadlines based on our suggested schedule. To complete graded assignments as part of this course
|
||||
defaultMessage: `which means that you are unable to participate in graded assignments. It looks like you missed
|
||||
some important deadlines based on our suggested schedule. To complete graded assignments as part of this course
|
||||
and shift the past due assignments into the future, you can upgrade today.`,
|
||||
description: 'Body in Upgrade To Reset Banner',
|
||||
},
|
||||
@@ -52,7 +52,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'datesBanner.resetDatesBanner.body': {
|
||||
id: 'datesBanner.resetDatesBanner.body',
|
||||
defaultMessage: `To keep yourself on track, you can update this schedule and shift the past due assignments into
|
||||
defaultMessage: `To keep yourself on track, you can update this schedule and shift the past due assignments into
|
||||
the future. Don’t worry—you won’t lose any of the progress you’ve made when you shift your due dates.`,
|
||||
description: 'Body in Reset Dates Banner',
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import useOfferAlert from '../../alerts/offer-alert';
|
||||
|
||||
import Sequence from './sequence';
|
||||
|
||||
import { CelebrationModal, shouldCelebrateOnSectionLoad } from './celebration';
|
||||
import { FirstSectionCelebrationModal, shouldCelebrateOnSectionLoad } from './celebration';
|
||||
import CourseBreadcrumbs from './CourseBreadcrumbs';
|
||||
import CourseSock from './course-sock';
|
||||
import ContentTools from './content-tools';
|
||||
@@ -78,7 +78,7 @@ function Course({
|
||||
previousSequenceHandler={previousSequenceHandler}
|
||||
/>
|
||||
{celebrationOpen && (
|
||||
<CelebrationModal
|
||||
<FirstSectionCelebrationModal
|
||||
courseId={courseId}
|
||||
open
|
||||
/>
|
||||
|
||||
@@ -71,9 +71,9 @@ describe('Course', () => {
|
||||
handleNextSectionCelebration(sequenceId, sequenceId, testData.unitId);
|
||||
render(<Course {...testData} />, { store: testStore });
|
||||
|
||||
const celebrationModal = screen.getByRole('dialog');
|
||||
expect(celebrationModal).toBeInTheDocument();
|
||||
expect(getByRole(celebrationModal, 'heading', { name: 'Congratulations!' })).toBeInTheDocument();
|
||||
const FirstSectionCelebrationModal = screen.getByRole('dialog');
|
||||
expect(FirstSectionCelebrationModal).toBeInTheDocument();
|
||||
expect(getByRole(FirstSectionCelebrationModal, 'heading', { name: 'Congratulations!' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays upgrade sock', async () => {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Modal } from '@edx/paragon';
|
||||
import { layoutGenerator } from 'react-break';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import DiscussionMobile from './assets/FirstDiscussion_mobile.png';
|
||||
import DiscussionTablet from './assets/FirstDiscussion_desktop_500.png';
|
||||
import messages from './messages';
|
||||
import { recordFirstDiscussionCelebration } from './utils';
|
||||
import { updateModel } from '../../../generic/model-store';
|
||||
|
||||
function FirstDiscussionCelebrationModal({
|
||||
courseId, firstDiscussionUserBucket, intl, open, ...rest
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const layout = layoutGenerator({
|
||||
mobile: 0,
|
||||
tablet: 400,
|
||||
});
|
||||
|
||||
const OnMobile = layout.is('mobile');
|
||||
const OnAtLeastTablet = layout.isAtLeast('tablet');
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
recordFirstDiscussionCelebration(courseId);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
let normativeDataBodyText;
|
||||
// Bucket 2 corresponds to showing normative data in the body text
|
||||
if (firstDiscussionUserBucket === 2) {
|
||||
if (Math.random() > 0.5) {
|
||||
normativeDataBodyText = (<p className="mt-3">{intl.formatMessage(messages.discussionBodyText1)}</p>);
|
||||
} else {
|
||||
normativeDataBodyText = (<p className="mt-3">{intl.formatMessage(messages.discussionBodyText2)}</p>);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('normativeDataBodyText:', normativeDataBodyText);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
body={(
|
||||
<>
|
||||
<p>{intl.formatMessage(messages.conversation)}</p>
|
||||
<OnMobile>
|
||||
<img src={DiscussionMobile} alt="" className="img-fluid" />
|
||||
</OnMobile>
|
||||
<OnAtLeastTablet>
|
||||
<img src={DiscussionTablet} alt="" className="img-fluid" />
|
||||
</OnAtLeastTablet>
|
||||
{normativeDataBodyText}
|
||||
</>
|
||||
)}
|
||||
closeText={intl.formatMessage(messages.keepItUp)}
|
||||
onClose={() => {
|
||||
// Update our local copy of course data from LMS
|
||||
console.log('updating model');
|
||||
dispatch(updateModel({
|
||||
modelType: 'courses',
|
||||
model: {
|
||||
id: courseId,
|
||||
celebrations: {
|
||||
firstDiscussion: false,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}}
|
||||
open={open}
|
||||
title={intl.formatMessage(messages.firstDiscussionPost)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
FirstDiscussionCelebrationModal.defaultProps = {
|
||||
open: false,
|
||||
firstDiscussionUserBucket: 2,
|
||||
};
|
||||
|
||||
FirstDiscussionCelebrationModal.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
firstDiscussionUserBucket: PropTypes.number,
|
||||
intl: intlShape.isRequired,
|
||||
open: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default injectIntl(FirstDiscussionCelebrationModal);
|
||||
@@ -10,7 +10,7 @@ import messages from './messages';
|
||||
import SocialIcons from '../../social-share/SocialIcons';
|
||||
import { recordFirstSectionCelebration } from './utils';
|
||||
|
||||
function CelebrationModal({
|
||||
function FirstSectionCelebrationModal({
|
||||
courseId, intl, open, ...rest
|
||||
}) {
|
||||
const layout = layoutGenerator({
|
||||
@@ -58,14 +58,14 @@ function CelebrationModal({
|
||||
);
|
||||
}
|
||||
|
||||
CelebrationModal.defaultProps = {
|
||||
FirstSectionCelebrationModal.defaultProps = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
CelebrationModal.propTypes = {
|
||||
FirstSectionCelebrationModal.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
open: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default injectIntl(CelebrationModal);
|
||||
export default injectIntl(FirstSectionCelebrationModal);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 961 B |
Binary file not shown.
|
After Width: | Height: | Size: 742 B |
@@ -4,9 +4,7 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
// Does not block on answer
|
||||
export function postFirstSectionCelebrationComplete(courseId) {
|
||||
export function postCelebrationComplete(courseId, payload) {
|
||||
const url = new URL(`${getConfig().LMS_BASE_URL}/api/courseware/celebration/${courseId}`);
|
||||
getAuthenticatedHttpClient().post(url.href, {
|
||||
first_section: false,
|
||||
});
|
||||
getAuthenticatedHttpClient().post(url.href, payload);
|
||||
}
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
export { default as CelebrationModal } from './CelebrationModal';
|
||||
export { handleNextSectionCelebration, shouldCelebrateOnSectionLoad } from './utils';
|
||||
export { default as FirstDiscussionCelebrationModal } from './FirstDiscussionCelebrationModal';
|
||||
export { default as FirstSectionCelebrationModal } from './FirstSectionCelebrationModal';
|
||||
export {
|
||||
handleNextSectionCelebration,
|
||||
shouldCelebrateOnSectionLoad,
|
||||
shouldCelebrateOnDiscussionPost,
|
||||
} from './utils';
|
||||
|
||||
@@ -9,20 +9,41 @@ const messages = defineMessages({
|
||||
id: 'learning.celebration.congrats',
|
||||
defaultMessage: 'Congratulations!',
|
||||
},
|
||||
conversation: {
|
||||
id: 'learning.celebration.conversation',
|
||||
defaultMessage: 'Nice job being part of the conversation.',
|
||||
},
|
||||
discussionBodyText1: {
|
||||
id: 'learning.celebration.discussionBodyText1',
|
||||
defaultMessage: 'Learners who participate in discussions are 10x more likely to complete their course than those who don’t.',
|
||||
},
|
||||
discussionBodyText2: {
|
||||
id: 'learning.celebration.discussionBodyText2',
|
||||
defaultMessage: 'Learners who participate in discussions complete 3x as much course content on average vs. those who don’t.',
|
||||
},
|
||||
earned: {
|
||||
id: 'learning.celebration.earned',
|
||||
defaultMessage: 'You earned it!',
|
||||
},
|
||||
emailSubject: {
|
||||
id: 'learning.celebration.emailSubject',
|
||||
defaultMessage: "I'm on my way to completing {title} online with {platform}!",
|
||||
defaultMessage: 'I’m on my way to completing {title} online with {platform}!',
|
||||
description: 'Subject when sharing course progress via email',
|
||||
},
|
||||
firstDiscussionPost: {
|
||||
id: 'learning.celebration.firstDiscussionPost',
|
||||
defaultMessage: 'First Discussion post!',
|
||||
},
|
||||
forward: {
|
||||
id: 'learning.celebration.forward',
|
||||
defaultMessage: 'Keep going',
|
||||
description: 'Button to close celebration dialog and get back to course',
|
||||
},
|
||||
keepItUp: {
|
||||
id: 'learning.celebration.keepItUp',
|
||||
defaultMessage: 'Keep it up',
|
||||
description: 'Button to close celebration dialog and get back to course',
|
||||
},
|
||||
share: {
|
||||
id: 'learning.celebration.share',
|
||||
defaultMessage: 'Take a moment to celebrate and share your progress.',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { postFirstSectionCelebrationComplete } from './data/api';
|
||||
import { postCelebrationComplete } from './data/api';
|
||||
import { clearLocalStorage, getLocalStorage, setLocalStorage } from '../../../data/localStorage';
|
||||
import { updateModel } from '../../../generic/model-store';
|
||||
|
||||
const CELEBRATION_LOCAL_STORAGE_KEY = 'CelebrationModal.showOnSectionLoad';
|
||||
const CELEBRATION_LOCAL_STORAGE_KEY = 'FirstSectionCelebrationModal.showOnSectionLoad';
|
||||
|
||||
// Records clicks through the end of a section, so that we can know whether we should celebrate when we finish loading
|
||||
function handleNextSectionCelebration(sequenceId, nextSequenceId, nextUnitId) {
|
||||
@@ -16,18 +16,34 @@ function handleNextSectionCelebration(sequenceId, nextSequenceId, nextUnitId) {
|
||||
});
|
||||
}
|
||||
|
||||
function recordFirstSectionCelebration(courseId) {
|
||||
// Tell the LMS
|
||||
postFirstSectionCelebrationComplete(courseId);
|
||||
|
||||
// Tell our analytics
|
||||
function sendCelebrationSegmentEvent(courseId, eventName) {
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
sendTrackEvent('edx.ui.lms.celebration.first_section.opened', {
|
||||
sendTrackEvent(eventName, {
|
||||
course_id: courseId,
|
||||
is_staff: administrator,
|
||||
});
|
||||
}
|
||||
|
||||
function recordFirstSectionCelebration(courseId) {
|
||||
// Tell the LMS
|
||||
postCelebrationComplete(courseId, { first_section: false });
|
||||
|
||||
// Tell our analytics
|
||||
sendCelebrationSegmentEvent(courseId, 'edx.ui.lms.celebration.first_section.opened');
|
||||
}
|
||||
|
||||
function recordFirstDiscussionCelebration(courseId) {
|
||||
/* postCelebrationComplete should start being used once the Discussion MFE exists
|
||||
and we no longer record the first discussion post in the JS handler in edx-platform
|
||||
See edx-platform/common/static/common/js/discussion/views/new_post_view.js and
|
||||
edx-platform/cms/static/common/js/discussion/views/discussion_thread_view.js */
|
||||
// Tell the LMS
|
||||
// postCelebrationComplete(courseId, { first_discussion: false });
|
||||
|
||||
// Tell our analytics
|
||||
sendCelebrationSegmentEvent(courseId, 'edx.ui.lms.celebration.first_discussion.opened');
|
||||
}
|
||||
|
||||
// Looks at local storage to see whether we just came from the end of a section.
|
||||
// Note! This does have side effects (will clear some local storage and may start an api call).
|
||||
function shouldCelebrateOnSectionLoad(courseId, sequenceId, unitId, celebrateFirstSection, dispatch) {
|
||||
@@ -63,4 +79,16 @@ function shouldCelebrateOnSectionLoad(courseId, sequenceId, unitId, celebrateFir
|
||||
return shouldCelebrate;
|
||||
}
|
||||
|
||||
export { handleNextSectionCelebration, recordFirstSectionCelebration, shouldCelebrateOnSectionLoad };
|
||||
function shouldCelebrateOnDiscussionPost(firstDiscussion, firstDiscussionUserBucket) {
|
||||
// Bucket 0 === Control group which does not get the discussion celebration.
|
||||
// That check can be removed when we stop using the flag as an experiment.
|
||||
return firstDiscussion && firstDiscussionUserBucket !== 0;
|
||||
}
|
||||
|
||||
export {
|
||||
handleNextSectionCelebration,
|
||||
recordFirstDiscussionCelebration,
|
||||
recordFirstSectionCelebration,
|
||||
shouldCelebrateOnSectionLoad,
|
||||
shouldCelebrateOnDiscussionPost,
|
||||
};
|
||||
|
||||
@@ -98,13 +98,21 @@ class Calculator extends Component {
|
||||
<FormattedMessage
|
||||
tagName="h6"
|
||||
id="calculator.instructions"
|
||||
defaultMessage="For detailed information, see the {expressions_link}."
|
||||
defaultMessage="For detailed information, see {expressions_link} in the {edx_guide}."
|
||||
values={{
|
||||
expressions_link: (
|
||||
<a href={getConfig().SUPPORT_URL_CALCULATOR_MATH}>
|
||||
<a href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/completing_assignments/SFD_mathformatting.html#math-formatting">
|
||||
<FormattedMessage
|
||||
id="calculator.instructions.support.title"
|
||||
defaultMessage="Help Center"
|
||||
id="calculator.instructions.expressions.link.title"
|
||||
defaultMessage="Entering Mathematical and Scientific Expressions"
|
||||
/>
|
||||
</a>
|
||||
),
|
||||
edx_guide: (
|
||||
<a href="https://edx-guide-for-students.readthedocs.io/en/latest/index.html">
|
||||
<FormattedMessage
|
||||
id="calculator.instructions.edx.guide.link.title"
|
||||
defaultMessage="edX Guide for Students"
|
||||
/>
|
||||
</a>
|
||||
),
|
||||
|
||||
@@ -61,11 +61,12 @@ function CourseCelebration({ intl }) {
|
||||
{intl.formatMessage(messages.dashboardLink)}
|
||||
</Hyperlink>
|
||||
);
|
||||
const idVerificationSupportLink = getConfig().SUPPORT_URL_ID_VERIFICATION && (
|
||||
// todo: remove this hardcoded link to edX support
|
||||
const idVerificationSupportLink = getConfig().SUPPORT_URL && (
|
||||
<Hyperlink
|
||||
className="text-gray-700"
|
||||
style={{ textDecoration: 'underline' }}
|
||||
destination={getConfig().SUPPORT_URL_ID_VERIFICATION}
|
||||
destination={`${getConfig().SUPPORT_URL}/hc/en-us/articles/206503858-How-do-I-verify-my-identity`}
|
||||
>
|
||||
{intl.formatMessage(messages.idVerificationSupportLink)}
|
||||
</Hyperlink>
|
||||
@@ -187,11 +188,12 @@ function CourseCelebration({ intl }) {
|
||||
values={{ price: verifiedMode.currencySymbol + verifiedMode.price }}
|
||||
/>
|
||||
<br />
|
||||
{getConfig().SUPPORT_URL_VERIFIED_CERTIFICATE && (
|
||||
{ /* todo: remove this hardcoded link to edX support */ }
|
||||
{getConfig().SUPPORT_URL && (
|
||||
<Hyperlink
|
||||
className="text-gray-700"
|
||||
style={{ textDecoration: 'underline' }}
|
||||
destination={getConfig().SUPPORT_URL_VERIFIED_CERTIFICATE}
|
||||
destination={`${getConfig().SUPPORT_URL}/hc/en-us/articles/206502008-What-is-a-verified-certificate`}
|
||||
>
|
||||
{intl.formatMessage(messages.verifiedCertificateSupportLink)}
|
||||
</Hyperlink>
|
||||
|
||||
@@ -42,7 +42,7 @@ const messages = defineMessages({
|
||||
dashboardLink: {
|
||||
id: 'courseExit.dashboardLink',
|
||||
defaultMessage: 'Dashboard',
|
||||
description: "Link to user's dashboard",
|
||||
description: 'Link to user’s dashboard',
|
||||
},
|
||||
downloadButton: {
|
||||
id: 'courseCelebration.downloadButton',
|
||||
@@ -69,7 +69,7 @@ const messages = defineMessages({
|
||||
linkedinAddToProfileButton: {
|
||||
id: 'courseCelebration.linkedinAddToProfileButton',
|
||||
defaultMessage: 'Add to LinkedIn profile',
|
||||
description: "Button to add certificate information to the user's LinkedIn profile",
|
||||
description: 'Button to add certificate information to the user’s LinkedIn profile',
|
||||
},
|
||||
nextButtonComplete: {
|
||||
id: 'learn.sequence.navigation.complete.button', // for historical reasons
|
||||
@@ -82,7 +82,7 @@ const messages = defineMessages({
|
||||
profileLink: {
|
||||
id: 'courseExit.profileLink',
|
||||
defaultMessage: 'Profile',
|
||||
description: "Link to user's profile",
|
||||
description: 'Link to user’s profile',
|
||||
},
|
||||
requestCertificateBodyText: {
|
||||
id: 'courseCelebration.requestCertificateBodyText',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import LearnerQuote1 from './assets/learner-quote.png';
|
||||
import LearnerQuote2 from './assets/learner-quote2.png';
|
||||
@@ -37,10 +36,7 @@ export default class CourseSock extends Component {
|
||||
<h2 className="mt-3 mb-4">
|
||||
<FormattedMessage
|
||||
id="coursesock.upsell.verifiedcert"
|
||||
defaultMessage="{siteName} Verified Certificate"
|
||||
values={{
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
defaultMessage="edX Verified Certificate"
|
||||
/>
|
||||
</h2>
|
||||
<div className="row flex-row-reverse">
|
||||
@@ -98,10 +94,7 @@ export default class CourseSock extends Component {
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="coursesock.upsell.reason4"
|
||||
defaultMessage="Certificate purchases help {siteName} continue to offer free courses"
|
||||
values={{
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
defaultMessage="Certificate purchases help edX continue to offer free courses"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -141,10 +134,7 @@ export default class CourseSock extends Component {
|
||||
<h3 className="h5">
|
||||
<FormattedMessage
|
||||
id="coursesock.upsell.storytitle"
|
||||
defaultMessage="{siteName} Learner Stories"
|
||||
values={{
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
defaultMessage="edX Learner Stories"
|
||||
/>
|
||||
</h3>
|
||||
<div className="media my-3">
|
||||
@@ -160,11 +150,8 @@ export default class CourseSock extends Component {
|
||||
— <FormattedMessage
|
||||
id="coursesock.upsell.learner"
|
||||
description="Name of learner"
|
||||
defaultMessage="{name}, {siteName} Learner"
|
||||
values={{
|
||||
name: 'Christina Fong',
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
defaultMessage="{ name }, edX Learner"
|
||||
values={{ name: 'Christina Fong' }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
@@ -182,11 +169,8 @@ export default class CourseSock extends Component {
|
||||
— <FormattedMessage
|
||||
id="coursesock.upsell.learner"
|
||||
description="Name of learner"
|
||||
defaultMessage="{name}, {siteName} Learner"
|
||||
values={{
|
||||
name: 'Cheryl Troell',
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
defaultMessage="{ name }, edX Learner"
|
||||
values={{ name: 'Cheryl Troell' }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Modal } from '@edx/paragon';
|
||||
import messages from './messages';
|
||||
import BookmarkButton from '../bookmark/BookmarkButton';
|
||||
import { FirstDiscussionCelebrationModal, shouldCelebrateOnDiscussionPost } from '../celebration';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import PageLoading from '../../../generic/PageLoading';
|
||||
import { processEvent } from '../../../course-home/data/thunks';
|
||||
@@ -65,15 +66,21 @@ function Unit({
|
||||
|
||||
const [iframeHeight, setIframeHeight] = useState(0);
|
||||
const [hasLoaded, setHasLoaded] = useState(false);
|
||||
const [discussionPosted, setDiscussionPosted] = useState(false);
|
||||
const [modalOptions, setModalOptions] = useState({ open: false });
|
||||
|
||||
const unit = useModel('units', id);
|
||||
const course = useModel('courses', courseId);
|
||||
const {
|
||||
celebrations: {
|
||||
firstDiscussion,
|
||||
firstDiscussionUserBucket,
|
||||
},
|
||||
contentTypeGatingEnabled,
|
||||
} = course;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const shouldCelebrateDiscussionPost = shouldCelebrateOnDiscussionPost(firstDiscussion, firstDiscussionUserBucket);
|
||||
|
||||
// Do not remove this hook. See function description.
|
||||
useLoadBearingHook(id);
|
||||
@@ -109,6 +116,9 @@ function Unit({
|
||||
return () => global.removeEventListener('message', messageEventListenerRef.current);
|
||||
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded]);
|
||||
|
||||
console.log('shouldCelebrateDiscussionPost:', shouldCelebrateDiscussionPost);
|
||||
console.log('discussionPosted:', discussionPosted);
|
||||
|
||||
return (
|
||||
<div className="unit">
|
||||
<h2 className="mb-0 h4">{unit.title}</h2>
|
||||
@@ -147,17 +157,21 @@ function Unit({
|
||||
allow="microphone *; camera *; midi *; geolocation *; encrypted-media *"
|
||||
frameBorder="0"
|
||||
src={modalOptions.url}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
title={modalOptions.title}
|
||||
onClose={() => { setModalOptions({ open: false }); }}
|
||||
open
|
||||
dialogClassName="modal-lti"
|
||||
dialogClassName="modal-lg"
|
||||
/>
|
||||
)}
|
||||
{discussionPosted && shouldCelebrateDiscussionPost && (
|
||||
<FirstDiscussionCelebrationModal
|
||||
courseId={courseId}
|
||||
firstDiscussionUserBucket={firstDiscussionUserBucket}
|
||||
open
|
||||
/>
|
||||
)}
|
||||
<div className="unit-iframe-wrapper">
|
||||
@@ -170,9 +184,11 @@ function Unit({
|
||||
scrolling="no"
|
||||
referrerPolicy="origin"
|
||||
onLoad={() => {
|
||||
window.onmessage = function handleResetDates(e) {
|
||||
if (e.data.event_name) {
|
||||
window.onmessage = function handleMessageEvent(e) {
|
||||
if (e.data.event_name === 'post_event') {
|
||||
dispatch(processEvent(e.data, fetchCourse));
|
||||
} else if (e.data.event_name === 'discussion_post') {
|
||||
setDiscussionPosted(true);
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
||||
@@ -8,7 +8,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'learn.contentLock.complete.prerequisite': {
|
||||
id: 'learn.contentLock.complete.prerequisite',
|
||||
defaultMessage: "You must complete the prerequisite: '{prereqSectionName}' to access this content.",
|
||||
defaultMessage: 'You must complete the prerequisite: "{prereqSectionName}" to access this content.',
|
||||
description: 'Message shown to indicate which prerequisite the student must complete prior to accessing the locked content. {prereqSectionName} is the name of the prerequisite.',
|
||||
},
|
||||
'learn.contentLock.goToSection': {
|
||||
|
||||
@@ -54,9 +54,7 @@ function SocialIcons({
|
||||
});
|
||||
};
|
||||
|
||||
const socialUtmCampaign = getConfig().SOCIAL_UTM_MILESTONE_CAMPAIGN
|
||||
? `utm_campaign=${getConfig().SOCIAL_UTM_MILESTONE_CAMPAIGN}&` : '';
|
||||
const socialUtmMarketingUrl = `${marketingUrl}?${socialUtmCampaign}utm_medium=social`;
|
||||
const socialUtmMarketingUrl = `${marketingUrl}?utm_campaign=edxmilestone&utm_medium=social`;
|
||||
|
||||
return (
|
||||
<div className={`social-icons ${className}`}>
|
||||
@@ -93,7 +91,7 @@ function SocialIcons({
|
||||
body={emailBody ? `${intl.formatMessage(emailBody)}\n\n` : ''}
|
||||
className="ml-2"
|
||||
subject={emailSubject ? intl.formatMessage(emailSubject, { platform: getConfig().SITE_NAME, title }) : ''}
|
||||
url={`${marketingUrl}?${socialUtmCampaign}utm_medium=email&utm_source=email`}
|
||||
url={`${marketingUrl}?utm_campaign=edxmilestone&utm_medium=email&utm_source=email`}
|
||||
>
|
||||
<EmailIcon round size={32} />
|
||||
<span className="sr-only">{intl.formatMessage(messages.shareEmail)}</span>
|
||||
@@ -107,7 +105,7 @@ SocialIcons.defaultProps = {
|
||||
className: '',
|
||||
emailBody: messages.defaultEmailBody,
|
||||
emailSubject: null,
|
||||
hashtags: [getConfig().TWITTER_HASHTAG],
|
||||
hashtags: ['myedxjourney'],
|
||||
socialMessage: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -81,13 +81,8 @@ initialize({
|
||||
config: () => {
|
||||
mergeConfig({
|
||||
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,
|
||||
SOCIAL_UTM_MILESTONE_CAMPAIGN: process.env.SOCIAL_UTM_MILESTONE_CAMPAIGN || null,
|
||||
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null,
|
||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||
SUPPORT_URL_CALCULATOR_MATH: process.env.SUPPORT_URL_CALCULATOR_MATH || null,
|
||||
SUPPORT_URL_ID_VERIFICATION: process.env.SUPPORT_URL_ID_VERIFICATION || null,
|
||||
SUPPORT_URL_VERIFIED_CERTIFICATE: process.env.SUPPORT_URL_VERIFIED_CERTIFICATE || null,
|
||||
TWITTER_HASHTAG: process.env.TWITTER_HASHTAG || null,
|
||||
TWITTER_URL: process.env.TWITTER_URL || null,
|
||||
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null,
|
||||
}, 'LearnerAppConfig');
|
||||
|
||||
@@ -319,27 +319,6 @@ $primary: #1176B2;
|
||||
}
|
||||
}
|
||||
|
||||
// This class forces any modals using 'modal-lti' as their dialogClassName to take up the whole
|
||||
// window (retaining padding around the edge). Bootstrap modals don't have a full-screen
|
||||
// size like this. Because of the hack below around react-focus-on's div, it would be better long-term to pull this into Paragon and perhaps call it "modal-full" or something like that.
|
||||
.modal-lti {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
// I don't like this. We need to set a height of 100% on a div created by react-focus-on, a
|
||||
// package we use in our Modal. That div has no class name or ID, so instead we're uniquely
|
||||
// identifying it by based on a unique attribute it has which its siblings don't share.
|
||||
> div[data-focus-lock-disabled=false] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Along with setting the height of modal-content's parent div from react-focus-on, we need to
|
||||
// set modal-content's height as well to get the modal to expand to full-screen height.
|
||||
.modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Import component-specific sass files
|
||||
@import 'courseware/course/celebration/CelebrationModal.scss';
|
||||
@import 'courseware/course/content-tools/calculator/calculator.scss';
|
||||
|
||||
Reference in New Issue
Block a user