import { actions as balance } from 'features/balances'
import { actions as configuration } from 'features/configuration'
import { actions as core } from 'features/core'
+import { actions as initialization } from 'features/initialization'
import { actions as mockhsm } from 'features/mockhsm'
import { actions as testnet } from 'features/testnet'
import { actions as transaction } from 'features/transactions'
balance,
configuration,
core,
+ initialization,
key: mockhsm,
testnet,
transaction,
dispatch({ type: 'CLOSE_DROPDOWN' })
}
},
+ showInitialization: () => {
+ return (dispatch, getState) => {
+ // Need a default here, since locationBeforeTransitions gets cleared
+ // during logout.
+ let pathname = (getState().routing.locationBeforeTransitions || {}).pathname
+
+ if (pathname !== 'initialization') {
+ dispatch(push('/initialization'))
+ }
+ }
+ },
showConfiguration: () => {
return (dispatch, getState) => {
// Need a default here, since locationBeforeTransitions gets cleared
import React from 'react'
import { connect } from 'react-redux'
import actions from 'actions'
-import { Main, Config, Login, Loading, Register ,Modal } from './'
+import { Main, Config, Login, Loading, Init, Register, Modal } from './'
+import { Initialization } from 'features/initialization/components/index'
import moment from 'moment'
import { withI18n } from 'react-i18next'
authOk,
configKnown,
configured,
- location
+ location,
+ accountInit
} = props
if (!authOk || !configKnown) {
return
}
- if (configured) {
- if (location.pathname === '/' ||
- location.pathname.indexOf('configuration') >= 0) {
+ if (accountInit|| this.state.noAccountItem) {
+ if (location.pathname === '/' ) {
this.props.showRoot()
}
} else {
- this.props.showConfiguration()
+ this.props.showInitialization()
}
}
} else if (!this.props.configured) {
layout = <Config>{this.props.children}</Config>
} else if (!this.props.accountInit && this.state.noAccountItem){
- layout = <Register>{this.props.children}</Register>
+ layout = <Config>{this.props.children}</Config>
} else{
layout = <Main>{this.props.children}</Main>
}
(dispatch) => ({
fetchInfo: options => dispatch(actions.core.fetchCoreInfo(options)),
showRoot: () => dispatch(actions.app.showRoot),
- showConfiguration: () => dispatch(actions.app.showConfiguration()),
+ showInitialization: () => dispatch(actions.app.showInitialization()),
fetchAccountItem: () => dispatch(actions.account.fetchItems())
})
)( withI18n() (Container) )
--- /dev/null
+import { chainClient } from 'utility/environment'
+import { actions as coreActions } from 'features/core'
+import { fetchTestnetInfo } from 'features/testnet/actions'
+
+const retry = (dispatch, promise, count = 10) => {
+ return dispatch(promise).catch((err) => {
+ var currentTime = new Date().getTime()
+ while (currentTime + 200 >= new Date().getTime()) { /* wait for retry */ }
+
+ if (count >= 1) {
+ retry(dispatch, promise, count - 1)
+ } else {
+ throw(err)
+ }
+ })
+}
+
+let actions = {
+ submitConfiguration: (data) => {
+ const configureWithRetry = (dispatch, config) => {
+ return chainClient().config.configure(config)
+ .then(() => retry(dispatch, coreActions.fetchCoreInfo({throw: true})))
+ }
+
+ return (dispatch) => {
+ if (data.type == 'testnet') {
+ return dispatch(fetchTestnetInfo()).then(testnet =>
+ configureWithRetry(dispatch, testnet))
+ } else {
+ if (data.type == 'new') {
+ data = {
+ isGenerator: true,
+ isSigner: true,
+ quorum: 1,
+ }
+ }
+
+ delete data.type
+ return configureWithRetry(dispatch, data)
+ }
+ }
+ }
+}
+
+export default actions
--- /dev/null
+import React from 'react'
+import styles from './Index.scss'
+import { Link } from 'react-router'
+
+class Index extends React.Component {
+ constructor(props) {
+ super(props)
+ }
+
+ render() {
+ return (
+ <div className={styles.main}>
+ <Link to='/initialization/register'>
+ Create Wallet
+ </Link>
+ <button>
+ Restore Wallet
+ </button>
+ </div>
+ )
+ }
+}
+
+export default Index
--- /dev/null
+.main {
+ background: $background-inverse-color;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding-top: 50px;
+ display: block;
+ overflow: auto;
+}
+
+.choice {
+ margin-left: 10px;
+}
+
+.image {
+ width: 150px;
+ position: absolute;
+ top: calc(50px);
+ left: calc(50% - 75px);
+}
+
+.title{
+ text-align: center;
+ color: white;
+}
+
+.switch {
+ display: flex;
+ justify-content: space-around;
+
+ margin-top: 10px;
+}
+
+.formWarpper {
+ display: flex;
+ justify-content: space-around;
+
+ margin-top: 30px;
+}
+
+.form {
+ background: $background-color;
+ border-radius: $border-radius-standard;
+ width: 500px;
+ padding: 30px;
+}
--- /dev/null
+import React from 'react'
+import {connect} from 'react-redux'
+import {ErrorBanner, TextField, PasswordField} from 'features/shared/components'
+import actions from 'actions'
+import styles from './Register.scss'
+import {reduxForm} from 'redux-form'
+import {chainClient} from 'utility/environment'
+import {withNamespaces} from 'react-i18next'
+
+class Register extends React.Component {
+ constructor(props) {
+ super(props)
+ this.connection = chainClient().connection
+
+ this.submitWithErrors = this.submitWithErrors.bind(this)
+ }
+
+ componentDidMount() {
+ this.setState({
+ init: true
+ })
+ }
+
+ submitWithErrors(data) {
+ return new Promise((resolve, reject) => {
+ this.props.registerKey(data)
+ .catch((err) => reject({_error: err.message}))
+ })
+ }
+
+ setMode(isInit) {
+ this.setState({
+ init: isInit
+ })
+ }
+
+ restore() {
+ const element = document.getElementById('bytom-restore-file-upload-init')
+ element.click()
+ }
+
+ handleFileChange(event) {
+ const files = event.target.files
+ if (files.length <= 0) {
+ this.setState({key: null})
+ return
+ }
+
+ const fileReader = new FileReader()
+ fileReader.onload = fileLoadedEvent => {
+ const backupData = JSON.parse(fileLoadedEvent.target.result)
+ this.connection.request('/restore-wallet', backupData).then(resp => {
+ if (resp.status === 'fail') {
+ this.props.showError(new Error(resp.msg))
+ return
+ }
+ this.props.success()
+ })
+ }
+ fileReader.readAsText(files[0], 'UTF-8')
+
+ const fileElement = document.getElementById('bytom-restore-file-upload-init')
+ fileElement.value = ''
+ }
+
+ render() {
+ const t = this.props.t
+
+ const {
+ fields: {keyAlias, password, repeatPassword, accountAlias},
+ error,
+ handleSubmit,
+ submitting
+ } = this.props
+
+ return (
+ <div className={styles.main}>
+ {
+ this.state && this.state.init &&
+ <div>
+ <h2 className={styles.title}>{t('init.title')}</h2>
+ <div className={styles.formWarpper}>
+ <form className={styles.form} onSubmit={handleSubmit(this.submitWithErrors)}>
+ <TextField
+ title={t('form.accountAlias')}
+ placeholder={t('init.accountPlaceholder')}
+ fieldProps={accountAlias} />
+ <TextField
+ title={t('form.keyAlias')}
+ placeholder={t('init.keyPlaceholder')}
+ fieldProps={keyAlias}/>
+ <PasswordField
+ title={t('init.keyPassword')}
+ placeholder={t('init.passwordPlaceholder')}
+ fieldProps={password} />
+ <PasswordField
+ title={t('init.repeatKeyPassword')}
+ placeholder={t('init.repeatPlaceHolder')}
+ fieldProps={repeatPassword} />
+
+ {error &&
+ <ErrorBanner
+ title={t('init.errorTitle')}
+ error={error}/>}
+
+ <button type='submit' className='btn btn-primary' disabled={submitting}>
+ {t('init.register')}
+ </button>
+ <a className={`${styles.choice} ${(this.state && this.state.init) ? '' : styles.active}`}
+ href='javascript:;' onClick={this.setMode.bind(this, false)}>
+ {t('init.restoreWallet')}
+ </a>
+ </form>
+ </div>
+ </div>
+ }
+ {
+ this.state && !this.state.init &&
+ <div>
+ <h2 className={styles.title}>{t('init.restoreWallet')}</h2>
+ <div className={styles.formWarpper}>
+ <form className={styles.form} onSubmit={handleSubmit(this.submitWithErrors)}>
+ <button className='btn btn-primary' onClick={this.restore.bind(this)}>
+ {t('init.restore')}
+ </button>
+ <a className={`${styles.choice} ${(this.state && this.state.init) ? styles.active : ''}`}
+ href='javascript:;' onClick={this.setMode.bind(this, true)}>
+ {t('init.title')}
+ </a>
+
+ <p></p>
+ <p>{t('init.restoreLabel')}</p>
+
+ <input id='bytom-restore-file-upload-init' type='file' style={{'display': 'none'}}
+ onChange={this.handleFileChange.bind(this)}/>
+ </form>
+ </div>
+ </div>
+ }
+ </div>
+ )
+ }
+}
+
+const validate = (values, props) => {
+ const errors = {}
+ const t = props.t
+
+ if (!values.keyAlias) {
+ errors.keyAlias = t('key.aliasRequired')
+ }
+ if (!values.password) {
+ errors.password = t('key.passwordRequired')
+ } else if (values.password.length < 5) {
+ errors.password = ( t('key.reset.newPWarning') )
+ }
+ if (values.password !== values.repeatPassword) {
+ errors.repeatPassword = ( t('key.reset.repeatPWarning') )
+ }
+ if (!values.accountAlias) {
+ errors.accountAlias = ( t('account.new.aliasWarning') )
+ }
+
+ return errors
+}
+
+export default withNamespaces('translations')( connect(
+ () => ({}),
+ (dispatch) => ({
+ registerKey: (token) => dispatch(actions.core.registerKey(token)),
+ showError: (err) => dispatch({type: 'ERROR', payload: err}),
+ success: () => dispatch({type: 'CREATE_REGISTER_ACCOUNT'})
+ })
+)(reduxForm({
+ form: 'initDefaultPassword',
+ fields: ['keyAlias', 'password', 'repeatPassword', 'accountAlias'],
+ validate
+})(Register)))
--- /dev/null
+.main {
+ background: $background-inverse-color;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding-top: 50px;
+ display: block;
+ overflow: auto;
+}
+
+.choice {
+ margin-left: 10px;
+}
+
+.image {
+ width: 150px;
+ position: absolute;
+ top: calc(50px);
+ left: calc(50% - 75px);
+}
+
+.title{
+ text-align: center;
+ color: white;
+}
+
+.switch {
+ display: flex;
+ justify-content: space-around;
+
+ margin-top: 10px;
+}
+
+.formWarpper {
+ display: flex;
+ justify-content: space-around;
+
+ margin-top: 30px;
+}
+
+.form {
+ background: $background-color;
+ border-radius: $border-radius-standard;
+ width: 500px;
+ padding: 30px;
+}
--- /dev/null
+import Index from './Index/Index'
+import Register from './Register/Register'
+
+export {
+ Index,
+ Register
+}
--- /dev/null
+import actions from './actions'
+import routes from './routes'
+
+export {
+ actions,
+ routes,
+}
--- /dev/null
+import { RoutingContainer } from 'features/shared/components'
+import { Index, Register } from './components'
+
+export default {
+ path: 'initialization',
+ component: RoutingContainer,
+ indexRoute: { component: Index },
+ childRoutes: [
+ {
+ path: 'register',
+ component: Register
+ }
+ ]
+}
import { routes as balances } from 'features/balances'
import { routes as configuration } from 'features/configuration'
import { routes as core } from 'features/core'
+import { routes as initialization } from 'features/initialization'
import { routes as transactions } from 'features/transactions'
import { routes as transactionFeeds } from 'features/transactionFeeds'
import { routes as unspents } from 'features/unspents'
balances(store),
configuration,
core,
+ initialization,
transactions(store),
transactionFeeds(store),
unspents(store),