Skip to content

Commit

Permalink
feat: design changes for subscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
NawfalAhmed committed Apr 20, 2023
1 parent 1ef0847 commit 69cb395
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { messages as paragonMessages } from '@edx/paragon';
import messages from './i18n';
import configureStore from './store';
import NotFoundPage from './components/NotFoundPage';
import { ConnectedOrderHistoryPage } from './order-history';
import { OrderAndSubscriptionsPage } from './order-and-subscriptions';

import './index.scss';

Expand All @@ -27,7 +27,7 @@ subscribe(APP_READY, () => {
<Header />
<main>
<Switch>
<Route path="/orders" component={ConnectedOrderHistoryPage} />
<Route path="/orders" component={OrderAndSubscriptionsPage} />
<Route path="/notfound" component={NotFoundPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
Expand Down
1 change: 1 addition & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ $fa-font-path: "~font-awesome/fonts";
@import "~@edx/frontend-component-footer/dist/footer";

@import "./order-history/style";
@import "./order-and-subscriptions/style";

.word-break-all {
word-break: break-all !important;
Expand Down
42 changes: 42 additions & 0 deletions src/order-and-subscriptions/OrderAndSubscriptionsPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';

import Subscriptions from '../subscriptions';
import Payments from '../order-history';

import messages from './OrderAndSubscriptionsPage.messages';

const OrderAndSubscriptionsPage = ({ intl }) => {
const isB2CSubsEnabled = true;

const message = (id) => intl.formatMessage(messages[id]);

if (!isB2CSubsEnabled) {
return (
<div className="page__order-and-subscriptions container-fluid py-5">
<Payments isB2CSubsEnabled={false} />
</div>
);
}

return (
<div className="page__order-and-subscriptions container-fluid py-4.5">
<div className="section">
<h1 className="text-primary-700">
{message('ecommerce.order.history.main.heading')}
</h1>
<span className="text-dark-900">
{message('ecommerce.order.history.main.subtitle')}
</span>
</div>
<Subscriptions />
<Payments isB2CSubsEnabled />
</div>
);
};

OrderAndSubscriptionsPage.propTypes = {
intl: intlShape.isRequired,
};

export default injectIntl(OrderAndSubscriptionsPage);
17 changes: 17 additions & 0 deletions src/order-and-subscriptions/OrderAndSubscriptionsPage.messages.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
'ecommerce.order.history.main.heading': {
id: 'ecommerce.order.history.main.heading',
defaultMessage: 'My orders and subscriptions',
description: 'Heading for orders and subscriptions page.',
},
'ecommerce.order.history.main.subtitle': {
id: 'ecommerce.order.history.main.subtitle',
defaultMessage:
'Manage your program subscriptions and view your order history.',
description: 'Subtitle of Heading for orders and subscriptions page.',
},
});

export default messages;
23 changes: 23 additions & 0 deletions src/order-and-subscriptions/_style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.page__order-and-subscriptions {
display: flex;
flex-direction: column;
gap: 2.5rem;

section, .section {
display: flex;
flex-direction: column;
gap: 1rem;

.section-gap-sm {
gap: 0.75rem;
}

h1, h2 {
margin-bottom: 0;
}
}

.subscription-scrollable {
max-height: 295px;
}
}
1 change: 1 addition & 0 deletions src/order-and-subscriptions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as OrderAndSubscriptionsPage } from './OrderAndSubscriptionsPage';
51 changes: 32 additions & 19 deletions src/order-history/OrderHistoryPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,31 +184,44 @@ class OrderHistoryPage extends React.Component {
const hasOrders = orders.length > 0;

return (
<div className="page__order-history container-fluid py-5">
<h1>
{this.props.intl.formatMessage(messages['ecommerce.order.history.page.heading'])}
</h1>
{loadingError ? this.renderError() : null}
{loaded && hasOrders ? (
<>
<MediaQuery query="(max-width: 768px)">
{this.renderMobileOrdersTable()}
</MediaQuery>
<MediaQuery query="(min-width: 769px)">
{this.renderOrdersTable()}
</MediaQuery>
{this.renderPagination()}
</>
) : null}
{loaded && !hasOrders ? this.renderEmptyMessage() : null}
{loading ? this.renderLoading() : null}
</div>
<section className="page__order-history">
{this.props.isB2CSubsEnabled ? (
<h2>
{this.props.intl.formatMessage(
messages['ecommerce.order.history.payments.heading'],
)}
</h2>
) : (
<h1>
{this.props.intl.formatMessage(
messages['ecommerce.order.history.page.heading'],
)}
</h1>
)}
<div>
{loadingError ? this.renderError() : null}
{loaded && hasOrders ? (
<>
<MediaQuery query="(max-width: 768px)">
{this.renderMobileOrdersTable()}
</MediaQuery>
<MediaQuery query="(min-width: 769px)">
{this.renderOrdersTable()}
</MediaQuery>
{this.renderPagination()}
</>
) : null}
{loaded && !hasOrders ? this.renderEmptyMessage() : null}
{loading ? this.renderLoading() : null}
</div>
</section>
);
}
}

OrderHistoryPage.propTypes = {
intl: intlShape.isRequired,
isB2CSubsEnabled: PropTypes.bool.isRequired,
orders: PropTypes.arrayOf(PropTypes.shape({
datePlaced: PropTypes.string,
total: PropTypes.string,
Expand Down
5 changes: 5 additions & 0 deletions src/order-history/OrderHistoryPage.messages.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
'ecommerce.order.history.payments.heading': {
id: 'ecommerce.order.history.payments.heading',
defaultMessage: 'Payments',
description: 'Heading for payments section.',
},
'ecommerce.order.history.page.heading': {
id: 'ecommerce.order.history.page.heading',
defaultMessage: 'Order History',
Expand Down
10 changes: 3 additions & 7 deletions src/order-history/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import ConnectedOrderHistoryPage from './OrderHistoryPage';
import OrderHistoryPage from './OrderHistoryPage';
import reducer from './reducer';
import saga from './saga';
import { storeName } from './selectors';

export {
ConnectedOrderHistoryPage,
reducer,
saga,
storeName,
};
export default OrderHistoryPage;
export { reducer, saga, storeName };
57 changes: 57 additions & 0 deletions src/subscriptions/SubscriptionScrollView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';

import { Card, Badge, Scrollable } from '@edx/paragon';

const SubscriptionScrollView = ({
subscriptions = [
{
title: 'Blockchain Fundamentals',
org: 'University of California, Berkeley',
subscriptionStatus: 'trial',
},
{
title: 'Critical Thinking',
org: 'Simmons University',
subscriptionStatus: 'active',
},
{
title: 'Blockchain Fundamentals',
org: 'University of California, Berkeley',
subscriptionStatus: 'inActive',
},
{
title: 'Critical Thinking',
org: 'Simmons University',
subscriptionStatus: 'inactive',
},
],
}) => (
<Scrollable className="subscription-scrollable mx-n1">
<div className="section mx-1">
{subscriptions.map(({ title, org, subscriptionStatus }) => (
<Card className="bg-light-200 p-3">
<div className="section flex-column-reverse flex-sm-row align-items-start align-items-sm-center">
<h3 className="text-info-500 m-0">{title}</h3>
<Badge className="text-capitalize" variant="light">
{subscriptionStatus.toLowerCase()}
</Badge>
</div>
<p className="small text-gray-500 m-0">{org}</p>
</Card>
))}
</div>
</Scrollable>
);

SubscriptionScrollView.propTypes = {
subscriptions: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired,
org: PropTypes.string.isRequired,
subscriptionStatus: PropTypes.string.isRequired,
}),
),
};

export default SubscriptionScrollView;
52 changes: 52 additions & 0 deletions src/subscriptions/SubscriptionUpsell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { useMediaQuery } from 'react-responsive';

import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Alert, Badge, Button } from '@edx/paragon';
import { Search } from '@edx/paragon/icons';

const SubscriptionUpsell = () => (
<Alert
className="bg-light-200"
actions={[
<Button className="text-nowrap" iconBefore={Search}>
<FormattedMessage
id="ecommerce.order.history.subscription.upsell.button"
defaultMessage="Explore subscription options"
description="Button text for subscription upsell"
/>
</Button>,
]}
stacked={useMediaQuery({ query: '(max-width: 834px)' })}
>
<Alert.Heading
as="h4"
className="section section-gap-sm flex-sm-row align-items-start"
>
<Badge variant="warning">
<FormattedMessage
id="ecommerce.order.history.subscription.upsell.badge"
defaultMessage="New"
description="'New' Badge for subscription upsell"
/>
</Badge>
<FormattedMessage
id="ecommerce.order.history.subscription.upsell.heading"
defaultMessage="Monthly program subscriptions now available"
description="Heading for subscription upsell"
/>
</Alert.Heading>
<FormattedMessage
tagName="p"
id="ecommerce.order.history.subscription.upsell.message"
defaultMessage="An easier way to access popular programs with more control over how much you spend. Starting at {minSubscriptionPrice} per month after a {trialLength}-day free trial. Cancel anytime."
description="Message body for subscription upsell"
values={{
minSubscriptionPrice: '$39',
trialLength: 7,
}}
/>
</Alert>
);

export default SubscriptionUpsell;
55 changes: 55 additions & 0 deletions src/subscriptions/Subscriptions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import { Launch } from '@edx/paragon/icons';

import messages from './Subscriptions.messages';
import SubscriptionUpsell from './SubscriptionUpsell';
import SubscriptionScrollView from './SubscriptionScrollView';

const Subscriptions = ({ intl }) => {
const hasSubscriptions = true;

const message = (id, values = {}) => intl.formatMessage(messages[id], values);
const buttonLabel = message(
'ecommerce.order.history.subscriptions.manage.button',
);

const renderEmpty = () => (
<>
<span className="text-dark-900">
{message('ecommerce.order.history.subscriptions.subtitle.empty')}
</span>
<SubscriptionUpsell />
</>
);

const renderSubscriptions = () => (
<>
<div className="section flex-md-row align-items-start align-items-md-center justify-content-between">
<span className="text-dark-900">
{message('ecommerce.order.history.subscriptions.subtitle', {
buttonLabel: <i>{buttonLabel}</i>,
})}
</span>
<Button className="text-nowrap" size="sm" iconAfter={Launch}>
{buttonLabel}
</Button>
</div>
<SubscriptionScrollView />
</>
);

return (
<section>
<h2>{message('ecommerce.order.history.subscriptions.heading')}</h2>
{hasSubscriptions ? renderSubscriptions() : renderEmpty()}
</section>
);
};

Subscriptions.propTypes = {
intl: intlShape.isRequired,
};

export default injectIntl(Subscriptions);
28 changes: 28 additions & 0 deletions src/subscriptions/Subscriptions.messages.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
'ecommerce.order.history.subscriptions.heading': {
id: 'ecommerce.order.history.subscriptions.heading',
defaultMessage: 'Subscriptions',
description: 'Heading for subscriptions section.',
},
'ecommerce.order.history.subscriptions.subtitle': {
id: 'ecommerce.order.history.subscriptions.subtitle',
defaultMessage:
'To view your receipts, change your payment method or cancel your subscription, click {buttonLabel}.',
description: 'Subtitle for subscriptions section.',
},
'ecommerce.order.history.subscriptions.subtitle.empty': {
id: 'ecommerce.order.history.subscriptions.subtitle.empty',
defaultMessage: 'You do not have any active or previous subscriptions.',
description:
'Subtitle for subscriptions section when there are no subscriptions.',
},
'ecommerce.order.history.subscriptions.manage.button': {
id: 'ecommerce.order.history.subscriptions.manage.button',
defaultMessage: 'Manage my subscription',
description: 'Button text for managing subscriptions.',
},
});

export default messages;
Loading

0 comments on commit 69cb395

Please sign in to comment.