Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Apple pay frontend #1330

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/peacock/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dev d:
export eval `cat .env` && yarn dev

setup: clean
yarn --pure-lockfile
yarn --pure-lockfile

build:
$(call header, Building)
Expand Down
3 changes: 2 additions & 1 deletion demo/peacock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"lint": "eslint --ext .js --ext .jsx .",
"fix-lint": "eslint --ext .js --ext .jsx . --fix",
"flow": "flow check",
"dev": "gulp dev --optimize_for_size --stack_size=4096",
"test": "node_modules/mocha/bin/mocha",
Expand Down Expand Up @@ -87,7 +88,7 @@
"watchify": "^3.9.0"
},
"dependencies": {
"@foxcomm/api-js": "^1.2.0",
"@foxcomm/api-js": "^1.2.3",
"@foxcomm/babel-plugin-react-stylenames": "^3.1.0",
"@foxcomm/storefront-react": "^1.3.0",
"@foxcomm/wings": "^1.9.12",
Expand Down
36 changes: 33 additions & 3 deletions demo/peacock/src/components/cart/cart.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
@import "media-queries.css";
@import "variables.css";

.apple-pay {
background-color: black;
background-image: -webkit-named-image(apple-pay-logo-white);
background-size: cover;
background-origin: content-box;
background-repeat: no-repeat;
width: 100%;
height: 44px;
border-radius: 0;
margin-top: 20px;
padding: 15px 0;
}

.cart-box {
position: fixed;
z-index: 3;
Expand Down Expand Up @@ -86,6 +99,10 @@
border-radius: 0;
margin-top: 0;
}

&.with-apple-pay {
height: calc(100% - 186px);
}
}

.line-items {
Expand All @@ -102,6 +119,10 @@
height: 58px;
position: absolute;
bottom: 0;

&.with-apple-pay {
height: 136px;
}
}

@media (--small-only), (--medium-only) {
Expand All @@ -119,16 +140,25 @@
@media (--large) {
.cart-content {
height: calc(100% - 130px);

&.with-apple-pay {
height: calc(100% - 206px);
}
}

.cart-footer {
width: 100%;
height: 80px;
width: 100%;
position: absolute;
bottom: 0;
display: flex;
align-items: flex-start;
justify-content: center;
align-items: center;
justify-content: flex-start;
flex-direction: column;

&.with-apple-pay {
height: 156px;
}
}

.checkout-button {
Expand Down
170 changes: 148 additions & 22 deletions demo/peacock/src/components/cart/cart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,63 +8,78 @@ import { connect } from 'react-redux';
import { browserHistory } from 'lib/history';
import { autobind } from 'core-decorators';
import * as tracking from 'lib/analytics';

// localization
import localized from 'lib/i18n';
import { emailIsSet } from 'paragons/auth';
import sanitizeAll from 'sanitizers';
import sanitizeLineItems from 'sanitizers/line-items';

// actions
import * as actions from 'modules/cart';
import { checkApplePay, beginApplePay } from 'modules/checkout';

// components
import Currency from 'ui/currency';
import LineItem from './line-item';
import Button from 'ui/buttons';
import ErrorAlerts from '@foxcomm/wings/lib/ui/alerts/error-alerts';
import ErrorAlerts from 'ui/alerts/error-alerts';
import { skuIdentity } from '@foxcomm/wings/lib/paragons/sku';
import { parseError } from '@foxcomm/api-js';
import Overlay from 'ui/overlay/overlay';
import ActionLink from 'ui/action-link/action-link';

// styles
import styles from './cart.css';
import GuestAuth from 'pages/checkout/guest-auth/guest-auth';

// types
import type { Totals } from 'modules/cart';

// actions
import * as actions from 'modules/cart';
// styles
import styles from './cart.css';

type Props = {
fetch: Function,
deleteLineItem: Function,
updateLineItemQuantity: Function,
toggleCart: Function,
hideCart: Function,
skus: Array<any>,
fetch: Function, // signature
deleteLineItem: Function, // siganture
updateLineItemQuantity: Function, // signature
toggleCart: Function, // signature
hideCart: Function, // signature
skus: Array<mixed>,
coupon: ?Object,
promotion: ?Object,
totals: Totals,
user?: ?Object,
isVisible: boolean,
t: any,
applePayAvailable: boolean,
checkApplePay: () => void,
location: Object,
beginApplePay: (paymentRequest: Object) => Promise<*>,
};

type State = {
errors?: Array<any>,
errors?: ?Array<any>,
guestAuth: boolean,
};

class Cart extends Component {
props: Props;

state: State = {

guestAuth: false,
};

componentDidMount() {
this.props.checkApplePay();
if (this.props.user) {
this.props.fetch(this.props.user);
} else {
this.props.fetch();
}
}

componentWillReceiveProps(nextProps: Props) {
if (this.props.applePayAvailable !== nextProps.applePayAvailable) {
this.props.checkApplePay();
}
}

@autobind
deleteLineItem(sku) {
tracking.removeFromCart(sku, sku.quantity);
Expand All @@ -90,6 +105,12 @@ class Cart extends Component {
});
}

@autobind
isEmailSetForCheckout() {
const user = _.get(this.props, 'user', null);
return emailIsSet(user);
}

get lineItems() {
if (_.isEmpty(this.props.skus)) {
return (
Expand Down Expand Up @@ -124,28 +145,114 @@ class Cart extends Component {
});
}

@autobind
sanitize(err) {
const sanitizedLineItems = sanitizeLineItems(err, this.props.skus);

return sanitizedLineItems ? sanitizedLineItems : sanitizeAll(err);
}

get errorsLine() {
if (this.state.errors && !_.isEmpty(this.state.errors)) {
return <ErrorAlerts errors={this.state.errors} closeAction={this.closeError} />;
}
const { errors } = this.state;

if (!errors && _.isEmpty(errors)) return null;

return (
<ErrorAlerts
errors={errors}
sanitizeError={this.sanitize}
/>
);
}

@autobind
onCheckout() {
this.setState({ errors: null });
Promise.resolve(this.props.hideCart())
.then(() => {
browserHistory.push('/checkout');
})
;
}

@autobind
beginApplePay() {
const { total } = this.props.totals;
const amount = (parseFloat(total) / 100).toFixed(2);
const paymentRequest = {
countryCode: 'US',
currencyCode: 'USD',
total: {
label: 'Pure',
amount,
},
requiredShippingContactFields: [
'postalAddress',
'name',
'phone',
],
requiredBillingContactFields: [
'postalAddress',
'name',
],
};

this.props.beginApplePay(paymentRequest).then(() => {
this.setState({ errors: null });
browserHistory.push('/checkout/done');
})
.catch((err) => {
this.setState({
errors: parseError(err),
});
});
}

@autobind
checkAuth() {
const emailSet = this.isEmailSetForCheckout();

if (emailSet) {
this.setState({ guestAuth: false });
this.beginApplePay();
} else {
this.setState({ guestAuth: true });
}
}

get applePayButton() {
if (!this.props.applePayAvailable) return null;

const disabled = _.size(this.props.skus) < 1;
return (
<Button
styleName="apple-pay checkout-button"
onClick={this.checkAuth}
disabled={disabled}
/>
);
}

get guestAuth() {
const { guestAuth } = this.state;

if (!guestAuth) return null;

return (
<GuestAuth
isEditing={!this.isEmailSetForCheckout()}
location={this.props.location}
/>
);
}
render() {
const {
t,
totals,
toggleCart,
skus,
isVisible,
applePayAvailable,
} = this.props;

const cartClass = classNames({
Expand All @@ -154,6 +261,13 @@ class Cart extends Component {
});

const checkoutDisabled = _.size(skus) < 1;
const footerClasses = classNames(styles['cart-footer'], {
[styles['with-apple-pay']]: applePayAvailable,
});

const contentClasses = classNames(styles['cart-content'], {
[styles['with-apple-pay']]: applePayAvailable,
});

return (
<div styleName={cartClass}>
Expand All @@ -168,29 +282,41 @@ class Cart extends Component {
/>
</div>

<div styleName="cart-content">
<div className={contentClasses}>
{this.errorsLine}
<div styleName="line-items">
{this.lineItems}
</div>
{this.errorsLine}
</div>

<div styleName="cart-footer">
<div className={footerClasses}>
<Button onClick={this.onCheckout} disabled={checkoutDisabled} styleName="checkout-button">
<span>{t('Checkout')}</span>
<span styleName="subtotal-price">
<Currency value={totals.subTotal} />
</span>
</Button>
{this.applePayButton}
</div>
</div>
{this.guestAuth}
</div>
);
}
}

const mapStateToProps = state => ({ ...state.cart, ...state.auth });
const mapStateToProps = (state) => {
return {
...state.cart,
...state.auth,
applePayAvailable: _.get(state.checkout, 'applePayAvailable', false),
location: _.get(state.routing, 'location', {}),
};
};

export default connect(mapStateToProps, {
...actions,
checkApplePay,
beginApplePay,
})(localized(Cart));
Loading