diff --git a/TODO b/TODO index 9a8b20b..f1736dc 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,13 @@ + resume statistic dashboard with money values +pagination +pre-anchors Toaster +buy/offer untrusted asset + +credit backstream from last timestamp +ground - desktop app link and page diff --git a/app/js/actions-creators/account.js b/app/js/actions-creators/account.js index 6e0ec31..a691bdc 100644 --- a/app/js/actions-creators/account.js +++ b/app/js/actions-creators/account.js @@ -1,15 +1,48 @@ import { push } from 'react-router-redux'; import { StellarServer, StellarTools } from 'stellar-toolkit'; +import { Keypair } from 'stellar-sdk'; import { AsyncActions } from '../helpers/asyncActions'; import * as AccountActions from '../actions/account'; import { ASYNC_FETCH_ACCOUNT, ASYNC_CREATE_TEST_ACCOUNT } from '../constants/asyncActions'; +import * as routes from '../constants/routes'; +import { getLocalAccounts } from '../helpers/storage'; +import { getAccounts, getCurrentAccount } from '../selectors/account'; import { getNetwork } from '../selectors/stellarData'; const { getAccount, switchNetwork: switchNetworkInstance, generateTestPair } = StellarServer; const { KeypairInstance } = StellarTools; +const PROD = process.env.NODE_ENV === 'production'; + +export const resetAccount = () => (dispatch) => { + dispatch(push({ query: {} })); + dispatch(AccountActions.resetAccount()); +}; + +export const switchNetwork = network => (dispatch, getState) => { + const currentNetwork = getNetwork(getState()); + if (network === currentNetwork) return; + + dispatch(resetAccount()); + + switchNetworkInstance(network); + dispatch(AccountActions.switchNetwork(network)); +}; + +export const addAccount = keypair => (dispatch) => { + const newAccount = { + id: keypair.publicKey(), + keypair, + }; + + // TODO update existing : add seed, edit other fields ... + // TODO add network into account + dispatch(AccountActions.addAccount(newAccount)); +}; + +// TODO handle invalid keys export const setAccount = keys => (dispatch, getState) => { dispatch(AsyncActions.startFetch(ASYNC_FETCH_ACCOUNT)); @@ -17,36 +50,75 @@ export const setAccount = keys => (dispatch, getState) => { const network = getNetwork(getState()); return getAccount(keypair.publicKey()) - .then((account) => { - dispatch(AsyncActions.successFetch(ASYNC_FETCH_ACCOUNT, account)); - dispatch(AccountActions.setKeypair(keypair)); - - const putSecret = (keypair.canSign() && process.env.NODE_ENV === 'development'); - const query = { - accountId: putSecret ? undefined : keypair.publicKey(), - secretSeed: putSecret ? keypair.secret() : undefined, - network, + .then((stellarAccount) => { + dispatch(AsyncActions.successFetch(ASYNC_FETCH_ACCOUNT, stellarAccount)); + dispatch(resetAccount()); + dispatch(addAccount(keypair)); + dispatch(AccountActions.setCurrentAccountId(keypair.publicKey())); + + const putSecret = (keypair.canSign() && !PROD); + const routeUpdate = { + pathname: routes.Account_G(keypair.publicKey()), + query: { + secretSeed: putSecret ? keypair.secret() : undefined, + network, + }, }; - dispatch(push({ query })); + dispatch(push(routeUpdate)); - return account; + return stellarAccount; }) - .catch(error => dispatch(AsyncActions.errorFetch(ASYNC_FETCH_ACCOUNT, error))); + .catch((error) => { + dispatch(AsyncActions.errorFetch(ASYNC_FETCH_ACCOUNT, error)); + dispatch(push(routes.Root)); + throw error; + }); }; -export const resetAccount = () => (dispatch) => { - dispatch(push({ query: {} })); - dispatch(AccountActions.resetAccount()); +export const openAccountId = id => (dispatch, getState) => { + const state = getState(); + const localAccounts = getAccounts(state); + const currentAccount = getCurrentAccount(state); + + if (!id) return Promise.reject(); + if (id === 'null') { // TODO store constant or direct call reset + return dispatch(resetAccount()); + } + if (currentAccount && currentAccount.id === id) + return Promise.resolve(); + + const localAccount = localAccounts.find(a => (a.id === id)); + + let keypair = null; + if (localAccount) { + keypair = localAccount.keypair; + } else { + keypair = Keypair.fromPublicKey(id); + } + return dispatch(setAccount(keypair)); }; -export const switchNetwork = network => (dispatch, getState) => { - const currentNetwork = getNetwork(getState()); - if (network === currentNetwork) return; +export const onPageLoad = nextState => (dispatch) => { + // Retrieve stored accounts + const localAccounts = getLocalAccounts(); + dispatch(AccountActions.addAccounts(localAccounts)); - dispatch(resetAccount()); + const { location: { query } } = nextState; + if (query.network) { + dispatch(switchNetwork(query.network)); + } +}; - switchNetworkInstance(network); - dispatch(AccountActions.switchNetwork(network)); +export const onChangeAccountRoute = nextState => (dispatch) => { + const { params: { id }, location: { action, query } } = nextState; + if(action === 'PUSH') return; // Disable self-sent actions + + if (query.secretSeed) { + const keypair = Keypair.fromSecret(query.secretSeed); + dispatch(setAccount(keypair)); + } else { + dispatch(openAccountId(id)); + } }; export const createTestAccount = () => (dispatch) => { diff --git a/app/js/actions/account.js b/app/js/actions/account.js index 63abee0..e92a237 100644 --- a/app/js/actions/account.js +++ b/app/js/actions/account.js @@ -1,8 +1,8 @@ export const SWITCH_NETWORK = 'network:switch'; export const RESET_ACCOUNT = 'account:reset'; -export const SET_KEYPAIR = 'keypair:set'; - +export const ADD_ACCOUNT = 'account:add'; +export const SET_CURRENT_ACCOUNT_ID = 'account:set-id'; export function resetAccount() { return { @@ -10,10 +10,10 @@ export function resetAccount() { }; } -export function setKeypair(keypair) { +export function setCurrentAccountId(id) { return { - type: SET_KEYPAIR, - keypair, + type: SET_CURRENT_ACCOUNT_ID, + id, }; } @@ -23,3 +23,15 @@ export function switchNetwork(network) { network, }; } + +export function addAccount(account, accounts) { + return { + type: ADD_ACCOUNT, + account, + accounts, + }; +} + +export function addAccounts(accounts) { + return addAccount(null, accounts); +} diff --git a/app/js/bootstrap/router.js b/app/js/bootstrap/router.js index 21e8543..3312f1b 100644 --- a/app/js/bootstrap/router.js +++ b/app/js/bootstrap/router.js @@ -1,35 +1,52 @@ -import React from 'react'; -import { Router, Route, Redirect, browserHistory } from 'react-router'; +import React, { PropTypes } from 'react'; +import { Router, Route, IndexRoute, Redirect, browserHistory } from 'react-router'; +import { connect } from 'react-redux'; import { syncHistoryWithStore } from 'react-router-redux'; import store from './store'; import Layout from '../components/Layout'; +import Welcome from '../components/views/WelcomeView'; import * as routes from '../constants/routes'; import AppMode from '../elements/AppMode'; // import NotFound from '../components/views/NotFound'; import Desktop from '../components/views/Desktop'; - -import * as AccountManager from '../helpers/AccountManager'; +import { onPageLoad as onPageLoadAction, onChangeAccountRoute as onChangeAccountRouteAction } from '../actions-creators/account'; const history = syncHistoryWithStore(browserHistory, store); -const RouterContainer = () => +const RouterContainer = ({ onPageLoad, onChangeAccountRoute }) => - + + - + + ; -export default RouterContainer; +RouterContainer.propTypes = { + onPageLoad: PropTypes.func.isRequired, + onChangeAccountRoute: PropTypes.func.isRequired, +}; + +const mapDispatchToProps = { + onPageLoad: onPageLoadAction, + onChangeAccountRoute: onChangeAccountRouteAction, +}; + +export default connect(null, mapDispatchToProps)(RouterContainer); diff --git a/app/js/bootstrap/store.js b/app/js/bootstrap/store.js index 8f39192..3eca652 100644 --- a/app/js/bootstrap/store.js +++ b/app/js/bootstrap/store.js @@ -6,8 +6,6 @@ import { browserHistory } from 'react-router'; import { routerMiddleware } from 'react-router-redux'; import reducers from '../reducers'; -import { setStore } from '../helpers/AccountManager'; - import stellarStreamerMiddleware from '../middlewares/StellarStreamer'; import asyncActionsMiddleware from '../helpers/asyncActions/middleware'; @@ -26,6 +24,4 @@ const store = createStore( enhancer, ); -setStore(store); - export default store; diff --git a/app/js/components/views/WelcomeView.js b/app/js/components/views/WelcomeView.js index 6456ae6..e3d4782 100644 --- a/app/js/components/views/WelcomeView.js +++ b/app/js/components/views/WelcomeView.js @@ -51,6 +51,8 @@ const styles = { }, }; +// TODO reshow loader on account loading + class WelcomeScreen extends Component { render() { return ( diff --git a/app/js/constants/routes.js b/app/js/constants/routes.js index 676e50e..208f1fb 100644 --- a/app/js/constants/routes.js +++ b/app/js/constants/routes.js @@ -1,2 +1,5 @@ export const Root = '/'; +export const Account = '/account/:id'; export const Desktop = '/desktop'; + +export const Account_G = id => Account.replace(':id', id); diff --git a/app/js/elements/AppMode/component.js b/app/js/elements/AppMode/component.js index 8387af8..c44bdc3 100644 --- a/app/js/elements/AppMode/component.js +++ b/app/js/elements/AppMode/component.js @@ -1,7 +1,6 @@ import React, { PropTypes } from 'react'; import { Dimmer, Loader } from 'semantic-ui-react'; -import Welcome from '../../components/views/WelcomeView'; import PrivateView from '../../components/views/PrivateView'; import PublicView from '../../components/views/PublicView'; @@ -15,9 +14,6 @@ function AppMode({ isAccountLoading, accountSet, canSign }) { accountSet && (canSign ? : ) } - { - !accountSet && - } ); } diff --git a/app/js/elements/StellarContainers/AccountSelector/component.js b/app/js/elements/StellarContainers/AccountSelector/component.js index e85d9c9..e12e917 100644 --- a/app/js/elements/StellarContainers/AccountSelector/component.js +++ b/app/js/elements/StellarContainers/AccountSelector/component.js @@ -24,26 +24,6 @@ class AccountSelector extends Component { showSeed: false, resolving: false, }; - if (this.props.keypair) { - this.state.accountId = this.props.keypair.publicKey(); - this.state.secretSeed = this.props.keypair.canSign() ? this.props.keypair.secret() : ''; - } - } - - componentWillReceiveProps(newProps) { - if (this.props.keypair !== newProps.keypair) { - if (newProps.keypair) { - this.setState({ - accountId: newProps.keypair.publicKey(), - secretSeed: newProps.keypair.canSign() ? newProps.keypair.secret() : '', - }); - } else { - this.setState({ - accountId: '', - secretSeed: '', - }); - } - } } componentDidMount() { @@ -89,7 +69,8 @@ class AccountSelector extends Component { } newForm() { - const { values: { address } } = this.props; + const { values } = this.props; + const address = values.address || null; let buttonLabel = 'Invalid address'; const buttonContent = 'Go'; @@ -172,7 +153,6 @@ AccountSelector.propTypes = { isAccountLoading: PropTypes.bool, isCreatingTestAccount: PropTypes.bool, error: PropTypes.object, - keypair: PropTypes.object, setAccount: PropTypes.func.isRequired, createTestAccount: PropTypes.func.isRequired, network: PropTypes.string.isRequired, diff --git a/app/js/elements/StellarContainers/AccountSelector/container.js b/app/js/elements/StellarContainers/AccountSelector/container.js index c99ba0b..a711512 100644 --- a/app/js/elements/StellarContainers/AccountSelector/container.js +++ b/app/js/elements/StellarContainers/AccountSelector/container.js @@ -8,7 +8,6 @@ import { isAccountLoading, isCreatingTestAccount, getAccountError, - getKeypair, canSign, } from '../../../selectors/account'; import { @@ -22,7 +21,6 @@ const mapStateToProps = state => ({ isCreatingTestAccount: isCreatingTestAccount(state), canSign: canSign(state), error: getAccountError(state), - keypair: getKeypair(state), network: getNetwork(state), values: getFormValues(FORM_NAME)(state), diff --git a/app/js/elements/StellarContainers/CurrentAccount/component.js b/app/js/elements/StellarContainers/CurrentAccount/component.js index e038ad5..1bbff66 100644 --- a/app/js/elements/StellarContainers/CurrentAccount/component.js +++ b/app/js/elements/StellarContainers/CurrentAccount/component.js @@ -2,6 +2,8 @@ import React, { Component, PropTypes } from 'react'; import { Table, Container, Button, Header } from 'semantic-ui-react'; import Clipboard from 'clipboard'; +import * as routes from '../../../constants/routes'; + class CurrentAccount extends Component { constructor(props) { @@ -17,16 +19,38 @@ class CurrentAccount extends Component { } openOnNewTab() { - let url = '/?'; - url += `network=${this.props.network}`; - if (this.props.keypair.canSign()) { - url += `&secretSeed=${this.props.keypair.secret()}`; - } else { - url += `&accountId=${this.props.keypair.publicKey()}`; - } + let url = routes.Account_G(this.props.keypair.publicKey()); window.open(url); } + renderSeedBtn() { + const { keypair } = this.props; + const canSign = keypair.canSign(); + + if(canSign) { + return ( + this.state.showSeed ? +