```
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
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})
})
}
})
}
+ 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
{(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>
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} >
className={styles.languages}
noCaret
>
- {this.props.lang === 'zh' ? '中' : 'EN'}
+ {lang === 'zh' ? '中' : 'EN'}
</Dropdown.Toggle>
<Dropdown.Menu
className={styles.languagesMenu}
</div>
<Navigation />
+
+ <div className={styles.version}>
+ <span>
+ {lang==='zh'?'版本号':'Version'}: {version}
+ </span>
+ </div>
+
</div>
</div>
export default connect(
(state) => ({
canLogOut: state.core.requireClientToken,
+ version:state.core.version,
lang: state.core.lang,
connected: true,
showDropwdown: state.app.dropdownState == 'open',
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;
}
.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;
.close {
position: absolute;
right: 10px;
- top: 5px;
+ top: 10px;
}
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) {
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>
<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}>
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'
}
},
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;
+}
.main {
- padding-top: $gutter-size;
+ padding: $gutter-size;
border-top: 1px solid $border-inverse-color;
margin-top: $gutter-size;
}
<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>
<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>
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
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({
generatorBlockHeight,
generatorUrl,
localhostAuth,
+ newVersionCode,
mockhsm,
mingStatus,
crosscoreRpcVersion,
signer,
snapshot,
syncEstimates,
+ update,
validToken,
version,
lang,
}
}
+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
}
--- /dev/null
+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))
--- /dev/null
+.main {
+ margin: $gutter-size auto;
+ border-radius: $border-radius-standard;
+ background-color: $background-content-color;
+ padding: $gutter-size/2;
+}
+
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={[
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
}
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
+ },
})
-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,
path: ':id/reset-password',
component: ResetPassword,
},
+ {
+ path: ':id/check-password',
+ component: CheckPassword,
+ },
],skipFilter: true, name: 'Keys', name_zh:'密钥' })
\ No newline at end of file
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
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))
}
}
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(
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 {
</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(
<PageContent>
{Wrapper ? <Wrapper {...this.props.wrapperProps}>{items}</Wrapper> : items}
+
+ { ( label==='transactions' || label === 'unspent outputs') && pagination}
</PageContent>
</div>
)
}
}
-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 => {
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,
.reactConsoleContainer {
box-sizing: border-box;
- height: calc(100% - 116px);
+ height: calc(100% - 121px);
padding: 30px;
overflow: scroll;
background-color: $background-content-color;
.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 */
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 &&
}
}
+.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;
}
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' )}
}
render() {
+ const lang = this.props.lang
return (
<Section
title={this.props.title}
<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>
})}
.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;
--- /dev/null
+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}>
+ ←
+ </a>
+ </li>
+ <li className={styles.label}>{lang ==='zh'? '页面' :'Page'} {this.props.currentPage}</li>
+ <li>
+ <a className={nextClass} onClick={nextPage}>
+ →
+ </a>
+ </li>
+ </ul>
+ )
+ }
+}
+
+Pagination.propTypes = {
+ currentPage: React.PropTypes.number,
+ isLastPage: React.PropTypes.bool,
+ pushList: React.PropTypes.func,
+ currentFilter: React.PropTypes.object,
+}
+
+export default Pagination
}
}
-.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;
+}
+++ /dev/null
-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= "←"
- next= "→"
- 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
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'
ObjectSelectorField,
PageContent,
PageTitle,
- PaginationField,
+ Pagination,
PasswordField,
RawJsonButton,
RelativeTime,
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 = []
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())
// 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})
}
}
+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,
}
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'
}
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),
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
- }
- }
-}
-
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) {
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) =>
{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}>
}
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: [
--- /dev/null
+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
--- /dev/null
+.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;
+}
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),
}
return state
},
+ decodedTx
})
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
- }
- }
-}
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),
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
}
const formatIntNumToPosDecimal = (neu,pos) => {
- if(neu != null ){
+ if(neu != null && neu !== ''){
let neuString = neu.toString()
let neuLength = neuString.length
if(neuLength <= pos){
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'
--- /dev/null
+<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>