From 1531f3e91227ae3eeeebe8abcb9593c8a9c28655 Mon Sep 17 00:00:00 2001 From: Carla Duarte Date: Tue, 27 Oct 2020 14:58:35 -0400 Subject: [PATCH] AA-390: Add Social Share icons to Course Exit (#259) --- .../course/celebration/CelebrationModal.jsx | 9 +++- src/courseware/course/celebration/messages.js | 15 +----- .../course/course-exit/CourseCelebration.jsx | 26 ++++++---- .../course/course-exit/CourseExit.test.jsx | 18 +++++++ src/courseware/course/course-exit/messages.js | 5 ++ .../SocialIcons.jsx | 48 +++++++++++++++---- src/courseware/social-share/messages.js | 19 ++++++++ src/index.scss | 1 - src/setupTest.js | 1 + 9 files changed, 106 insertions(+), 36 deletions(-) rename src/courseware/{course/celebration => social-share}/SocialIcons.jsx (68%) create mode 100644 src/courseware/social-share/messages.js diff --git a/src/courseware/course/celebration/CelebrationModal.jsx b/src/courseware/course/celebration/CelebrationModal.jsx index cb8a57c8..927cbda7 100644 --- a/src/courseware/course/celebration/CelebrationModal.jsx +++ b/src/courseware/course/celebration/CelebrationModal.jsx @@ -7,7 +7,7 @@ import { layoutGenerator } from 'react-break'; import ClapsMobile from './assets/claps_280x201.gif'; import ClapsTablet from './assets/claps_456x328.gif'; import messages from './messages'; -import SocialIcons from './SocialIcons'; +import SocialIcons from '../../social-share/SocialIcons'; import { recordFirstSectionCelebration } from './utils'; function CelebrationModal({ @@ -41,7 +41,12 @@ function CelebrationModal({

{intl.formatMessage(messages.earned)} {intl.formatMessage(messages.share)}

- + )} closeText={intl.formatMessage(messages.forward)} diff --git a/src/courseware/course/celebration/messages.js b/src/courseware/course/celebration/messages.js index 1744187c..8fa81c65 100644 --- a/src/courseware/course/celebration/messages.js +++ b/src/courseware/course/celebration/messages.js @@ -13,11 +13,6 @@ const messages = defineMessages({ id: 'learning.celebration.earned', defaultMessage: 'You earned it!', }, - emailBody: { - id: 'learning.celebration.emailBody', - defaultMessage: 'What are you spending your time learning?', - description: 'Body when sharing course progress via email', - }, emailSubject: { id: 'learning.celebration.emailSubject', defaultMessage: "I'm on my way to completing {title} online with {platform}!", @@ -32,15 +27,7 @@ const messages = defineMessages({ id: 'learning.celebration.share', defaultMessage: 'Take a moment to celebrate and share your progress.', }, - shareEmail: { - id: 'learning.celebration.share.email', - defaultMessage: 'Share your progress via email.', - }, - shareService: { - id: 'learning.celebration.share.service', - defaultMessage: 'Share your progress on {service}.', - }, - social: { + socialMessage: { id: 'learning.celebration.social', defaultMessage: 'I’m on my way to completing {title} online with {platform}. What are you spending your time learning?', description: 'Shown when sharing course progress on a social network', diff --git a/src/courseware/course/course-exit/CourseCelebration.jsx b/src/courseware/course/course-exit/CourseCelebration.jsx index 67d29426..fe6c81d5 100644 --- a/src/courseware/course/course-exit/CourseCelebration.jsx +++ b/src/courseware/course/course-exit/CourseCelebration.jsx @@ -21,6 +21,7 @@ import { useModel } from '../../../generic/model-store'; import { requestCert } from '../../../course-home/data/thunks'; import DashboardFootnote from './DashboardFootnote'; import UpgradeFootnote from './UpgradeFootnote'; +import SocialIcons from '../../social-share/SocialIcons'; const LINKEDIN_BLUE = '#007fb1'; @@ -95,11 +96,11 @@ function CourseCelebration({ intl }) { let certificateImage = certificate; let footnote; let message; - let title; + let certHeader; // These cases are taken from the edx-platform `get_cert_data` function found in lms/courseware/views/views.py switch (certStatus) { case 'downloadable': - title = intl.formatMessage(messages.certificateHeaderDownloadable); + certHeader = intl.formatMessage(messages.certificateHeaderDownloadable); message = (

; - title = intl.formatMessage(messages.certificateHeaderNotAvailable); + certHeader = intl.formatMessage(messages.certificateHeaderNotAvailable); message = ( <>

@@ -150,14 +151,14 @@ function CourseCelebration({ intl }) { } case 'requesting': buttonText = intl.formatMessage(messages.requestCertificateButton); - title = intl.formatMessage(messages.certificateHeaderRequestable); + certHeader = intl.formatMessage(messages.certificateHeaderRequestable); message = (

{intl.formatMessage(messages.requestCertificateBodyText)}

); footnote = ; break; case 'unverified': buttonText = intl.formatMessage(messages.verifyIdentityButton); buttonLocation = verifyIdentityUrl; - title = intl.formatMessage(messages.certificateHeaderUnverified); + certHeader = intl.formatMessage(messages.certificateHeaderUnverified); // todo: check for idVerificationSupportLink null message = (

@@ -174,7 +175,7 @@ function CourseCelebration({ intl }) { case 'audit_passing': case 'honor_passing': if (verifiedMode) { - title = intl.formatMessage(messages.certificateHeaderUpgradable); + certHeader = intl.formatMessage(messages.certificateHeaderUpgradable); message = (

{intl.formatMessage(messages.shareHeader)} +
-
+
- {title && ( + {certHeader && (
-
{title}
+
{certHeader}
{message} {/* The requesting status needs a different button because it does a POST instead of a GET */} {certStatus === 'requesting' && ( diff --git a/src/courseware/course/course-exit/CourseExit.test.jsx b/src/courseware/course/course-exit/CourseExit.test.jsx index e30cdcd4..ea176258 100644 --- a/src/courseware/course/course-exit/CourseExit.test.jsx +++ b/src/courseware/course/course-exit/CourseExit.test.jsx @@ -118,6 +118,24 @@ describe('Course Exit Pages', () => { expect(screen.getByRole('button', { name: 'Request certificate' })).toBeInTheDocument(); }); + it('Displays social share icons', async () => { + setMetadata({ certificate_data: { cert_status: 'unverified' }, marketing_url: 'https://edx.org' }); + await fetchAndRender(); + expect(screen.getByRole('button', { name: 'linkedin' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'facebook' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'twitter' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'email' })).toBeInTheDocument(); + }); + + it('Does not display social share icons if no marketing URL', async () => { + setMetadata({ certificate_data: { cert_status: 'unverified' } }); + await fetchAndRender(); + expect(screen.queryByRole('button', { name: 'linkedin' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'facebook' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'twitter' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'email' })).not.toBeInTheDocument(); + }); + it('Displays verify identity link', async () => { setMetadata({ certificate_data: { cert_status: 'unverified' }, diff --git a/src/courseware/course/course-exit/messages.js b/src/courseware/course/course-exit/messages.js index 392587da..c2a2aaaf 100644 --- a/src/courseware/course/course-exit/messages.js +++ b/src/courseware/course/course-exit/messages.js @@ -97,6 +97,11 @@ const messages = defineMessages({ id: 'courseCelebration.shareHeader', defaultMessage: 'You have completed your course. Share your success on social media or email.', }, + socialMessage: { + id: 'courseExit.social.shareCompletionMessage', + defaultMessage: 'I just completed {title} with {platform}!', + description: 'Shown when sharing course progress on a social network', + }, upgradeButton: { id: 'courseExit.upgradeButton', defaultMessage: 'Upgrade now', diff --git a/src/courseware/course/celebration/SocialIcons.jsx b/src/courseware/social-share/SocialIcons.jsx similarity index 68% rename from src/courseware/course/celebration/SocialIcons.jsx rename to src/courseware/social-share/SocialIcons.jsx index 3c4e4d91..b093a33a 100644 --- a/src/courseware/course/celebration/SocialIcons.jsx +++ b/src/courseware/social-share/SocialIcons.jsx @@ -17,9 +17,18 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import messages from './messages'; -import { useModel } from '../../../generic/model-store'; +import { useModel } from '../../generic/model-store'; -function SocialIcons({ courseId, intl }) { +function SocialIcons({ + analyticsId, + className, + courseId, + emailBody, + emailSubject, + hashtags, + intl, + socialMessage, +}) { const { marketingUrl, title, @@ -33,8 +42,12 @@ function SocialIcons({ courseId, intl }) { const twitterAccount = twitterUrl && twitterUrl.substring(twitterUrl.lastIndexOf('/') + 1); const logClick = (service) => { + if (!analyticsId) { + return; + } + const { administrator } = getAuthenticatedUser(); - sendTrackEvent('edx.ui.lms.celebration.social_share.clicked', { + sendTrackEvent(analyticsId, { course_id: courseId, is_staff: administrator, service, @@ -44,7 +57,7 @@ function SocialIcons({ courseId, intl }) { const socialUtmMarketingUrl = `${marketingUrl}?utm_campaign=edxmilestone&utm_medium=social`; return ( -
+
logClick('linkedin')} url={`${socialUtmMarketingUrl}&utm_source=linkedin`} @@ -52,12 +65,12 @@ function SocialIcons({ courseId, intl }) { {intl.formatMessage(messages.shareService, { service: 'LinkedIn' })} - { twitterAccount && ( + {twitterAccount && ( logClick('twitter')} className="ml-2" - hashtags={['myedxjourney']} - title={intl.formatMessage(messages.social, { platform: `@${twitterAccount}`, title })} + hashtags={hashtags} + title={socialMessage ? intl.formatMessage(socialMessage, { platform: `@${twitterAccount}`, title }) : ''} url={`${socialUtmMarketingUrl}&utm_source=twitter`} > @@ -67,7 +80,7 @@ function SocialIcons({ courseId, intl }) { logClick('facebook')} className="ml-2" - quote={intl.formatMessage(messages.social, { platform: getConfig().SITE_NAME, title })} + quote={socialMessage ? intl.formatMessage(socialMessage, { platform: getConfig().SITE_NAME, title }) : ''} url={`${socialUtmMarketingUrl}&utm_source=facebook`} > @@ -75,9 +88,9 @@ function SocialIcons({ courseId, intl }) { logClick('email')} - body={`${intl.formatMessage(messages.emailBody)}\n\n`} + body={emailBody ? `${intl.formatMessage(emailBody)}\n\n` : ''} className="ml-2" - subject={intl.formatMessage(messages.emailSubject, { platform: getConfig().SITE_NAME, title })} + subject={emailSubject ? intl.formatMessage(emailSubject, { platform: getConfig().SITE_NAME, title }) : ''} url={`${marketingUrl}?utm_campaign=edxmilestone&utm_medium=email&utm_source=email`} > @@ -87,9 +100,24 @@ function SocialIcons({ courseId, intl }) { ); } +SocialIcons.defaultProps = { + analyticsId: '', + className: '', + emailBody: messages.defaultEmailBody, + emailSubject: null, + hashtags: ['myedxjourney'], + socialMessage: null, +}; + SocialIcons.propTypes = { + analyticsId: PropTypes.string, + className: PropTypes.string, courseId: PropTypes.string.isRequired, + emailBody: PropTypes.shape({}), + emailSubject: PropTypes.shape({}), + hashtags: PropTypes.arrayOf(PropTypes.string), intl: intlShape.isRequired, + socialMessage: PropTypes.shape({}), }; export default injectIntl(SocialIcons); diff --git a/src/courseware/social-share/messages.js b/src/courseware/social-share/messages.js new file mode 100644 index 00000000..3fc85ffd --- /dev/null +++ b/src/courseware/social-share/messages.js @@ -0,0 +1,19 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + defaultEmailBody: { + id: 'learning.celebration.emailBody', + defaultMessage: 'What are you spending your time learning?', + description: 'Body when sharing course progress via email', + }, + shareEmail: { + id: 'learning.social.shareEmail', + defaultMessage: 'Share your progress via email.', + }, + shareService: { + id: 'learning.social.shareService', + defaultMessage: 'Share your progress on {service}.', + }, +}); + +export default messages; diff --git a/src/index.scss b/src/index.scss index 45b86375..416b99b2 100755 --- a/src/index.scss +++ b/src/index.scss @@ -233,7 +233,6 @@ $primary: #1176B2; } .previous-btn, .next-btn { - min-width: 4rem; color: theme-color('gray', 700); display: inline-flex; justify-content: center; diff --git a/src/setupTest.js b/src/setupTest.js index 269ce6db..26de9e7a 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -63,6 +63,7 @@ export function initializeMockApp() { mergeConfig({ INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null, STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null, + TWITTER_URL: process.env.TWITTER_URL || null, authenticatedUser: { userId: 'abc123', username: 'Mock User',