feat: AA-1138: Adds in Weekly Goal Celebration Modal (#797)

The logic to show the modal is controlled by the backend.
Displays the modal only in courseware the first time the learner
hits their weekly learning goal. After viewing the goal, the
database row is updated to not show the modal again.

Also updates first section celebration to use the StandardModal
component as the Modal component has been deprecated.
This commit is contained in:
Dillon Dumesnil
2022-01-18 06:11:36 -08:00
committed by GitHub
parent 2789c7415b
commit b9d1bf0624
10 changed files with 220 additions and 61 deletions

View File

@@ -8,7 +8,7 @@ import { AlertList } from '../../generic/user-messages';
import Sequence from './sequence';
import { CelebrationModal, shouldCelebrateOnSectionLoad } from './celebration';
import { CelebrationModal, shouldCelebrateOnSectionLoad, WeeklyGoalCelebrationModal } from './celebration';
import ContentTools from './content-tools';
import CourseBreadcrumbs from './CourseBreadcrumbs';
import NotificationTrigger from './NotificationTrigger';
@@ -41,15 +41,22 @@ function Course({
const {
celebrations,
courseGoals,
verifiedMode,
} = course;
// Below the tabs, above the breadcrumbs alerts (appearing in the order listed here)
const dispatch = useDispatch();
const celebrateFirstSection = celebrations && celebrations.firstSection;
const celebrationOpen = shouldCelebrateOnSectionLoad(
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(shouldCelebrateOnSectionLoad(
courseId, sequenceId, unitId, celebrateFirstSection, dispatch, celebrations,
));
// If streakLengthToCelebrate is populated, that modal takes precedence. Wait til the next load to display
// the weekly goal celebration modal.
const [weeklyGoalCelebrationOpen, setWeeklyGoalCelebrationOpen] = useState(
celebrations && !celebrations.streakLengthToCelebrate && celebrations.weeklyGoal,
);
const daysPerWeek = courseGoals?.selectedGoal?.daysPerWeek;
// Responsive breakpoints for showing the notification button/tray
const shouldDisplayNotificationTriggerInCourse = useWindowSize().width >= responsiveBreakpoints.small.minWidth;
@@ -145,12 +152,17 @@ function Course({
//* * [MM-P2P] Experiment */
mmp2p={MMP2P}
/>
{celebrationOpen && (
<CelebrationModal
courseId={courseId}
open
/>
)}
<CelebrationModal
courseId={courseId}
isOpen={firstSectionCelebrationOpen}
onClose={() => setFirstSectionCelebrationOpen(false)}
/>
<WeeklyGoalCelebrationModal
courseId={courseId}
daysPerWeek={daysPerWeek}
isOpen={weeklyGoalCelebrationOpen}
onClose={() => setWeeklyGoalCelebrationOpen(false)}
/>
<ContentTools course={course} />
{ /** [MM-P2P] Experiment */ }
{ MMP2P.meta.modalLock && <MMP2PBlockModal options={MMP2P} /> }

View File

@@ -65,11 +65,7 @@ describe('Course', () => {
);
});
it('displays celebration modal', async () => {
// TODO: Remove these console mocks after merging https://github.com/edx/paragon/pull/526.
jest.spyOn(console, 'warn').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
it('displays first section celebration modal', async () => {
const courseMetadata = Factory.build('courseMetadata', { celebrations: { firstSection: true } });
const testStore = await initializeTestStore({ courseMetadata }, false);
const { courseware, models } = testStore.getState();
@@ -84,9 +80,27 @@ 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 weekly goal celebration modal', async () => {
const courseMetadata = Factory.build('courseMetadata', { celebrations: { weeklyGoal: true } });
const testStore = await initializeTestStore({ courseMetadata }, false);
const { courseware, models } = testStore.getState();
const { courseId, sequenceId } = courseware;
const testData = {
...mockData,
courseId,
sequenceId,
unitId: Object.values(models.units)[0].id,
};
render(<Course {...testData} />, { store: testStore });
const weeklyGoalCelebrationModal = screen.getByRole('dialog');
expect(weeklyGoalCelebrationModal).toBeInTheDocument();
expect(getByRole(weeklyGoalCelebrationModal, 'heading', { name: 'You met your goal!' })).toBeInTheDocument();
});
it('displays notification trigger and toggles active class on click', async () => {
@@ -171,8 +185,8 @@ describe('Course', () => {
loadUnit();
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
// expect the section and sequence "titles" to be loaded in as breadcrumb labels.
expect(screen.getByText('cdabcdabcdabcdabcdabcdabcdabcd13')).toBeInTheDocument();
expect(screen.getByText('cdabcdabcdabcdabcdabcdabcdabcd12')).toBeInTheDocument();
expect(screen.getByText(Object.values(models.sections)[0].title)).toBeInTheDocument();
expect(screen.getByText(Object.values(models.sequences)[0].title)).toBeInTheDocument();
});
it('passes handlers to the sequence', async () => {

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Modal } from '@edx/paragon';
import { ActionRow, Button, StandardModal } from '@edx/paragon';
import { layoutGenerator } from 'react-break';
import ClapsMobile from './assets/claps_280x201.gif';
@@ -12,7 +12,7 @@ import { recordFirstSectionCelebration } from './utils';
import { useModel } from '../../../generic/model-store';
function CelebrationModal({
courseId, intl, open, ...rest
courseId, intl, isOpen, onClose, ...rest
}) {
const { org } = useModel('coursewareMeta', courseId);
@@ -25,50 +25,53 @@ function CelebrationModal({
const OnAtLeastTablet = layout.isAtLeast('tablet');
useEffect(() => {
if (open) {
if (isOpen) {
recordFirstSectionCelebration(org, courseId);
}
}, [open]);
}, [isOpen]);
return (
<Modal
body={(
<>
<p>{intl.formatMessage(messages.completed)}</p>
<OnMobile>
<img src={ClapsMobile} alt="" className="img-fluid" />
</OnMobile>
<OnAtLeastTablet>
<img src={ClapsTablet} alt="" className="img-fluid w-100" />
</OnAtLeastTablet>
<p className="mt-3">
<strong>{intl.formatMessage(messages.earned)}</strong> {intl.formatMessage(messages.share)}
</p>
<SocialIcons
analyticsId="edx.ui.lms.celebration.social_share.clicked"
courseId={courseId}
emailSubject={messages.emailSubject}
socialMessage={messages.socialMessage}
/>
</>
<StandardModal
footerNode={(
<ActionRow isStacked className="pb-2">
<Button onClick={onClose}>{intl.formatMessage(messages.forward)}</Button>
</ActionRow>
)}
hasCloseButton={false}
isOpen={isOpen}
onClose={onClose}
title={(
<p className="h2 text-center mr-n5 pt-4">{intl.formatMessage(messages.congrats)}</p>
)}
closeText={intl.formatMessage(messages.forward)}
onClose={() => {}} // Don't do anything special, just having the modal close is enough (this is a required prop)
open={open}
title={intl.formatMessage(messages.congrats)}
{...rest}
/>
>
<>
<p className="text-center">{intl.formatMessage(messages.completed)}</p>
<OnMobile>
<img src={ClapsMobile} alt="" className="img-fluid" />
</OnMobile>
<OnAtLeastTablet>
<img src={ClapsTablet} alt="" className="img-fluid w-100" />
</OnAtLeastTablet>
<p className="mt-3 text-center">
<strong>{intl.formatMessage(messages.earned)}</strong> {intl.formatMessage(messages.share)}
</p>
<SocialIcons
analyticsId="edx.ui.lms.celebration.social_share.clicked"
courseId={courseId}
emailSubject={messages.emailSubject}
socialMessage={messages.socialMessage}
/>
</>
</StandardModal>
);
}
CelebrationModal.defaultProps = {
open: false,
};
CelebrationModal.propTypes = {
courseId: PropTypes.string.isRequired,
intl: intlShape.isRequired,
open: PropTypes.bool,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
};
export default injectIntl(CelebrationModal);

View File

@@ -0,0 +1,80 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
ActionRow, Button, Icon, StandardModal,
} from '@edx/paragon';
import { Lightbulb } from '@edx/paragon/icons';
import Target from './assets/target.svg';
import messages from './messages';
import { recordWeeklyGoalCelebration } from './utils';
import { useModel } from '../../../generic/model-store';
function WeeklyGoalCelebrationModal({
courseId, daysPerWeek, intl, isOpen, onClose, ...rest
}) {
const { org } = useModel('coursewareMeta', courseId);
useEffect(() => {
if (isOpen) {
recordWeeklyGoalCelebration(org, courseId);
}
}, [isOpen]);
return (
<StandardModal
footerNode={(
<ActionRow isStacked className="pb-2">
<Button onClick={onClose}>{intl.formatMessage(messages.keepItUp)}</Button>
</ActionRow>
)}
hasCloseButton={false}
isOpen={isOpen}
onClose={onClose}
title={(
<p className="h2 text-center mr-n5 pt-4">{intl.formatMessage(messages.goalMet)}</p>
)}
{...rest}
>
<>
<div className="text-center px-3">
<FormattedMessage
id="learning.celebration.goalCongrats"
defaultMessage="Congratulations, you met your learning goal of {nTimes} a week."
values={{
nTimes: (<strong>{daysPerWeek} {daysPerWeek === 1 ? 'time' : 'times'}</strong>),
}}
/>
</div>
<div className="d-flex justify-content-center py-4.5">
<img src={Target} alt="" />
</div>
<div className="py-3 pl-3 bg-light-300 small d-inline-flex">
<Icon
src={Lightbulb}
className="mr-2"
style={{ height: '21px', width: '22px' }}
/>
<FormattedMessage
id="learning.celebration.setGoal"
defaultMessage="Setting a goal can help you {strongText} in your course."
values={{
strongText: (<strong>achieve higher performance</strong>),
}}
/>
</div>
</>
</StandardModal>
);
}
WeeklyGoalCelebrationModal.propTypes = {
courseId: PropTypes.string.isRequired,
daysPerWeek: PropTypes.number.isRequired,
intl: intlShape.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
};
export default injectIntl(WeeklyGoalCelebrationModal);

View File

@@ -0,0 +1,24 @@
<svg width="139" height="160" viewBox="0 0 139 160" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M76.031 156.25V3.88753C76.0452 3.25836 75.8196 2.64733 75.4 2.17819C74.9803 1.70906 74.398 1.4169 73.7709 1.36091C73.4464 1.34336 73.1218 1.39213 72.8168 1.50427C72.5119 1.61641 72.233 1.78955 71.9973 2.0131C71.7615 2.23664 71.5739 2.50591 71.4459 2.80442C71.3178 3.10293 71.252 3.42441 71.2524 3.74921V156.25C71.2524 156.884 71.5042 157.491 71.9523 157.939C72.4003 158.387 73.0081 158.638 73.6417 158.638C74.2754 158.638 74.8831 158.387 75.3312 157.939C75.7793 157.491 76.031 156.884 76.031 156.25Z" fill="#2D494E"/>
<path d="M73.6419 159.561C72.7636 159.561 71.9212 159.212 71.3001 158.591C70.679 157.97 70.3301 157.128 70.3301 156.25V3.74917C70.3281 3.29891 70.4184 2.85302 70.5952 2.43889C70.772 2.02477 71.0317 1.65118 71.3583 1.34111C71.685 1.03103 72.0716 0.791028 72.4944 0.635847C72.9173 0.480666 73.3674 0.413593 73.8172 0.438751C74.6825 0.502353 75.4904 0.895619 76.074 1.53738C76.6577 2.17913 76.9726 3.02039 76.9537 3.88749V156.25C76.9537 157.128 76.6047 157.97 75.9837 158.591C75.3626 159.212 74.5202 159.561 73.6419 159.561ZM73.6419 2.283C73.4486 2.28052 73.2567 2.31675 73.0777 2.38956C72.8986 2.46236 72.7359 2.57027 72.5992 2.70691C72.4625 2.84355 72.3546 3.00618 72.2817 3.18518C72.2089 3.36418 72.1726 3.55595 72.1751 3.74917V156.25C72.1751 156.639 72.3297 157.012 72.6047 157.287C72.8798 157.562 73.2529 157.716 73.6419 157.716C74.0309 157.716 74.404 157.562 74.6791 157.287C74.9541 157.012 75.1087 156.639 75.1087 156.25V3.88749C75.1228 3.49496 74.988 3.11158 74.7315 2.81408C74.4749 2.51658 74.1154 2.32691 73.7249 2.283H73.6419Z" fill="#2D494E"/>
<path d="M29.399 156.97C37.7015 130.846 45.1738 108.097 52.5907 86.1505C52.8018 85.5776 52.7932 84.9468 52.5667 84.3798C52.3401 83.8129 51.9116 83.3498 51.3638 83.0798C51.0594 82.941 50.7292 82.8676 50.3947 82.8644C50.0601 82.8612 49.7286 82.9282 49.4216 83.0612C49.1146 83.1942 48.839 83.3902 48.6126 83.6364C48.3862 83.8827 48.2141 84.1737 48.1073 84.4907C40.6628 106.493 33.1905 129.324 24.8419 155.494C24.727 155.853 24.6986 156.233 24.7588 156.605C24.8189 156.976 24.9661 157.328 25.1882 157.632C25.4104 157.936 25.7012 158.183 26.0369 158.353C26.3727 158.523 26.744 158.612 27.1205 158.611C27.625 158.613 28.1172 158.455 28.5265 158.16C28.9358 157.865 29.2413 157.449 29.399 156.97Z" fill="#2D494E"/>
<path d="M27.1204 159.56C26.5983 159.561 26.0836 159.438 25.6181 159.201C25.1526 158.965 24.7496 158.622 24.442 158.2C24.1344 157.779 23.9309 157.29 23.8481 156.775C23.7653 156.26 23.8055 155.732 23.9654 155.236C32.3418 128.918 39.7402 106.363 47.2309 84.232C47.3812 83.7936 47.6215 83.3914 47.9362 83.0512C48.251 82.7109 48.6334 82.4401 49.0589 82.2561C49.4844 82.0721 49.9436 81.9789 50.4072 81.9825C50.8708 81.9861 51.3286 82.0865 51.7512 82.2771C52.5022 82.6469 53.0915 83.2791 53.4077 84.0539C53.7239 84.8288 53.745 85.6926 53.467 86.482C45.9947 108.613 38.624 131.076 30.2753 157.283C30.061 157.949 29.6393 158.529 29.0717 158.939C28.5042 159.349 27.8205 159.566 27.1204 159.56ZM50.3674 83.7894C50.0614 83.7892 49.763 83.8852 49.5146 84.064C49.2663 84.2427 49.0805 84.4951 48.9837 84.7853C41.4929 106.916 34.1037 129.49 25.7182 155.789C25.6479 156.01 25.6313 156.246 25.6698 156.475C25.7083 156.704 25.8008 156.921 25.9396 157.107C26.0735 157.295 26.2508 157.448 26.4564 157.552C26.662 157.656 26.8898 157.71 27.1204 157.707C27.4317 157.703 27.7341 157.603 27.9855 157.42C28.2368 157.236 28.4246 156.979 28.5226 156.683C36.8251 130.458 44.2051 107.949 51.7235 85.8457C51.8242 85.6211 51.8666 85.3748 51.847 85.1294C51.8274 84.8841 51.7463 84.6476 51.6112 84.4418C51.4761 84.236 51.2914 84.0676 51.074 83.9519C50.8566 83.8363 50.6136 83.7772 50.3674 83.7802V83.7894Z" fill="#2D494E"/>
<path d="M120.164 158.639C120.54 158.637 120.91 158.548 121.245 158.377C121.579 158.206 121.869 157.959 122.091 157.656C122.313 157.353 122.46 157.001 122.521 156.631C122.582 156.26 122.555 155.88 122.442 155.522C114.14 129.352 106.64 106.548 99.2046 84.5738C99.0332 84.0351 98.6802 83.5726 98.2059 83.2649C97.7315 82.9572 97.1651 82.8234 96.6032 82.8863C96.2525 82.9317 95.9163 83.0544 95.6188 83.2454C95.3213 83.4365 95.0699 83.6912 94.8828 83.9911C94.6957 84.291 94.5776 84.6287 94.5369 84.9799C94.4962 85.331 94.5339 85.6867 94.6475 86.0215C102.074 107.996 109.546 130.8 117.876 156.97C118.032 157.454 118.337 157.876 118.748 158.176C119.159 158.476 119.655 158.638 120.164 158.639Z" fill="#2D494E"/>
<path d="M120.164 159.56C119.458 159.563 118.77 159.339 118.201 158.923C117.632 158.506 117.211 157.919 117 157.245C108.633 130.974 101.253 108.437 93.7711 86.3157C93.6175 85.8593 93.5668 85.3746 93.6226 84.8964C93.6784 84.4181 93.8394 83.9581 94.094 83.5493C94.3524 83.131 94.701 82.7756 95.1144 82.5092C95.5278 82.2428 95.9954 82.072 96.4833 82.0094C97.2575 81.9163 98.0399 82.0955 98.6964 82.5161C99.3528 82.9367 99.8424 83.5726 100.081 84.3147C107.553 106.446 114.942 128.982 123.319 155.318C123.477 155.814 123.516 156.341 123.432 156.855C123.348 157.369 123.145 157.856 122.837 158.277C122.53 158.697 122.128 159.039 121.663 159.275C121.198 159.511 120.685 159.634 120.164 159.634V159.56ZM96.9076 83.7891H96.7323C96.516 83.8162 96.3083 83.8909 96.1243 84.0078C95.9403 84.1247 95.7845 84.2809 95.668 84.4652C95.5516 84.6494 95.4775 84.8572 95.4509 85.0735C95.4244 85.2899 95.4462 85.5094 95.5146 85.7163C103.005 107.847 110.385 130.393 118.762 156.72C118.858 157.016 119.046 157.274 119.298 157.458C119.549 157.641 119.852 157.741 120.164 157.743C120.395 157.743 120.623 157.688 120.829 157.583C121.035 157.478 121.213 157.326 121.348 157.139C121.484 156.952 121.574 156.735 121.61 156.507C121.646 156.279 121.628 156.045 121.557 155.825C113.19 129.545 105.81 106.999 98.3283 84.8956C98.2363 84.5862 98.0499 84.3133 97.7952 84.1149C97.5405 83.9165 97.2302 83.8026 96.9076 83.7891Z" fill="#2D494E"/>
<path d="M73.6419 138.628C108.878 138.628 137.442 110.076 137.442 74.8542C137.442 39.6327 108.878 11.0801 73.6419 11.0801C38.4061 11.0801 9.8418 39.6327 9.8418 74.8542C9.8418 110.076 38.4061 138.628 73.6419 138.628Z" fill="#03C7E8"/>
<path d="M73.642 139.559C60.8388 139.561 48.3227 135.768 37.6764 128.659C27.0301 121.55 18.7318 111.445 13.831 99.622C8.93017 87.7988 7.64693 74.7884 10.1436 62.2362C12.6402 49.6839 18.8046 38.1537 27.8572 29.1035C36.9097 20.0534 48.4438 13.8899 61.0008 11.3924C73.5578 8.89502 86.5737 10.1759 98.4024 15.073C110.231 19.9701 120.341 28.2636 127.455 38.9045C134.568 49.5455 138.365 62.056 138.365 74.8539C138.345 92.0073 131.52 108.453 119.387 120.583C107.253 132.713 90.8023 139.537 73.642 139.559ZM73.642 12.0019C61.2033 12.0001 49.0435 15.6855 38.7004 22.5921C28.3573 29.4987 20.2956 39.3162 15.5347 50.8029C10.7738 62.2897 9.52761 74.9297 11.9538 87.1245C14.38 99.3192 20.3696 110.521 29.165 119.313C37.9605 128.105 49.1667 134.092 61.3664 136.517C73.5661 138.942 86.2113 137.697 97.7027 132.938C109.194 128.179 119.016 120.12 125.925 109.781C132.834 99.4423 136.521 87.2875 136.52 74.8539C136.5 58.1905 129.869 42.2152 118.082 30.4324C106.294 18.6496 90.3121 12.0215 73.642 12.0019Z" fill="#2D494E"/>
<path d="M73.6419 127.701C102.84 127.701 126.51 104.04 126.51 74.8538C126.51 45.6672 102.84 22.0068 73.6419 22.0068C44.4435 22.0068 20.7734 45.6672 20.7734 74.8538C20.7734 104.04 44.4435 127.701 73.6419 127.701Z" fill="white"/>
<path d="M73.6421 128.623C63.0032 128.623 52.6033 125.47 43.7574 119.561C34.9115 113.653 28.017 105.256 23.9457 95.4306C19.8744 85.6056 18.8091 74.7944 20.8847 64.3642C22.9602 53.934 28.0833 44.3533 35.6061 36.8336C43.1289 29.3138 52.7136 24.1928 63.148 22.1181C73.5824 20.0435 84.398 21.1083 94.227 25.1779C104.056 29.2476 112.457 36.1393 118.368 44.9816C124.278 53.8238 127.433 64.2195 127.433 74.854C127.418 89.11 121.746 102.778 111.662 112.858C101.577 122.939 87.9038 128.608 73.6421 128.623ZM73.6421 22.9385C63.3677 22.9385 53.3242 25.984 44.7815 31.69C36.2388 37.396 29.5808 45.5061 25.6494 54.9946C21.718 64.4832 20.6898 74.924 22.6949 84.9967C24.7 95.0694 29.6483 104.321 36.914 111.583C44.1797 118.844 53.4364 123.789 63.5136 125.791C73.5907 127.794 84.0356 126.764 93.5273 122.833C103.019 118.901 111.131 112.245 116.838 103.704C122.545 95.1642 125.59 85.1242 125.588 74.854C125.571 61.0888 120.092 47.8924 110.354 38.1598C100.616 28.4272 87.4129 22.9531 73.6421 22.9385Z" fill="#2D494E"/>
<path d="M73.642 114.091C95.3205 114.091 112.894 96.5242 112.894 74.8545C112.894 53.1849 95.3205 35.6182 73.642 35.6182C51.9635 35.6182 34.3896 53.1849 34.3896 74.8545C34.3896 96.5242 51.9635 114.091 73.642 114.091Z" fill="#03C7E8"/>
<path d="M73.6419 115.041C65.693 115.05 57.92 112.702 51.3061 108.295C44.6922 103.887 39.5345 97.6184 36.4855 90.2806C33.4364 82.9428 32.633 74.8658 34.1768 67.0715C35.7206 59.2772 39.5423 52.1157 45.1585 46.4928C50.7746 40.8699 57.9329 37.0382 65.7279 35.4825C73.5229 33.9268 81.6044 34.717 88.9501 37.753C96.2957 40.789 102.576 45.9345 106.995 52.5386C111.415 59.1427 113.776 66.9088 113.78 74.8544C113.773 85.499 109.543 95.7065 102.02 103.239C94.4956 110.772 84.2908 115.016 73.6419 115.041ZM73.6419 36.5585C66.056 36.5494 58.6379 38.79 52.3264 42.9967C46.0149 47.2034 41.0937 53.1872 38.1856 60.1907C35.2776 67.1942 34.5134 74.9026 35.9897 82.3404C37.4661 89.7783 41.1166 96.6111 46.4794 101.974C51.8421 107.337 58.676 110.99 66.1161 112.469C73.5563 113.948 81.2682 113.188 88.276 110.285C95.2837 107.381 101.272 102.465 105.484 96.1581C109.695 89.8512 111.94 82.4372 111.935 74.8544C111.923 64.7029 107.885 54.9702 100.708 47.7886C93.5302 40.6069 83.7974 36.5621 73.6419 36.5401V36.5585Z" fill="#2D494E"/>
<path d="M73.6421 101.688C88.4681 101.688 100.487 89.6742 100.487 74.8543C100.487 60.0344 88.4681 48.0205 73.6421 48.0205C58.8162 48.0205 46.7974 60.0344 46.7974 74.8543C46.7974 89.6742 58.8162 101.688 73.6421 101.688Z" fill="white"/>
<path d="M73.6417 102.611C68.1495 102.612 62.7801 100.986 58.2126 97.9373C53.645 94.8885 50.0846 90.5543 47.9816 85.4827C45.8785 80.4112 45.3273 74.8301 46.3977 69.4453C47.468 64.0606 50.1119 59.1141 53.9948 55.2314C57.8778 51.3488 62.8254 48.7044 68.212 47.6326C73.5985 46.5609 79.1821 47.11 84.2564 49.2106C89.3308 51.3111 93.668 54.8686 96.7195 59.4333C99.771 63.9979 101.4 69.3646 101.4 74.8546C101.392 82.2121 98.4659 89.2663 93.2621 94.4697C88.0583 99.6731 81.0022 102.601 73.6417 102.611ZM73.6417 48.9521C68.514 48.9503 63.5009 50.4687 59.2366 53.3152C54.9723 56.1617 51.6484 60.2085 49.6852 64.9436C47.7221 69.6787 47.208 74.8894 48.2079 79.9167C49.2078 84.9439 51.6769 89.5618 55.3027 93.1861C58.9286 96.8105 63.5483 99.2786 68.5776 100.278C73.6069 101.278 78.8197 100.764 83.5568 98.8013C88.2938 96.839 92.3422 93.5164 95.1899 89.2538C98.0376 84.9913 99.5566 79.9802 99.5547 74.8546C99.545 67.9878 96.8117 61.4051 91.9542 56.5496C87.0967 51.694 80.5113 48.9619 73.6417 48.9521Z" fill="#2D494E"/>
<path d="M73.6422 89.7185C81.8551 89.7185 88.5129 83.0634 88.5129 74.8539C88.5129 66.6444 81.8551 59.9893 73.6422 59.9893C65.4293 59.9893 58.7715 66.6444 58.7715 74.8539C58.7715 83.0634 65.4293 89.7185 73.6422 89.7185Z" fill="#03C7E8"/>
<path d="M73.6418 90.6409C70.5182 90.6409 67.4648 89.7151 64.8676 87.9804C62.2704 86.2457 60.2462 83.7802 59.0508 80.8955C57.8555 78.0108 57.5427 74.8366 58.1521 71.7743C58.7615 68.712 60.2656 65.899 62.4744 63.6912C64.6831 61.4834 67.4972 59.9799 70.5607 59.3707C73.6243 58.7616 76.7998 59.0742 79.6856 60.2691C82.5715 61.4639 85.038 63.4874 86.7734 66.0835C88.5088 68.6796 89.435 71.7318 89.435 74.8542C89.4302 79.0396 87.7647 83.0522 84.8039 86.0117C81.8432 88.9712 77.829 90.636 73.6418 90.6409ZM73.6418 60.9116C70.8832 60.9116 68.1864 61.7293 65.8926 63.2614C63.5989 64.7934 61.8111 66.9709 60.7554 69.5186C59.6997 72.0662 59.4234 74.8696 59.9616 77.5742C60.4998 80.2788 61.8283 82.7631 63.779 84.713C65.7297 86.6629 68.215 87.9908 70.9207 88.5288C73.6264 89.0668 76.4309 88.7906 78.9796 87.7354C81.5283 86.6801 83.7067 84.8931 85.2393 82.6002C86.772 80.3074 87.59 77.6117 87.59 74.8542C87.5852 71.1579 86.1141 67.6144 83.4993 65.0007C80.8846 62.387 77.3396 60.9165 73.6418 60.9116Z" fill="#2D494E"/>
<path d="M16.087 29.3936L1.55762 35.9222L22.6737 52.1884L37.8764 46.25L16.087 29.3936Z" fill="#D23228"/>
<path d="M22.6738 53.1101C22.4686 53.1095 22.2687 53.045 22.1019 52.9256L0.995066 36.6594C0.869677 36.5619 0.771571 36.4338 0.710241 36.2874C0.648911 36.141 0.626435 35.9812 0.644991 35.8235C0.663546 35.6659 0.722505 35.5157 0.816158 35.3875C0.90981 35.2593 1.03499 35.1574 1.17957 35.0918L15.7089 28.5631C15.8587 28.4983 16.0231 28.4744 16.1851 28.4939C16.3472 28.5133 16.5012 28.5754 16.6314 28.6738L38.4209 45.5302C38.5517 45.6298 38.6535 45.7626 38.7158 45.9147C38.778 46.0669 38.7984 46.2329 38.7749 46.3956C38.7514 46.5583 38.6848 46.7118 38.582 46.8401C38.4792 46.9684 38.3439 47.067 38.1903 47.1255L22.9875 53.064C22.8859 53.0952 22.7801 53.1108 22.6738 53.1101ZM3.30132 36.1245L22.7845 51.1459L35.9855 45.9913L15.9949 30.4627L3.30132 36.1245Z" fill="#2D494E"/>
<path d="M15.7734 28.4801L20.8564 15.1738L39.5832 33.063L37.8765 46.2494L15.7734 28.4801Z" fill="#D23228"/>
<path d="M37.8765 47.1723C37.6653 47.1732 37.4601 47.1016 37.2953 46.9694L15.1553 29.2001C15.0021 29.0779 14.8918 28.9102 14.8404 28.7212C14.789 28.5322 14.7991 28.3317 14.8693 28.1489L19.9523 14.8518C20.0069 14.709 20.0962 14.5819 20.2121 14.4821C20.3281 14.3823 20.4671 14.3129 20.6165 14.2801C20.777 14.2353 20.9467 14.2347 21.1075 14.2786C21.2683 14.3224 21.4143 14.409 21.5298 14.5291L40.2658 32.4183C40.3701 32.5182 40.4494 32.6411 40.4975 32.7773C40.5456 32.9134 40.561 33.0589 40.5425 33.2021L38.8267 46.3885C38.8056 46.5512 38.7415 46.7053 38.641 46.835C38.5404 46.9647 38.4071 47.0652 38.2547 47.1262C38.1332 47.1677 38.0044 47.1834 37.8765 47.1723ZM16.9173 28.1765L37.2123 44.5074L38.6514 33.4419L21.253 16.8436L16.9173 28.1765Z" fill="#2D494E"/>
<path d="M73.8816 77.2336C73.3986 77.2305 72.9306 77.0649 72.5532 76.7634L6.86202 24.5804C6.62514 24.4106 6.42525 24.1945 6.27454 23.9451C6.12382 23.6958 6.02541 23.4184 5.9853 23.1298C5.94518 22.8412 5.9642 22.5475 6.04119 22.2666C6.11818 21.9856 6.25153 21.7232 6.43315 21.4953C6.61476 21.2674 6.84085 21.0789 7.09764 20.9411C7.35444 20.8033 7.63658 20.7191 7.92693 20.6937C8.21728 20.6683 8.50976 20.7022 8.7866 20.7934C9.06343 20.8845 9.31883 21.0309 9.53727 21.2238L75.2193 73.4253C75.5669 73.703 75.8198 74.0819 75.9428 74.5094C76.0659 74.937 76.0531 75.3922 75.9063 75.8122C75.7595 76.2322 75.4859 76.5963 75.1232 76.8542C74.7606 77.1121 74.3267 77.2512 73.8816 77.2521V77.2336Z" fill="#F0CC00"/>
<path d="M73.8816 78.1551C73.19 78.153 72.5198 77.9156 71.9812 77.482L6.28997 25.299C5.65946 24.7849 5.25872 24.0418 5.17571 23.2327C5.09269 22.4237 5.33418 21.6147 5.84717 20.9834C6.35467 20.3496 7.09197 19.9418 7.89873 19.8485C8.70548 19.7553 9.51645 19.9842 10.1552 20.4855L75.8465 72.6961C76.3434 73.0936 76.7046 73.6356 76.88 74.2472C77.0555 74.8587 77.0366 75.5097 76.8259 76.11C76.6152 76.7104 76.2232 77.2305 75.704 77.5985C75.1849 77.9665 74.5641 78.1642 73.9277 78.1643L73.8816 78.1551ZM8.19955 21.6566C7.94584 21.6583 7.69891 21.7386 7.49274 21.8864C7.28658 22.0342 7.13133 22.2423 7.04837 22.4819C6.96541 22.7216 6.95881 22.9811 7.02951 23.2247C7.1002 23.4683 7.24469 23.6839 7.44309 23.842L73.1251 76.0434C73.3815 76.2369 73.7024 76.3247 74.0217 76.2886C74.3409 76.2526 74.6341 76.0955 74.841 75.8498C74.9416 75.7239 75.0164 75.5794 75.0611 75.4246C75.1058 75.2697 75.1196 75.1076 75.1016 74.9475C75.0836 74.7873 75.0342 74.6323 74.9562 74.4912C74.8783 74.3502 74.7733 74.2259 74.6472 74.1254L8.96523 21.9055C8.74169 21.7479 8.47301 21.667 8.19955 21.675V21.6566Z" fill="#2D494E"/>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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, data) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/courseware/celebration/${courseId}`);
getAuthenticatedHttpClient().post(url.href, {
first_section: false,
});
getAuthenticatedHttpClient().post(url.href, data);
}

View File

@@ -1,2 +1,3 @@
export { default as CelebrationModal } from './CelebrationModal';
export { default as WeeklyGoalCelebrationModal } from './WeeklyGoalCelebrationModal';
export { handleNextSectionCelebration, shouldCelebrateOnSectionLoad } from './utils';

View File

@@ -23,6 +23,15 @@ const messages = defineMessages({
defaultMessage: 'Keep going',
description: 'Button to close celebration dialog and get back to course',
},
goalMet: {
id: 'learning.celebration.goalMet',
defaultMessage: 'You met your goal!',
},
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.',

View File

@@ -1,7 +1,7 @@
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';
@@ -18,7 +18,7 @@ function handleNextSectionCelebration(sequenceId, nextSequenceId, nextUnitId) {
function recordFirstSectionCelebration(org, courseId) {
// Tell the LMS
postFirstSectionCelebrationComplete(courseId);
postCelebrationComplete(courseId, { first_section: false });
// Tell our analytics
const { administrator } = getAuthenticatedUser();
@@ -30,6 +30,19 @@ function recordFirstSectionCelebration(org, courseId) {
});
}
function recordWeeklyGoalCelebration(org, courseId) {
// Tell the LMS
postCelebrationComplete(courseId, { weekly_goal: false });
// Tell our analytics
const { administrator } = getAuthenticatedUser();
sendTrackEvent('edx.ui.lms.celebration.weekly_goal.opened', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
});
}
// 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, celebrations) {
@@ -51,7 +64,7 @@ function shouldCelebrateOnSectionLoad(courseId, sequenceId, unitId, celebrateFir
// If we are going to celebrate a streak then we will not also celebrate the first section.
// We will still mark the first section as celebrated, so that we don't incorrectly celebrate the second section.
shouldCelebrate = false;
postFirstSectionCelebrationComplete(courseId);
postCelebrationComplete(courseId, { first_section: false });
}
if (sequenceId !== prevSequenceId && !onTargetUnit) {
@@ -74,4 +87,9 @@ function shouldCelebrateOnSectionLoad(courseId, sequenceId, unitId, celebrateFir
return shouldCelebrate;
}
export { handleNextSectionCelebration, recordFirstSectionCelebration, shouldCelebrateOnSectionLoad };
export {
handleNextSectionCelebration,
recordFirstSectionCelebration,
recordWeeklyGoalCelebration,
shouldCelebrateOnSectionLoad,
};

View File

@@ -198,7 +198,7 @@ function normalizeMetadata(metadata) {
accessExpiration: camelCaseObject(data.access_expiration),
canShowUpgradeSock: data.can_show_upgrade_sock,
contentTypeGatingEnabled: data.content_type_gating_enabled,
courseGoals: data.course_goals,
courseGoals: camelCaseObject(data.course_goals),
id: data.id,
title: data.name,
number: data.number,