OSDN Git Service

trigger the estimate fee on the button click and the ammount onblur
[bytom/bytom-dashboard.git] / src / features / transactions / components / New / NormalTransactionForm.jsx
1 import {
2   BaseNew,
3   FormSection,
4   TextField,
5   Autocomplete,
6   ObjectSelectorField,
7   AmountUnitField,
8   AmountInputMask,
9   ErrorBanner,
10   GasField
11 } from 'features/shared/components'
12 import {chainClient} from 'utility/environment'
13 import {reduxForm} from 'redux-form'
14 import React from 'react'
15 import styles from './New.scss'
16 import disableAutocomplete from 'utility/disableAutocomplete'
17 import { btmID } from 'utility/environment'
18 import actions from 'actions'
19 import ConfirmModal from './ConfirmModal/ConfirmModal'
20
21
22 class NormalTxForm extends React.Component {
23   constructor(props) {
24     super(props)
25     this.connection = chainClient().connection
26     this.state = {
27       estimateGas:null,
28       counter: 1
29     }
30
31     this.submitWithValidation = this.submitWithValidation.bind(this)
32     this.disableSubmit = this.disableSubmit.bind(this)
33     this.addReceiverItem = this.addReceiverItem.bind(this)
34   }
35
36   disableSubmit(props) {
37     const hasValue = target => {
38       return !!(target && target.value)
39     }
40
41     return !( (this.state.estimateGas) &&
42       (hasValue(props.accountId) || hasValue(props.accountAlias)) &&
43       (hasValue(props.assetId) || hasValue(props.assetAlias)) &&
44       hasValue(props.address) && (hasValue(props.amount))
45     )
46   }
47
48   submitWithValidation(data) {
49     return new Promise((resolve, reject) => {
50       this.props.submitForm(Object.assign({}, data, {state: this.state, form: 'normalTx'}))
51         .then(() => {
52           this.props.closeModal()
53           this.props.destroyForm()
54         })
55         .catch((err) => {
56           if(err.message !== 'PasswordWrong'){
57             this.props.closeModal()
58           }
59           reject({_error: err})
60         })
61     })
62   }
63
64   confirmedTransaction(e, assetDecimal){
65     e.preventDefault()
66     this.props.showModal(
67       <ConfirmModal
68         cancel={this.props.closeModal}
69         onSubmit={this.submitWithValidation}
70         gas={this.state.estimateGas}
71         btmAmountUnit={this.props.btmAmountUnit}
72         assetDecimal={assetDecimal}
73         asset={this.props.asset}
74         lang = {this.props.lang}
75       />
76     )
77   }
78
79   addReceiverItem() {
80     const counter = this.state.counter
81     this.props.fields.receivers.addField({
82       id: counter
83     })
84     this.setState({
85       counter: counter+1,
86       estimateGas: null
87     })
88   }
89
90   removeReceiverItem(index) {
91     const receiver = this.props.fields.receivers
92     const promise = new Promise(function(resolve, reject) {
93       try {
94         receiver.removeField(index)
95         resolve()
96       } catch (err) {
97         reject(err)
98       }
99     })
100
101     promise.then(
102       () =>  this.estimateNormalTransactionGas()
103     )
104   }
105
106   estimateNormalTransactionGas() {
107     const transaction = this.props.fields
108     const accountAlias = transaction.accountAlias.value
109     const accountId = transaction.accountId.value
110     const assetAlias = transaction.assetAlias.value
111     const assetId = transaction.assetId.value
112     const receivers = transaction.receivers
113     const addresses = receivers.map(x => x.address.value)
114     const amounts = receivers.map(x => Number(x.amount.value))
115
116     const noAccount = !accountAlias && !accountId
117     const noAsset = !assetAlias && !assetId
118
119     if ( addresses.includes('') || amounts.includes(0)|| noAccount || noAsset) {
120       this.setState({estimateGas: null})
121       return
122     }
123
124     const totalAmount = amounts.reduce((prev, next) => prev + next)
125
126     const spendAction = {
127       accountAlias,
128       accountId,
129       assetAlias,
130       assetId,
131       amount: totalAmount,
132       type: 'spend_account'
133     }
134
135     const gasAction = {
136       accountAlias,
137       accountId,
138       assetAlias: 'BTM',
139       amount: Math.pow(10, 7),
140       type: 'spend_account'
141     }
142
143     const actions = [spendAction, gasAction]
144     receivers.forEach((receiver)=>{
145       actions.push(
146         {
147           address: receiver.address.value,
148           assetAlias,
149           assetId,
150           amount:Number(receiver.amount.value),
151           type: 'control_address'
152         }
153       )
154     })
155
156     const body = {actions, ttl: 1}
157     this.connection.request('/build-transaction', body).then(resp => {
158       if (resp.status === 'fail') {
159         this.setState({estimateGas: null})
160         this.props.showError(new Error(resp.msg))
161         return
162       }
163
164       return this.connection.request('/estimate-transaction-gas', {
165         transactionTemplate: resp.data
166       }).then(resp => {
167         if (resp.status === 'fail') {
168           this.setState({estimateGas: null})
169           this.props.showError(new Error(resp.msg))
170           return
171         }
172         this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
173       })
174     })
175   }
176
177   render() {
178     const {
179       fields: {accountId, accountAlias, assetId, assetAlias, receivers, gasLevel},
180       error,
181       submitting
182     } = this.props
183     const lang = this.props.lang;
184     [accountAlias, accountId, assetAlias, assetId].forEach(key => {
185       key.onBlur = this.estimateNormalTransactionGas.bind(this)
186     });
187     (receivers.map(receiver => receiver.amount)).forEach(amount =>{
188       amount.onBlur = this.estimateNormalTransactionGas.bind(this)
189     })
190
191     let submitLabel = lang === 'zh' ? '提交交易' : 'Submit transaction'
192
193     const assetDecimal = this.props.assetDecimal(this.props.fields) || 0
194
195     const showAvailableBalance = (accountAlias.value || accountId.value) &&
196       (assetAlias.value || assetId.value)
197
198     const availableBalance = this.props.balanceAmount(this.props.fields, assetDecimal)
199
200     const showBtmAmountUnit = (assetAlias.value === 'BTM' || assetId.value === btmID)
201
202     return (
203         <form
204           className={styles.container}
205           onSubmit={e => this.confirmedTransaction(e, assetDecimal)}
206           {...disableAutocomplete}
207         >
208           <div>
209             <label className={styles.title}>{lang === 'zh' ? '从' : 'From'}</label>
210             <div className={`${styles.mainBox} ${styles.item}`}>
211               <ObjectSelectorField
212                 key='account-selector-field'
213                 keyIndex='normaltx-account'
214                 lang={lang}
215                 title={lang === 'zh' ? '账户' : 'Account'}
216                 aliasField={Autocomplete.AccountAlias}
217                 fieldProps={{
218                   id: accountId,
219                   alias: accountAlias
220                 }}
221               />
222               <div>
223                 <ObjectSelectorField
224                   key='asset-selector-field'
225                   keyIndex='normaltx-asset'
226                   lang={lang}
227                   title={lang === 'zh' ? '资产' : 'Asset'}
228                   aliasField={Autocomplete.AssetAlias}
229                   fieldProps={{
230                     id: assetId,
231                     alias: assetAlias
232                   }}
233                 />
234                 {showAvailableBalance && availableBalance &&
235                 <small className={styles.balanceHint}>{lang === 'zh' ? '可用余额:' : 'Available balance:'} {availableBalance}</small>}
236               </div>
237             </div>
238
239             <label className={styles.title}>{lang === 'zh' ? '至' : 'To'}</label>
240
241             <div className={styles.mainBox}>
242             {receivers.map((receiver, index) =>
243               <div
244                 className={styles.item}
245                 key={receiver.id.value}>
246                 <TextField title={lang === 'zh' ? '地址' : 'Address'} fieldProps={{
247                   ...receiver.address,
248                   onBlur: (e) => {
249                     receiver.address.onBlur(e)
250                     this.estimateNormalTransactionGas()
251                   },
252                 }}/>
253
254                 {!showBtmAmountUnit &&
255                 <AmountInputMask title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={receiver.amount} decimal={assetDecimal}
256                 />}
257                 {showBtmAmountUnit &&
258                 <AmountUnitField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={receiver.amount}/>
259                 }
260                 {index===0 ?
261                   <a href='#' className='btn btn-sm ' onClick={this.addReceiverItem}>+</a> :
262                   <a href='#' className='btn btn-sm btn-danger' onClick={() => this.removeReceiverItem(index)}>-</a>
263                 }
264
265               </div>
266             )}
267             </div>
268
269             <label className={styles.title}>{lang === 'zh' ? '选择手续费' : 'Select Fee'}</label>
270             <GasField
271               gas={this.state.estimateGas}
272               fieldProps={gasLevel}
273               btmAmountUnit={this.props.btmAmountUnit}
274             />
275           </div>
276
277           <FormSection className={styles.submitSection}>
278             {error && error.message !== 'PasswordWrong' &&
279             <ErrorBanner
280               title='Error submitting form'
281               error={error} />}
282
283             <div className={styles.submit}>
284               <button type='submit' className='btn btn-primary'
285                       disabled={submitting || this.disableSubmit(this.props.fields)}>
286                 {submitLabel}
287               </button>
288             </div>
289           </FormSection>
290         </form>
291     )
292   }
293 }
294
295 const validate = (values, props) => {
296   const errors = {gas: {}}
297   const lang = props.lang
298
299   // Numerical
300   if (values.amount && !/^\d+(\.\d+)?$/i.test(values.amount)) {
301     errors.amount = ( lang === 'zh' ? '请输入数字' : 'Invalid amount type' )
302   }
303   return errors
304 }
305
306 const asyncValidate = (values) => {
307   const errors = []
308   const promises = []
309
310   values.receivers.forEach((receiver, idx) => {
311     const address = values.receivers[idx].address
312     if ( !address || address.length === 0)
313       promises.push(Promise.resolve())
314     else{
315       promises.push(
316         chainClient().accounts.validateAddresses(address)
317           .then(
318             (resp) => {
319               if (!resp.data.valid) {
320                 errors[idx] = {address: 'invalid address'}
321               }
322               return {}
323             }
324           ))
325     }
326   })
327
328   return Promise.all(promises).then(() => {
329     if (errors.length > 0) throw {
330       receivers: errors
331     }
332     return {}
333   })
334 }
335
336
337 export default BaseNew.connect(
338   BaseNew.mapStateToProps('transaction'),
339   (dispatch) => ({
340     showError: err => dispatch({type: 'ERROR', payload: err}),
341     closeModal: () => dispatch(actions.app.hideModal),
342     showModal: (body) => dispatch(actions.app.showModal(
343       body,
344       actions.app.hideModal,
345       null,
346       {
347         dialog: true,
348         noCloseBtn: true
349       }
350     )),
351     ...BaseNew.mapDispatchToProps('transaction')(dispatch)
352   }),
353   reduxForm({
354     form: 'NormalTransactionForm',
355     fields: [
356       'accountAlias',
357       'accountId',
358       'assetAlias',
359       'assetId',
360       'receivers[].id',
361       'receivers[].amount',
362       'receivers[].address',
363       'gasLevel',
364     ],
365     asyncValidate,
366     asyncBlurFields: ['receivers[].address'],
367     validate,
368     touchOnChange: true,
369     initialValues: {
370       gasLevel: '1',
371       receivers:[{
372         id: 0,
373         amount:'',
374         address:''
375       }]
376     },
377   })(NormalTxForm)
378 )
379
380