UNPKG

occsn-checkout

Version:

Occasion checkout experience boilerplate component built using React-Redux and Mitragyna

183 lines (144 loc) 5.11 kB
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>; } }