Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/account routing #14

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -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

Expand Down
114 changes: 93 additions & 21 deletions app/js/actions-creators/account.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,124 @@
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));

const keypair = KeypairInstance(keys);
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) => {
Expand Down
22 changes: 17 additions & 5 deletions app/js/actions/account.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
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 {
type: RESET_ACCOUNT,
};
}

export function setKeypair(keypair) {
export function setCurrentAccountId(id) {
return {
type: SET_KEYPAIR,
keypair,
type: SET_CURRENT_ACCOUNT_ID,
id,
};
}

Expand All @@ -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);
}
37 changes: 27 additions & 10 deletions app/js/bootstrap/router.js
Original file line number Diff line number Diff line change
@@ -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 }) =>
<Router history={history}>
<Route component={Layout}>
<Route
component={Layout}
onEnter={onPageLoad}
path={routes.Root}
>
<Route
path={routes.Root}
path={routes.Account}
onEnter={onChangeAccountRoute}
component={AppMode}
onEnter={AccountManager.onPageLoad}
/>

<Route
path={routes.Desktop}
component={Desktop}
/>

<Redirect from="*" to="/" />
<IndexRoute component={Welcome} />
<Redirect from="*" to={routes.Root} />
</Route>
</Router>;

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);
4 changes: 0 additions & 4 deletions app/js/bootstrap/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -26,6 +24,4 @@ const store = createStore(
enhancer,
);

setStore(store);

export default store;
2 changes: 2 additions & 0 deletions app/js/components/views/WelcomeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const styles = {
},
};

// TODO reshow loader on account loading

class WelcomeScreen extends Component {
render() {
return (
Expand Down
3 changes: 3 additions & 0 deletions app/js/constants/routes.js
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 0 additions & 4 deletions app/js/elements/AppMode/component.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -15,9 +14,6 @@ function AppMode({ isAccountLoading, accountSet, canSign }) {
accountSet &&
(canSign ? <PrivateView /> : <PublicView />)
}
{
!accountSet && <Welcome />
}
</div>
);
}
Expand Down
24 changes: 2 additions & 22 deletions app/js/elements/StellarContainers/AccountSelector/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
isAccountLoading,
isCreatingTestAccount,
getAccountError,
getKeypair,
canSign,
} from '../../../selectors/account';
import {
Expand All @@ -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),
Expand Down
Loading