UNPKG

occsn-checkout

Version:

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

314 lines (270 loc) 10.3 kB
import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import Decimal from 'decimal.js-light'; import _ from 'underscore'; import { Resource } from 'mitragyna'; import { Button } from 'reactstrap'; import occsn from '../libs/Occasion'; import TimeSlotsContainer from '../containers/TimeSlotsContainer.jsx'; import Attendees from './Order/Attendees.jsx'; import Customer from './Order/Customer.jsx'; import MissingAnswers from './Order/MissingAnswers.jsx'; import Pricing from './Order/Pricing.jsx'; import PaymentForm from './Order/PaymentForm.jsx'; import Questions from './Order/Questions.jsx'; import Redeemables from './Order/Redeemables.jsx'; export default class Order extends PureComponent { static propTypes = { activeTimeSlotsCollection: PropTypes.shape({ __collection: PropTypes.arrayOf(PropTypes.instanceOf(occsn.TimeSlot)) }), timeSlotsFromCalendar: PropTypes.shape({ __collection: PropTypes.arrayOf(PropTypes.instanceOf(occsn.TimeSlot)) }), afterError: PropTypes.func.isRequired, afterUpdate: PropTypes.func.isRequired, bookingOrder: PropTypes.bool, findRedeemable: PropTypes.func.isRequired, setSkipAttendees: PropTypes.func, skipAttendees: PropTypes.bool, subject: PropTypes.instanceOf(occsn.Order).isRequired, }; static contextTypes = { componentProps: PropTypes.object, }; constructor() { super(); _.bindAll(this, 'acceptsPayment', 'allowedToBookOrder', 'headerForSection', 'showPaymentForm', 'showPrice', ); } acceptsPayment() { const { subject } = this.props; return subject.product().merchant().pspName != 'cash'; } allowedToBookOrder() { const { bookingOrder, subject } = this.props; if(!subject || !this.missingAnswers) return false; return !bookingOrder && this.missingAnswers.missingRequiredAnswers(subject).empty(); } // Mitragyna callback beforeSubmit(subject) { if(this.acceptsPayment() && !subject.outstandingBalance.isZero()) { return this.paymentForm.chargeOutstandingBalanceToPaymentMethod(subject); } else { return subject; } } // @param [String] section the name of the section to get the custom or default section title for, rendered as <h2> headerForSection(section) { const { subject } = this.props; let product = subject.product(); switch(section) { case 'contact': return <h2 className="container-title" id="widgetContactTitle"> { _.isNull(product.widgetContactTitle) ? ( "Contact Information" ) : ( product.widgetContactTitle )} </h2>; case 'timeSlots': return <h2 className="container-title" id="widgetTimeSlotsTitle"> { _.isNull(product.widgetTimeSlotsTitle) ? ( "Time Slots" ) : ( product.widgetTimeSlotsTitle )} </h2>; case 'questions': return <h2 className="container-title" id="widgetQuestionsTitle"> { _.isNull(product.widgetQuestionsTitle) ? ( "Additional Information" ) : ( product.widgetQuestionsTitle )} </h2>; case 'attendees': return <h2 className="container-title" id="widgetAttendeesTitle"> Attendee Information </h2>; case 'payment': return <h2 className="container-title" id="widgetPaymentTitle"> { _.isNull(product.widgetPaymentTitle) ? ( "Payment Information" ) : ( product.widgetPaymentTitle )} </h2>; case 'redeemables': return <h2 className="container-title" id="widgetRedeemableTitle"> Coupons and Gift Cards </h2>; case 'totalDue': return <h2 className="container-title" id="widgetTotalDueTitle"> { _.isNull(product.widgetTotalDueTitle) ? ( "Total Due Today" ) : ( product.widgetTotalDueTitle )} </h2>; } } // Determines if should show Attendees // @note If the product is quantity aware && has attendeeQuestions, and order.quantity > 0, show Attendees // // @return [Boolean] whether or not to show Attendees showAttendees() { let { subject } = this.props; let product = subject.product(); if(subject.newResource()) return false; return !product.attendeeQuestions.empty() && subject.quantity > 0; } // Determines if should show TimeSlotsContainer // @note If the product has firstTimeSlotStartsAt and requiresTimeSlotSelection, and a timeSlot has not been // pre-selected via window.OCCSN.time_slot_id, then show TimeSlotsContainer // // @return [Boolean] whether or not to show TimeSlotsContainer showTimeSlots() { let { subject } = this.props; let product = subject.product(); return product.firstTimeSlotStartsAt && (product.requiresTimeSlotSelection || product.sellsSessions) && !window.OCCSN.time_slot_id; } // Determines if should show Questions // @note If the product.questions() is empty, don't show questions // // @return [Boolean] whether or not to show Questions showQuestions() { let { subject } = this.props; let product = subject.product(); return !product.questions().empty(); } // Determines if should show Redeemables // @note If the product is not free && hasRedeemables then show Redeemables // // @return [Boolean] whether or not to show Redeemables showRedeemables() { let { subject } = this.props; let product = subject.product(); if(subject.newResource()) return false; return !product.free && product.hasRedeemables && ((subject.subtotal && !subject.subtotal.isZero()) || window.OCCSN.coupon_code); } // Determines if should show the payment form // @note If the product is free or outstandingBalance is zero, do not show the payment form // // @return [Boolean] whether or not to show the payment form showPaymentForm() { let { subject } = this.props; let product = subject.product(); if(subject.newResource()) return false; return !(product.free || !subject.outstandingBalance || subject.outstandingBalance.isZero()); } // Determines if should show the price output // @note If the product is free or price is null (required price-calculating questions are missing answers), // do not show the price display // // @return [Boolean] whether or not to show the payment form showPrice() { let { subject } = this.props; let product = subject.product(); if(subject.newResource()) return false; return !product.free && subject.price; } render() { let { afterError, afterUpdate, bookingOrder, findRedeemable, setSkipAttendees, skipAttendees, subject } = this.props; let { componentProps } = this.context; let customer = subject.customer(); let product = subject.product(); return <section className="order-container"> { this.showTimeSlots() ? ( <section className="time-slots-container"> <a name="time-slots" id="time-slots-anchor"></a> { this.headerForSection('timeSlots') } <h6 className="container-description">Please select time and date:</h6> <TimeSlotsContainer order={subject} /> </section> ) : (null) } <section className="customer-container"> <a name="customer" id="customer-anchor"></a> { this.headerForSection('contact') } <h6 className="container-description">Please fill in your information to complete your order:</h6> <Resource component={ Customer } reflection="customer" subject={ customer }></Resource> </section> { this.showQuestions() ? ( <section className="questions-container"> <a name="questions" id="questions-anchor"></a> { this.headerForSection('questions') } <Questions subject={subject} questions={ product.questions().target() }></Questions> </section> ) : null } { this.showAttendees() ? ( <section className="attendees-container"> <a name="attendees" id="attendees-anchor"></a> { this.headerForSection('attendees') } <Attendees questions={product.attendeeQuestions} setSkipAttendees={setSkipAttendees} skipAttendees={skipAttendees} subject={subject}></Attendees> </section> ) : null } { this.showRedeemables() ? ( <section className="redeemables-container"> <a name="redeemables" id="redeemables-anchor"></a> { this.headerForSection('redeemables') } <Redeemables findRedeemable={findRedeemable} order={subject} onChange={afterUpdate} onErrors={afterError}></Redeemables> </section> ) : null } { this.showPaymentForm() ? ( <section className="payment-container"> <a name="payment" id="payment-anchor"></a> { this.headerForSection('payment') } <PaymentForm order={subject} ref={(form) => this.paymentForm = form }></PaymentForm> </section> ) : null } { this.showPrice() ? ( <section className="total-due-container"> <a name="total-due" id="total-due-anchor"></a> { this.headerForSection('totalDue') } <Pricing order={subject}></Pricing> </section> ) : null } <section className="missing-answers-container"> <a name="missing-answers"></a> <MissingAnswers order={subject} ref={(r) => this.missingAnswers = r }></MissingAnswers> </section> <section className="book-order-container"> <Button block className="book-order-button" color="success" id="bookOrder" size="lg" type="submit" disabled={ !this.allowedToBookOrder() } > { bookingOrder ? ( componentProps.orderBooking ? ( React.createElement(componentProps.orderBooking) ) : (null) ) : ( <span>{ product.orderButtonText }</span> ) } </Button> </section> </section>; } }