OSDN Git Service

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