OSDN Git Service

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