OSDN Git Service

43d7dd5a13ceec3e9648e89a04c1fe0d40c2eae1
[bytom/bytom-electron.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   SubmitIndicator
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 { normalizeBTMAmountUnit } from 'utility/buildInOutDisplay'
18
19 const rangeOptions = [
20   {
21     label: 'Standard',
22     label_zh: '标准',
23     ratio: 1
24   },
25   {
26     label: 'Fast',
27     label_zh: '快速',
28     ratio: 2
29   },
30   {
31     label: 'Customize',
32     label_zh: '自定义',
33     value: ''
34   }
35 ]
36
37 const btmID = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
38
39 class NormalTxForm extends React.Component {
40   constructor(props) {
41     super(props)
42     this.connection = chainClient().connection
43     this.state = {
44       estimateGas:null
45     }
46
47     this.submitWithValidation = this.submitWithValidation.bind(this)
48     this.disableSubmit = this.disableSubmit.bind(this)
49   }
50
51   componentDidMount() {
52     this.props.fields.gas.type.onChange(rangeOptions[0].label)
53     this.props.fields.gas.price.onChange(rangeOptions[0].value)
54   }
55
56   disableSubmit(props) {
57     const hasValue = target => {
58       return !!(target && target.value)
59     }
60
61     return !( (this.state.estimateGas || hasValue(props.gas.price))&&
62       (hasValue(props.accountId) || hasValue(props.accountAlias)) &&
63       (hasValue(props.assetId) || hasValue(props.assetAlias)) &&
64       hasValue(props.address) && (hasValue(props.amount)) &&
65       hasValue(props.password)
66     )
67   }
68
69   submitWithValidation(data) {
70     return new Promise((resolve, reject) => {
71       this.props.submitForm(Object.assign({}, data, {state: this.state, form: 'normalTx'}))
72         .catch((err) => {
73           const response = {}
74
75           if (err.data) {
76             response.actions = []
77
78             err.data.forEach((error) => {
79               response.actions[error.data.actionIndex] = {type: error}
80             })
81           }
82
83           response['_error'] = err
84           return reject(response)
85         })
86     })
87   }
88
89   estimateNormalTransactionGas() {
90     const transaction = this.props.fields
91     const address = transaction.address.value
92     const amount = transaction.amount.value
93     const accountAlias = transaction.accountAlias.value
94     const accountId = transaction.accountId.value
95     const assetAlias = transaction.assetAlias.value
96     const assetId = transaction.assetId.value
97
98     const noAccount = !accountAlias && !accountId
99     const noAsset = !assetAlias && !assetId
100
101     if (!address || !amount || noAccount || noAsset) {
102       this.setState({estimateGas: null})
103       return
104     }
105
106     const spendAction = {
107       accountAlias,
108       accountId,
109       assetAlias,
110       assetId,
111       amount: Number(amount),
112       type: 'spend_account'
113     }
114     const receiveAction = {
115       address,
116       assetAlias,
117       assetId,
118       amount: Number(amount),
119       type: 'control_address'
120     }
121
122     const gasAction = {
123       accountAlias,
124       accountId,
125       assetAlias: 'BTM',
126       amount: Math.pow(10, 7),
127       type: 'spend_account'
128     }
129
130     const actions = [spendAction, receiveAction, gasAction]
131     const body = {actions, ttl: 1}
132     this.connection.request('/build-transaction', body).then(resp => {
133       if (resp.status === 'fail') {
134         this.setState({estimateGas: null})
135         this.props.showError(new Error(resp.msg))
136         return
137       }
138
139       return this.connection.request('/estimate-transaction-gas', {
140         transactionTemplate: resp.data
141       }).then(resp => {
142         if (resp.status === 'fail') {
143           this.setState({estimateGas: null})
144           this.props.showError(new Error(resp.msg))
145           return
146         }
147         this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
148       })
149     })
150   }
151
152   render() {
153     const {
154       fields: {accountId, accountAlias, assetId, assetAlias, address, amount, gas, password},
155       error,
156       handleSubmit,
157       submitting
158     } = this.props
159     const lang = this.props.lang;
160     [amount, accountAlias, accountId, assetAlias, assetId].forEach(key => {
161       key.onBlur = this.estimateNormalTransactionGas.bind(this)
162     })
163
164     let submitLabel = lang === 'zh' ? '提交交易' : 'Submit transaction'
165
166     const gasOnChange = event => {
167       gas.type.onChange(event)
168
169       const range = rangeOptions.find(item => item.label === event.target.value)
170       gas.price.onChange(range.value)
171     }
172     const assetDecimal = this.props.assetDecimal(this.props.fields)
173
174     const showAvailableBalance = (accountAlias.value || accountId.value) &&
175       (assetAlias.value || assetId.value)
176
177     const availableBalance = this.props.balanceAmount(this.props.fields, assetDecimal)
178
179     const showBtmAmountUnit = (assetAlias.value === 'BTM' || assetId.value === btmID)
180
181     return (
182         <form
183           onSubmit={handleSubmit(this.submitWithValidation)} {...disableAutocomplete}
184           onKeyDown={(e) => { this.props.handleKeyDown(e, handleSubmit(this.submitWithValidation), this.disableSubmit(this.props.fields)) }}>
185           <FormSection title={lang === 'zh' ? '简单交易' : 'Normal Trasaction'}>
186             <label className={styles.title}>{lang === 'zh' ? '从' : 'From'}</label>
187             <div className={styles.main}>
188               <ObjectSelectorField
189                 key='account-selector-field'
190                 lang={lang}
191                 title={lang === 'zh' ? '账户' : 'Account'}
192                 aliasField={Autocomplete.AccountAlias}
193                 fieldProps={{
194                   id: accountId,
195                   alias: accountAlias
196                 }}
197               />
198               <ObjectSelectorField
199                 key='asset-selector-field'
200                 lang={lang}
201                 title={lang === 'zh' ? '资产' : 'Asset'}
202                 aliasField={Autocomplete.AssetAlias}
203                 fieldProps={{
204                   id: assetId,
205                   alias: assetAlias
206                 }}
207               />
208               {showAvailableBalance && availableBalance &&
209               <small className={styles.balanceHint}>{availableBalance} {lang === 'zh' ? '可用' : 'available'} </small>}
210             </div>
211
212             <label className={styles.title}>{lang === 'zh' ? '至' : 'To'}</label>
213             <div className={styles.main}>
214               <TextField title={lang === 'zh' ? '地址' : 'Address'} fieldProps={{
215                 ...address,
216                 onBlur: (e) => {
217                   address.onBlur(e)
218                   this.estimateNormalTransactionGas()
219                 },
220               }}/>
221               {!showBtmAmountUnit && !assetDecimal &&
222               <TextField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount}
223               />}
224               {!showBtmAmountUnit && assetDecimal &&
225               <AmountInputMask title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount} decimal={assetDecimal}
226               />}
227               {showBtmAmountUnit &&
228               <AmountUnitField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount}/>
229               }
230             </div>
231
232             <label className={styles.title}>Gas</label>
233             <table className={styles.optionsBtnContianer}>
234               {rangeOptions.map((option) =>
235                 <tr className={styles.optionsBtn}>
236                   <td className={styles.optionsLabel}>
237                     <label>
238                       <input type='radio'
239                              {...gas.type}
240                              onChange={gasOnChange}
241                              value={option.label}
242                              checked={option.label == gas.type.value}
243                       />
244                       {lang === 'zh' ? option.label_zh : option.label}
245                     </label>
246                   </td>
247                   <td>
248                     {
249                       option.label == gas.type.value && option.label !== 'Customize'
250                       && this.state.estimateGas && ((lang === 'zh' ? '估算' : 'estimated') + '   ' + normalizeBTMAmountUnit(btmID,
251                         option.ratio * this.state.estimateGas,
252                         this.props.btmAmountUnit))
253                     }
254                     {
255                       option.label === 'Customize' && gas.type.value === 'Customize' &&
256                       <div>
257                         <AmountUnitField
258                           autoFocus={true}
259                           fieldProps={gas.price}
260                           placeholder='Enter gas'/>
261                       </div>
262                     }
263                   </td>
264                 </tr>
265               )}
266             </table>
267
268             <label className={styles.title}>{lang === 'zh' ? '密码' : 'Password'}</label>
269             <TextField placeholder={lang === 'zh' ? '请输入密码' : 'Please enter the password'} fieldProps={password}
270                        autoFocus={false} type={'password'}/>
271           </FormSection>
272
273           <FormSection className={styles.submitSection}>
274             {error &&
275             <ErrorBanner
276               title='Error submitting form'
277               error={error} />}
278
279             <div className={styles.submit}>
280               <button type='submit' className='btn btn-primary' disabled={submitting || this.disableSubmit(this.props.fields)}>
281                 {submitLabel ||  ( lang === 'zh' ? '提交' : 'Submit' )}
282               </button>
283
284               {submitting &&
285               <SubmitIndicator />
286               }
287             </div>
288           </FormSection>
289         </form>
290     )
291   }
292 }
293
294 const validate = (values, props) => {
295   const errors = {gas: {}}
296   const lang = props.lang
297
298   // Numerical
299   if (values.amount && !/^\d+(\.\d+)?$/i.test(values.amount)) {
300     errors.amount = ( lang === 'zh' ? '请输入数字' : 'Invalid amount type' )
301   }
302   return errors
303 }
304
305 const asyncValidate = (values) => {
306   return new Promise((resolve, reject) => {
307     const address = values.address
308     chainClient().accounts.validateAddresses(address)
309       .then(
310         (resp) => {
311           if(!resp.data.valid){
312             reject({ address: 'invalid address'})
313           }else {
314             resolve()
315           }
316         }
317       ).catch((err) => {
318         reject({ address: err })
319       })
320   })
321 }
322
323
324 export default BaseNew.connect(
325   BaseNew.mapStateToProps('transaction'),
326   (dispatch) => ({
327     showError: err => dispatch({type: 'ERROR', payload: err}),
328     ...BaseNew.mapDispatchToProps('transaction')(dispatch)
329   }),
330   reduxForm({
331     form: 'NormalTransactionForm',
332     fields: [
333       'accountAlias',
334       'accountId',
335       'amount',
336       'assetAlias',
337       'assetId',
338       'gas',
339       'gas.type',
340       'gas.price',
341       'address',
342       'submitAction',
343       'password'
344     ],
345     asyncValidate,
346     asyncBlurFields: [ 'address'],
347     validate,
348     touchOnChange: true,
349     initialValues: {
350       submitAction: 'submit',
351     },
352   }
353   )(NormalTxForm)
354 )
355
356