OasisDEX/oasis-react

View on GitHub
src/components/OfferTakeForm.jsx

Summary

Maintainability
A
1 hr
Test Coverage
F
14%
import React, { PureComponent } from "react";
import { PropTypes } from "prop-types";
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Field, reduxForm } from "redux-form/immutable";

import web3 from "../bootstrap/web3";

import offerTakes from "../store/selectors/offerTakes";
import { TAKE_BUY_OFFER, TAKE_SELL_OFFER } from "../store/reducers/offerTakes";
import offerTakesReducer from "../store/reducers/offerTakes";
import tokens from "../store/selectors/tokens";
import balances from "../store/selectors/balances";
import {
  formatValue,
  greaterThanZeroValidator,
  // normalize,
  numericFormatValidator
} from "../utils/forms/offers";

import OasisButton from "../components/OasisButton";

import styles from "./OfferTakeForm.scss";
import tableStyles from "../styles/modules/_table.scss";
import CSSModules from "react-css-modules";
import MaskedTokenAmountInput from "./MaskedTokenAmountInput";
import { SETMAXBTN_HIDE_DELAY_MS } from "../constants";
import platform from "../store/selectors/platform";
import isNumericAndGreaterThanZero from "../utils/numbers/isNumericAndGreaterThanZero";

const fieldStyle = { textAlign: "right" };

const propTypes = PropTypes && {
  activeOfferTakeOfferData: ImmutablePropTypes.map.isRequired,
  buyToken: PropTypes.string.isRequired,
  sellToken: PropTypes.string.isRequired,
  offerTakeType: PropTypes.string.isRequired,
  activeTradingPairPrecision: PropTypes.number.isRequired,
  activeBaseTokenBalance: PropTypes.string.isRequired,
  onFormChange: PropTypes.func
};

const defaultProps = {};

const validateVolume = [greaterThanZeroValidator, numericFormatValidator];
const validateTotal = [greaterThanZeroValidator, numericFormatValidator];

export class OfferTakeForm extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      showMaxButton: false
    };
    this.componentIsUnmounted = false;
    this.currentSetMaxTimeout = null;

    this.onFormChange = this.onFormChange.bind(this);
    this.onVolumeFieldChange = this.onVolumeFieldChange.bind(this);
    this.onTotalFieldChange = this.onTotalFieldChange.bind(this);
    this.onSetBuyMax = this.onSetBuyMax.bind(this);
    this.onSetSellMax = this.onSetSellMax.bind(this);
    this.onSetMaxFocus = this.onSetMaxFocus.bind(this);
    this.onSetMaxBlur = this.onSetMaxBlur.bind(this);
    this.onTotalFieldSectionBlur = this.onTotalFieldSectionBlur.bind(this);
    this.onTotalFieldSectionFocus = this.onTotalFieldSectionFocus.bind(this);
    // this.estimateGas = throttle(this.props.estimateGas, 500);
  }

  onSetBuyMax() {
    this.props.actions.buyMax();
    // this.estimateGas();
  }
  onSetSellMax() {
    this.props.actions.sellMax();
    // this.estimateGas();
  }

  onVolumeFieldChange(event, newValue) {
    const { volumeFieldValueChanged } = this.props.actions;
    if (this.componentIsUnmounted === false) {
      setTimeout(() => volumeFieldValueChanged(newValue), 0);
    }
  }

  onTotalFieldChange(event, newValue) {
    const { totalFieldValueChanged } = this.props.actions;
    if (this.componentIsUnmounted === false) {
      setTimeout(() => totalFieldValueChanged(newValue), 0);
    }
  }

  setMaxButton() {

    if (false === this.state.showMaxButton) {
      return null;
    }

    const balanceOfTokenToPayWith = this.props.offerTakeType === TAKE_BUY_OFFER ?
      this.props.activeBaseTokenBalance :
      this.props.activeQuoteTokenBalance;

    if (web3.toBigNumber(balanceOfTokenToPayWith).gt(0)) {
      const {
        disableForm,
        globalFormLock,
        activeBaseTokenBalance,
        activeQuoteTokenBalance
      } = this.props;
      switch (this.props.offerTakeType) {
        case TAKE_BUY_OFFER:
          return (
            <OasisButton
              className={tableStyles.inputBtn}
              type="button"
              color="success"
              size="xs"
              onClick={this.onSetSellMax}
              onFocus={this.onSetMaxFocus}
              onBlur={this.onSetMaxBlur}
              disabled={
                disableForm ||
                globalFormLock ||
                !isNumericAndGreaterThanZero(activeBaseTokenBalance)
              }
            >
              Sell max
            </OasisButton>
          );
        case TAKE_SELL_OFFER:
          return (
            <OasisButton
              className={tableStyles.inputBtn}
              type="button"
              color="danger"
              size="xs"
              onClick={this.onSetBuyMax}
              disabled={
                disableForm ||
                globalFormLock ||
                !isNumericAndGreaterThanZero(activeQuoteTokenBalance)
              }
            >
              Buy max
            </OasisButton>
          );
      }
    } else {
      return null;
    }
  }

  renderPriceField() {
    const { priceToken } = this.getTokensByOfferTakeType();
    return (
      <tr>
        <th>Price</th>
        <td className={tableStyles.withInput}>
          <Field
            autoComplete="off"
            style={fieldStyle}
            name="price"
            component="input"
            format={formatValue}
            placeholder={0}
            // normalize={normalize}
            type="text"
            disabled={true}
          />
        </td>
        <td className={tableStyles.currency}>{priceToken}</td>
      </tr>
    );
  }

  renderAmountField() {
    const { volumeToken } = this.getTokensByOfferTakeType();
    const { disableForm, globalFormLock } = this.props;

    return (
      <tr>
        <th>Amount</th>
        <td className={tableStyles.withInput}>
          <Field
            autoComplete="off"
            style={fieldStyle}
            name="volume"
            component={MaskedTokenAmountInput}
            type="text"
            validate={validateVolume}
            min={0}
            placeholder={0}
            disabled={disableForm || globalFormLock}
            onChange={this.onVolumeFieldChange}
          />
        </td>
        <td className={tableStyles.currency}>{volumeToken}</td>
      </tr>
    );
  }

  renderTotalField() {
    const { totalToken } = this.getTokensByOfferTakeType();
    const { globalFormLock, disableForm } = this.props;
    return (
      <tr>
        <th>Total</th>
        <td className={tableStyles.withInput}>
          <div className={tableStyles.inputGroup}>
            {this.setMaxButton()}
            <div
              className={tableStyles.inputGroupEventHandlerChild}
              onFocus={this.onTotalFieldSectionFocus}
              onBlur={this.onTotalFieldSectionBlur}
            >
              <Field
                autoComplete="off"
                style={fieldStyle}
                min={0}
                name="total"
                component={MaskedTokenAmountInput}
                type="text"
                validate={validateTotal}
                placeholder={0}
                disabled={disableForm || globalFormLock}
                onChange={this.onTotalFieldChange}
              />
            </div>
          </div>
        </td>
        <td className={tableStyles.currency}>{totalToken}</td>
      </tr>
    );
  }

  getTokensByOfferTakeType() {
    const { offerTakeType, buyToken, sellToken } = this.props;
    let priceToken = null,
      volumeToken = null,
      totalToken = null;
    switch (offerTakeType) {
      case TAKE_BUY_OFFER:
        {
          priceToken = buyToken;
          volumeToken = sellToken;
          totalToken = buyToken;
        }
        break;
      case TAKE_SELL_OFFER:
        {
          priceToken = sellToken;
          volumeToken = buyToken;
          totalToken = sellToken;
        }
        break;
    }
    return { priceToken, volumeToken, totalToken };
  }

  onFormChange() {
    const { onFormChange } = this.props;
    if (this.componentIsUnmounted === false) {
      onFormChange && onFormChange();
    }
  }

  render() {
    const { handleSubmit } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        <table className={tableStyles.table}>
          <tbody>
            {this.renderPriceField()}
            {this.renderAmountField()}
            {this.renderTotalField()}
          </tbody>
        </table>
      </form>
    );
  }

  onSetMaxFocus() {
    clearTimeout(this.currentSetMaxTimeout);
  }

  onSetMaxBlur() {
    this.setState({ showMaxButton: false });
  }

  onTotalFieldSectionFocus() {
    if (this.componentIsUnmounted === false) {
      this.setState({ showMaxButton: true });
    }
  }

  onTotalFieldSectionBlur() {
    if (this.componentIsUnmounted === false) {
      this.currentSetMaxTimeout = setTimeout(
        () => this.setState({ showMaxButton: false }),
        SETMAXBTN_HIDE_DELAY_MS
      );
    }
  }

  componentWillUnmount() {
    this.componentIsUnmounted = true;
  }
}

OfferTakeForm.displayName = "OfferTakeForm";
OfferTakeForm.propTypes = propTypes;
OfferTakeForm.defaultProps = defaultProps;

export function mapStateToProps(state) {
  return {
    activeOfferTakeOfferData: offerTakes.activeOfferTakeOfferData(state),
    buyToken: offerTakes.activeOfferTakeBuyToken(state),
    sellToken: offerTakes.activeOfferTakeSellToken(state),
    offerTakeType: offerTakes.activeOfferTakeType(state),
    activeTradingPairPrecision: tokens.precision(state),
    activeBaseTokenBalance: balances.activeBaseTokenBalance(state),
    activeQuoteTokenBalance: balances.activeQuoteTokenBalance(state),
    globalFormLock: platform.globalFormLock(state)
  };
}

export function mapDispatchToProps(dispatch) {
  const actions = {
    volumeFieldValueChanged:
      offerTakesReducer.actions.volumeFieldValueChangedEpic,
    totalFieldValueChanged:
      offerTakesReducer.actions.totalFieldValueChangedEpic,
    buyMax: offerTakesReducer.actions.buyMaxEpic,
    sellMax: offerTakesReducer.actions.sellMaxEpic
  };
  return { actions: bindActionCreators(actions, dispatch) };
}

export default connect(mapStateToProps, mapDispatchToProps)(
  reduxForm({ form: "takeOffer" })(
    CSSModules(OfferTakeForm, { styles, tableStyles }, { allowMultiple: true })
  )
);