-

+ );
+ expirationBanner =
;
+ upsellMessage =
;
+ offerCode = (
+
+ {offer.code}),
+ }}
/>
-
-
-
- {onLearnMore && (
-
-
-
- )}
-
-
+ );
+ } else {
+ const accessExpirationDate = new Date(accessExpiration.expirationDate);
+ const hoursToAccessExpiration = Math.floor((accessExpirationDate - correctedTime) / 1000 / 60 / 60);
+
+ if (hoursToAccessExpiration >= (7 * 24)) {
+ upgradeCardHeaderText = (
+
+ );
+ expirationBanner = (
+
+ );
+ upsellMessage =
;
+ } else { // more urgent messaging if there's less than 7 days left
+ upgradeCardHeaderText = (
+
+ );
+ expirationBanner =
;
+ upsellMessage = (
+
+ );
+ }
+ }
+ } else { // FBE is turned off
+ upgradeCardHeaderText = (
+
+ );
+ upsellMessage = (
);
+ }
+
+ return (
+
+
+ {expirationBanner}
+
+ {upsellMessage}
+
+ {offerCode}
);
}
UpgradeCard.propTypes = {
courseId: PropTypes.string.isRequired,
- intl: intlShape.isRequired,
- onLearnMore: PropTypes.func,
+ org: PropTypes.string.isRequired,
+ accessExpiration: PropTypes.shape({
+ expirationDate: PropTypes.string,
+ }),
+ contentTypeGatingEnabled: PropTypes.bool,
+ offer: PropTypes.shape({
+ expirationDate: PropTypes.string,
+ percentage: PropTypes.number,
+ code: PropTypes.string,
+ }),
+ timeOffsetMillis: PropTypes.number,
+ userTimezone: PropTypes.string,
+ verifiedMode: PropTypes.shape({
+ currencySymbol: PropTypes.string.isRequired,
+ price: PropTypes.number.isRequired,
+ upgradeUrl: PropTypes.string.isRequired,
+ }),
};
UpgradeCard.defaultProps = {
- onLearnMore: null,
+ accessExpiration: null,
+ contentTypeGatingEnabled: false,
+ offer: null,
+ timeOffsetMillis: 0,
+ userTimezone: null,
+ verifiedMode: null,
};
export default injectIntl(UpgradeCard);
diff --git a/src/course-home/outline-tab/widgets/UpgradeCard.scss b/src/course-home/outline-tab/widgets/UpgradeCard.scss
index 5f369519..226c04d8 100644
--- a/src/course-home/outline-tab/widgets/UpgradeCard.scss
+++ b/src/course-home/outline-tab/widgets/UpgradeCard.scss
@@ -1,4 +1,53 @@
-.outline-sidebar-upgrade-card {
- border: 1px solid $dark-500;
- border-top: 5px solid $dark-500;
+.upgrade-card {
+ border-radius: 0 !important;
}
+
+.upgrade-card-header{
+ margin: 1.25rem;
+
+}
+
+.upsell-warning{
+ background-color: $danger-100;
+}
+
+.upsell-warning-light{
+ background-color: $warning-100;
+}
+
+.upsell-warning, .upsell-warning-light{
+ padding-left: 1.25rem;
+ padding-right: 1.25rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.upgrade-card-ul{
+ margin-left: 3rem;
+ padding-top: 0.875rem;
+ padding-right: 1.25rem;
+}
+
+.upgrade-card-li{
+ left: -2.125rem;
+}
+
+.upgrade-card-button{
+ margin-left: 1.25rem;
+ margin-right: 1.25rem;
+ margin-bottom: 1.25rem;
+}
+
+.discount-info {
+ border-top: 1px solid rgba(0, 0, 0, 0.125);
+ padding-top: .75rem;
+ padding-bottom: .75rem;
+}
+
+.inline-link-underline {
+ text-decoration: underline;
+}
+
+.upgrade-card .upgrade-card-message a{
+ color: $primary-500;
+}
\ No newline at end of file
diff --git a/src/course-home/outline-tab/widgets/UpgradeCard.test.jsx b/src/course-home/outline-tab/widgets/UpgradeCard.test.jsx
new file mode 100644
index 00000000..427403d6
--- /dev/null
+++ b/src/course-home/outline-tab/widgets/UpgradeCard.test.jsx
@@ -0,0 +1,185 @@
+import React from 'react';
+import { Factory } from 'rosie';
+
+import { initializeMockApp, render, screen } from '../../../setupTest';
+import UpgradeCard from './UpgradeCard';
+
+initializeMockApp();
+jest.mock('@edx/frontend-platform/analytics');
+const dateNow = new Date('2021-04-13T11:01:58.135Z');
+jest
+ .spyOn(global.Date, 'now')
+ .mockImplementation(() => dateNow.valueOf());
+
+describe('Upgrade Card', () => {
+ function buildAndRender(attributes) {
+ const upgradeCardData = Factory.build('upgradeCardData', { ...attributes });
+ render(
);
+ }
+
+ it('does not render when there is no verified mode', async () => {
+ buildAndRender({ verifiedMode: null });
+ expect(screen.queryByRole('link', { name: 'Upgrade for $149' })).not.toBeInTheDocument();
+ });
+
+ it('renders non-FBE when there is a verified mode but no FBE', async () => {
+ buildAndRender();
+ expect(screen.getByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument();
+ expect(screen.getByText(/Earn a.*?of completion to showcase on your resume/s).textContent).toMatch('Earn a verified certificate of completion to showcase on your resume');
+ expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our non-profit mission at edX');
+ expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument();
+ });
+
+ it('renders FBE expiration within an hour properly', async () => {
+ const expirationDate = new Date(dateNow);
+ expirationDate.setMinutes(expirationDate.getMinutes() + 45);
+ buildAndRender({
+ accessExpiration: {
+ expirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ });
+ expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument();
+ expect(screen.getByText('Less than 1 hour left')).toBeInTheDocument();
+ expect(screen.getByText(/You will lose all access to this course.*?on/s).textContent).toMatch('You will lose all access to this course, including any progress, on April 13.');
+ expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading.');
+ expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument();
+ });
+
+ it('renders FBE expiration within 24 hours properly', async () => {
+ const expirationDate = new Date(dateNow);
+ expirationDate.setHours(expirationDate.getHours() + 12);
+ buildAndRender({
+ accessExpiration: {
+ expirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ });
+ expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument();
+ expect(screen.getByText('12 hours left')).toBeInTheDocument();
+ expect(screen.getByText(/You will lose all access to this course.*?on/s).textContent).toMatch('You will lose all access to this course, including any progress, on April 13.');
+ expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading.');
+ expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument();
+ });
+
+ it('renders FBE expiration within 7 days properly', async () => {
+ const expirationDate = new Date(dateNow);
+ expirationDate.setDate(expirationDate.getDate() + 6);
+ buildAndRender({
+ accessExpiration: {
+ expirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ });
+ expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument();
+ expect(screen.getByText('6 days left')).toBeInTheDocument(); // setting the time to 12 will mean that it's slightly less than 12
+ expect(screen.getByText(/You will lose all access to this course.*?on/s).textContent).toMatch('You will lose all access to this course, including any progress, on April 19.');
+ expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading.');
+ expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument();
+ });
+
+ it('renders FBE expiration greater than 7 days properly', async () => {
+ const expirationDate = new Date(dateNow);
+ expirationDate.setDate(expirationDate.getDate() + 14);
+ buildAndRender({
+ accessExpiration: {
+ expirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ });
+ expect(screen.getByRole('heading', { name: 'Upgrade your course today' })).toBeInTheDocument();
+ expect(screen.getByText(/Course access will expire/s).textContent).toMatch('Course access will expire April 27');
+ expect(screen.getByText(/Earn a.*?of completion to showcase on your resume/s).textContent).toMatch('Earn a verified certificate of completion to showcase on your resume');
+ expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments');
+ expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends');
+ expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our non-profit mission at edX');
+ expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument();
+ });
+
+ it('renders discount less than an hour properly', async () => {
+ const accessExpirationDate = new Date(dateNow);
+ accessExpirationDate.setDate(accessExpirationDate.getDate() + 21);
+ const discountExpirationDate = new Date(dateNow);
+ discountExpirationDate.setMinutes(discountExpirationDate.getMinutes() + 30);
+ buildAndRender({
+ accessExpiration: {
+ accessExpirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ offer: {
+ expirationDate: discountExpirationDate,
+ percentage: 15,
+ code: 'Welcome15',
+ discountedPrice: '126.65',
+ originalPrice: '149',
+ upgradeUrl: 'www.exampleUpgradeUrl.com',
+ },
+ });
+ expect(screen.getByRole('heading', { name: '15% First-Time Learner Discount' })).toBeInTheDocument();
+ expect(screen.getByText('Less than 1 hour left')).toBeInTheDocument();
+ expect(screen.getByText(/Earn a.*?of completion to showcase on your resume/s).textContent).toMatch('Earn a verified certificate of completion to showcase on your resume');
+ expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments');
+ expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends');
+ expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our non-profit mission at edX');
+ expect(screen.getByText(/Upgrade for/).textContent).toMatch('126.65 (149)');
+ expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout');
+ });
+
+ it('renders discount less than a day properly', async () => {
+ const accessExpirationDate = new Date(dateNow);
+ accessExpirationDate.setDate(accessExpirationDate.getDate() + 21);
+ const discountExpirationDate = new Date(dateNow);
+ discountExpirationDate.setHours(discountExpirationDate.getHours() + 12);
+ buildAndRender({
+ accessExpiration: {
+ accessExpirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ offer: {
+ expirationDate: discountExpirationDate,
+ percentage: 15,
+ code: 'Welcome15',
+ discountedPrice: '126.65',
+ originalPrice: '149',
+ upgradeUrl: 'www.exampleUpgradeUrl.com',
+ },
+ });
+ expect(screen.getByRole('heading', { name: '15% First-Time Learner Discount' })).toBeInTheDocument();
+ expect(screen.getByText(/hours left/s).textContent).toMatch('12 hours left');
+ expect(screen.getByText(/Earn a.*?of completion to showcase on your resume/s).textContent).toMatch('Earn a verified certificate of completion to showcase on your resume');
+ expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments');
+ expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends');
+ expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our non-profit mission at edX');
+ expect(screen.getByText(/Upgrade for/).textContent).toMatch('126.65 (149)');
+ expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout');
+ });
+
+ it('renders discount less a week properly', async () => {
+ const accessExpirationDate = new Date(dateNow);
+ accessExpirationDate.setDate(accessExpirationDate.getDate() + 21);
+ const discountExpirationDate = new Date(dateNow);
+ discountExpirationDate.setDate(discountExpirationDate.getDate() + 6);
+ buildAndRender({
+ accessExpiration: {
+ accessExpirationDate,
+ },
+ contentTypeGatingEnabled: true,
+ offer: {
+ expirationDate: discountExpirationDate,
+ percentage: 15,
+ code: 'Welcome15',
+ discountedPrice: '126.65',
+ originalPrice: '149',
+ upgradeUrl: 'www.exampleUpgradeUrl.com',
+ },
+ });
+ expect(screen.getByRole('heading', { name: '15% First-Time Learner Discount' })).toBeInTheDocument();
+ expect(screen.getByText(/days left/s).textContent).toMatch('6 days left');
+ expect(screen.getByText(/Earn a.*?of completion to showcase on your resume/s).textContent).toMatch('Earn a verified certificate of completion to showcase on your resume');
+ expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments');
+ expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends');
+ expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our non-profit mission at edX');
+ expect(screen.getByText(/Upgrade for/).textContent).toMatch('126.65 (149)');
+ expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout');
+ });
+});