occsn-checkout
Version:
Occasion checkout experience boilerplate component built using React-Redux and Mitragyna
183 lines (144 loc) • 5.11 kB
JSX
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Decimal from 'decimal.js-light';
import _ from 'underscore';
import ActiveResource from 'active-resource';
import occsn from '../../libs/Occasion'
import { ErrorsFor } from 'mitragyna';
import { Button, Col, Card, FormGroup, InputGroup, InputGroupAddon, Input, FormFeedback } from 'reactstrap';
import Coupon from './Redeemables/Coupon.jsx';
import GiftCard from './Redeemables/GiftCard.jsx';
export default class Redeemables extends PureComponent {
static propTypes = {
findRedeemable: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onErrors: PropTypes.func.isRequired,
order: PropTypes.instanceOf(occsn.Order).isRequired,
};
static childContextTypes = {
removeRedeemable: PropTypes.func,
};
constructor() {
super();
_.bindAll(this,
'addErrors',
'addRedeemable',
'checkForRedeemable',
'handleChange',
'showInput',
'removeRedeemable',
);
}
componentWillMount() {
this.setState({ code: '' });
}
addErrors(errors) {
let { onErrors, order } = this.props;
let newOrder = order.clone();
// TODO: Remove when there is better way to copy these read-only attributes of order
ActiveResource.Collection.build([
'subtotal',
'couponAmount',
'tax',
'taxPercentage',
'giftCardAmount',
'price',
'total',
'outstandingBalance',
'quantity',
])
.each((attr) => {
newOrder[attr] = order[attr];
});
newOrder.errors().clear();
errors.each((e) => {
e.field = 'redeemables.' + e.parameter.replace('filter/', '');
newOrder.errors().push(e);
});
onErrors(newOrder);
}
addRedeemable(redeemable) {
let { onChange, order } = this.props;
this.setState({ code: '' });
if(redeemable.isA(occsn.Coupon)) {
onChange(order.assignAttributes({ coupon: redeemable }))
} else if(redeemable.isA(occsn.GiftCard)) {
var outstandingBalance = order.outstandingBalance;
var giftCardValue = new Decimal(redeemable.value);
var transactionAmount;
if(outstandingBalance.greaterThan(giftCardValue)) {
transactionAmount = giftCardValue;
} else {
transactionAmount = outstandingBalance;
}
order = order.clone();
order.charge(redeemable, transactionAmount.toString());
onChange(order);
}
}
checkForRedeemable(code) {
let { findRedeemable, order } = this.props;
findRedeemable(order.product(), code, this.addRedeemable, this.addErrors);
}
getChildContext() {
return {
removeRedeemable: this.removeRedeemable,
}
}
handleChange(e) {
this.setState({ code: e.target.value });
}
// If true, display the input to search for more redeemables
//
// @note Displays in two cases:
// - outstandingBalance is greater than zero (more gift cards can be added)
// - outstandingBalance is zero, but a coupon has not been added (coupon can still lower order.price)
//
// @return [Boolean] whether or not to show the input to search for more redeemables
showInput() {
const { order } = this.props;
return (order.outstandingBalance && !order.outstandingBalance.isZero()) || _.isNull(order.coupon())
}
removeRedeemable(redeemable) {
let { onChange, order } = this.props;
if(redeemable.isA(occsn.Coupon)) {
onChange(order.assignAttributes({ coupon: null }))
} else if(redeemable.isA(occsn.GiftCard)) {
order = order.clone();
order.removeCharge(redeemable);
onChange(order);
}
}
render() {
let { order } = this.props;
let { code } = this.state;
let currency = order.product().merchant().currency();
let redeemables = [];
if(!_.isNull(order.coupon())) {
redeemables.push(<Coupon currency={currency.name} subject={order.coupon()}></Coupon>);
}
let giftCardTransactions =
order.transactions().target()
.select((t) => { return t.paymentMethod().isA(occsn.GiftCard) });
giftCardTransactions.each((giftCardTransaction) => {
redeemables.push(<GiftCard currency={currency.name} subject={giftCardTransaction}></GiftCard>);
});
return <section className="redeemables">
<section className="redeemables-list">
{ redeemables }
</section>
{ this.showInput() ? (
<FormGroup className="redeemable-code">
<InputGroup className={ order.errors().forField('redeemables.code').empty() ? '' : 'is-invalid' }>
<Input className="redeemable-code-input" onChange={ this.handleChange } value={ code } />
<InputGroupAddon addonType="append">
<Button className="redeemable-code-input-button" onClick={ () => this.checkForRedeemable(code) }>Search</Button>
</InputGroupAddon>
</InputGroup>
<ErrorsFor className="redeemable-code-errors" component={FormFeedback} field='redeemables.code'></ErrorsFor>
</FormGroup>
) : (null)
}
</section>;
}
}