AugurProject/augur-ui

View on GitHub
src/modules/forking/components/migrate-rep-form/migrate-rep-form.jsx

Summary

Maintainability
D
1 day
Test Coverage
/* eslint jsx-a11y/label-has-for: 0 */

import React, { Component } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { BigNumber, createBigNumber } from "utils/create-big-number";

import Input from "modules/common/components/input/input";
import FormStyles from "modules/common/less/form";
import { SCALAR } from "modules/markets/constants/market-types";
import { ExclamationCircle as InputErrorIcon } from "modules/common/components/icons";
import Styles from "modules/forking/components/migrate-rep-form/migrate-rep-form.styles";
import FormattedMigrationTotals from "modules/forking/components/migrate-rep-form/formatted-migration-totals";

export default class MigrateRepForm extends Component {
  static propTypes = {
    market: PropTypes.object.isRequired,
    updateState: PropTypes.func.isRequired,
    getForkMigrationTotals: PropTypes.func.isRequired,
    validations: PropTypes.object.isRequired,
    selectedOutcome: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
      .isRequired,
    selectedOutcomeName: PropTypes.string.isRequired,
    accountREP: PropTypes.string.isRequired,
    currentBlockNumber: PropTypes.number.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      inputRepAmount: "",
      inputSelectedOutcome: ""
    };

    this.focusTextInput = this.focusTextInput.bind(this);
    this.validateOutcome = this.validateOutcome.bind(this);
  }

  componentDidUpdate(prevProps) {
    const { updateState, validations, accountREP } = this.props;

    if (prevProps.accountREP !== accountREP) {
      const updatedValidations = { ...validations };

      if (this.state.inputRepAmount > accountREP) {
        updatedValidations.repAmount = `Migrate REP amount is greater then your available amount`;
      } else {
        delete updatedValidations.repAmount;
      }
      updateState({
        validations: updatedValidations
      });
    }
  }

  checkRepAmount(repAmount, updatedValidations) {
    if (repAmount === "" || repAmount == null || repAmount <= 0) {
      updatedValidations.repAmount = "The REP amount field is required.";
    } else if (repAmount > this.props.accountREP) {
      updatedValidations.repAmount = `Migrate REP amount is greater then your available amount`;
    } else {
      delete updatedValidations.repAmount;
    }
    return updatedValidations;
  }

  validateRepAmount(rawRepAmount, isMax) {
    const { updateState, validations } = this.props;
    const updatedValidations = { ...validations };

    let repAmount = rawRepAmount;

    if (repAmount !== "" && !BigNumber.isBigNumber(repAmount) && !isMax) {
      repAmount = createBigNumber(rawRepAmount);
      repAmount = repAmount.toNumber();
    }

    this.checkRepAmount(repAmount, updatedValidations);

    this.setState({
      inputRepAmount: repAmount
    });

    updateState({
      validations: updatedValidations,
      repAmount: repAmount.toString()
    });
  }

  validateOutcome(selectedOutcome, selectedOutcomeName, isMarketInValid) {
    const { updateState, validations } = this.props;
    const updatedValidations = { ...validations };
    updatedValidations.selectedOutcome = true;
    delete updatedValidations.err;
    let isInvalid = isMarketInValid;

    // outcome with id of 0.5 means invalid
    if (selectedOutcome === "0.5") isInvalid = true;

    this.checkRepAmount(this.state.inputRepAmount, updatedValidations);

    this.setState({
      inputSelectedOutcome: ""
    });

    updateState({
      validations: updatedValidations,
      selectedOutcome,
      selectedOutcomeName: selectedOutcomeName.toString(),
      isMarketInValid: isInvalid
    });
  }

  focusTextInput() {
    this.textInput.focus();
  }

  validateScalar(value, humanName, min, max, tickSize, isInvalid) {
    const { updateState, validations } = this.props;
    const updatedValidations = { ...validations };
    const { inputRepAmount } = this.state;

    if (value === "") {
      this.focusTextInput();
    }
    if (isInvalid) {
      delete updatedValidations.err;
      updatedValidations.selectedOutcome = true;
    } else {
      const minValue = parseFloat(min);
      const maxValue = parseFloat(max);
      const valueValue = parseFloat(value);
      const bnValue = createBigNumber(valueValue || 0);
      const bnTickSize = createBigNumber(tickSize);

      switch (true) {
        case value === "":
          updatedValidations.err = `The ${humanName} field is required.`;
          break;
        case isNaN(valueValue):
          updatedValidations.err = `The ${humanName} field is a number.`;
          break;
        case valueValue > maxValue || valueValue < minValue:
          updatedValidations.err = `Please enter a ${humanName} between ${min} and ${max}.`;
          break;
        case bnValue.mod(bnTickSize).gt("0"):
          updatedValidations.err = `The ${humanName} field must be a multiple of ${tickSize}.`;
          break;
        default:
          delete updatedValidations.err;
          updatedValidations.selectedOutcome = true;
          break;
      }
    }

    this.checkRepAmount(inputRepAmount, updatedValidations);

    this.setState({
      inputSelectedOutcome: value
    });

    updateState({
      validations: updatedValidations,
      selectedOutcome: value,
      selectedOutcomeName: value.toString(),
      isMarketInValid: isInvalid
    });
  }

  render() {
    const {
      accountREP,
      market,
      selectedOutcome,
      selectedOutcomeName,
      validations,
      getForkMigrationTotals,
      currentBlockNumber
    } = this.props;
    const {
      marketType,
      minPrice,
      maxPrice,
      tickSize,
      scalarDenomination
    } = market;
    const { inputSelectedOutcome, inputRepAmount } = this.state;

    return (
      <ul
        className={classNames(
          Styles.MigrateRepForm__fields,
          FormStyles.Form__fields
        )}
      >
        <li>
          <h3>
            Choose carefully. Migrating REP is an irreversible, one-way
            operation.
          </h3>
        </li>
        <li>
          <ul className={FormStyles["Form__radio-buttons--per-line"]}>
            <li>
              <label style={{ marginBottom: "1.5rem" }}>
                <span>Outcome</span>
              </label>
            </li>
            <FormattedMigrationTotals
              validateOutcome={this.validateOutcome}
              market={market}
              getForkMigrationTotals={getForkMigrationTotals}
              currentBlockNumber={currentBlockNumber}
              selectedOutcome={selectedOutcome}
            />
            {marketType === SCALAR && (
              <ul className={FormStyles["Form__radio-buttons--per-line-long"]}>
                <li>
                  <button
                    className={classNames({
                      [`${FormStyles.active}`]: inputSelectedOutcome !== ""
                    })}
                    onClick={e => {
                      this.validateScalar(
                        "",
                        "selected outcome",
                        minPrice,
                        maxPrice,
                        tickSize,
                        false
                      );
                    }}
                  />
                  <input
                    id="sr__input--outcome-scalar"
                    type="number"
                    ref={input => {
                      this.textInput = input;
                    }}
                    min={minPrice}
                    max={maxPrice}
                    step={tickSize}
                    placeholder={scalarDenomination}
                    value={inputSelectedOutcome}
                    className={classNames(FormStyles.Form__input, {
                      [`${FormStyles["Form__error--field"]}`]:
                        validations.hasOwnProperty("err") &&
                        validations.selectedOutcome
                    })}
                    onChange={e => {
                      this.validateScalar(
                        e.target.value,
                        "outcome",
                        minPrice,
                        maxPrice,
                        tickSize,
                        false
                      );
                    }}
                  />
                </li>
                <li>
                  {validations.hasOwnProperty("err") && (
                    <span className={FormStyles.Form__error}>
                      {InputErrorIcon}
                      {validations.err}
                    </span>
                  )}
                </li>
              </ul>
            )}
          </ul>
        </li>
        <li className={FormStyles["field--short"]}>
          <label>
            <span htmlFor="sr__input--repAmount">Migrate REP</span>
          </label>
          <ul className={FormStyles["Form__radio-buttons--per-line-inline"]}>
            <li>
              <Input
                id="sr__input--repAmount"
                type="number"
                min="0"
                className={classNames({
                  [`${FormStyles["Form__error--field"]}`]:
                    validations.hasOwnProperty("repAmount") &&
                    validations.selectedOutcome
                })}
                value={inputRepAmount}
                placeholder="0.0000 REP"
                onChange={value => this.validateRepAmount(value)}
                autoComplete="off"
                maxButton={Boolean(
                  selectedOutcomeName && selectedOutcomeName.length > 0
                )}
                onMaxButtonClick={() =>
                  this.validateRepAmount(accountREP, true)
                }
                darkMaxBtn
              />
            </li>
            <li>
              {validations.hasOwnProperty("repAmount") &&
                validations.repAmount.length && (
                  <span className={FormStyles["Form__error--even"]}>
                    {InputErrorIcon}
                    {validations.repAmount}
                  </span>
                )}
            </li>
          </ul>
        </li>
      </ul>
    );
  }
}