import {
BaseNew,
- FormSection,
TextField,
Autocomplete,
ObjectSelectorField,
- AmountUnitField,
- AmountInputMask,
- ErrorBanner,
- SubmitIndicator
+ AmountField,
+ GasField
} from 'features/shared/components'
import {chainClient} from 'utility/environment'
import {reduxForm} from 'redux-form'
import React from 'react'
import styles from './New.scss'
-import disableAutocomplete from 'utility/disableAutocomplete'
-import { normalizeBTMAmountUnit } from 'utility/buildInOutDisplay'
-
-const rangeOptions = [
- {
- label: 'Standard',
- label_zh: '标准',
- ratio: 1
- },
- {
- label: 'Fast',
- label_zh: '快速',
- ratio: 2
- },
- {
- label: 'Customize',
- label_zh: '自定义',
- value: ''
- }
-]
-
-const btmID = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
+import TxContainer from './NewTransactionsContainer/TxContainer'
+import { btmID } from 'utility/environment'
+import actions from 'actions'
+import ConfirmModal from './ConfirmModal/ConfirmModal'
+import { balance , getAssetDecimal, normalTxActionBuilder} from '../../transactions'
+import {withNamespaces} from 'react-i18next'
class NormalTxForm extends React.Component {
constructor(props) {
super(props)
this.connection = chainClient().connection
this.state = {
- estimateGas:null
+ estimateGas:null,
+ chainGas: 0,
+ counter: 1
}
this.submitWithValidation = this.submitWithValidation.bind(this)
this.disableSubmit = this.disableSubmit.bind(this)
+ this.addReceiverItem = this.addReceiverItem.bind(this)
}
- componentDidMount() {
- this.props.fields.gas.type.onChange(rangeOptions[0].label)
- this.props.fields.gas.price.onChange(rangeOptions[0].value)
- }
-
- disableSubmit(props) {
- const hasValue = target => {
- return !!(target && target.value)
- }
-
- return !( (this.state.estimateGas || hasValue(props.gas.price))&&
- (hasValue(props.accountId) || hasValue(props.accountAlias)) &&
- (hasValue(props.assetId) || hasValue(props.assetAlias)) &&
- hasValue(props.address) && (hasValue(props.amount)) &&
- hasValue(props.password)
- )
+ disableSubmit() {
+ return !(this.state.estimateGas)
}
submitWithValidation(data) {
return new Promise((resolve, reject) => {
this.props.submitForm(Object.assign({}, data, {state: this.state, form: 'normalTx'}))
+ .then(() => {
+ this.props.closeModal()
+ this.props.destroyForm()
+ })
.catch((err) => {
- const response = {}
+ if(err.message !== 'PasswordWrong'){
+ this.props.closeModal()
+ }
+ reject({_error: err})
+ })
+ })
+ }
- if (err.data) {
- response.actions = []
+ confirmedTransaction(e, assetDecimal){
+ e.preventDefault()
+ this.props.showModal(
+ <ConfirmModal
+ cancel={this.props.closeModal}
+ onSubmit={this.submitWithValidation}
+ gas={this.state.estimateGas}
+ chainGas={this.state.chainGas}
+ btmAmountUnit={this.props.btmAmountUnit}
+ assetDecimal={assetDecimal}
+ asset={this.props.asset}
+ />
+ )
+ }
- err.data.forEach((error) => {
- response.actions[error.data.actionIndex] = {type: error}
- })
- }
+ addReceiverItem() {
+ const counter = this.state.counter
+ this.props.fields.receivers.addField({
+ id: counter
+ })
+ this.setState({
+ counter: counter+1,
+ estimateGas: null
+ })
+ }
- response['_error'] = err
- return reject(response)
- })
+ removeReceiverItem(index) {
+ const receiver = this.props.fields.receivers
+ const promise = new Promise(function(resolve, reject) {
+ try {
+ receiver.removeField(index)
+ } catch (err) {
+ reject(err)
+ }
+ resolve()
})
+
+ promise.then(() => this.estimateNormalTransactionGas())
}
estimateNormalTransactionGas() {
- const transaction = this.props.fields
- const address = transaction.address.value
- const amount = transaction.amount.value
- const accountAlias = transaction.accountAlias.value
- const accountId = transaction.accountId.value
- const assetAlias = transaction.assetAlias.value
- const assetId = transaction.assetId.value
+ const transaction = this.props.values
+ const accountAlias = transaction.accountAlias
+ const accountId = transaction.accountId
+ const assetAlias = transaction.assetAlias
+ const assetId = transaction.assetId
+ const receivers = transaction.receivers
+ const addresses = receivers.map(x => x.address)
+ const amounts = receivers.map(x => Number(x.amount))
+
+ const {t, i18n} = this.props
const noAccount = !accountAlias && !accountId
const noAsset = !assetAlias && !assetId
- if (!address || !amount || noAccount || noAsset) {
+ if ( addresses.includes('') || amounts.includes(NaN)|| amounts.includes(0)|| noAccount || noAsset) {
this.setState({estimateGas: null})
return
}
- const spendAction = {
- accountAlias,
- accountId,
- assetAlias,
- assetId,
- amount: Number(amount),
- type: 'spend_account'
- }
- const receiveAction = {
- address,
- assetAlias,
- assetId,
- amount: Number(amount),
- type: 'control_address'
- }
+ const isBTM = (assetAlias==='BTM') || (assetId === btmID)
- const gasAction = {
- accountAlias,
- accountId,
- assetAlias: 'BTM',
- amount: Math.pow(10, 7),
- type: 'spend_account'
- }
+ const actions = normalTxActionBuilder(transaction, Math.pow(10, 7), 'amount' )
- const actions = [spendAction, receiveAction, gasAction]
const body = {actions, ttl: 1}
- this.connection.request('/build-transaction', body).then(resp => {
- if (resp.status === 'fail') {
- this.setState({estimateGas: null})
- this.props.showError(new Error(resp.msg))
- return
- }
-
- return this.connection.request('/estimate-transaction-gas', {
- transactionTemplate: resp.data
- }).then(resp => {
- if (resp.status === 'fail') {
- this.setState({estimateGas: null})
- this.props.showError(new Error(resp.msg))
- return
- }
- this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
+ if(isBTM){
+ this.connection.request('/build-chain-transactions', body).then(resp => {
+ return this.connection.request('/estimate-chain-transaction-gas', {
+ transactionTemplates: resp.data
+ }).then(resp => {
+ this.setState({
+ estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000,
+ chainGas: Math.ceil(resp.data.chainTxNeu/100000)*100000
+ })
+ }).catch(err =>{
+ throw err
+ })
+ }).catch(err=>{
+ this.setState({estimateGas: null, address: null})
+ const errorMsg = err.code && i18n.exists(`btmError.${err.code}`) && t(`btmError.${err.code}`) || err.msg
+ this.props.showError(new Error(errorMsg))
})
- })
+ }else{
+ this.connection.request('/build-transaction', body).then(resp => {
+ return this.connection.request('/estimate-transaction-gas', {
+ transactionTemplate: resp.data
+ }).then(resp => {
+ this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
+ }).catch(err =>{
+ throw err
+ })
+ }).catch(err=>{
+ this.setState({estimateGas: null, address: null})
+ const errorMsg = err.code && i18n.exists(`btmError.${err.code}`) && t(`btmError.${err.code}`) || err.msg
+ this.props.showError(new Error(errorMsg))
+ })
+ }
}
render() {
const {
- fields: {accountId, accountAlias, assetId, assetAlias, address, amount, gas, password},
+ fields: {accountId, accountAlias, assetId, assetAlias, receivers, gasLevel},
error,
- handleSubmit,
submitting
} = this.props
- const lang = this.props.lang;
- [amount, accountAlias, accountId, assetAlias, assetId].forEach(key => {
+ const t = this.props.t;
+ [accountAlias, accountId, assetAlias, assetId].forEach(key => {
key.onBlur = this.estimateNormalTransactionGas.bind(this)
+ });
+ (receivers.map(receiver => receiver.amount)).forEach(amount =>{
+ amount.onBlur = this.estimateNormalTransactionGas.bind(this)
})
- let submitLabel = lang === 'zh' ? '提交交易' : 'Submit transaction'
+ let submitLabel = t('transaction.new.submit')
- const gasOnChange = event => {
- gas.type.onChange(event)
-
- const range = rangeOptions.find(item => item.label === event.target.value)
- gas.price.onChange(range.value)
- }
- const assetDecimal = this.props.assetDecimal(this.props.fields) || 0
+ const assetDecimal = getAssetDecimal(this.props.fields, this.props.asset) || 0
const showAvailableBalance = (accountAlias.value || accountId.value) &&
(assetAlias.value || assetId.value)
- const availableBalance = this.props.balanceAmount(this.props.fields, assetDecimal)
+ const availableBalance = balance(this.props.values, assetDecimal, this.props.balances, this.props.btmAmountUnit)
const showBtmAmountUnit = (assetAlias.value === 'BTM' || assetId.value === btmID)
return (
- <form
- onSubmit={handleSubmit(this.submitWithValidation)} {...disableAutocomplete}
- onKeyDown={(e) => { this.props.handleKeyDown(e, handleSubmit(this.submitWithValidation), this.disableSubmit(this.props.fields)) }}>
- <FormSection title={lang === 'zh' ? '简单交易' : 'Normal Trasaction'}>
- <label className={styles.title}>{lang === 'zh' ? '从' : 'From'}</label>
- <div className={styles.main}>
- <ObjectSelectorField
- key='account-selector-field'
- keyIndex='normaltx-account'
- lang={lang}
- title={lang === 'zh' ? '账户' : 'Account'}
- aliasField={Autocomplete.AccountAlias}
- fieldProps={{
- id: accountId,
- alias: accountAlias
- }}
- />
+ <TxContainer
+ error={error}
+ onSubmit={e => this.confirmedTransaction(e, assetDecimal)}
+ submitting={submitting}
+ submitLabel= {submitLabel}
+ disabled={this.disableSubmit()}
+ className={styles.container}
+ >
+ <div className={styles.borderBottom}>
+ <label className={styles.title}>{t('transaction.normal.from')}</label>
+ <div className={`${styles.mainBox} `}>
+ <ObjectSelectorField
+ key='account-selector-field'
+ keyIndex='normaltx-account'
+ title={t('form.account')}
+ aliasField={Autocomplete.AccountAlias}
+ fieldProps={{
+ id: accountId,
+ alias: accountAlias
+ }}
+ />
+ <div>
<ObjectSelectorField
key='asset-selector-field'
keyIndex='normaltx-asset'
- lang={lang}
- title={lang === 'zh' ? '资产' : 'Asset'}
+ title={ t('form.asset')}
aliasField={Autocomplete.AssetAlias}
fieldProps={{
id: assetId,
}}
/>
{showAvailableBalance && availableBalance &&
- <small className={styles.balanceHint}>{availableBalance} {lang === 'zh' ? '可用' : 'available'} </small>}
+ <small className={styles.balanceHint}>{t('transaction.normal.availableBalance')} {availableBalance}</small>}
</div>
-
- <label className={styles.title}>{lang === 'zh' ? '至' : 'To'}</label>
- <div className={styles.main}>
- <TextField title={lang === 'zh' ? '地址' : 'Address'} fieldProps={{
- ...address,
- onBlur: (e) => {
- address.onBlur(e)
- this.estimateNormalTransactionGas()
- },
- }}/>
- {!showBtmAmountUnit &&
- <AmountInputMask title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount} decimal={assetDecimal}
- />}
- {showBtmAmountUnit &&
- <AmountUnitField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={amount}/>
- }
- </div>
-
- <label className={styles.title}>Gas</label>
- <table>
- <tbody className={styles.optionsBtnContianer}>
- {rangeOptions.map((option) =>
- <tr className={styles.optionsBtn}>
- <td className={styles.optionsLabel}>
- <label>
- <input type='radio'
- {...gas.type}
- onChange={gasOnChange}
- value={option.label}
- checked={option.label == gas.type.value}
- />
- {lang === 'zh' ? option.label_zh : option.label}
- </label>
- </td>
- <td>
- {
- option.label == gas.type.value && option.label !== 'Customize'
- && this.state.estimateGas && ((lang === 'zh' ? '估算' : 'estimated') + ' ' + normalizeBTMAmountUnit(btmID,
- option.ratio * this.state.estimateGas,
- this.props.btmAmountUnit))
- }
- {
- option.label === 'Customize' && gas.type.value === 'Customize' &&
- <div>
- <AmountUnitField
- autoFocus={true}
- fieldProps={gas.price}
- placeholder='Enter gas'/>
- </div>
- }
- </td>
- </tr>
- )}
- </tbody>
- </table>
-
- <label className={styles.title}>{lang === 'zh' ? '密码' : 'Password'}</label>
- <TextField placeholder={lang === 'zh' ? '请输入密码' : 'Please enter the password'} fieldProps={password}
- autoFocus={false} type={'password'}/>
- </FormSection>
-
- <FormSection className={styles.submitSection}>
- {error &&
- <ErrorBanner
- title='Error submitting form'
- error={error} />}
-
- <div className={styles.submit}>
- <button type='submit' className='btn btn-primary' disabled={submitting || this.disableSubmit(this.props.fields)}>
- {submitLabel || ( lang === 'zh' ? '提交' : 'Submit' )}
- </button>
-
- {submitting &&
- <SubmitIndicator />
- }
- </div>
- </FormSection>
- </form>
+ </div>
+
+ <label className={styles.title}>{t('transaction.normal.to')}</label>
+
+ <div className={styles.mainBox}>
+ {receivers.map((receiver, index) =>
+ <div
+ className={this.props.tutorialVisible? styles.tutorialItem: styles.subjectField}
+ key={receiver.id.value}>
+ <TextField title={t('form.address')} fieldProps={{
+ ...receiver.address,
+ onBlur: (e) => {
+ receiver.address.onBlur(e)
+ this.estimateNormalTransactionGas()
+ },
+ }}/>
+
+ <AmountField
+ isBTM={showBtmAmountUnit}
+ title={t('form.amount')}
+ fieldProps={receiver.amount}
+ decimal={assetDecimal}
+ />
+
+ <button
+ className={`btn btn-danger btn-xs ${styles.deleteButton}`}
+ tabIndex='-1'
+ type='button'
+ onClick={() => this.removeReceiverItem(index)}
+ >
+ {t('commonWords.remove')}
+ </button>
+ </div>
+ )}
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.addReceiverItem}
+ >
+ {t('commonWords.addField')}
+ </button>
+ </div>
+
+ <label className={styles.title}>{t('transaction.normal.selectFee')}</label>
+ <div className={styles.txFeeBox}>
+ <GasField
+ gas={this.state.estimateGas}
+ chainGas={this.state.chainGas}
+ fieldProps={gasLevel}
+ btmAmountUnit={this.props.btmAmountUnit}
+ />
+ <span className={styles.feeDescription}> {t('transaction.normal.feeDescription')}</span>
+ </div>
+ </div>
+ </TxContainer>
)
}
}
const validate = (values, props) => {
const errors = {gas: {}}
- const lang = props.lang
+ const t = props.t
// Numerical
if (values.amount && !/^\d+(\.\d+)?$/i.test(values.amount)) {
- errors.amount = ( lang === 'zh' ? '请输入数字' : 'Invalid amount type' )
+ errors.amount = ( t('errorMessage.amountError') )
}
return errors
}
-const asyncValidate = (values) => {
- return new Promise((resolve, reject) => {
- const address = values.address
- chainClient().accounts.validateAddresses(address)
- .then(
- (resp) => {
- if(!resp.data.valid){
- reject({ address: 'invalid address'})
- }else {
- resolve()
- }
- }
- ).catch((err) => {
- reject({ address: err })
- })
+const asyncValidate = (values, dispatch, props) => {
+ const errors = []
+ const promises = []
+
+ values.receivers.forEach((receiver, idx) => {
+ const address = values.receivers[idx].address
+ if ( !address || address.length === 0)
+ promises.push(Promise.resolve())
+ else{
+ promises.push(
+ chainClient().accounts.validateAddresses(address)
+ .then(
+ (resp) => {
+ if (!resp.data.valid) {
+ errors[idx] = {address: props.t('errorMessage.addressError')}
+ }
+ return {}
+ }
+ ))
+ }
+ })
+
+ return Promise.all(promises).then(() => {
+ if (errors.length > 0) throw {
+ receivers: errors
+ }
+ return {}
})
}
+const mapDispatchToProps = (dispatch) => ({
+ showError: err => dispatch({type: 'ERROR', payload: err}),
+ closeModal: () => dispatch(actions.app.hideModal),
+ showModal: (body) => dispatch(actions.app.showModal(
+ body,
+ actions.app.hideModal,
+ null,
+ {
+ dialog: true,
+ noCloseBtn: true
+ }
+ )),
+ ...BaseNew.mapDispatchToProps('transaction')(dispatch)
+})
-export default BaseNew.connect(
+export default withNamespaces('translations') (BaseNew.connect(
BaseNew.mapStateToProps('transaction'),
- (dispatch) => ({
- showError: err => dispatch({type: 'ERROR', payload: err}),
- ...BaseNew.mapDispatchToProps('transaction')(dispatch)
- }),
+ mapDispatchToProps,
reduxForm({
form: 'NormalTransactionForm',
fields: [
'accountAlias',
'accountId',
- 'amount',
'assetAlias',
'assetId',
- 'gas',
- 'gas.type',
- 'gas.price',
- 'address',
- 'submitAction',
- 'password'
+ 'receivers[].id',
+ 'receivers[].amount',
+ 'receivers[].address',
+ 'gasLevel',
],
asyncValidate,
- asyncBlurFields: [ 'address'],
+ asyncBlurFields: ['receivers[].address'],
validate,
touchOnChange: true,
initialValues: {
- submitAction: 'submit',
+ gasLevel: '1',
+ receivers:[{
+ id: 0,
+ amount:'',
+ address:''
+ }]
},
- }
- )(NormalTxForm)
-)
-
-
+ })(NormalTxForm)
+))