OasisDEX/oasis-react

View on GitHub
src/containers/OasisOfferMakeForm.jsx

Summary

Maintainability
A
2 hrs
Test Coverage
F
14%
import React from "react";
import { PropTypes } from "prop-types";
// import throttle from 'lodash/throttle';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Field, reduxForm } from "redux-form/immutable";

import offerMakes from "../store/selectors/offerMakes";
import {
  MAKE_BUY_OFFER,
  MAKE_SELL_OFFER,
  SETMAXBTN_HIDE_DELAY_MS
} from "../constants";
import offerMakesReducer from "../store/reducers/offerMakes";
import tokens from "../store/selectors/tokens";
import balances from "../store/selectors/balances";
import {
  greaterThanZeroValidator,
  numericFormatValidator
} from "../utils/forms/offers";

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

import styles from "./OasisOfferMakeForm.scss";
import tableStyles from "../styles/modules/_table.scss";
import CSSModules from "react-css-modules";
import OasisVolumeIsGreaterThanUserBalance from "../components/OasisVolumeIsGreaterThanUserBalance";
// import { formatAmount, PRICE_DECIMAL } from '../utils/tokens/pair';
// import isNumeric from '../utils/numbers/isNumeric';
import MaskedTokenAmountInput from "../components/MaskedTokenAmountInput";
import platform from "../store/selectors/platform";
import isNumericAndGreaterThanZero from "../utils/numbers/isNumericAndGreaterThanZero";

const propTypes = PropTypes && {
  // activeOfferMakeOfferData: ImmutablePropTypes.map.isRequired,
  offerMakeType: PropTypes.string.isRequired,
  // isUserTokenBalanceSufficient: PropTypes.bool.isRequired,
  activeBaseTokenBalance: PropTypes.string,
  activeQuoteTokenBalance: PropTypes.string,
  disableForm: PropTypes.bool,
  shouldFormUpdate: PropTypes.bool.isRequired,
  onFormChange: PropTypes.func
};

const defaultProps = {
  shouldFormUpdate: true
};

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

export class OfferMakeForm extends React.Component {
  constructor(props) {
    super(props);
    this.componentIsUnmounted = false;
    this.currentSetMaxTimeout = null;

    this.state = {
      showMaxButton: false
    };

    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.onPriceFieldChange = this.onPriceFieldChange.bind(this);
    this.onTotalFieldSectionBlur = this.onTotalFieldSectionBlur.bind(this);
    this.onTotalFieldSectionFocus = this.onTotalFieldSectionFocus.bind(this);
    this.onFormChange = this.onFormChange.bind(this);
    // this.estimateGas = throttle(this.props.estimateGas || (() => null), 500);
  }

  onSetBuyMax() {
    this.props.actions.buyMax(this.props.offerMakeType);
  }

  onSetSellMax() {
    this.props.actions.sellMax(this.props.offerMakeType);
  }

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

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

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

  setMaxButton() {
    if (false === this.state.showMaxButton) {
      return null;
    }
    const {
      currentFormValues = {},
      disableForm,
      globalFormLock,
      activeBaseTokenBalance,
      activeQuoteTokenBalance
    } = this.props;
    switch (this.props.offerMakeType) {
      case MAKE_BUY_OFFER:
        return (
          <OasisButton
            className={tableStyles.inputBtn}
            disabled={
              greaterThanZeroValidator(currentFormValues.price) ||
              disableForm ||
              globalFormLock ||
              !isNumericAndGreaterThanZero(activeQuoteTokenBalance)
            }
            type="button"
            color="success"
            size="xs"
            onClick={this.onSetBuyMax}
            onFocus={this.onSetMaxFocus}
            onBlur={this.onSetMaxBlur}
          >
            Buy max
          </OasisButton>
        );
      case MAKE_SELL_OFFER:
        return (
          <OasisButton
            className={tableStyles.inputBtn}
            disabled={
              greaterThanZeroValidator(currentFormValues.price) ||
              disableForm ||
              globalFormLock ||
              !isNumericAndGreaterThanZero(activeBaseTokenBalance)
            }
            type="button"
            color="danger"
            size="xs"
            onClick={this.onSetSellMax}
            onFocus={this.onSetMaxFocus}
            onBlur={this.onSetMaxBlur}
          >
            Sell max
          </OasisButton>
        );
    }
    // }
  }

  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(
        () => {
          if (this.componentIsUnmounted === false) {
            this.setState({ showMaxButton: false });
          }
        },
        SETMAXBTN_HIDE_DELAY_MS
      );
    }
  }

  renderPriceField() {
    const { disableForm, globalFormLock } = this.props;
    return (
      <Field
        autoComplete="off"
        name="price"
        component={MaskedTokenAmountInput}
        placeholder={0}
        disabled={disableForm || globalFormLock}
        type="text"
        onChange={this.onPriceFieldChange}
      />
    );
  }

  renderAmountField() {
    const { currentFormValues = {}, disableForm, globalFormLock } = this.props;
    return (
      <Field
        autoComplete="off"
        name="volume"
        component={MaskedTokenAmountInput}
        type="text"
        validate={validateVolume}
        min={0}
        placeholder={0}
        disabled={
          greaterThanZeroValidator(currentFormValues.price) ||
          disableForm ||
          globalFormLock
        }
        onChange={this.onVolumeFieldChange}
      />
    );
  }

  renderTotalField() {
    const { currentFormValues = {}, disableForm, globalFormLock } = this.props;
    return (
      <div
        className={tableStyles.inputGroupEventHandlerChild}
        onFocus={this.onTotalFieldSectionFocus}
        onBlur={this.onTotalFieldSectionBlur}
      >
        <Field
          autoComplete="off"
          min={0}
          onChange={this.onTotalFieldChange}
          name="total"
          component={MaskedTokenAmountInput}
          type="text"
          validate={validateTotal}
          placeholder={0}
          disabled={
            greaterThanZeroValidator(currentFormValues.price) ||
            // greaterThanZeroValidator(currentFormValues.volume) ||
            disableForm ||
            globalFormLock
          }
        />
      </div>
    );
  }

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

  render() {
    const {
      baseToken,
      quoteToken,
      handleSubmit,
      isUserTokenBalanceSufficient
    } = this.props;

    return (
      <form onSubmit={handleSubmit} onChange={this.onFormChange}>
        <table className={tableStyles.table}>
          <tbody>
            <tr>
              <th>Price</th>
              <td className={tableStyles.withInput}>
                {this.renderPriceField()}
              </td>
              <td className={tableStyles.currency}> {quoteToken}</td>
            </tr>
            <tr>
              <th>Amount</th>
              <td className={tableStyles.withInput}>
                {this.renderAmountField()}
              </td>
              <td className={tableStyles.currency}>
                {baseToken}
                <div>
                  {isUserTokenBalanceSufficient && (
                    <OasisVolumeIsGreaterThanUserBalance
                      offerMax={isUserTokenBalanceSufficient}
                    />
                  )}
                </div>
              </td>
            </tr>
            <tr>
              <th>Total</th>
              <td
                className={tableStyles.withInput}
                onBlur={this.onTotalFieldSectionBlur}
                onFocus={this.onTotalFieldSectionFocus}
              >
                <div className={tableStyles.inputGroup}>
                  {this.setMaxButton()}
                  {this.renderTotalField()}
                </div>
              </td>
              <td className={tableStyles.currency}>{quoteToken}</td>
            </tr>
          </tbody>
        </table>
      </form>
    );
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.shouldFormUpdate && !nextProps.globalFormLock) {
      return nextState !== this.state || nextProps !== this.props;
    } else {
      return false;
    }
  }
  componentWillUnmount() {
    this.componentIsUnmounted = true;
  }
}

OfferMakeForm.displayName = "OfferMakeForm";
OfferMakeForm.propTypes = propTypes;
OfferMakeForm.defaultProps = defaultProps;

export function mapStateToProps(state, props) {
  return {
    currentFormValues: offerMakes.currentFormValues(state)(props.form),
    activeTradingPairPrecision: tokens.precision(state),
    hasSufficientTokenAmount: offerMakes.hasSufficientTokenAmount(state)(props.offerMakeType),
    activeBaseTokenBalance: balances.activeBaseTokenBalance(state),
    activeQuoteTokenBalance: balances.activeQuoteTokenBalance(state),
    globalFormLock: platform.globalFormLock(state)
  };
}

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

export default connect(mapStateToProps, mapDispatchToProps)(
  reduxForm({})(
    CSSModules(OfferMakeForm, { styles, tableStyles }, { allowMultiple: true })
  )
);