OSDN Git Service

merge dashboard into electron
authorZhiting Lin <zlin035@uottawa.ca>
Tue, 28 Aug 2018 06:18:16 +0000 (14:18 +0800)
committerZhiting Lin <zlin035@uottawa.ca>
Tue, 28 Aug 2018 06:18:16 +0000 (14:18 +0800)
45 files changed:
README.md
src/features/accounts/components/AccountShow.jsx
src/features/app/components/Main/Main.jsx
src/features/app/components/Main/Main.scss
src/features/app/components/Modal/Modal.scss
src/features/app/components/Navigation/Navigation.jsx
src/features/app/components/Navigation/Navigation.scss
src/features/app/components/Sync/Sync.scss
src/features/backup/components/Backup.jsx
src/features/core/components/CoreIndex/CoreIndex.jsx
src/features/core/reducers.js
src/features/mockhsm/actions.js
src/features/mockhsm/components/CheckPassword/CheckPassword.jsx [new file with mode: 0644]
src/features/mockhsm/components/CheckPassword/CheckPassword.scss [new file with mode: 0644]
src/features/mockhsm/components/Show.jsx
src/features/mockhsm/components/index.js
src/features/mockhsm/reducers.js
src/features/mockhsm/routes.js
src/features/shared/actions/list.js
src/features/shared/components/AmountInputMask/AmountInputMask.jsx
src/features/shared/components/BaseList/BaseList.jsx
src/features/shared/components/ConsoleSection/ConsoleSection.scss
src/features/shared/components/ConsoleSection/ListItem/ListItem.scss
src/features/shared/components/ErrorBanner/ErrorBanner.jsx
src/features/shared/components/ErrorBanner/ErrorBanner.scss
src/features/shared/components/FormContainer/FormContainer.jsx
src/features/shared/components/KeyValueTable/KeyValueTable.jsx
src/features/shared/components/KeyValueTable/KeyValueTable.scss
src/features/shared/components/Pagination/Pagination.jsx [new file with mode: 0644]
src/features/shared/components/Pagination/Pagination.scss
src/features/shared/components/Pagination/PaginationField.jsx [deleted file]
src/features/shared/components/index.js
src/features/shared/routes.js
src/features/transactions/actions.js
src/features/transactions/components/List.jsx
src/features/transactions/components/New/AdvancedTransactionForm.jsx
src/features/transactions/components/New/MultiSignTransactionDetails/TransactionDetails.jsx [new file with mode: 0644]
src/features/transactions/components/New/MultiSignTransactionDetails/TransactionDetails.scss [new file with mode: 0644]
src/features/transactions/reducers.js
src/features/unspents/components/List.jsx
src/sdk/api/mockHsmKeys.js
src/sdk/api/transactions.js
src/utility/buildInOutDisplay.js
src/utility/environment.js
static/images/warning.svg [new file with mode: 0755]

index 73df780..df069e9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -23,11 +23,11 @@ npm start
 ```
 
 By default, the development server uses the following environment variables
-with default values to connect to a local Chain Core instance:
+with default values to connect to a local Bytom Core instance:
 
 ```
 API_URL=http://localhost:3000/api
-PROXY_API_HOST=http://localhost:1999
+PROXY_API_HOST=http://localhost:9888
 ```
 
 #### Style Guide
index ca5b5bf..a96d3e4 100644 (file)
@@ -25,8 +25,9 @@ class AccountShow extends BaseShow {
       if (data.status !== 'success') {
         return
       }
-      const normalAddresses = data.data.filter(address => !address.change).map(address => address.address)
-      const changeAddresses = data.data.filter(address => address.change).map(address => address.address)
+      const normalAddresses = data.data.filter(address => !address.change).map((address) => {return {address: address.address, program: address.controlProgram}})
+      const changeAddresses = data.data.filter(address => address.change).map((address) => {return {address: address.address, program: address.controlProgram}})
+
       this.setState({addresses: normalAddresses, changeAddresses})
     })
   }
@@ -56,6 +57,16 @@ class AccountShow extends BaseShow {
     })
   }
 
+  showProgram(program){
+    const lang = this.props.lang
+    this.props.showModal(
+      <div>
+        <p>{lang === 'zh' ? '拷贝这个合约程序以用于交易中:' : 'Copy this control program to use in a transaction:'}</p>
+        <CopyableBlock value={program} lang={lang}/>
+      </div>
+    )
+  }
+
   render() {
     const item = this.props.item
     const lang = this.props.lang
@@ -111,17 +122,21 @@ class AccountShow extends BaseShow {
 
           {(this.state.addresses || []).length > 0 &&
           <KeyValueTable title={lang === 'zh' ? '地址' : 'Addresses'}
-                         items={this.state.addresses.map((address, index) => ({
+                         lang={lang}
+                         items={this.state.addresses.map((item, index) => ({
                            label: index,
-                           value: address
+                           value: item.address,
+                           program: (e => this.showProgram(item.program))
                          }))}/>
           }
 
           {(this.state.changeAddresses || []).length > 0 &&
           <KeyValueTable title={lang === 'zh' ? '找零地址' : 'Addresses for Change'}
-                         items={this.state.changeAddresses.map((address, index) => ({
+                         lang={lang}
+                         items={this.state.changeAddresses.map((item, index) => ({
                            label: index,
-                           value: address
+                           value: item.address,
+                           program: (e => this.showProgram(item.program))
                          }))}/>
           }
         </PageContent>
index 5e89150..14ff781 100644 (file)
@@ -32,6 +32,9 @@ class Main extends React.Component {
   render() {
     let logo = require('images/logo-bytom-white.svg')
 
+    const version = this.props.version
+    const lang = this.props.lang
+
     return (
       <div className={styles.main}
            onClick={this.props.closeDropdown} >
@@ -53,7 +56,7 @@ class Main extends React.Component {
                   className={styles.languages}
                   noCaret
                 >
-                  {this.props.lang === 'zh' ? '中' : 'EN'}
+                  {lang === 'zh' ? '中' : 'EN'}
                 </Dropdown.Toggle>
                 <Dropdown.Menu
                   className={styles.languagesMenu}
@@ -72,6 +75,13 @@ class Main extends React.Component {
             </div>
 
             <Navigation />
+
+            <div className={styles.version}>
+              <span>
+                {lang==='zh'?'版本号':'Version'}: {version}
+              </span>
+            </div>
+
           </div>
         </div>
 
@@ -92,6 +102,7 @@ class Main extends React.Component {
 export default connect(
   (state) => ({
     canLogOut: state.core.requireClientToken,
+    version:state.core.version,
     lang: state.core.lang,
     connected: true,
     showDropwdown: state.app.dropdownState == 'open',
index 2e526bf..f467f7b 100644 (file)
   z-index: 10;
 }
 
+.version {
+  position: fixed;
+  width: $sidebar-width;
+  bottom: 0;
+  border-top: solid 1px $border-inverse-color;
+
+  padding: $gutter-size/2 $gutter-size;
+  background: $background-inverse-color;
+  font-size: $font-size-chrome;
+}
+
 .logo {
   border-bottom: 1px solid $border-inverse-color;
   height: $title-height;
index 455b697..37f909d 100644 (file)
@@ -48,8 +48,8 @@
 }
 
 .title {
-  height: 50px;
-  padding: 10px 30px;
+  height: 55px;
+  padding: $gutter-size/2 $gutter-size;
   border-bottom: 1px solid $border-color;
   font-size: $font-size-section-title;
   color: $text-strong-color;
@@ -58,5 +58,5 @@
 .close {
   position: absolute;
   right: 10px;
-  top: 5px;
+  top: 10px;
 }
index 4be4ae3..99591ad 100644 (file)
@@ -5,7 +5,7 @@ import styles from './Navigation.scss'
 import {navIcon} from '../../utils'
 import Sync from '../Sync/Sync'
 import appAction from '../../../app/actions'
-import {docsRoot} from '../../../../utility/environment'
+import {docsRoot, releaseUrl} from '../../../../utility/environment'
 
 class Navigation extends React.Component {
   constructor(props) {
@@ -31,12 +31,17 @@ class Navigation extends React.Component {
     const lang = this.props.lang
     return (
       <div className={styles.main}>
+        {this.props.update && <div className={`${styles.updateWarning} ${styles.smallFont}`}>
+          <a href={releaseUrl} target='_blank'>
+            <img src={require('images/warning.svg')} className={styles.warningIcon} />
+            {this.props.newVersionCode}{lang === 'zh'? '版本更新': ' update available'}
+          </a>
+        </div>}
         <ul className={styles.navigation}>
           <li className={styles.navigationTitle}>{lang === 'zh' ? '核心数据' : 'core data'}</li>
           <li>
             <Link to='/transactions' activeClassName={styles.active}>
               {navIcon('transaction', styles)}
-              {}
               {lang === 'zh' ? '交易' : 'Transactions'}
             </Link>
           </li>
@@ -62,14 +67,12 @@ class Navigation extends React.Component {
 
         <ul className={styles.navigation}>
           <li className={styles.navigationTitle}>{lang === 'zh' ? '服务' : 'services' }</li>
-          {this.props.mockhsm &&
           <li>
             <Link to='/keys' activeClassName={styles.active}>
               {navIcon('mockhsm', styles)}
               {lang === 'zh' ? '密钥' : 'Keys'}
             </Link>
           </li>
-          }
         </ul>
 
         { this.props.showNavAdvance && <ul className={styles.navigation}>
@@ -113,20 +116,12 @@ class Navigation extends React.Component {
 
 export default connect(
   state => {
-    let docVersion = ''
-
-    const versionComponents = state.core.version.match('^([0-9]\\.[0-9])\\.')
-    if (versionComponents != null) {
-      docVersion = versionComponents[1]
-    }
-
     return {
+      newVersionCode: state.core.newVersionCode,
+      update: state.core.update,
       coreData: state.core.coreData,
       routing: state.routing, // required for <Link>s to update active state on navigation
-      showSync: state.core.configured && !state.core.generator,
       lang: state.core.lang,
-      mockhsm: true,
-      docVersion,
       showNavAdvance: state.app.navAdvancedState === 'advance'
     }
   },
index 7fe8263..ab8ff14 100644 (file)
     margin-top: -2px;
   }
 }
+
+.warningIcon {
+  height: 10px;
+  padding-right: 5px;
+}
+
+.updateWarning{
+  background-color:  transparentize($background-content-color, 0.9);
+  padding: 10px $gutter-size/4;
+  color: white;
+  margin: $gutter-size/2 $gutter-size 0px;
+  border-radius: 3px;
+}
index 5fbceff..a8c2826 100644 (file)
@@ -1,5 +1,5 @@
 .main {
-  padding-top: $gutter-size;
+  padding: $gutter-size;
   border-top: 1px solid $border-inverse-color;
   margin-top: $gutter-size;
 }
index facc797..0f33526 100644 (file)
@@ -111,8 +111,8 @@ class Backup extends React.Component {
                     <p>
                       {
                         lang === 'zh' ?
-                        '这个选项将从文件中恢复钱包数据。 如果你的钱包余额显示不正确,请点扫描钱包的按钮。' :
-                        'This option will restore the wallet data form files. You might need to rescan your wallet, if you balance is not up to date'
+                        '这个选项将从文件中恢复钱包数据。 如果你的钱包余额显示不正确,请点扫描钱包的按钮。' :
+                        'This option will restore the wallet data form a file. You might need to rescan your wallet, if you balance is not up to date.'
                       }
                     </p>
                   </div>
index 69ee798..1d5c88f 100644 (file)
@@ -74,7 +74,7 @@ class CoreIndex extends React.Component {
             <tbody>
             <tr className={styles.row}>
               <td className={styles.row_label}>{lang === 'zh' ? '核心版本号' : 'Core Versions'}:</td>
-              <td><code>{coreData? coreData['version'] : null}</code></td>
+              <td><code>{coreData? coreData['versionInfo']['version']: null}</code></td>
             </tr>
             <tr className={styles.row}>
               <td className={styles.row_label}>{lang === 'zh' ? '语言' : 'Language'}:</td>
index e81afc7..9ca64c7 100644 (file)
@@ -7,7 +7,7 @@ const LONG_TIME_FORMAT = 'YYYY-MM-DD, h:mm:ss a'
 
 const coreConfigReducer = (key, state, defaultState, action) => {
   if (action.type == 'UPDATE_CORE_INFO') {
-    return action.param[key] || defaultState
+    return action.param.data[key] || defaultState
   }
 
   return state || defaultState
@@ -262,7 +262,26 @@ const snapshot = (state = null, action) => {
   return state
 }
 
-const version = (state, action) => coreConfigReducer('version', state, 'N/A', action)
+const version = (state = 'N/A', action) => {
+  if (action.type == 'UPDATE_CORE_INFO') {
+    return action.param.data.versionInfo.version
+  }
+  return state
+}
+
+const newVersionCode = (state = 'N/A', action) => {
+  if (action.type == 'UPDATE_CORE_INFO') {
+    return action.param.data.versionInfo.newVersion
+  }
+  return state
+}
+
+const update = (state = false, action) => {
+  if (action.type == 'UPDATE_CORE_INFO') {
+    return action.param.data.versionInfo.update > 0
+  }
+  return state
+}
 
 
 export default combineReducers({
@@ -281,6 +300,7 @@ export default combineReducers({
   generatorBlockHeight,
   generatorUrl,
   localhostAuth,
+  newVersionCode,
   mockhsm,
   mingStatus,
   crosscoreRpcVersion,
@@ -293,6 +313,7 @@ export default combineReducers({
   signer,
   snapshot,
   syncEstimates,
+  update,
   validToken,
   version,
   lang,
index d283851..8186ca2 100644 (file)
@@ -36,26 +36,41 @@ const resetPassword = {
   }
 }
 
+const checkPassword = (data) => (dispatch) => {
+  return clientApi().checkPassword(data)
+    .then((resp) => {
+      if(resp.status === 'fail'){
+        throw new Error(resp.msg)
+      }else if(!resp.data.checkResult){
+        throw new Error('Your Password is wrong')
+      }
+      dispatch({ type: 'KEY_PASSWORD_SUCCESS'})
+    })
+}
+
+const createExport =  (arg, fileName) => (dispatch) => {
+  clientApi().export(arg).then(resp => {
+    if(resp.status == 'success'){
+      const privateKey = resp.data.privateKey
+
+      var element = document.createElement('a')
+      element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(privateKey))
+      element.setAttribute('download', fileName)
+      element.style.display = 'none'
+      document.body.appendChild(element)
+      element.click()
+
+      document.body.removeChild(element)
+    }else if(resp.status == 'fail'){
+      dispatch({ type: 'ERROR', payload: {message: resp.msg} })
+    }
+  })
+}
+
 export default {
   ...create,
   ...list,
   ...resetPassword,
-  createExport: (arg, fileName) => (dispatch) => {
-    clientApi().export(arg).then(resp => {
-      if(resp.status == 'success'){
-        const privateKey = resp.data.privateKey
-
-        var element = document.createElement('a')
-        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(privateKey))
-        element.setAttribute('download', fileName)
-        element.style.display = 'none'
-        document.body.appendChild(element)
-        element.click()
-
-        document.body.removeChild(element)
-      }else if(resp.status == 'fail'){
-        dispatch({ type: 'ERROR', payload: {message: resp.msg} })
-      }
-    })
-  }
+  checkPassword,
+  createExport
 }
diff --git a/src/features/mockhsm/components/CheckPassword/CheckPassword.jsx b/src/features/mockhsm/components/CheckPassword/CheckPassword.jsx
new file mode 100644 (file)
index 0000000..f3a2b09
--- /dev/null
@@ -0,0 +1,100 @@
+import React, {Component} from 'react'
+import { reduxForm } from 'redux-form'
+
+import { FormContainer, FormSection, PasswordField} from 'features/shared/components'
+
+class CheckPassword extends Component {
+  constructor(props) {
+    super(props)
+    this.submitWithErrors = this.submitWithErrors.bind(this)
+    this.state = {}
+  }
+
+  submitWithErrors(data, xpub) {
+    return new Promise((resolve, reject) => {
+      const arg = {
+        'xpub': xpub,
+        'password': data.password,
+      }
+      this.props.checkPassword(arg)
+        .then(() =>
+          resolve()
+        )
+        .catch((err) => reject({_error: err.message}))
+    })
+  }
+
+  componentDidMount() {
+    this.props.fetchItem().then(resp => {
+      if (resp.data.length == 0) {
+        this.setState({notFound: true})
+      }
+    })
+  }
+
+  render() {
+    if (this.state.notFound) {
+      return <NotFound />
+    }
+    const item = this.props.item
+    const lang = this.props.lang
+
+    if (!item) {
+      return <div>Loading...</div>
+    }
+
+    const success = this.props.successMsg
+
+    const {
+      fields: { password },
+      error,
+      handleSubmit,
+    } = this.props
+
+    const title = <span>
+      {lang === 'zh' ? '密码校验' : 'Try Password'}
+      <code>{item.alias}</code>
+    </span>
+
+    return (
+      <FormContainer
+        error={error}
+        success={success}
+        label={title}
+        onSubmit={handleSubmit(value => this.submitWithErrors(value, item.xpub))}
+        submitLabel= { lang === 'zh' ? '密码校验' : 'Try Password' }
+        lang={lang}>
+
+        <FormSection>
+          <PasswordField
+            title = { lang === 'zh' ? '密码' : 'Password' }
+            placeholder={ lang === 'zh' ? '请输入校验密码' : 'Please entered the password that you need to check.' }
+            fieldProps={password}
+            />
+        </FormSection>
+      </FormContainer>
+    )
+  }
+}
+
+import {connect} from 'react-redux'
+import actions from 'actions'
+
+const mapStateToProps = (state, ownProps) => ({
+  item: state.key.items[ownProps.params.id],
+  lang: state.core.lang,
+  successMsg: state.key.success
+})
+
+const mapDispatchToProps = ( dispatch ) => ({
+  fetchItem: () => dispatch(actions.key.fetchItems()),
+  checkPassword: (params) => dispatch(actions.key.checkPassword(params))
+})
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(reduxForm({
+  form: 'CheckPassword',
+  fields: ['password'],
+})(CheckPassword))
diff --git a/src/features/mockhsm/components/CheckPassword/CheckPassword.scss b/src/features/mockhsm/components/CheckPassword/CheckPassword.scss
new file mode 100644 (file)
index 0000000..50c9687
--- /dev/null
@@ -0,0 +1,7 @@
+.main {
+  margin: $gutter-size auto;
+  border-radius: $border-radius-standard;
+  background-color: $background-content-color;
+  padding: $gutter-size/2;
+}
+
index 4486651..004c0b6 100644 (file)
@@ -50,6 +50,7 @@ class Show extends BaseShow {
             object='key'
             title={lang === 'zh' ? '详情' : 'Details'}
             actions={[
+              <Link key='check-password-btn' className='btn btn-link' to={`/keys/${item.id}/check-password`}>{lang === 'zh' ? '密码校验' : 'Try Password' }</Link>,
               <Link key='reset-password-btn' className='btn btn-link' to={`/keys/${item.id}/reset-password`}>{lang === 'zh' ? '重置密码' : 'Reset Password' }</Link>
             ]}
             items={[
index c24a357..72b69e0 100644 (file)
@@ -2,10 +2,12 @@ import List from './List'
 import New from './New'
 import Show from './Show'
 import ResetPassword from './ResetPassword/ResetPassword'
+import CheckPassword from './CheckPassword/CheckPassword'
 
 export {
   List,
   New,
   Show,
-  ResetPassword
+  ResetPassword,
+  CheckPassword
 }
index e60cb7c..e02c9f8 100644 (file)
@@ -8,4 +8,13 @@ export default combineReducers({
   items: reducers.itemsReducer(type, idFunc),
   queries: reducers.queriesReducer(type, idFunc),
   autocompleteIsLoaded: reducers.autocompleteIsLoadedReducer(type),
+  success: (state = '', action) => {
+    if (action.type == 'KEY_PASSWORD_SUCCESS') {
+      return 'Your key password is correct.'
+    }
+    if(action.type == 'redux-form/CHANGE'){
+      return ''
+    }
+    return state
+  },
 })
index 2ff66b6..b15f971 100644 (file)
@@ -1,4 +1,4 @@
-import { List, New, Show, ResetPassword } from './components'
+import { List, New, Show, ResetPassword, CheckPassword } from './components'
 import { makeRoutes } from 'features/shared'
 
 export default (store) => makeRoutes(store, 'key', List, New, Show,
@@ -8,4 +8,8 @@ export default (store) => makeRoutes(store, 'key', List, New, Show,
         path: ':id/reset-password',
         component: ResetPassword,
       },
+      {
+        path: ':id/check-password',
+        component: CheckPassword,
+      },
     ],skipFilter: true, name: 'Keys', name_zh:'密钥' })
\ No newline at end of file
index b55703e..ad0e817 100644 (file)
@@ -43,6 +43,8 @@ export default function(type, options = {}) {
     return (dispatch, getState) => {
       const getFilterStore = () => getState()[type].queries[listId] || {}
 
+      options.pageNumber = pageNumber
+
       const fetchNextPage = () =>
         dispatch(_load(query, getFilterStore(), options)).then((resp) => {
           if (!resp || resp.type == 'ERROR') return
@@ -85,6 +87,13 @@ export default function(type, options = {}) {
         if (query.filter) params.filter = filter
         if (query.sumBy) params.sumBy = query.sumBy.split(',')
 
+        if(requestOptions.pageNumber !== -1){
+          const count = requestOptions.pageSize
+          const from = ( count ) * ( requestOptions.pageNumber -1 )
+          params.from = from
+          params.count = count
+        }
+
         promise = dispatch(fetchItems(params))
       }
 
index ab37680..51fd6a7 100644 (file)
@@ -36,7 +36,7 @@ class AmountInputMask extends React.Component {
   }
 
   componentDidUpdate(prevProps){
-    if(prevProps.decimal !== this.props.decimal){
+    if(prevProps.decimal !== this.props.decimal && prevProps.fieldProps.value){
       const value = (prevProps.fieldProps.value/ Math.pow(10, prevProps.decimal)).toString()
 
       this.props.fieldProps.onChange(
index ff2ff26..931f0c4 100644 (file)
@@ -2,8 +2,9 @@ import React from 'react'
 import actions from 'actions'
 import { connect as reduxConnect } from 'react-redux'
 import { pluralize, capitalize, humanize } from 'utility/string'
+import {UTXOpageSize, pageSize} from 'utility/environment'
 import componentClassNames from 'utility/componentClassNames'
-import { PageContent, PageTitle } from '../'
+import { PageContent, PageTitle, Pagination } from '../'
 import EmptyList from './EmptyList'
 
 class ItemList extends React.Component {
@@ -65,8 +66,22 @@ class ItemList extends React.Component {
         </div>
       )
     } else {
+      let pagination = <Pagination
+        currentPage={this.props.currentPage}
+        currentFilter={this.props.currentFilter}
+        isLastPage={this.props.isLastPage}
+        pushList={this.props.pushList}
+        lang={this.props.lang}
+      />
+
       const items = this.props.items.map((item) =>
-        <this.props.listItemComponent key={item.id} item={item} lang={this.props.lang} btmAmountUnit={this.props.btmAmountUnit} {...this.props.itemActions}/>)
+        <this.props.listItemComponent
+          key={item.id}
+          item={item}
+          lang={this.props.lang}
+          btmAmountUnit={this.props.btmAmountUnit}
+          {...this.props.itemActions}/>)
+
       const Wrapper = this.props.wrapperComponent
 
       return(
@@ -75,6 +90,8 @@ class ItemList extends React.Component {
 
           <PageContent>
             {Wrapper ? <Wrapper {...this.props.wrapperProps}>{items}</Wrapper> : items}
+
+            { ( label==='transactions' || label === 'unspent outputs') && pagination}
           </PageContent>
         </div>
       )
@@ -82,8 +99,14 @@ class ItemList extends React.Component {
   }
 }
 
-export const mapStateToProps = (type, itemComponent, additionalProps = {}) => (state) => {
+export const mapStateToProps = (type, itemComponent, additionalProps = {}) => (state, ownProps ) => {
+  const paginationArray =[ 'unspent', 'transaction' ]
+
   let items = Object.assign({}, state[type].items)
+  const highestBlock = state.core.coreData && state.core.coreData.highestBlock
+  const count = (type === 'unspent')? UTXOpageSize: pageSize
+
+  const currentPage = paginationArray.includes(type) && Math.max(parseInt(ownProps.location.query.page) || 1, 1)
 
   if (type === 'key') {
     (state[type].importStatus || []).forEach(status => {
@@ -95,15 +118,23 @@ export const mapStateToProps = (type, itemComponent, additionalProps = {}) => (s
 
   let target = []
   for (let key in items) {
+    items[key].highest = highestBlock
     target.push(items[key])
   }
 
+  const isLastPage = target.length <count
+
   return {
     items: target,
     loadedOnce: state[type].queries.loadedOnce,
     type: type,
+
+    isLastPage: isLastPage,
+    currentPage: currentPage,
+
     lang: state.core.lang,
     btmAmountUnit: state.core.btmAmountUnit,
+
     listItemComponent: itemComponent,
     noResults: target.length == 0,
     showFirstTimeFlow: target.length == 0,
index 6ca273e..de6c614 100644 (file)
@@ -4,7 +4,7 @@
 
 .reactConsoleContainer {
   box-sizing: border-box;
-  height: calc(100% - 116px);
+  height: calc(100% - 121px);
   padding: 30px;
   overflow: scroll;
   background-color: $background-content-color;
index 9689404..c728c6e 100644 (file)
@@ -7,12 +7,12 @@
 
 .title{
   border-bottom: 1px solid $border-color;
-  padding: 5px 10px;
+  padding: 10px;
   color: $text-strong-color;
 }
 
 .messageBox{
-  padding: 5px 10px;
+  padding: 10px;
   white-space: pre-wrap;      /* CSS3 */
   white-space: -moz-pre-wrap; /* Firefox */
   white-space: -pre-wrap;     /* Opera <7 */
index b84a5b2..d4b5b27 100644 (file)
@@ -4,10 +4,11 @@ import styles from './ErrorBanner.scss'
 class ErrorBanner extends React.Component {
   render() {
     const error = this.props.error || ''
-    const message = error.chainMessage || error.message || error
+    const success = this.props.success
+    const message = error.chainMessage || error.message || error || success
 
     return (
-      <div className={styles.main}>
+      <div className={success? styles.mainSuccess:  styles.main }>
         {this.props.title && <strong>{this.props.title}<br/></strong>}
 
         {message &&
index 25bc3e7..d5e828a 100644 (file)
   }
 }
 
+.mainSuccess {
+  background: $success-background;
+  color: $success;
+  border: 1px solid $success-border;
+  border-radius: $border-radius-standard;
+  padding: 20px;
+  margin-bottom: 20px;
+  word-wrap: break-word;
+
+  a {
+    color: $success;
+    text-decoration: underline;
+  }
+}
+
 .message {
   margin-bottom: 15px;
 }
index 0dd8b71..c5526b5 100644 (file)
@@ -24,6 +24,11 @@ class FormContainer extends React.Component {
                     title='Error submitting form'
                     error={this.props.error} />}
 
+                {this.props.success &&
+                  <ErrorBanner
+                    title='Success'
+                    success={this.props.success} />}
+
                 <div className={styles.submit}>
                   <button type='submit' className='btn btn-primary' disabled={this.props.submitting || this.props.disabled}>
                     {this.props.submitLabel ||  ( lang === 'zh' ? '提交' : 'Submit' )}
index 11035a2..9de401a 100644 (file)
@@ -45,6 +45,7 @@ class KeyValueTable extends React.Component {
   }
 
   render() {
+    const lang = this.props.lang
     return (
       <Section
         title={this.props.title}
@@ -57,8 +58,11 @@ class KeyValueTable extends React.Component {
               <td className={styles.value}>{this.renderValue(item)}
                 {item.editUrl && <Link to={item.editUrl} className={styles.edit}>
                   <span
-                    className={`${styles.pencil} glyphicon glyphicon-pencil`}></span>{this.props.lang === 'zh' ? '编辑' : 'Edit'}
+                    className={`${styles.pencil} glyphicon glyphicon-pencil`}></span>{lang === 'zh' ? '编辑' : 'Edit'}
                 </Link>}
+                {item.program && <button  onClick={item.program} className={`${styles.detail} ${styles.edit} btn btn-link`}>
+                  { lang === 'zh' ? '合约程序': 'Program' }
+                </button>}
               </td>
             </tr>
           })}
index 9c2a4d1..4ea8364 100644 (file)
 
 .edit {
   float: right;
-  font-family: Nitti Grotesk;
+  font-family: $font-family-sans-serif;
   font-size: $font-size-base;
 }
 
+.detail {
+  padding: 0px;
+  margin: 0px;
+  line-height: 15px;
+}
+
 .pencil {
   padding-right: 5px;
   font-size: 10px;
diff --git a/src/features/shared/components/Pagination/Pagination.jsx b/src/features/shared/components/Pagination/Pagination.jsx
new file mode 100644 (file)
index 0000000..4b2f6a3
--- /dev/null
@@ -0,0 +1,37 @@
+import React from 'react'
+import styles from './Pagination.scss'
+
+class Pagination extends React.Component {
+  render() {
+    const lang = this.props.lang
+    const prevClass = `${styles.button} ${this.props.currentPage > 1 ? '' : styles.disabled}`
+    const nextClass = `${styles.button} ${this.props.isLastPage ? styles.disabled : ''}`
+    const nextPage = () => this.props.pushList(this.props.currentFilter, this.props.currentPage + 1)
+    const prevPage = () => this.props.pushList(this.props.currentFilter, this.props.currentPage - 1)
+
+    return (
+      <ul className={styles.main}>
+        <li>
+          <a className={prevClass} onClick={prevPage}>
+            &larr;
+          </a>
+        </li>
+        <li className={styles.label}>{lang ==='zh'? '页面' :'Page'} {this.props.currentPage}</li>
+        <li>
+          <a className={nextClass} onClick={nextPage}>
+            &rarr;
+          </a>
+        </li>
+      </ul>
+    )
+  }
+}
+
+Pagination.propTypes = {
+  currentPage: React.PropTypes.number,
+  isLastPage: React.PropTypes.bool,
+  pushList: React.PropTypes.func,
+  currentFilter: React.PropTypes.object,
+}
+
+export default Pagination
index ee6f039..1c95285 100644 (file)
   }
 }
 
-.pagination-S {
-  list-style-type:none;
-  li{
-    position: relative;
-    display: inline-block;
-    padding: 0 2px;
-    a,span{
-      position: relative;
-      float: left;
-      line-height: 30px;
-      text-decoration: none;
-      color: $highlight-default;;
-      height: 30px;
-      width: 30px;
-      border-radius: 15px;
-      &:hover{
-        color: $highlight-secondary;
-        background-color: $background-color;
-      }
-    }
-    span[aria-label="Previous"],
-    span[aria-label="Next"]
-    {
-      border: 2px solid $highlight-default;
-      color: $highlight-default;
-      cursor: pointer;
-      display: block;
-      font-size: $font-size-btn-lg;
-      line-height: 26px;
-      &:hover {
-        background-color: $highlight-default;
-        color: $background-color;
-      }
-    }
-  }
-  :global .active{
-    a{
-      background-color: $highlight-default;
-      color: $background-color;
-    }
-  }
-  :global .disabled {
-    span{
-      border-color: $text-color;
-      color: $text-color;
-    }
-    span[aria-label="Previous"],
-    span[aria-label="Next"]{
-      display:none;
-    }
+
+.button {
+  border: 2px solid $highlight-default;
+  color: $highlight-default;
+  cursor: pointer;
+  display: block;
+  line-height: 26px;
+  font-size: $font-size-btn-lg;
+  height: 30px;
+  width: 30px;
+  border-radius: 15px;
+
+  &:hover {
+    border-color: $highlight-secondary;
   }
 }
 
+.disabled {
+  border-color: $text-color;
+  color: $text-color;
+  opacity: 0;
+  pointer-events: none;
+}
diff --git a/src/features/shared/components/Pagination/PaginationField.jsx b/src/features/shared/components/Pagination/PaginationField.jsx
deleted file mode 100644 (file)
index a1c6dbc..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react'
-import styles from './Pagination.scss'
-import { Pagination } from 'react-bootstrap'
-
-class PaginationField extends React.Component {
-  render() {
-    const handlePageChange = (PageNumber) => this.props.pushList([],PageNumber)
-    const noOfPaginationBtn = 5
-    const centerClassName = 'text-center'
-    return (
-     <div className={centerClassName}>
-        <Pagination
-          bsClass={styles['pagination-S']}
-          prev=  "&larr;"
-          next=  "&rarr;"
-          boundaryLinks
-          activePage = {this.props.currentPage}
-          items = {this.props.totalNumberPage}
-          maxButtons = {noOfPaginationBtn}
-          onSelect = {handlePageChange}
-        />
-      </div>
-    )
-  }
-}
-
-PaginationField.propTypes = {
-  currentPage: React.PropTypes.number,
-  totalNumberPage: React.PropTypes.number,
-  pushList: React.PropTypes.func,
-}
-
-export default PaginationField
index 0c06450..77f2479 100644 (file)
@@ -22,7 +22,7 @@ import NotFound from './NotFound'
 import ObjectSelectorField from './ObjectSelectorField/ObjectSelectorField'
 import PageContent from './PageContent/PageContent'
 import PageTitle from './PageTitle/PageTitle'
-import PaginationField from './Pagination/PaginationField'
+import Pagination from './Pagination/Pagination'
 import PasswordField from './PasswordField/PasswordField'
 import RawJsonButton from './RawJsonButton'
 import RelativeTime from './RelativeTime'
@@ -60,7 +60,7 @@ export {
   ObjectSelectorField,
   PageContent,
   PageTitle,
-  PaginationField,
+  Pagination,
   PasswordField,
   RawJsonButton,
   RelativeTime,
index d71f9d0..b18aad9 100644 (file)
@@ -1,10 +1,22 @@
 import { RoutingContainer } from 'features/shared/components'
 import { humanize } from 'utility/string'
+import { UTXOpageSize, pageSize } from 'utility/environment'
 import actions from 'actions'
 
 const makeRoutes = (store, type, List, New, Show, options = {}) => {
-  const loadPage = () => {
-    store.dispatch(actions[type].fetchAll())
+  const loadPage = ( state ) => {
+    if(type === 'transaction' || type === 'unspent'){
+      const query = state.location.query
+      const pageNumber = parseInt(state.location.query.page || 1)
+      const pageSizes = (type === 'unspent')? UTXOpageSize: pageSize
+      if (pageNumber == 1) {
+        store.dispatch(actions[type].fetchPage(query, pageNumber, { refresh: true, pageSize: pageSizes }))
+      } else {
+        store.dispatch(actions[type].fetchPage(query, pageNumber,  { pageSize: pageSizes }))
+      }
+    }else{
+      store.dispatch(actions[type].fetchAll())
+    }
   }
 
   const childRoutes = []
index 89d6745..6574494 100644 (file)
@@ -99,15 +99,14 @@ function preprocessTransaction(formParams) {
 form.submitForm = (formParams) => function (dispatch) {
   const client = chainClient()
 
-  const buildPromise = (formParams.state.showAdvanced && formParams.signTransaction) ? null :
-    client.transactions.build(builder => {
-      const processed = preprocessTransaction(formParams)
+  const builderFunction = ( builder ) => {
+    const processed = preprocessTransaction(formParams)
 
-      builder.actions = processed.actions
-      if (processed.baseTransaction) {
-        builder.baseTransaction = processed.baseTransaction
-      }
-    })
+    builder.actions = processed.actions
+    if (processed.baseTransaction) {
+      builder.baseTransaction = processed.baseTransaction
+    }
+  }
 
   const submitSucceeded = () => {
     dispatch(form.created())
@@ -121,21 +120,43 @@ form.submitForm = (formParams) => function (dispatch) {
 
   // normal transactions
   if(formParams.form === 'normalTx'){
-    return buildPromise
-      .then(tpl => {
+
+    const accountId = formParams.accountId
+    const accountAlias = formParams.accountAlias
+    const accountInfo = Object.assign({},  accountAlias!== ''? {alias: accountAlias}: {id: accountId})
+
+    return client.accounts.query(accountInfo)
+      .then( resp => {
+        if(resp.data[0].xpubs.length > 1){
+          throw new Error('Your account has multiple keys, please use advanced transactions.')
+        }
+        const body = Object.assign({}, {xpub: resp.data[0].xpubs[0], password: formParams.password})
+        return client.mockHsm.keys.checkPassword(body)
+      })
+      .then( result => {
+        if(!result.data.checkResult){
+          throw new Error('Your password is wrong, please check your password.')
+        }
+        return client.transactions.build(builderFunction)
+      })
+      .then( tpl => {
         const body = Object.assign({}, {password: formParams.password, transaction: tpl.data})
         return client.transactions.sign(body)
       })
       .then(signed => {
         if(!signed.data.signComplete){
-          throw new Error('Signature failed, it might be your password is wrong.')
+          throw new Error('Signature failed.')
         }
         return client.transactions.submit(signed.data.transaction.rawTransaction)
       })
       .then(submitSucceeded)
   }
+
   //advanced transactions
   else{
+    const buildPromise = (formParams.state.showAdvanced && formParams.signTransaction) ? null :
+      client.transactions.build(builderFunction)
+
     if (formParams.submitAction == 'submit') {
       const signAndSubmitTransaction = (transaction) => {
         const body = Object.assign({}, {password: formParams.password, transaction: transaction})
@@ -182,7 +203,24 @@ form.submitForm = (formParams) => function (dispatch) {
   }
 }
 
+const decode = (data) => {
+  return (dispatch) => {
+    return  chainClient().transactions.decodeTransaction(data)
+      .then((resp) => {
+        if (resp.status === 'fail') {
+          dispatch({type: 'ERROR', payload: {'message': resp.msg}})
+        } else {
+          dispatch({type: 'DECODE_TRANSACTION', data:resp.data})
+        }
+      })
+      .catch(err => {
+        throw {_error: err}
+      })
+  }
+}
+
 export default {
   ...list,
   ...form,
+  decode,
 }
index 7d6e6a7..0c69ee8 100644 (file)
@@ -1,8 +1,8 @@
 import React from 'react'
-import { BaseList, PaginationField } from 'features/shared/components'
+import { BaseList, Pagination } from 'features/shared/components'
+import { pageSize} from 'utility/environment'
 import ListItem from './ListItem/ListItem'
 import actions from 'actions'
-import { pageSize } from '../../../utility/environment'
 
 const type = 'transaction'
 
@@ -16,20 +16,13 @@ class List extends React.Component {
   }
   render() {
     const ItemList = BaseList.ItemList
-    return <div>
-      <ItemList {...this.props}/>
-      {!this.props.noResults && this.props.totalNumberPage > 1 && <PaginationField
-        currentPage = { this.props.currentPage }
-        totalNumberPage = { this.props.totalNumberPage }
-        pushList = { this.props.pushList }/>}
-    </div>
+    return (<ItemList {...this.props} />)
   }
 }
 
 export default BaseList.connect(
   (state, ownProps) => ({
-    ...mapStateToProps(type,ListItem)(state,ownProps),
-    blockHeight: state.core.blockHeight
+    ...BaseList.mapStateToProps(type, ListItem)(state, ownProps)
   }),
   (dispatch) => ({
     ...BaseList.mapDispatchToProps(type)(dispatch),
@@ -38,33 +31,3 @@ export default BaseList.connect(
   List
 )
 
-const mapStateToProps = (type, itemComponent, additionalProps = {}) => {
-  return (state, ownProps) => {
-    const currentPage = Math.max(parseInt(ownProps.location.query.page) || 1, 1)
-    const totalItems = state[type].items
-    const keysArray = Object.keys(totalItems)
-    const totalNumberPage = Math.ceil(keysArray.length/pageSize)
-    const startIndex = (currentPage - 1) * pageSize
-    const highestBlock = state.core.coreData && state.core.coreData.highestBlock
-    const currentItems = keysArray.slice(startIndex, startIndex + pageSize).map(
-      id => totalItems[id]
-    ).filter(item => item != undefined)
-    currentItems.forEach(item => item.highest = highestBlock)
-
-    return {
-      currentPage: currentPage,
-      totalNumberPage: totalNumberPage,
-      items: currentItems,
-      loadedOnce: state[type].queries.loadedOnce,
-      type: type,
-      lang: state.core.lang,
-      btmAmountUnit: state.core.btmAmountUnit,
-      listItemComponent: itemComponent,
-      noResults: currentItems.length == 0,
-      showFirstTimeFlow: currentItems.length == 0,
-      highestBlock: highestBlock,
-      ...additionalProps
-    }
-  }
-}
-
index fa1e702..01a319b 100644 (file)
@@ -2,17 +2,18 @@ import {
   BaseNew,
   FormSection,
   FieldLabel,
-  TextField,
   SubmitIndicator,
   ErrorBanner,
   PasswordField
 } from 'features/shared/components'
+import TransactionDetails from './MultiSignTransactionDetails/TransactionDetails'
 import {DropdownButton, MenuItem} from 'react-bootstrap'
 import {reduxForm} from 'redux-form'
 import ActionItem from './FormActionItem'
 import React from 'react'
 import styles from './New.scss'
 import disableAutocomplete from 'utility/disableAutocomplete'
+import actions from 'actions'
 
 class AdvancedTxForm extends React.Component {
   constructor(props) {
@@ -90,7 +91,8 @@ class AdvancedTxForm extends React.Component {
 
     return (
       <form onSubmit={handleSubmit(this.submitWithValidation)} {...disableAutocomplete}
-            onKeyDown={(e) => { this.props.handleKeyDown(e, handleSubmit(this.submitWithValidation), this.disableSubmit(actions)) }}>
+            // onKeyDown={(e) => { this.props.handleKeyDown(e, handleSubmit(this.submitWithValidation), this.disableSubmit(actions)) }}
+      >
 
         <FormSection title='Actions'>
           {actions.map((action, index) =>
@@ -137,11 +139,13 @@ class AdvancedTxForm extends React.Component {
         {this.state.showAdvanced &&
         <FormSection title={lang === 'zh' ? '高级选项' : 'Advanced Options'}>
           <div>
-            <TextField
-              title={lang === 'zh' ? '带签名交易' : 'To sign transaction'}
-              placeholder={lang === 'zh' ? '在这里复制交易 HEX ...' : 'Paste transaction hex here...'}
+            <TransactionDetails
+              lang={lang}
               fieldProps={signTransaction}
-              autoFocus={true}/>
+              decode={this.props.decode}
+              transaction={this.props.decodedTx}
+              showJsonModal={this.props.showJsonModal}
+            />
 
             <FieldLabel>{lang === 'zh' ? '交易构建类型' : 'Transaction Build Type'}</FieldLabel>
             <table className={styles.submitTable}>
@@ -232,8 +236,20 @@ const validate = (values, props) => {
 }
 
 export default BaseNew.connect(
-  BaseNew.mapStateToProps('transaction'),
-  BaseNew.mapDispatchToProps('transaction'),
+  (state, ownProps) => ({
+    ...BaseNew.mapStateToProps('transaction')(state, ownProps),
+    decodedTx: state.transaction.decodedTx
+  }),
+  (dispatch) => ({
+    ...BaseNew.mapDispatchToProps('transaction')(dispatch),
+    decode: (transaction) => dispatch( actions.transaction.decode(transaction)),
+    showJsonModal: (body) => dispatch(actions.app.showModal(
+      body,
+      actions.app.hideModal,
+      null,
+      { wide: true }
+    )),
+  }),
   reduxForm({
     form: 'AdvancedTransactionForm',
     fields: [
diff --git a/src/features/transactions/components/New/MultiSignTransactionDetails/TransactionDetails.jsx b/src/features/transactions/components/New/MultiSignTransactionDetails/TransactionDetails.jsx
new file mode 100644 (file)
index 0000000..1890822
--- /dev/null
@@ -0,0 +1,57 @@
+import React from 'react'
+import pick from 'lodash/pick'
+import { FieldLabel } from 'features/shared/components'
+import disableAutocomplete from 'utility/disableAutocomplete'
+import styles from './TransactionDetails.scss'
+
+
+const TEXT_FIELD_PROPS = [
+  'value',
+  'onBlur',
+  'onChange',
+  'onFocus',
+  'name'
+]
+
+class TransactionDetails extends React.Component {
+  constructor(props) {
+    super(props)
+    this.showDetailTransactions = this.showDetailTransactions.bind(this)
+  }
+
+  showDetailTransactions(e, json){
+    e.preventDefault()
+    const rawTransaction = JSON.parse(json).rawTransaction
+    this.props.decode(rawTransaction).then(() => {
+      this.props.showJsonModal(<pre>{JSON.stringify(this.props.transaction, null, 2)}</pre>)
+    })
+  }
+
+  render() {
+    const fieldProps = pick(this.props.fieldProps, TEXT_FIELD_PROPS)
+    const {touched, error} = this.props.fieldProps
+
+    const lang = this.props.lang
+
+    return(
+      <div className='form-group'>
+        <FieldLabel>{lang === 'zh' ? '带签名交易' : 'To sign transaction'}</FieldLabel>
+        <input className='form-control'
+               type='text'
+               placeholder={lang === 'zh' ? '在这里复制交易 HEX ...' : 'Paste transaction hex here...'}
+               autoFocus={true}
+               {...disableAutocomplete}
+               {...fieldProps} />
+
+        {touched && error && <span className='text-danger'><strong>{error}</strong></span>}
+        {this.props.hint && <span className='help-block'>{this.props.hint}</span>}
+          <button className={`btn btn-link ${styles.btn}`}
+                  onClick={(e) => this.showDetailTransactions(e, fieldProps.value)}>
+            {lang === 'zh' ? '展示交易内容':'Show transaction details'}
+          </button>
+      </div>
+    )
+  }
+}
+
+export default TransactionDetails
diff --git a/src/features/transactions/components/New/MultiSignTransactionDetails/TransactionDetails.scss b/src/features/transactions/components/New/MultiSignTransactionDetails/TransactionDetails.scss
new file mode 100644 (file)
index 0000000..c114569
--- /dev/null
@@ -0,0 +1,64 @@
+.view{
+  margin-top: $gutter-size;
+}
+
+.main {
+  padding: $gutter-size;
+  margin-bottom: $gutter-size;
+  word-break: break-word;
+  background: $background-content-color;
+}
+
+.btn {
+  margin-top: 5px;
+  padding: 0px;
+  float: right;
+  font-size: $font-size-caps;
+}
+
+.table{
+  background: $background-color;
+  width: 100%;
+  margin-bottom: $gutter-size;
+
+  code{
+    font-size: $font-size-code;
+  }
+
+  thead {
+    border-bottom: 1px solid $border-color;
+  }
+
+  td, th {
+    padding-top: 6px;
+    padding-bottom: 6px;
+    padding-right: 10px;
+  }
+
+  td {
+    border-bottom: 1px solid $border-light-color;
+  }
+  tr:last-of-type td {
+    border-bottom: none;
+  }
+
+  a {
+    .rawId {
+      color: $highlight-default;
+    }
+
+    &:hover .rawId {
+      text-decoration: underline;
+    }
+  }
+}
+
+.colLabel {
+  color: $text-strong-color;
+  text-align: right;
+  width: 25%;
+}
+
+.txID{
+  margin-bottom: $gutter-size;
+}
index 9af7f6c..97ffd95 100644 (file)
@@ -4,6 +4,13 @@ import { combineReducers } from 'redux'
 const type = 'transaction'
 const maxGeneratedHistory = 50
 
+const decodedTx = (state = [], action) => {
+  if (action.type == 'DECODE_TRANSACTION') {
+    return action.data
+  }
+  return state
+}
+
 export default combineReducers({
   items: reducers.itemsReducer(type),
   queries: reducers.queriesReducer(type),
@@ -13,4 +20,5 @@ export default combineReducers({
     }
     return state
   },
+  decodedTx
 })
index aea094a..50ea34d 100644 (file)
@@ -1,58 +1,16 @@
 import React from 'react'
-import { BaseList, PaginationField } from 'features/shared/components'
+import { BaseList } from 'features/shared/components'
 import ListItem from './ListItem'
-import {UTXOpageSize, UTXOUTXOpageSize} from '../../../utility/environment'
 
 const type = 'unspent'
 
-class List extends React.Component {
-  render() {
-    const ItemList = BaseList.ItemList
-    return <div>
-      <ItemList {...this.props}/>
-      {!this.props.noResults && <PaginationField
-        currentPage = { this.props.currentPage }
-        totalNumberPage = { this.props.totalNumberPage }
-        pushList = { this.props.pushList }/>}
-    </div>
-  }
-}
-
 const newStateToProps = (state, ownProps) => ({
-  ...mapStateToProps(type, ListItem)(state, ownProps),
+  ...BaseList.mapStateToProps(type, ListItem)(state, ownProps),
   skipCreate: true,
   label: 'unspent outputs'
 })
 
 export default BaseList.connect(
   newStateToProps,
-  BaseList.mapDispatchToProps(type),
-  List
+  BaseList.mapDispatchToProps(type)
 )
-
-const mapStateToProps = (type, itemComponent, additionalProps = {}) => {
-  return (state, ownProps) => {
-    const currentPage = Math.max(parseInt(ownProps.location.query.page) || 1, 1)
-    const totalItems = state[type].items
-    const keysArray = Object.keys(totalItems)
-    const totalNumberPage = Math.ceil(keysArray.length/UTXOpageSize)
-    const startIndex = (currentPage - 1) * UTXOpageSize
-    const currentItems = keysArray.slice(startIndex, startIndex + UTXOpageSize).map(
-      id => totalItems[id]
-    ).filter(item => item != undefined)
-
-    return {
-      currentPage: currentPage,
-      totalNumberPage: totalNumberPage,
-      items: currentItems,
-      loadedOnce: state[type].queries.loadedOnce,
-      type: type,
-      listItemComponent: itemComponent,
-      noResults: currentItems.length == 0,
-      showFirstTimeFlow: currentItems.length == 0,
-      btmAmountUnit : state.core.btmAmountUnit,
-      lang: state.core.lang,
-      ...additionalProps
-    }
-  }
-}
index a19d072..765abf5 100644 (file)
@@ -20,7 +20,9 @@ const mockHsmKeysAPI = (client) => {
       return shared.query(client, 'mockHsm.keys', '/list-keys', params, {cb})
     },
 
-    resetPassword: (params, cb) =>  client.request('/reset-key-password', params),
+    resetPassword: (params) =>  client.request('/reset-key-password', params),
+
+    checkPassword:  (params) =>  client.request('/check-key-password', params),
 
     queryAll: (params, processor, cb) => shared.queryAll(client, 'mockHsm.keys', params, processor, cb),
 
index ee24ebc..2a351fb 100644 (file)
@@ -103,6 +103,11 @@ const transactionsAPI = (client) => {
       return shared.createBatch(client, '/build-transaction', builders, {cb})
     },
 
+    decodeTransaction: (raw_transaction, cb) => shared.tryCallback(
+      client.request('/decode-raw-transaction', {'raw_transaction': raw_transaction}).then(resp => checkForError(resp)),
+      cb
+    ),
+
     sign: (template, cb) => finalize(template)
       .then(finalized => client.request('/sign-transaction', finalized ).then(resp => checkForError(resp)),
         cb
index 2033003..c445c8d 100644 (file)
@@ -157,7 +157,7 @@ const addZeroToDecimalPos = (src,pos) => {
 }
 
 const formatIntNumToPosDecimal = (neu,pos) => {
-  if(neu != null ){
+  if(neu != null && neu !== ''){
     let neuString = neu.toString()
     let neuLength = neuString.length
     if(neuLength <= pos){
index e06cb1e..ce9cc61 100644 (file)
@@ -35,3 +35,5 @@ export const UTXOpageSize = 10
 export const testnetInfoUrl = process.env.TESTNET_INFO_URL || 'https://testnet-info.chain.com'
 export const testnetUrl = process.env.TESTNET_GENERATOR_URL || 'https://testnet.chain.com'
 export const docsRoot = 'https://github.com/bytom/bytom/wiki'
+
+export const releaseUrl = 'https://github.com/Bytom/bytom/releases'
diff --git a/static/images/warning.svg b/static/images/warning.svg
new file mode 100755 (executable)
index 0000000..77a34f3
--- /dev/null
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.817 21.301">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: #feaa17;
+      }
+    </style>
+  </defs>
+  <path id="警告" class="cls-1" d="M195.6,215.759l-9.374-16.25a2.517,2.517,0,0,0-4.364,0l-9.388,16.25a2.517,2.517,0,0,0,0,2.525,2.485,2.485,0,0,0,2.182,1.263h18.775a2.514,2.514,0,0,0,2.169-3.788Zm-11.57,1.455a1.578,1.578,0,1,1,1.578-1.578A1.579,1.579,0,0,1,184.03,217.214Zm1.249-5.394a1.048,1.048,0,0,1-1.043.974h-.4a1.039,1.039,0,0,1-1.043-.974l-.508-7.439a1.054,1.054,0,0,1,1.043-1.125h1.427a1.054,1.054,0,0,1,1.043,1.125Z" transform="translate(-172.135 -198.246)"/>
+</svg>