import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { useIntl, FormattedDate, FormattedMessage, injectIntl, } from '@edx/frontend-platform/i18n'; import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; import { Button, Icon, IconButton } from '@openedx/paragon'; import { Close } from '@openedx/paragon/icons'; import { setLocalStorage } from '../../data/localStorage'; import { UpgradeButton } from '../upgrade-button'; import { VerifiedCertBullet, UnlockGradedBullet, FullAccessBullet, SupportMissionBullet, } from '../upsell-bullets/UpsellBullets'; import messages from '../messages'; const UpsellNoFBECardContent = () => ( ); const UpsellFBEFarAwayCardContent = () => ( ); const UpsellFBESoonCardContent = ({ accessExpirationDate, timezoneFormatArgs }) => { const includingAnyProgress = ( ); const date = ( ); const benefitsOfUpgrading = ( ); return (

); }; UpsellFBESoonCardContent.propTypes = { accessExpirationDate: PropTypes.PropTypes.instanceOf(Date).isRequired, timezoneFormatArgs: PropTypes.shape({ timeZone: PropTypes.string, }), }; UpsellFBESoonCardContent.defaultProps = { timezoneFormatArgs: {}, }; const PastExpirationCardContent = () => (

); const ExpirationCountdown = ({ courseId, hoursToExpiration, setupgradeNotificationCurrentState, type, }) => { let expirationText; if (hoursToExpiration >= 24) { // More than 1 day left // setupgradeNotificationCurrentState is available in NotificationTray (not course home) if (setupgradeNotificationCurrentState) { if (type === 'access') { setupgradeNotificationCurrentState('accessDaysLeft'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessDaysLeft'); } if (type === 'offer') { setupgradeNotificationCurrentState('FPDdaysLeft'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'FPDdaysLeft'); } } expirationText = ( ); } else if (hoursToExpiration >= 1) { // More than 1 hour left // setupgradeNotificationCurrentState is available in NotificationTray (not course home) if (setupgradeNotificationCurrentState) { if (type === 'access') { setupgradeNotificationCurrentState('accessHoursLeft'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessHoursLeft'); } if (type === 'offer') { setupgradeNotificationCurrentState('FPDHoursLeft'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'FPDHoursLeft'); } } expirationText = ( ); } else { // Less than 1 hour // setupgradeNotificationCurrentState is available in NotificationTray (not course home) if (setupgradeNotificationCurrentState) { if (type === 'access') { setupgradeNotificationCurrentState('accessLastHour'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessLastHour'); } if (type === 'offer') { setupgradeNotificationCurrentState('FPDLastHour'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'FPDLastHour'); } } expirationText = ( ); } return (
{expirationText}
); }; ExpirationCountdown.propTypes = { courseId: PropTypes.string.isRequired, hoursToExpiration: PropTypes.number.isRequired, setupgradeNotificationCurrentState: PropTypes.func, type: PropTypes.string, }; ExpirationCountdown.defaultProps = { setupgradeNotificationCurrentState: null, type: null, }; const AccessExpirationDateBanner = ({ courseId, accessExpirationDate, timezoneFormatArgs, setupgradeNotificationCurrentState, }) => { if (setupgradeNotificationCurrentState) { setupgradeNotificationCurrentState('accessDateView'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessDateView'); } return (
), }} />
); }; AccessExpirationDateBanner.propTypes = { courseId: PropTypes.string.isRequired, accessExpirationDate: PropTypes.PropTypes.instanceOf(Date).isRequired, timezoneFormatArgs: PropTypes.shape({ timeZone: PropTypes.string, }), setupgradeNotificationCurrentState: PropTypes.func, }; AccessExpirationDateBanner.defaultProps = { timezoneFormatArgs: {}, setupgradeNotificationCurrentState: null, }; const PastExpirationDateBanner = ({ courseId, accessExpirationDate, timezoneFormatArgs, setupgradeNotificationCurrentState, }) => { if (setupgradeNotificationCurrentState) { setupgradeNotificationCurrentState('PastExpirationDate'); setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'PastExpirationDate'); } return (
), }} />
); }; PastExpirationDateBanner.propTypes = { courseId: PropTypes.string.isRequired, accessExpirationDate: PropTypes.PropTypes.instanceOf(Date).isRequired, timezoneFormatArgs: PropTypes.shape({ timeZone: PropTypes.string, }), setupgradeNotificationCurrentState: PropTypes.func, }; PastExpirationDateBanner.defaultProps = { timezoneFormatArgs: {}, setupgradeNotificationCurrentState: null, }; const UpgradeNotification = ({ accessExpiration, contentTypeGatingEnabled, marketingUrl, courseId, offer, org, setupgradeNotificationCurrentState, shouldDisplayBorder, timeOffsetMillis, upsellPageName, userTimezone, verifiedMode, toggleSidebar, }) => { const intl = useIntl(); const dateNow = Date.now(); const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {}; const correctedTime = new Date(dateNow + timeOffsetMillis); const accessExpirationDate = accessExpiration ? new Date(accessExpiration.expirationDate) : null; const pastExpirationDeadline = accessExpiration ? new Date(dateNow) > accessExpirationDate : false; const eventProperties = { org_key: org, courserun_key: courseId, }; const promotionEventProperties = { creative: 'sidebarupsell', name: 'In-Course Verification Prompt', position: 'sidebar-message', promotion_id: 'courseware_verified_certificate_upsell', ...eventProperties, }; useEffect(() => { sendTrackingLogEvent('edx.bi.course.upgrade.sidebarupsell.displayed', eventProperties); sendTrackEvent('Promotion Viewed', promotionEventProperties); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (!verifiedMode) { return null; } const logClick = () => { sendTrackingLogEvent('edx.bi.course.upgrade.sidebarupsell.clicked', eventProperties); sendTrackingLogEvent('edx.course.enrollment.upgrade.clicked', { ...eventProperties, location: 'sidebar-message', }); sendTrackEvent('Promotion Clicked', promotionEventProperties); sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', { ...eventProperties, linkCategory: 'green_upgrade', linkName: `${upsellPageName}_green`, linkType: 'button', pageName: upsellPageName, }); }; const logClickPastExpiration = () => { sendTrackEvent('edx.bi.ecommerce.upgrade_notification.past_expiration.button_clicked', { ...eventProperties, linkCategory: 'upgrade_notification', linkName: `${upsellPageName}_course_details`, linkType: 'button', pageName: upsellPageName, }); }; /* There are 5 parts that change in the upgrade card: upgradeNotificationHeaderText expirationBanner upsellMessage callToActionButton offerCode */ let upgradeNotificationHeaderText; let expirationBanner; let upsellMessage; let callToActionButton; let offerCode; if (!!accessExpiration && !!contentTypeGatingEnabled) { const hoursToAccessExpiration = Math.floor((accessExpirationDate - correctedTime) / 1000 / 60 / 60); if (hoursToAccessExpiration >= (7 * 24)) { if (offer) { // countdown to the first purchase discount if there is one const hoursToDiscountExpiration = Math.floor((new Date(offer.expirationDate) - correctedTime) / 1000 / 60 / 60); upgradeNotificationHeaderText = ( ); expirationBanner = ( ); } else { upgradeNotificationHeaderText = ( ); expirationBanner = ( ); } upsellMessage = ; } else if (hoursToAccessExpiration < (7 * 24) && hoursToAccessExpiration >= 0) { // more urgent messaging if there's less than 7 days left to access expiration upgradeNotificationHeaderText = ( ); expirationBanner = ( ); upsellMessage = ( ); } else { // access expiration deadline has passed upgradeNotificationHeaderText = ( ); expirationBanner = ( ); upsellMessage = ( ); } } else { // FBE is turned off upgradeNotificationHeaderText = ( ); upsellMessage = (); } if (pastExpirationDeadline) { callToActionButton = ( ); } else { callToActionButton = ( ); } if (offer) { // if there's a first purchase discount, message the code at the bottom offerCode = (
{offer.code}), }} />
); } return (

{upgradeNotificationHeaderText} {!!toggleSidebar && (
)}

{expirationBanner}
{upsellMessage}
{callToActionButton}
{offerCode}
); }; UpgradeNotification.propTypes = { courseId: PropTypes.string.isRequired, org: PropTypes.string.isRequired, accessExpiration: PropTypes.shape({ expirationDate: PropTypes.string, }), contentTypeGatingEnabled: PropTypes.bool, marketingUrl: PropTypes.string, offer: PropTypes.shape({ expirationDate: PropTypes.string, percentage: PropTypes.number, code: PropTypes.string, }), toggleSidebar: PropTypes.func, shouldDisplayBorder: PropTypes.bool, setupgradeNotificationCurrentState: PropTypes.func, timeOffsetMillis: PropTypes.number, upsellPageName: PropTypes.string.isRequired, userTimezone: PropTypes.string, verifiedMode: PropTypes.shape({ currencySymbol: PropTypes.string.isRequired, price: PropTypes.number.isRequired, upgradeUrl: PropTypes.string.isRequired, }), }; UpgradeNotification.defaultProps = { accessExpiration: null, contentTypeGatingEnabled: false, marketingUrl: null, offer: null, setupgradeNotificationCurrentState: null, shouldDisplayBorder: null, timeOffsetMillis: 0, userTimezone: null, verifiedMode: null, toggleSidebar: null, }; export default injectIntl(UpgradeNotification);