OSDN Git Service

shown the correct chain gas amount for chain tranctions.
[bytom/bytom-electron.git] / src / features / transactions / components / New / NormalTransactionForm.jsx
index 34d6763..d48e3b4 100644 (file)
 import {
   BaseNew,
-  FormSection,
   TextField,
   Autocomplete,
   ObjectSelectorField,
-  AmountUnitField,
-  AmountInputMask,
-  ErrorBanner,
-  SubmitIndicator
+  AmountField,
+  GasField
 } from 'features/shared/components'
 import {chainClient} from 'utility/environment'
 import {reduxForm} from 'redux-form'
 import React from 'react'
 import styles from './New.scss'
-import disableAutocomplete from 'utility/disableAutocomplete'
-import { normalizeBTMAmountUnit } from 'utility/buildInOutDisplay'
-
-const rangeOptions = [
-  {
-    label: 'Standard',
-    label_zh: '标准',
-    ratio: 1
-  },
-  {
-    label: 'Fast',
-    label_zh: '快速',
-    ratio: 2
-  },
-  {
-    label: 'Customize',
-    label_zh: '自定义',
-    value: ''
-  }
-]
-
-const btmID = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
+import  TxContainer  from './NewTransactionsContainer/TxContainer'
+import { btmID } from 'utility/environment'
+import actions from 'actions'
+import ConfirmModal from './ConfirmModal/ConfirmModal'
+import { balance , getAssetDecimal, normalTxActionBuilder} from '../../transactions'
+import {withNamespaces} from 'react-i18next'
 
 class NormalTxForm extends React.Component {
   constructor(props) {
     super(props)
     this.connection = chainClient().connection
     this.state = {
-      estimateGas:null
+      estimateGas:null,
+      chainGas: 0,
+      counter: 1
     }
 
     this.submitWithValidation = this.submitWithValidation.bind(this)
     this.disableSubmit = this.disableSubmit.bind(this)
+    this.addReceiverItem = this.addReceiverItem.bind(this)
   }
 
-  componentDidMount() {
-    this.props.fields.gas.type.onChange(rangeOptions[0].label)
-    this.props.fields.gas.price.onChange(rangeOptions[0].value)
-  }
-
-  disableSubmit(props) {
-    const hasValue = target => {
-      return !!(target && target.value)
-    }
-
-    return !( (this.state.estimateGas || hasValue(props.gas.price))&&
-      (hasValue(props.accountId) || hasValue(props.accountAlias)) &&
-      (hasValue(props.assetId) || hasValue(props.assetAlias)) &&
-      hasValue(props.address) && (hasValue(props.amount)) &&
-      hasValue(props.password)
-    )
+  disableSubmit() {
+    return !(this.state.estimateGas)
   }
 
   submitWithValidation(data) {
     return new Promise((resolve, reject) => {
       this.props.submitForm(Object.assign({}, data, {state: this.state, form: 'normalTx'}))
+        .then(() => {
+          this.props.closeModal()
+          this.props.destroyForm()
+        })
         .catch((err) => {
-          const response = {}
+          if(err.message !== 'PasswordWrong'){
+            this.props.closeModal()
+          }
+          reject({_error: err})
+        })
+    })
+  }
 
-          if (err.data) {
-            response.actions = []
+  confirmedTransaction(e, assetDecimal){
+    e.preventDefault()
+    this.props.showModal(
+      <ConfirmModal
+        cancel={this.props.closeModal}
+        onSubmit={this.submitWithValidation}
+        gas={this.state.estimateGas}
+        chainGas={this.state.chainGas}
+        btmAmountUnit={this.props.btmAmountUnit}
+        assetDecimal={assetDecimal}
+        asset={this.props.asset}
+      />
+    )
+  }
 
-            err.data.forEach((error) => {
-              response.actions[error.data.actionIndex] = {type: error}
-            })
-          }
+  addReceiverItem() {
+    const counter = this.state.counter
+    this.props.fields.receivers.addField({
+      id: counter
+    })
+    this.setState({
+      counter: counter+1,
+      estimateGas: null
+    })
+  }
 
-          response['_error'] = err
-          return reject(response)
-        })
+  removeReceiverItem(index) {
+    const receiver = this.props.fields.receivers
+    const promise = new Promise(function(resolve, reject) {
+      try {
+        receiver.removeField(index)
+      } catch (err) {
+        reject(err)
+      }
+      resolve()
     })
+
+    promise.then(() =>  this.estimateNormalTransactionGas())
   }
 
   estimateNormalTransactionGas() {
-    const transaction = this.props.fields
-    const address = transaction.address.value
-    const amount = transaction.amount.value
-    const accountAlias = transaction.accountAlias.value
-    const accountId = transaction.accountId.value
-    const assetAlias = transaction.assetAlias.value
-    const assetId = transaction.assetId.value
+    const transaction = this.props.values
+    const accountAlias = transaction.accountAlias
+    const accountId = transaction.accountId
+    const assetAlias = transaction.assetAlias
+    const assetId = transaction.assetId
+    const receivers = transaction.receivers
+    const addresses = receivers.map(x => x.address)
+    const amounts = receivers.map(x => Number(x.amount))
+
+    const {t, i18n} = this.props
 
     const noAccount = !accountAlias && !accountId
     const noAsset = !assetAlias && !assetId
 
-    if (!address || !amount || noAccount || noAsset) {
+    if ( addresses.includes('') || amounts.includes(NaN)|| amounts.includes(0)|| noAccount || noAsset) {
       this.setState({estimateGas: null})
       return
     }
 
-    const spendAction = {
-      accountAlias,
-      accountId,
-      assetAlias,
-      assetId,
-      amount: Number(amount),
-      type: 'spend_account'
-    }
-    const receiveAction = {
-      address,
-      assetAlias,
-      assetId,
-      amount: Number(amount),
-      type: 'control_address'
-    }
+    const isBTM = (assetAlias==='BTM') || (assetId === btmID)
 
-    const gasAction = {
-      accountAlias,
-      accountId,
-      assetAlias: 'BTM',
-      amount: Math.pow(10, 7),
-      type: 'spend_account'
-    }
+    const actions = normalTxActionBuilder(transaction, Math.pow(10, 7), 'amount' )
 
-    const actions = [spendAction, receiveAction, gasAction]
     const body = {actions, ttl: 1}
-    this.connection.request('/build-transaction', body).then(resp => {
-      if (resp.status === 'fail') {
-        this.setState({estimateGas: null})
-        this.props.showError(new Error(resp.msg))
-        return
-      }
-
-      return this.connection.request('/estimate-transaction-gas', {
-        transactionTemplate: resp.data
-      }).then(resp => {
-        if (resp.status === 'fail') {
-          this.setState({estimateGas: null})
-          this.props.showError(new Error(resp.msg))
-          return
-        }
-        this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
+    if(isBTM){
+      this.connection.request('/build-chain-transactions', body).then(resp => {
+        return this.connection.request('/estimate-chain-transaction-gas', {
+          transactionTemplates: resp.data
+        }).then(resp => {
+          this.setState({
+            estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000,
+            chainGas: Math.ceil(resp.data.chainTxNeu/100000)*100000
+          })
+        }).catch(err =>{
+          throw err
+        })
+      }).catch(err=>{
+        this.setState({estimateGas: null, address: null})
+        const errorMsg =  err.code && i18n.exists(`btmError.${err.code}`) && t(`btmError.${err.code}`) || err.msg
+        this.props.showError(new Error(errorMsg))
       })
-    })
+    }else{
+      this.connection.request('/build-transaction', body).then(resp => {
+        return this.connection.request('/estimate-transaction-gas', {
+          transactionTemplate: resp.data
+        }).then(resp => {
+          this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
+        }).catch(err =>{
+          throw err
+        })
+      }).catch(err=>{
+        this.setState({estimateGas: null, address: null})
+        const errorMsg =  err.code && i18n.exists(`btmError.${err.code}`) && t(`btmError.${err.code}`) || err.msg
+        this.props.showError(new Error(errorMsg))
+      })
+    }
   }
 
   render() {
     const {
-      fields: {accountId, accountAlias, assetId, assetAlias, address, amount, gas, password},
+      fields: {accountId, accountAlias, assetId, assetAlias, receivers, gasLevel},
       error,
-      handleSubmit,
       submitting
     } = this.props
-    const lang = this.props.lang;
-    [amount, accountAlias, accountId, assetAlias, assetId].forEach(key => {
+    const t = this.props.t;
+    [accountAlias, accountId, assetAlias, assetId].forEach(key => {
       key.onBlur = this.estimateNormalTransactionGas.bind(this)
+    });
+    (receivers.map(receiver => receiver.amount)).forEach(amount =>{
+      amount.onBlur = this.estimateNormalTransactionGas.bind(this)
     })
 
-    let submitLabel = lang === 'zh' ? '提交交易' : 'Submit transaction'
+    let submitLabel = t('transaction.new.submit')
 
-    const gasOnChange = event => {
-      gas.type.onChange(event)
-
-      const range = rangeOptions.find(item => item.label === event.target.value)
-      gas.price.onChange(range.value)
-    }
-    const assetDecimal = this.props.assetDecimal(this.props.fields) || 0
+    const assetDecimal = getAssetDecimal(this.props.fields, this.props.asset) || 0
 
     const showAvailableBalance = (accountAlias.value || accountId.value) &&
       (assetAlias.value || assetId.value)
 
-    const availableBalance = this.props.balanceAmount(this.props.fields, assetDecimal)
+    const availableBalance = balance(this.props.values, assetDecimal, this.props.balances, this.props.btmAmountUnit)
 
     const showBtmAmountUnit = (assetAlias.value === 'BTM' || assetId.value === btmID)
 
     return (
-        <form
-          onSubmit={handleSubmit(this.submitWithValidation)} {...disableAutocomplete}
-          onKeyDown={(e) => { this.props.handleKeyDown(e, handleSubmit(this.submitWithValidation), this.disableSubmit(this.props.fields)) }}>
-          <FormSection title={lang === 'zh' ? '简单交易' : 'Normal Trasaction'}>
-            <label className={styles.title}>{lang === 'zh' ? '从' : 'From'}</label>
-            <div className={styles.main}>
-              <ObjectSelectorField
-                key='account-selector-field'
-                keyIndex='normaltx-account'
-                lang={lang}
-                title={lang === 'zh' ? '账户' : 'Account'}
-                aliasField={Autocomplete.AccountAlias}
-                fieldProps={{
-                  id: accountId,
-                  alias: accountAlias
-                }}
-              />
+      <TxContainer
+        error={error}
+        onSubmit={e => this.confirmedTransaction(e, assetDecimal)}
+        submitting={submitting}
+        submitLabel= {submitLabel}
+        disabled={this.disableSubmit()}
+        className={styles.container}
+      >
+        <div className={styles.borderBottom}>
+          <label className={styles.title}>{t('transaction.normal.from')}</label>
+          <div className={`${styles.mainBox} `}>
+            <ObjectSelectorField
+              key='account-selector-field'
+              keyIndex='normaltx-account'
+              title={t('form.account')}
+              aliasField={Autocomplete.AccountAlias}
+              fieldProps={{
+                id: accountId,
+                alias: accountAlias
+              }}
+            />
+            <div>
               <ObjectSelectorField
                 key='asset-selector-field'
                 keyIndex='normaltx-asset'
-                lang={lang}
-                title={lang === 'zh' ? '资产' : 'Asset'}
+                title={ t('form.asset')}
                 aliasField={Autocomplete.AssetAlias}
                 fieldProps={{
                   id: assetId,
@@ -208,150 +210,149 @@ class NormalTxForm extends React.Component {
                 }}
               />
               {showAvailableBalance && availableBalance &&
-              <small className={styles.balanceHint}>{availableBalance} {lang === 'zh' ? '可用' : 'available'} </small>}
+                <small className={styles.balanceHint}>{t('transaction.normal.availableBalance')} {availableBalance}</small>}
             </div>
-
-            <label className={styles.title}>{lang === 'zh' ? '至' : 'To'}</label>
-            <div className={styles.main}>
-              <TextField title={lang === 'zh' ? '地址' : 'Address'} fieldProps={{
-                ...address,
-                onBlur: (e) => {
-                  address.onBlur(e)
-                  this.estimateNormalTransactionGas()
-                },
-              }}/>
-              {!showBtmAmountUnit &&
-              <AmountInputMask title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount} decimal={assetDecimal}
-              />}
-              {showBtmAmountUnit &&
-              <AmountUnitField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount}/>
-              }
-            </div>
-
-            <label className={styles.title}>Gas</label>
-            <table>
-              <tbody className={styles.optionsBtnContianer}>
-                {rangeOptions.map((option) =>
-                  <tr className={styles.optionsBtn}>
-                    <td className={styles.optionsLabel}>
-                      <label>
-                        <input type='radio'
-                               {...gas.type}
-                               onChange={gasOnChange}
-                               value={option.label}
-                               checked={option.label == gas.type.value}
-                        />
-                        {lang === 'zh' ? option.label_zh : option.label}
-                      </label>
-                    </td>
-                    <td>
-                      {
-                        option.label == gas.type.value && option.label !== 'Customize'
-                        && this.state.estimateGas && ((lang === 'zh' ? '估算' : 'estimated') + '   ' + normalizeBTMAmountUnit(btmID,
-                          option.ratio * this.state.estimateGas,
-                          this.props.btmAmountUnit))
-                      }
-                      {
-                        option.label === 'Customize' && gas.type.value === 'Customize' &&
-                        <div>
-                          <AmountUnitField
-                            autoFocus={true}
-                            fieldProps={gas.price}
-                            placeholder='Enter gas'/>
-                        </div>
-                      }
-                    </td>
-                  </tr>
-                )}
-              </tbody>
-            </table>
-
-            <label className={styles.title}>{lang === 'zh' ? '密码' : 'Password'}</label>
-            <TextField placeholder={lang === 'zh' ? '请输入密码' : 'Please enter the password'} fieldProps={password}
-                       autoFocus={false} type={'password'}/>
-          </FormSection>
-
-          <FormSection className={styles.submitSection}>
-            {error &&
-            <ErrorBanner
-              title='Error submitting form'
-              error={error} />}
-
-            <div className={styles.submit}>
-              <button type='submit' className='btn btn-primary' disabled={submitting || this.disableSubmit(this.props.fields)}>
-                {submitLabel ||  ( lang === 'zh' ? '提交' : 'Submit' )}
-              </button>
-
-              {submitting &&
-              <SubmitIndicator />
-              }
-            </div>
-          </FormSection>
-        </form>
+          </div>
+
+          <label className={styles.title}>{t('transaction.normal.to')}</label>
+
+          <div className={styles.mainBox}>
+            {receivers.map((receiver, index) =>
+              <div
+                className={this.props.tutorialVisible? styles.tutorialItem: styles.subjectField}
+                key={receiver.id.value}>
+                <TextField title={t('form.address')} fieldProps={{
+                  ...receiver.address,
+                  onBlur: (e) => {
+                    receiver.address.onBlur(e)
+                    this.estimateNormalTransactionGas()
+                  },
+                }}/>
+
+                <AmountField
+                  isBTM={showBtmAmountUnit}
+                  title={t('form.amount')}
+                  fieldProps={receiver.amount}
+                  decimal={assetDecimal}
+                />
+
+                <button
+                  className={`btn btn-danger btn-xs ${styles.deleteButton}`}
+                  tabIndex='-1'
+                  type='button'
+                  onClick={() => this.removeReceiverItem(index)}
+                >
+                  {t('commonWords.remove')}
+                </button>
+              </div>
+            )}
+            <button
+              type='button'
+              className='btn btn-default'
+              onClick={this.addReceiverItem}
+            >
+              {t('commonWords.addField')}
+            </button>
+          </div>
+
+          <label className={styles.title}>{t('transaction.normal.selectFee')}</label>
+          <div className={styles.txFeeBox}>
+            <GasField
+              gas={this.state.estimateGas}
+              chainGas={this.state.chainGas}
+              fieldProps={gasLevel}
+              btmAmountUnit={this.props.btmAmountUnit}
+            />
+            <span className={styles.feeDescription}> {t('transaction.normal.feeDescription')}</span>
+          </div>
+        </div>
+      </TxContainer>
     )
   }
 }
 
 const validate = (values, props) => {
   const errors = {gas: {}}
-  const lang = props.lang
+  const t = props.t
 
   // Numerical
   if (values.amount && !/^\d+(\.\d+)?$/i.test(values.amount)) {
-    errors.amount = ( lang === 'zh' ? '请输入数字' : 'Invalid amount type' )
+    errors.amount = ( t('errorMessage.amountError') )
   }
   return errors
 }
 
-const asyncValidate = (values) => {
-  return new Promise((resolve, reject) => {
-    const address = values.address
-    chainClient().accounts.validateAddresses(address)
-      .then(
-        (resp) => {
-          if(!resp.data.valid){
-            reject({ address: 'invalid address'})
-          }else {
-            resolve()
-          }
-        }
-      ).catch((err) => {
-        reject({ address: err })
-      })
+const asyncValidate = (values, dispatch, props) => {
+  const errors = []
+  const promises = []
+
+  values.receivers.forEach((receiver, idx) => {
+    const address = values.receivers[idx].address
+    if ( !address || address.length === 0)
+      promises.push(Promise.resolve())
+    else{
+      promises.push(
+        chainClient().accounts.validateAddresses(address)
+          .then(
+            (resp) => {
+              if (!resp.data.valid) {
+                errors[idx] = {address: props.t('errorMessage.addressError')}
+              }
+              return {}
+            }
+          ))
+    }
+  })
+
+  return Promise.all(promises).then(() => {
+    if (errors.length > 0) throw {
+      receivers: errors
+    }
+    return {}
   })
 }
 
+const mapDispatchToProps = (dispatch) => ({
+  showError: err => dispatch({type: 'ERROR', payload: err}),
+  closeModal: () => dispatch(actions.app.hideModal),
+  showModal: (body) => dispatch(actions.app.showModal(
+    body,
+    actions.app.hideModal,
+    null,
+    {
+      dialog: true,
+      noCloseBtn: true
+    }
+  )),
+  ...BaseNew.mapDispatchToProps('transaction')(dispatch)
+})
 
-export default BaseNew.connect(
+export default withNamespaces('translations') (BaseNew.connect(
   BaseNew.mapStateToProps('transaction'),
-  (dispatch) => ({
-    showError: err => dispatch({type: 'ERROR', payload: err}),
-    ...BaseNew.mapDispatchToProps('transaction')(dispatch)
-  }),
+  mapDispatchToProps,
   reduxForm({
     form: 'NormalTransactionForm',
     fields: [
       'accountAlias',
       'accountId',
-      'amount',
       'assetAlias',
       'assetId',
-      'gas',
-      'gas.type',
-      'gas.price',
-      'address',
-      'submitAction',
-      'password'
+      'receivers[].id',
+      'receivers[].amount',
+      'receivers[].address',
+      'gasLevel',
     ],
     asyncValidate,
-    asyncBlurFields: [ 'address'],
+    asyncBlurFields: ['receivers[].address'],
     validate,
     touchOnChange: true,
     initialValues: {
-      submitAction: 'submit',
+      gasLevel: '1',
+      receivers:[{
+        id: 0,
+        amount:'',
+        address:''
+      }]
     },
-  }
-  )(NormalTxForm)
-)
-
-
+  })(NormalTxForm)
+))