AA-390: Add Social Share icons to Course Exit (#259)

This commit is contained in:
Carla Duarte
2020-10-27 14:58:35 -04:00
committed by GitHub
parent 6f415544be
commit 1531f3e912
9 changed files with 106 additions and 36 deletions

View File

@@ -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({
<p className="mt-3">
<strong>{intl.formatMessage(messages.earned)}</strong> {intl.formatMessage(messages.share)}
</p>
<SocialIcons courseId={courseId} />
<SocialIcons
analyticsId="edx.ui.lms.celebration.social_share.clicked"
courseId={courseId}
emailSubject={messages.emailSubject}
socialMessage={messages.socialMessage}
/>
</>
)}
closeText={intl.formatMessage(messages.forward)}

View File

@@ -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: 'Im 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',

View File

@@ -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 = (
<p>
<FormattedMessage
@@ -123,7 +124,7 @@ function CourseCelebration({ intl }) {
break;
case 'earned_but_not_available': {
const endDate = <FormattedDate value={end} day="numeric" month="long" year="numeric" />;
title = intl.formatMessage(messages.certificateHeaderNotAvailable);
certHeader = intl.formatMessage(messages.certificateHeaderNotAvailable);
message = (
<>
<p>
@@ -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 = (<p>{intl.formatMessage(messages.requestCertificateBodyText)}</p>);
footnote = <DashboardFootnote />;
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 = (
<p>
@@ -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 = (
<p>
<FormattedMessage
@@ -225,8 +226,15 @@ function CourseCelebration({ intl }) {
</div>
<div className="col-12 p-0 font-weight-normal lead text-center">
{intl.formatMessage(messages.shareHeader)}
<SocialIcons
analyticsId="edx.ui.lms.course_exit.social_share.clicked"
className="mt-2"
courseId={courseId}
emailSubject={messages.socialMessage}
socialMessage={messages.socialMessage}
/>
</div>
<div className="col-12 my-4 px-0 px-md-5 text-center">
<div className="col-12 mt-3 mb-4 px-0 px-md-5 text-center">
<OnMobile>
<img
src={CelebrationMobile}
@@ -244,10 +252,10 @@ function CourseCelebration({ intl }) {
</OnAtLeastTablet>
</div>
<div className="col-12 px-0 px-md-5">
{title && (
{certHeader && (
<Alert variant="primary" className="row w-100 m-0">
<div className="col order-1 order-md-0 pl-0 pr-0 pr-md-5">
<div className="h4">{title}</div>
<div className="h4">{certHeader}</div>
{message}
{/* The requesting status needs a different button because it does a POST instead of a GET */}
{certStatus === 'requesting' && (

View File

@@ -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(<CourseCelebration />);
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(<CourseCelebration />);
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' },

View File

@@ -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',

View File

@@ -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 (
<div className="social-icons">
<div className={`social-icons ${className}`}>
<LinkedinShareButton
beforeOnClick={() => logClick('linkedin')}
url={`${socialUtmMarketingUrl}&utm_source=linkedin`}
@@ -52,12 +65,12 @@ function SocialIcons({ courseId, intl }) {
<LinkedinIcon round size={32} />
<span className="sr-only">{intl.formatMessage(messages.shareService, { service: 'LinkedIn' })}</span>
</LinkedinShareButton>
{ twitterAccount && (
{twitterAccount && (
<TwitterShareButton
beforeOnClick={() => 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`}
>
<TwitterIcon round size={32} />
@@ -67,7 +80,7 @@ function SocialIcons({ courseId, intl }) {
<FacebookShareButton
beforeOnClick={() => 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`}
>
<FacebookIcon round size={32} />
@@ -75,9 +88,9 @@ function SocialIcons({ courseId, intl }) {
</FacebookShareButton>
<EmailShareButton
beforeOnClick={() => 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`}
>
<EmailIcon round size={32} />
@@ -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);

View File

@@ -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;

View File

@@ -233,7 +233,6 @@ $primary: #1176B2;
}
.previous-btn, .next-btn {
min-width: 4rem;
color: theme-color('gray', 700);
display: inline-flex;
justify-content: center;

View File

@@ -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',