OSDN Git Service

show amount widget accordingly to the normal transaction
[bytom/bytom-electron.git] / src / features / transactions / components / New / New.jsx
1 import { BaseNew, FormContainer, FormSection, FieldLabel, JsonField, TextField, Autocomplete, ObjectSelectorField, AmountUnitField } from 'features/shared/components'
2 import { DropdownButton, MenuItem } from 'react-bootstrap'
3 import { reduxForm } from 'redux-form'
4 import ActionItem from './FormActionItem'
5 import React from 'react'
6 import styles from './New.scss'
7 import balanceActions from 'features/balances/actions'
8 import { normalizeBTMAmountUnit, formatBTMAmount, parseBTMAmount } from 'utility/buildInOutDisplay'
9
10 const rangeOptions = [
11   {
12     label: 'Standard',
13     label_zh: '标准',
14     value: '20000000'
15   },
16   {
17     label: 'Fast',
18     label_zh: '快速',
19     value: '25000000'
20   },
21   {
22     label: 'Customize',
23     label_zh: '自定义',
24     value: ''
25   }
26 ]
27
28 class Form extends React.Component {
29   constructor(props) {
30     super(props)
31     this.state = {
32       showDropdown: false,
33       showAdvanced: false
34     }
35
36     this.submitWithValidation = this.submitWithValidation.bind(this)
37     this.addActionItem = this.addActionItem.bind(this)
38     this.removeActionItem = this.removeActionItem.bind(this)
39     this.toggleDropwdown = this.toggleDropwdown.bind(this)
40     this.closeDropdown = this.closeDropdown.bind(this)
41     this.disableSubmit = this.disableSubmit.bind(this)
42   }
43
44   componentDidMount() {
45     if (!this.props.autocompleteIsLoaded) {
46       this.props.fetchAll().then(() => {
47         this.props.didLoadAutocomplete()
48       })
49     }
50
51     this.props.fields.normalTransaction.gas.type.onChange(rangeOptions[0].label)
52     this.props.fields.normalTransaction.gas.price.onChange(rangeOptions[0].value)
53   }
54
55   balanceAmount(normalTransaction) {
56     let balances = this.props.balances
57     let filteredBalances = balances
58     if (normalTransaction.accountAlias.value) {
59       filteredBalances = filteredBalances.filter(balance => balance.accountAlias === normalTransaction.accountAlias.value)
60     }
61     if (normalTransaction.accountId.value) {
62       filteredBalances = filteredBalances.filter(balance => balance.accountId === normalTransaction.accountId.value)
63     }
64     if (normalTransaction.assetAlias.value) {
65       filteredBalances = filteredBalances.filter(balance => balance.assetAlias === normalTransaction.assetAlias.value)
66     }
67     if (normalTransaction.assetId.value) {
68       filteredBalances = filteredBalances.filter(balance => balance.assetId === normalTransaction.assetId.value)
69     }
70
71     return filteredBalances.length === 1 ? normalizeBTMAmountUnit(filteredBalances[0].assetId, filteredBalances[0].amount, this.props.btmAmountUnit) : null
72   }
73
74   toggleDropwdown() {
75     this.setState({ showDropdown: !this.state.showDropdown })
76   }
77
78   closeDropdown() {
79     this.setState({ showDropdown: false })
80   }
81
82   addActionItem(type) {
83     this.props.fields.actions.addField({
84       type: type,
85       referenceData: '{\n\t\n}'
86     })
87     this.closeDropdown()
88   }
89
90   disableSubmit(actions, normalTransaction) {
91     if (this.state.showAdvanceTx) {
92       return actions.length == 0 && !this.state.showAdvanced
93     }
94
95     const hasValue = target => {
96       return !!(target && target.value)
97     }
98
99     return !((hasValue(normalTransaction.accountId) || hasValue(normalTransaction.accountAlias)) &&
100       (hasValue(normalTransaction.assetId)|| hasValue(normalTransaction.assetAlias)) &&
101       hasValue(normalTransaction.address) && (hasValue(normalTransaction.amount)))
102   }
103
104   removeActionItem(index) {
105     this.props.fields.actions.removeField(index)
106   }
107
108   emptyActions(actions){
109     if(actions.length != 0){
110       actions.map(()=> this.removeActionItem(0))
111     }
112   }
113
114   submitWithValidation(data) {
115     return new Promise((resolve, reject) => {
116       this.props.submitForm(data)
117         .catch((err) => {
118           const response = {}
119
120           if (err.data) {
121             response.actions = []
122
123             err.data.forEach((error) => {
124               response.actions[error.data.actionIndex] = {type: error}
125             })
126           }
127
128           response['_error'] = err
129           return reject(response)
130         })
131     })
132   }
133
134
135   render() {
136     const {
137       fields: { baseTransaction, actions, submitAction, password, normalTransaction },
138       error,
139       handleSubmit,
140       submitting
141     } = this.props
142     const lang = this.props.lang
143
144     let submitLabel = lang === 'zh' ? '提交交易' : 'Submit transaction'
145     if (submitAction.value == 'generate') {
146       submitLabel = lang === 'zh' ? '生成交易hex' : 'Generate transaction hex'
147     }
148
149     const gasOnChange = event => {
150       normalTransaction.gas.type.onChange(event)
151
152       const range = rangeOptions.find(item => item.label === event.target.value)
153       normalTransaction.gas.price.onChange(range.value)
154     }
155
156     const showAvailableBalance = (normalTransaction.accountAlias.value || normalTransaction.accountId.value) &&
157       (normalTransaction.assetAlias.value || normalTransaction.assetId.value)
158     const availableBalance = this.balanceAmount(normalTransaction)
159
160     const showBtmAmountUnit = (normalTransaction.assetAlias.value === 'btm' ||
161       normalTransaction.assetId.value === 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
162
163     return(
164       <FormContainer
165         error={error}
166         label={ lang === 'zh' ? '新建交易' :'New transaction' }
167         submitLabel={submitLabel}
168         onSubmit={handleSubmit(this.submitWithValidation)}
169         showSubmitIndicator={true}
170         submitting={submitting}
171         disabled={this.disableSubmit(actions, normalTransaction)} >
172
173
174         <div className={`btn-group ${styles.btnGroup}`} role='group'>
175           <button
176             className={`btn btn-default ${this.state.showAdvanceTx ? null: 'active'}`}
177             onClick={(e) => {
178               e.preventDefault()
179               this.emptyActions(actions)
180               this.setState({showAdvanceTx: false})
181             }} >
182             { lang === 'zh' ? '简单交易' : 'Normal' }
183           </button>
184           <button
185             className={`btn btn-default ${this.state.showAdvanceTx ? 'active': null}`}
186             onClick={(e) => {
187               e.preventDefault()
188               this.setState({showAdvanceTx: true})
189             }} >
190             { lang === 'zh' ? '高级交易' : 'Advanced' }
191           </button>
192         </div>
193
194         { !this.state.showAdvanceTx && <FormSection title={ lang === 'zh' ? '简单交易' : 'Normal Trasaction' }>
195           <label className={styles.title}>{ lang === 'zh' ? '从' : 'From' }</label>
196           <div className={styles.main}>
197             <ObjectSelectorField
198               title={ lang === 'zh' ? '账户' :'Account' }
199               aliasField={Autocomplete.AccountAlias}
200               fieldProps={{
201                 id: normalTransaction.accountId,
202                 alias: normalTransaction.accountAlias
203               }}
204             />
205             <ObjectSelectorField
206               title={ lang === 'zh' ? '资产' : 'Asset' }
207               aliasField={Autocomplete.AssetAlias}
208               fieldProps={{
209                 id: normalTransaction.assetId,
210                 alias: normalTransaction.assetAlias
211               }}
212             />
213             {showAvailableBalance && availableBalance &&
214             <small className={styles.balanceHint}>{availableBalance} available</small>}
215           </div>
216
217           <label className={styles.title}>{ lang === 'zh' ? '至' : 'To' }</label>
218           <div className={styles.main}>
219             <TextField title='Address' fieldProps={normalTransaction.address}/>
220             {!showBtmAmountUnit && <TextField title='Amount' fieldProps={normalTransaction.amount}
221             />}
222             {showBtmAmountUnit && <AmountUnitField
223               title='Amount'
224               fieldProps={{
225                 ...normalTransaction.amount,
226               }}
227               format={formatBTMAmount}
228               normalize={parseBTMAmount}
229             />}
230           </div>
231
232           <label className={styles.title}>Gas</label>
233           <div>
234             <label className={styles.optionsBtnContianer}>
235               {rangeOptions.map((option) =>
236                 <label>
237                   <label>
238                     <input type='radio'
239                            {...normalTransaction.gas.type}
240                            onChange={gasOnChange}
241                            value={option.label}
242                            checked={option.label == normalTransaction.gas.type.value}
243                     />
244                     { lang === 'zh' ? option.label_zh :  option.label }
245                   </label>
246                   {
247                     option.label === 'Customize' && normalTransaction.gas.type.value === 'Customize' &&
248                     <label>
249                       <TextField
250                         autoFocus={true}
251                         fieldProps={normalTransaction.gas.price}
252                         placeholder='Enter gas' />
253                     </label>
254                   }
255                 </label>
256               )}
257             </label>
258           </div>
259
260         </FormSection>}
261
262         { this.state.showAdvanceTx && <FormSection title='Actions'>
263           {actions.map((action, index) =>
264             <ActionItem
265               key={index}
266               index={index}
267               fieldProps={action}
268               accounts={this.props.accounts}
269               assets={this.props.assets}
270               remove={this.removeActionItem}
271             />)}
272
273           <div className={`btn-group ${styles.addActionContainer} ${this.state.showDropdown && 'open'}`}>
274             <DropdownButton
275               className={`btn btn-default ${styles.addAction}`}
276               id='input-dropdown-addon'
277               title='+ Add action'
278               onSelect={this.addActionItem}
279             >
280               <MenuItem eventKey='issue'>Issue</MenuItem>
281               <MenuItem eventKey='spend_account'>Spend from account</MenuItem>
282               {/*<MenuItem eventKey='spend_account_unspent_output'>Spend unspent output</MenuItem>*/}
283               <MenuItem eventKey='control_account'>Control with account</MenuItem>
284               <MenuItem eventKey='control_receiver'>Control with receiver</MenuItem>
285               <MenuItem eventKey='control_address'>Control with address</MenuItem>
286               <MenuItem eventKey='retire'>Retire</MenuItem>
287               {/*<MenuItem eventKey='set_transaction_reference_data'>Set transaction reference data</MenuItem>*/}
288             </DropdownButton>
289           </div>
290         </FormSection>}
291
292         {this.state.showAdvanceTx && !this.state.showAdvanced &&
293           <FormSection>
294             <a href='#'
295               className={styles.showAdvanced}
296               onClick={(e) => {
297                 e.preventDefault()
298                 this.setState({showAdvanced: true})
299               }}
300             >
301               { lang === 'zh' ? '显示高级选项' : 'Show advanced options'}
302             </a>
303           </FormSection>
304         }
305
306         {this.state.showAdvanceTx && this.state.showAdvanced && <FormSection title={ lang === 'zh' ? '高级选项' :'Advanced Options' }>
307           <div>
308             <TextField
309               title='Base transaction'
310               placeholder='Paste transaction hex here...'
311               fieldProps={baseTransaction}
312               autoFocus={true} />
313
314             <FieldLabel>Transaction Build Type</FieldLabel>
315             <table className={styles.submitTable}>
316               <tbody>
317               <tr>
318                 <td><input id='submit_action_submit' type='radio' {...submitAction} value='submit' checked={submitAction.value == 'submit'} /></td>
319                 <td>
320                   <label htmlFor='submit_action_submit'>{ lang === 'zh' ? '向区块链提交交易' :'Submit transaction to blockchain' }</label>
321                   <br />
322                   <label htmlFor='submit_action_submit' className={styles.submitDescription}>
323                     { lang === 'zh' ? '此次交易将通过密钥签名然后提交到区块链。' :
324                       'This transaction will be signed by the MockHSM and submitted to the blockchain.' }
325                   </label>
326                 </td>
327               </tr>
328               <tr>
329                 <td><input id='submit_action_generate' type='radio' {...submitAction} value='generate' checked={submitAction.value == 'generate'} /></td>
330                 <td>
331                   <label htmlFor='submit_action_generate'>{ lang === 'zh' ? '允许更多actions' : 'Allow additional actions' }</label>
332                   <br />
333                   <label htmlFor='submit_action_generate' className={styles.submitDescription}>
334                     { lang === 'zh' ? '这些actions将通过密钥签名然后作为一个交易 hex 字符串返回, 字符串可以用做base transaction于 multi-party swap。' +
335                       '此次交易将持续有效一个小时。' :
336                       'These actions will be signed by the MockHSM and returned as a transaction hex string, ' +
337                       'which should be used as the base transaction in a multi-party swap. This transaction ' +
338                       'will be valid for one hour.'  }
339                   </label>
340                 </td>
341               </tr>
342               </tbody>
343             </table>
344           </div>
345         </FormSection>}
346       </FormContainer>
347     )
348   }
349 }
350
351 const validate = values => {
352   const errors = {actions: {}}
353
354   // Base transaction
355   let baseTx = values.baseTransaction || ''
356   if (baseTx.trim().match(/[^0-9a-fA-F]/)) {
357     errors.baseTransaction = 'Base transaction must be a hex string.'
358   }
359
360   // Actions
361   let fieldError
362   values.actions.forEach((action, index) => {
363     fieldError = JsonField.validator(values.actions[index].referenceData)
364     if (fieldError) {
365       errors.actions[index] = {...errors.actions[index], referenceData: fieldError}
366     }
367   })
368
369   return errors
370 }
371
372 export default BaseNew.connect(
373   (state) => {
374     let balances = []
375     for (let key in state.balance.items) {
376       balances.push(state.balance.items[key])
377     }
378
379     return {
380       autocompleteIsLoaded: state.key.autocompleteIsLoaded,
381       lang: state.core.lang,
382       btmAmountUnit: state.core.btmAmountUnit,
383       balances,
384       ...BaseNew.mapStateToProps('transaction')(state)
385     }
386   },
387   (dispatch) => ({
388     didLoadAutocomplete: () => dispatch(balanceActions.didLoadAutocomplete),
389     fetchAll: (cb) => dispatch(balanceActions.fetchAll(cb)),
390     ...BaseNew.mapDispatchToProps('transaction')(dispatch)
391   }),
392   reduxForm({
393     form: 'NewTransactionForm',
394     fields: [
395       'baseTransaction',
396       'actions[].accountId',
397       'actions[].accountAlias',
398       'actions[].assetId',
399       'actions[].assetAlias',
400       'actions[].amount',
401       'actions[].receiver',
402       'actions[].outputId',
403       'actions[].referenceData',
404       'actions[].type',
405       'actions[].address',
406       'actions[].password',
407       'normalTransaction.accountAlias',
408       'normalTransaction.accountId',
409       'normalTransaction.amount',
410       'normalTransaction.assetAlias',
411       'normalTransaction.assetId',
412       'normalTransaction.gas',
413       'normalTransaction.gas.type',
414       'normalTransaction.gas.price',
415       'normalTransaction.address',
416       'submitAction',
417       'password'
418     ],
419     validate,
420     initialValues: {
421       submitAction: 'submit',
422     },
423   }
424   )(Form)
425 )
426
427