AA-449: Show discounted price in upgrade buttons & course exit (#311)

If an offer is active for the user, show the discounted price (and
a struck-out original price) on upgrade buttons in the course sock
and outline sidebar.

Also show the discount code and price in the course exit upgrade
screen.
This commit is contained in:
Michael Terry
2020-12-17 10:18:57 -05:00
committed by GitHub
parent c16da21602
commit 37a4dcce18
12 changed files with 245 additions and 91 deletions

View File

@@ -4,12 +4,12 @@ import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import LearnerQuote1 from './assets/learner-quote.png';
import LearnerQuote2 from './assets/learner-quote2.png';
import { UpgradeButton } from '../upgrade-button';
import VerifiedCert from '../assets/edX_certificate.png';
export default class CourseSock extends Component {
constructor(props) {
super(props);
this.verifiedMode = props.verifiedMode;
this.state = { showUpsell: false };
this.sockElement = React.createRef();
}
@@ -31,7 +31,7 @@ export default class CourseSock extends Component {
}
render() {
if (!this.verifiedMode) {
if (!this.props.verifiedMode) {
return null;
}
@@ -65,22 +65,14 @@ export default class CourseSock extends Component {
</div>
<div className="position-relative flex-grow-1 d-flex flex-column justify-content-end align-items-md-end">
<div style={{ position: 'sticky', bottom: '4rem' }}>
<a
href={this.verifiedMode.upgradeUrl}
className="btn btn-success btn-lg btn-upgrade focusable mb-3"
<UpgradeButton
size="lg"
offer={this.props.offer}
verifiedMode={this.props.verifiedMode}
className="mb-3"
data-creative="original_sock"
data-position="sock"
>
<FormattedMessage
id="coursesock.upsell.upgrade"
defaultMessage="Upgrade ({symbol}{price} {currency})"
values={{
symbol: this.verifiedMode.currencySymbol,
price: this.verifiedMode.price,
currency: this.verifiedMode.currency,
}}
/>
</a>
/>
</div>
</div>
</div>
@@ -215,12 +207,11 @@ export default class CourseSock extends Component {
}
}
CourseSock.propTypes = {
verifiedMode: PropTypes.shape({
price: PropTypes.number,
currency: PropTypes.string,
currencySymbol: PropTypes.string,
sku: PropTypes.string,
upgradeUrl: PropTypes.string,
}).isRequired,
CourseSock.defaultProps = {
offer: null,
};
CourseSock.propTypes = {
offer: PropTypes.shape({}),
verifiedMode: PropTypes.shape({}).isRequired,
};

View File

@@ -32,8 +32,8 @@ describe('Course Sock', () => {
fireEvent.click(upsellButton);
expect(screen.getByText('edX Verified Certificate')).toBeInTheDocument();
const { currencySymbol, price, currency } = mockData.verifiedMode;
expect(screen.getByText(`Upgrade (${currencySymbol}${price} ${currency})`)).toBeInTheDocument();
const { currencySymbol, price } = mockData.verifiedMode;
expect(screen.getByText(`Upgrade (${currencySymbol}${price})`)).toBeInTheDocument();
fireEvent.click(upsellButton);
expect(screen.queryByText('edX Verified Certificate')).not.toBeInTheDocument();

View File

@@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
function FormattedPricing(props) {
const {
inline,
intl,
offer,
verifiedMode,
} = props;
if (!offer) {
const {
currencySymbol,
price,
} = verifiedMode;
return `${currencySymbol}${price}`;
}
const {
discountedPrice,
originalPrice,
} = offer;
// The inline style is meant for being embedded in a sentence - it bolds the discount and leaves the original price
// as a parenthetical. The normal styling is more suited for a button, where the price and discount are side by side.
if (inline) {
return (
<>
<span className="font-weight-bold">{discountedPrice}</span>
&nbsp;(
<span className="sr-only">
{intl.formatMessage(messages.srInlinePrices, { originalPrice })}
</span>
<span aria-hidden="true">
<del>{originalPrice}</del>
</span>
)
</>
);
}
return (
<>
<span className="sr-only">
{intl.formatMessage(messages.srPrices, { discountedPrice, originalPrice })}
</span>
<span aria-hidden="true">
{/* the price discount and price original classes can be removed post REV-1512 experiment */}
<span className="price discount">{discountedPrice}</span> <del className="price original">{originalPrice}</del>
</span>
</>
);
}
FormattedPricing.defaultProps = {
inline: false,
offer: null,
verifiedMode: null,
};
FormattedPricing.propTypes = {
inline: PropTypes.bool,
intl: intlShape.isRequired,
offer: PropTypes.shape({
discountedPrice: PropTypes.string.isRequired,
originalPrice: PropTypes.string.isRequired,
upgradeUrl: PropTypes.string.isRequired,
}),
verifiedMode: PropTypes.shape({
currencySymbol: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
upgradeUrl: PropTypes.string.isRequired,
}),
};
export default injectIntl(FormattedPricing);

View File

@@ -0,0 +1,59 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import FormattedPricing from './FormattedPricing';
function UpgradeButton(props) {
const {
intl,
offer,
onClick,
verifiedMode,
...rest
} = props;
// Prefer offer's url in case it is ever different (though it is not at time of this writing)
const url = offer ? offer.upgradeUrl : verifiedMode.upgradeUrl;
return (
<Button
variant="success"
href={url}
onClick={onClick}
{...rest}
>
<FormattedMessage
id="learning.upgradeButton.buttonText"
defaultMessage="Upgrade ({pricing})"
values={{
pricing: (
<FormattedPricing
offer={offer}
verifiedMode={verifiedMode}
/>
),
}}
/>
</Button>
);
}
UpgradeButton.defaultProps = {
offer: null,
onClick: null,
};
UpgradeButton.propTypes = {
intl: intlShape.isRequired,
offer: PropTypes.shape({
upgradeUrl: PropTypes.string.isRequired,
}),
onClick: PropTypes.func,
verifiedMode: PropTypes.shape({
upgradeUrl: PropTypes.string.isRequired,
}).isRequired,
};
export default injectIntl(UpgradeButton);

View File

@@ -0,0 +1,7 @@
import FormattedPricing from './FormattedPricing';
import UpgradeButton from './UpgradeButton';
export {
FormattedPricing,
UpgradeButton,
};

View File

@@ -0,0 +1,14 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
srPrices: {
id: 'learning.offer.screenReaderPrices', // historic id
defaultMessage: 'Original price: {originalPrice}, discount price: {discountedPrice}',
},
srInlinePrices: {
id: 'learning.upgradeButton.screenReaderInlinePrices',
defaultMessage: 'Original price: {originalPrice}',
},
});
export default messages;