diff --git a/__tests__/components/App.test.js b/__tests__/components/App.test.js index b3ddc0c86..c922df180 100644 --- a/__tests__/components/App.test.js +++ b/__tests__/components/App.test.js @@ -11,21 +11,29 @@ import { LOADED } from '../../app/values/state' const initialState = { api: { + APP: { + batch: true, + mapping: ['NETWORK', 'SETTINGS'] + }, NETWORK: { batch: false, state: LOADED, - data: MAIN_NETWORK_ID + data: MAIN_NETWORK_ID, + loadedCount: 1 + }, + SETTINGS: { + batch: false, + state: LOADED, + data: {}, + loadedCount: 1 } }, account: { }, - metadata: { - }, wallet: { transactions: [] }, modal: { - }, price: { NEO: 40.5, @@ -52,15 +60,8 @@ const setup = (state, shallowRender = true) => { describe('App', () => { test('app initializes settings', (done) => { storage.get = jest.fn((key, callback) => { - const receivedKeys = [] - receivedKeys[key] = true - - storage.get = jest.fn((key, callback) => { - receivedKeys[key] = true - expect(receivedKeys['settings']).toEqual(true) - expect(receivedKeys['userWallet']).toEqual(true) - done() - }) + expect(key).toEqual('userWallet') + done() }) setup(initialState, false) }) diff --git a/__tests__/components/Claim.test.js b/__tests__/components/Claim.test.js index cf47e06b8..475e8cef0 100644 --- a/__tests__/components/Claim.test.js +++ b/__tests__/components/Claim.test.js @@ -18,6 +18,11 @@ const initialState = { batch: false, state: LOADED, data: MAIN_NETWORK_ID + }, + SETTINGS: { + batch: false, + state: LOADED, + data: {} } }, claim: { diff --git a/__tests__/components/TransactionHistory.test.js b/__tests__/components/TransactionHistory.test.js index 3b43c802c..0937d66d9 100644 --- a/__tests__/components/TransactionHistory.test.js +++ b/__tests__/components/TransactionHistory.test.js @@ -16,6 +16,11 @@ const initialState = { batch: false, state: LOADED, data: MAIN_NETWORK_ID + }, + SETTINGS: { + batch: false, + state: LOADED, + data: {} } }, account: { @@ -23,8 +28,6 @@ const initialState = { wif: 'L4SLRcPgqNMAMwM3nFSxnh36f1v5omjPg3Ewy1tg2PnEon8AcHou', address: 'AWy7RNBVr9vDadRMK9p7i7Z1tL7GrLAxoh' }, - metadata: { - }, wallet: { transactions: [] }, diff --git a/__tests__/components/WalletInfo.test.js b/__tests__/components/WalletInfo.test.js index 7dfe295ca..e18ea7a3f 100644 --- a/__tests__/components/WalletInfo.test.js +++ b/__tests__/components/WalletInfo.test.js @@ -3,6 +3,7 @@ import * as neonjs from 'neon-js' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' import thunk from 'redux-thunk' +import { merge } from 'lodash' import { mount, shallow } from 'enzyme' import { @@ -11,7 +12,6 @@ import { } from '../../app/modules/wallet' import { SHOW_NOTIFICATION } from '../../app/modules/notifications' import { LOADING_TRANSACTIONS } from '../../app/modules/transactions' -import { SET_HEIGHT } from '../../app/modules/metadata' import { DEFAULT_CURRENCY_CODE, MAIN_NETWORK_ID } from '../../app/core/constants' import { LOADED } from '../../app/values/state' @@ -53,6 +53,13 @@ const initialState = { batch: false, state: LOADED, data: MAIN_NETWORK_ID + }, + SETTINGS: { + batch: false, + state: LOADED, + data: { + currency: DEFAULT_CURRENCY_CODE + } } }, account: { @@ -67,8 +74,7 @@ const initialState = { }, price: { NEO: 25.48, - GAS: 18.1, - currency: DEFAULT_CURRENCY_CODE + GAS: 18.1 }, claim: { claimAmount: 0.5 @@ -141,24 +147,18 @@ describe('WalletInfo', () => { isLoadingTransactions: true } }) - expect(actions[1]).toEqual({ + expect(actions[2]).toEqual({ type: LOADING_TRANSACTIONS, payload: { isLoadingTransactions: false } }) - expect(actions[2]).toEqual({ + expect(actions[3]).toEqual({ type: SET_TRANSACTION_HISTORY, payload: { transactions: [] } }) - expect(actions[3]).toEqual({ - type: SET_HEIGHT, - payload: { - blockHeight: 586435 - } - }) expect(actions[4]).toEqual({ type: SET_BALANCE, payload: { @@ -183,10 +183,10 @@ describe('WalletInfo', () => { // }) }) test('correctly renders data from state with non-default currency', done => { - const testState = { - ...initialState, - price: { NEO: 1.11, GAS: 0.55, currency: 'eur' } - } + const testState = merge(initialState, { + api: { SETTINGS: { data: { currency: 'eur' } } }, + price: { NEO: 1.11, GAS: 0.55 } + }) const { wrapper } = setup(testState, false) const neoWalletValue = wrapper.find('.neoWalletValue') diff --git a/__tests__/components/__snapshots__/WalletInfo.test.js.snap b/__tests__/components/__snapshots__/WalletInfo.test.js.snap index fc2ab8716..17517a903 100644 --- a/__tests__/components/__snapshots__/WalletInfo.test.js.snap +++ b/__tests__/components/__snapshots__/WalletInfo.test.js.snap @@ -1,11 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`WalletInfo renders without crashing 1`] = ` -<Connect(withData(mapProps(WalletInfo))) +<Connect(withData(mapProps(Connect(withData(Connect(withActions(WalletInfo))))))) GAS="1000.0001601" NEO="100001" address="ANqUrhv99rwCiFTL6N1An9NH5UVkPYxTuw" - currencyCode="usd" gasPrice={18.1} loadWalletData={[Function]} neoPrice={25.48} @@ -25,7 +24,6 @@ exports[`WalletInfo renders without crashing 1`] = ` } oldParticipateInSale={[Function]} participateInSale={[Function]} - setUserGeneratedTokens={[Function]} showErrorNotification={[Function]} showModal={[Function]} showSuccessNotification={[Function]} diff --git a/__tests__/modules/metadata.test.js b/__tests__/modules/metadata.test.js deleted file mode 100644 index 72bea7668..000000000 --- a/__tests__/modules/metadata.test.js +++ /dev/null @@ -1,97 +0,0 @@ -import metadataReducer, { setBlockHeight, setBlockExplorer, SET_HEIGHT, SET_EXPLORER } from '../../app/modules/metadata' - -describe('metadata module tests', () => { - const blockHeight = 10 - const blockExplorer = 'Neoscan' - - const initialState = { - blockHeight: 0, - blockExplorer: 'Neotracker', - tokens: [ - { - 'id': '1', - 'isUserGenerated': false, - 'networkId': '1', - 'scriptHash': 'b951ecbbc5fe37a9c280a76cb0ce0014827294cf' - }, - { - 'id': '2', - 'isUserGenerated': false, - 'networkId': '1', - 'scriptHash': 'ecc6b20d3ccac1ee9ef109af5a7cdb85706b1df9' - }, - { - 'id': '3', - 'isUserGenerated': false, - 'networkId': '1', - 'scriptHash': '2328008e6f6c7bd157a342e789389eb034d9cbc4' - }, - { - 'id': '4', - 'isUserGenerated': false, - 'networkId': '1', - 'scriptHash': '0d821bd7b6d53f5c2b40e217c6defc8bbe896cf5' - }, - { - 'id': '5', - 'isUserGenerated': false, - 'networkId': '2', - 'scriptHash': 'b951ecbbc5fe37a9c280a76cb0ce0014827294cf' - }, - { - 'id': '6', - 'isUserGenerated': false, - 'networkId': '2', - 'scriptHash': '5b7074e873973a6ed3708862f219a6fbf4d1c411' - }, - { - 'id': '7', - 'isUserGenerated': false, - 'networkId': '2', - 'scriptHash': '0d821bd7b6d53f5c2b40e217c6defc8bbe896cf5' - } - ] - } - - describe('setBlockHeight tests', () => { - const expectedAction = { - type: SET_HEIGHT, - payload: { - blockHeight - } - } - - test('setBlockHeight action works', () => { - expect(setBlockHeight(blockHeight)).toEqual(expectedAction) - }) - - test('metadata reducer should handle SET_HEIGHT', () => { - const expectedState = { - ...initialState, - blockHeight - } - expect(metadataReducer(undefined, expectedAction)).toEqual(expectedState) - }) - }) - - describe('setBlockExplorer tests', () => { - const expectedAction = { - type: SET_EXPLORER, - payload: { - blockExplorer - } - } - - test('setBlockExplorer action works', () => { - expect(setBlockExplorer(blockExplorer)).toEqual(expect.any(Function)) - }) - - test('metadata reducer should handle SET_EXPLORER', () => { - const expectedState = { - ...initialState, - blockExplorer - } - expect(metadataReducer(undefined, expectedAction)).toEqual(expectedState) - }) - }) -}) diff --git a/__tests__/modules/price.test.js b/__tests__/modules/price.test.js index d00e5c051..c829f7618 100644 --- a/__tests__/modules/price.test.js +++ b/__tests__/modules/price.test.js @@ -1,14 +1,12 @@ -import walletReducer, { setNEOPrice, setGASPrice, setCurrency, resetPrice, SET_CURRENCY, SET_GAS_PRICE, SET_NEO_PRICE, RESET_PRICE } from '../../app/modules/price' +import walletReducer, { setNEOPrice, setGASPrice, resetPrice, SET_GAS_PRICE, SET_NEO_PRICE, RESET_PRICE } from '../../app/modules/price' describe('wallet module tests', () => { const NEO = 28.10 const GAS = 18.20 - const currency = 'eur' const initialState = { NEO: 0, - GAS: 0, - currency: 'usd' + GAS: 0 } describe('setNEOPrice tests', () => { @@ -57,29 +55,6 @@ describe('wallet module tests', () => { }) }) - describe('setCurrency tests', () => { - const expectedAction = { - type: SET_CURRENCY, - payload: { currency } - } - - test('setCurrency action works', () => { - expect(setCurrency(currency)).toEqual(expect.any(Function)) - }) - - test('setCurrency reducer should return the initial state', () => { - expect(walletReducer(undefined, {})).toEqual(initialState) - }) - - test('wallet reducer should handle SET_CURRENCY', () => { - const expectedState = { - ...initialState, - currency - } - expect(walletReducer(undefined, expectedAction)).toEqual(expectedState) - }) - }) - describe('resetPrice tests', () => { const expectedAction = { type: RESET_PRICE diff --git a/__tests__/store/reducers.test.js b/__tests__/store/reducers.test.js index 5956ca94d..8d1397ebc 100644 --- a/__tests__/store/reducers.test.js +++ b/__tests__/store/reducers.test.js @@ -11,8 +11,7 @@ describe('root reducer', () => { price: expect.any(Object), transactions: expect.any(Object), dashboard: expect.any(Object), - metadata: expect.any(Object), - notifications: expect.any(Array), + notifications: expect.any(Object), claim: expect.any(Object), modal: expect.any(Object), sale: expect.any(Object) diff --git a/app/actions/appActions.js b/app/actions/appActions.js index ce7478aad..63c845ca9 100644 --- a/app/actions/appActions.js +++ b/app/actions/appActions.js @@ -7,6 +7,6 @@ import settingsActions from './settingsActions' export const ID = 'APP' export default createBatchActions(ID, { - height: blockHeightActions, + blockHeight: blockHeightActions, settings: settingsActions }) diff --git a/app/actions/settingsActions.js b/app/actions/settingsActions.js index 2e3332b8a..ef3586792 100644 --- a/app/actions/settingsActions.js +++ b/app/actions/settingsActions.js @@ -1,35 +1,39 @@ // @flow -import { pick } from 'lodash' +import { pick, keys } from 'lodash' import createRequestActions from '../util/api/createRequestActions' import { getStorage, setStorage } from '../core/storage' -import { EXPLORERS, CURRENCIES } from '../core/constants' +import { getDefaultTokens } from '../core/nep5' +import { EXPLORERS, DEFAULT_CURRENCY_CODE } from '../core/constants' -type Props = { - currency: string +type Settings = { + currency?: string, + blockExplorer?: string, + tokens?: Array<TokenItemType> } -const DEFAULT_SETTINGS = { - currency: CURRENCIES.usd, - blockExplorer: EXPLORERS.NEO_TRACKER +const DEFAULT_SETTINGS: Settings = { + currency: DEFAULT_CURRENCY_CODE, + blockExplorer: EXPLORERS.NEO_TRACKER, + tokens: getDefaultTokens() } -const getSettings = async () => ({ +const getSettings = async (): Promise<Settings> => ({ ...DEFAULT_SETTINGS, ...await getStorage('settings') }) export const ID = 'SETTINGS' -export default createRequestActions(ID, () => async (state: Object) => { +export const updateSettingsActions = createRequestActions(ID, (values: Settings = {}) => async (state: Object): Promise<Settings> => { const settings = await getSettings() - return pick(settings, 'currency', 'blockExplorer') -}) - -export const setCurrency = createRequestActions(ID, ({ currency }: Props) => async (state: Object) => { - const settings = await getSettings() - const newSettings = { ...settings, currency } + const newSettings = { ...settings, ...values } await setStorage('settings', newSettings) return newSettings }) + +export default createRequestActions(ID, () => async (state: Object): Promise<Settings> => { + const settings = await getSettings() + return pick(settings, keys(DEFAULT_SETTINGS)) +}) diff --git a/app/components/Blockchain/Address/index.js b/app/components/Blockchain/Address/index.js index ffccf2aee..70a595f19 100644 --- a/app/components/Blockchain/Address/index.js +++ b/app/components/Blockchain/Address/index.js @@ -1,16 +1,11 @@ // @flow -import { connect, type MapStateToProps } from 'react-redux' import { compose } from 'recompose' import Address from './Address' import withNetworkData from '../../../hocs/withNetworkData' -import { getBlockExplorer } from '../../../modules/metadata' - -const mapStateToProps: MapStateToProps<*, *, *> = (state: Object) => ({ - explorer: getBlockExplorer(state) -}) +import withExplorerData from '../../../hocs/withExplorerData' export default compose( - connect(mapStateToProps), - withNetworkData() + withNetworkData(), + withExplorerData() )(Address) diff --git a/app/components/Blockchain/Transaction/index.js b/app/components/Blockchain/Transaction/index.js index 5a303dd90..f573eeb53 100644 --- a/app/components/Blockchain/Transaction/index.js +++ b/app/components/Blockchain/Transaction/index.js @@ -1,16 +1,11 @@ // @flow -import { connect, type MapStateToProps } from 'react-redux' import { compose } from 'recompose' import Transaction from './Transaction' import withNetworkData from '../../../hocs/withNetworkData' -import { getBlockExplorer } from '../../../modules/metadata' - -const mapStateToProps: MapStateToProps<*, *, *> = (state: Object) => ({ - explorer: getBlockExplorer(state) -}) +import withExplorerData from '../../../hocs/withExplorerData' export default compose( - connect(mapStateToProps), - withNetworkData() + withNetworkData(), + withExplorerData() )(Transaction) diff --git a/app/components/Modals/TokenModal/TokenModal.jsx b/app/components/Modals/TokenModal/TokenModal.jsx index cd3b92442..fdb6a814d 100644 --- a/app/components/Modals/TokenModal/TokenModal.jsx +++ b/app/components/Modals/TokenModal/TokenModal.jsx @@ -7,6 +7,7 @@ import { getNewTokenItem, validateTokens } from './utils' import BaseModal from '../BaseModal' import Button from '../../Button' import Row from './Row' +import NetworkSwitch from '../../../containers/App/Header/NetworkSwitch' import Add from 'react-icons/lib/md/add' @@ -14,8 +15,7 @@ import styles from './TokenModal.scss' type Props = { hideModal: () => any, - networkId?: string, - networks: Array<NetworkItemType>, + networkId: string, setUserGeneratedTokens: Function, tokens: Array<TokenItemType>, showErrorNotification: Object => any, @@ -26,7 +26,6 @@ type InputErrorType = 'scriptHash'; type State = { tokens: Array<TokenItemType>, - networkId: string, errorItemId: ?number, errorType: ?InputErrorType } @@ -38,7 +37,6 @@ class TokenModal extends Component<Props, State> { state = { tokens: this.props.tokens, - networkId: this.props.networkId || this.props.networks[0].id, errorItemId: null, errorType: null } @@ -52,9 +50,8 @@ class TokenModal extends Component<Props, State> { } addToken = () => { - const { tokens, networkId } = this.state this.setState({ - tokens: [...tokens, getNewTokenItem(networkId)] + tokens: [...this.state.tokens, getNewTokenItem(this.props.networkId)] }) } @@ -94,15 +91,9 @@ class TokenModal extends Component<Props, State> { }) } - updateNetworkId = (e: Object) => { - this.setState({ - networkId: e.target.value - }) - } - render () { - const { hideModal, networks } = this.props - const { tokens, errorItemId, errorType, networkId } = this.state + const { hideModal, networkId } = this.props + const { tokens, errorItemId, errorType } = this.state return ( <BaseModal @@ -122,13 +113,7 @@ class TokenModal extends Component<Props, State> { </Button> <div className={styles.switchNetworkContainer}> <span className={styles.switchNetworkLabel}>Network:</span> - <select defaultValue={networkId} onChange={this.updateNetworkId}> - {networks.map(({ label, id }: NetworkItemType) => ( - <option key={`networkOption${id}`} value={id}> - {label} - </option> - ))} - </select> + <NetworkSwitch /> </div> </div> <form diff --git a/app/components/Modals/TokenModal/index.js b/app/components/Modals/TokenModal/index.js index 17e85d188..fe8214170 100644 --- a/app/components/Modals/TokenModal/index.js +++ b/app/components/Modals/TokenModal/index.js @@ -1 +1,11 @@ -export { default } from './TokenModal' +// @flow +import { compose } from 'recompose' + +import TokenModal from './TokenModal' +import withNetworkData from '../../../hocs/withNetworkData' +import withTokensData from '../../../hocs/withTokensData' + +export default compose( + withNetworkData(), + withTokensData() +)(TokenModal) diff --git a/app/containers/App/App.jsx b/app/containers/App/App.jsx index 263c9229d..a38afbd33 100644 --- a/app/containers/App/App.jsx +++ b/app/containers/App/App.jsx @@ -14,16 +14,14 @@ import styles from './App.scss' type Props = { children: React$Node, checkVersion: Function, - initSettings: Function, showErrorNotification: Function } class App extends Component<Props> { componentDidMount () { - const { checkVersion, initSettings, showErrorNotification } = this.props + const { checkVersion, showErrorNotification } = this.props checkVersion() - initSettings() upgradeUserWalletNEP6() .catch((e) => { showErrorNotification({ message: `Error upgrading legacy wallet: ${e.message}` }) diff --git a/app/containers/App/Header/Header.jsx b/app/containers/App/Header/Header.jsx index c8130ef94..022a92164 100644 --- a/app/containers/App/Header/Header.jsx +++ b/app/containers/App/Header/Header.jsx @@ -16,27 +16,19 @@ import logo from '../../../images/neon-logo2.png' const Logo = () => <div><img src={logo} width='60px' /></div> type Props = { - blockHeight: number, neoPrice: number, gasPrice: number, currencyCode: string, isLoggedIn: boolean, - networkId: string, - networks: Array<NetworkItemType>, - logout: () => any, - onChangeNetwork: Function + logout: () => any } const Header = ({ - blockHeight, logout, neoPrice, gasPrice, currencyCode, - isLoggedIn, - networkId, - networks, - onChangeNetwork + isLoggedIn }: Props) => ( <div className={styles.container}> <Logo /> @@ -47,13 +39,9 @@ const Header = ({ gasPrice={gasPrice} currencyCode={currencyCode} /> - <WalletBlockHeight blockHeight={blockHeight} /> + <WalletBlockHeight /> <WalletVersion version={version} /> - <NetworkSwitch - networkId={networkId} - networks={networks} - onChange={onChangeNetwork} - /> + <NetworkSwitch /> <Logout onClick={logout} /> </div> } diff --git a/app/containers/App/Header/NetworkSwitch/NetworkSwitch.jsx b/app/containers/App/Header/NetworkSwitch/NetworkSwitch.jsx index 5866635bc..d96e5129e 100644 --- a/app/containers/App/Header/NetworkSwitch/NetworkSwitch.jsx +++ b/app/containers/App/Header/NetworkSwitch/NetworkSwitch.jsx @@ -1,7 +1,8 @@ // @flow import React, { Component } from 'react' -import headerStyles from '../Header.scss' +import { getNetworks } from '../../../../core/networks' +import styles from '../Header.scss' type Props = { networkId: string, @@ -10,11 +11,15 @@ type Props = { } export default class NetworkSwitch extends Component<Props> { + static defaultProps = { + networks: getNetworks() + } + render () { const { networkId, networks } = this.props return ( - <div id='network' className={headerStyles.navBarItem}> - <span className={headerStyles.navBarItemLabel}>Running on</span> + <div id='network' className={styles.navBarItem}> + <span className={styles.navBarItemLabel}>Running on</span> <select defaultValue={networkId} onChange={this.handleChange} className='networkSelector'> {networks.map(({ label, id }: NetworkItemType) => <option key={`networkOption${id}`} value={id}>{label}</option> diff --git a/app/containers/App/Header/WalletBlockHeight/index.js b/app/containers/App/Header/WalletBlockHeight/index.js index 091a2687c..d7556d668 100644 --- a/app/containers/App/Header/WalletBlockHeight/index.js +++ b/app/containers/App/Header/WalletBlockHeight/index.js @@ -1 +1,14 @@ -export { default } from './WalletBlockHeight' +// @flow +import { compose } from 'recompose' + +import WalletBlockHeight from './WalletBlockHeight' +import withData from '../../../../hocs/api/withData' +import appActions from '../../../../actions/appActions' + +const mapAppDataToProps = ({ blockHeight }): Object => ({ + blockHeight +}) + +export default compose( + withData(appActions, mapAppDataToProps) +)(WalletBlockHeight) diff --git a/app/containers/App/Header/index.js b/app/containers/App/Header/index.js index 06aa85f31..a610753eb 100644 --- a/app/containers/App/Header/index.js +++ b/app/containers/App/Header/index.js @@ -3,25 +3,17 @@ import { connect, type MapStateToProps } from 'react-redux' import { bindActionCreators } from 'redux' import { compose } from 'recompose' -import withActions from '../../../hocs/api/withActions' -import withNetworkData from '../../../hocs/withNetworkData' -import networkActions from '../../../actions/networkActions' -import { type Actions } from '../../../values/api' +import withCurrencyData from '../../../hocs/withCurrencyData' import { logout, getAddress, getLoggedIn } from '../../../modules/account' -import { getBlockHeight } from '../../../modules/metadata' -import { getNetworks } from '../../../core/networks' -import { getNEOPrice, getGASPrice, getCurrency } from '../../../modules/price' +import { getNEOPrice, getGASPrice } from '../../../modules/price' import Header from './Header' const mapStateToProps: MapStateToProps<*, *, *> = (state: Object) => ({ - blockHeight: getBlockHeight(state), address: getAddress(state), neoPrice: getNEOPrice(state), gasPrice: getGASPrice(state), - currencyCode: getCurrency(state), - isLoggedIn: getLoggedIn(state), - networks: getNetworks() + isLoggedIn: getLoggedIn(state) }) const actionCreators = { @@ -30,12 +22,7 @@ const actionCreators = { const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) -const mapActionsToProps = (actions: Actions, props: Object): Object => ({ - onChangeNetwork: (networkId) => actions.request({ networkId }) -}) - export default compose( connect(mapStateToProps, mapDispatchToProps), - withNetworkData(), - withActions(networkActions, mapActionsToProps) + withCurrencyData('currencyCode') )(Header) diff --git a/app/containers/App/Loading.js b/app/containers/App/Loading.js index 69377b2ea..4f9fcb489 100644 --- a/app/containers/App/Loading.js +++ b/app/containers/App/Loading.js @@ -4,8 +4,6 @@ import Loader from '../../components/Loader' import styles from './Loading.scss' export default () => { - console.log('rendered loading state') - return ( <div className={styles.loading}> <Loader /> diff --git a/app/containers/App/index.js b/app/containers/App/index.js index f25d827d1..0ca884483 100644 --- a/app/containers/App/index.js +++ b/app/containers/App/index.js @@ -3,29 +3,29 @@ import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import { compose } from 'recompose' -// import withData from '../../hocs/api/withData' +import withData from '../../hocs/api/withData' import withFetch from '../../hocs/api/withFetch' -// import withReload from '../../hocs/api/withReload' +import withReload from '../../hocs/api/withReload' import withProgressComponents from '../../hocs/api/withProgressComponents' -// import appActions from '../../actions/appActions' +import appActions from '../../actions/appActions' +import alreadyLoaded from '../../hocs/api/progressStrategies/alreadyLoadedStrategy' import withNetworkData from '../../hocs/withNetworkData' import networkActions from '../../actions/networkActions' -import { checkVersion, initSettings } from '../../modules/metadata' +import { checkVersion } from '../../modules/metadata' import { showErrorNotification } from '../../modules/notifications' -import { INITIAL, LOADING } from '../../values/state' +import { LOADING } from '../../values/state' import App from './App' import Loading from './Loading' const actionCreators = { checkVersion, - initSettings, showErrorNotification } const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) -// const mapAppDataToProps = ({ height, settings }) => ({ height, settings }) +const mapAppDataToProps = ({ height, settings }) => ({ height, settings }) export default compose( // Old way of fetching data, need to refactor this out... @@ -36,16 +36,18 @@ export default compose( withFetch(networkActions), withNetworkData(), withProgressComponents(networkActions, { - [INITIAL]: Loading, // TODO: refactor such that LOADING and INITIAL are treated the same [LOADING]: Loading + }, { + strategy: alreadyLoaded + }), + + // Fetch application data based upon the selected network. Reload data when the network changes. + withFetch(appActions), + withData(appActions, mapAppDataToProps), + withReload(appActions, ['networkId']), + withProgressComponents(appActions, { + [LOADING]: Loading + }, { + strategy: alreadyLoaded }) - - // // Fetch application data based upon the selected network. Reload data when the network changes. - // withFetch(appActions), - // withData(appActions, mapAppDataToProps), - // withReload(appActions, ['net']), - // withProgressComponents(appActions, { - // [INITIAL]: Loading, - // [LOADING]: Loading - // }) )(App) diff --git a/app/containers/Settings/Settings.jsx b/app/containers/Settings/Settings.jsx index 508c4a409..fb4c897e7 100644 --- a/app/containers/Settings/Settings.jsx +++ b/app/containers/Settings/Settings.jsx @@ -24,7 +24,6 @@ type Props = { showModal: (string, Object) => any, showSuccessNotification: Object => any, showErrorNotification: Object => any, - allTokens: Array<TokenItemType>, setUserGeneratedTokens: () => any, networks: Array<NetworkItemType> } @@ -184,13 +183,11 @@ export default class Settings extends Component<Props, State> { openTokenModal = () => { const { setUserGeneratedTokens, - allTokens, showModal, networks, showErrorNotification } = this.props showModal(MODAL_TYPES.TOKEN, { - tokens: allTokens, networks, setUserGeneratedTokens, showErrorNotification diff --git a/app/containers/Settings/index.js b/app/containers/Settings/index.js index 4fba21921..fa51939d9 100644 --- a/app/containers/Settings/index.js +++ b/app/containers/Settings/index.js @@ -1,40 +1,41 @@ // @flow import { connect } from 'react-redux' import { bindActionCreators } from 'redux' +import { compose } from 'recompose' +import Settings from './Settings' +import withActions from '../../hocs/api/withActions' +import withExplorerData from '../../hocs/withExplorerData' +import withCurrencyData from '../../hocs/withCurrencyData' import { setAccounts, getAccounts } from '../../modules/account' -import { - setBlockExplorer, - getBlockExplorer, - getAllTokens, - setUserGeneratedTokens -} from '../../modules/metadata' +import { updateSettingsActions } from '../../actions/settingsActions' import { getNetworks } from '../../core/networks' -import { setCurrency, getCurrency } from '../../modules/price' import { showErrorNotification, showSuccessNotification } from '../../modules/notifications' - import { showModal } from '../../modules/modal' -import Settings from './Settings' - const mapStateToProps = (state: Object) => ({ - explorer: getBlockExplorer(state), - currency: getCurrency(state), accounts: getAccounts(state), - networks: getNetworks(), - allTokens: getAllTokens(state) + networks: getNetworks() }) const actionCreators = { setAccounts, - setBlockExplorer, - setCurrency, showModal, showErrorNotification, - showSuccessNotification, - setUserGeneratedTokens + showSuccessNotification } const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) -export default connect(mapStateToProps, mapDispatchToProps)(Settings) +const mapActionsToProps = (actions) => ({ + setCurrency: (currency) => actions.request({ currency }), + setBlockExplorer: (blockExplorer) => actions.request({ blockExplorer }), + setUserGeneratedTokens: (tokens) => actions.request({ tokens }) +}) + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withExplorerData(), + withCurrencyData(), + withActions(updateSettingsActions, mapActionsToProps) +)(Settings) diff --git a/app/containers/WalletInfo/index.js b/app/containers/WalletInfo/index.js index 8d88702c4..517e57ba2 100644 --- a/app/containers/WalletInfo/index.js +++ b/app/containers/WalletInfo/index.js @@ -3,13 +3,15 @@ import { connect, type MapStateToProps } from 'react-redux' import { bindActionCreators } from 'redux' import { compose } from 'recompose' +import withActions from '../../hocs/api/withActions' import withNetworkData from '../../hocs/withNetworkData' +import withCurrencyData from '../../hocs/withCurrencyData' +import { updateSettingsActions } from '../../actions/settingsActions' import { getNetworks } from '../../core/networks' import { showErrorNotification, showSuccessNotification } from '../../modules/notifications' import { getAddress } from '../../modules/account' -import { getAllTokens, setUserGeneratedTokens } from '../../modules/metadata' import { loadWalletData, getNEO, getGAS, getTokenBalances } from '../../modules/wallet' -import { getNEOPrice, getGASPrice, getCurrency } from '../../modules/price' +import { getNEOPrice, getGASPrice } from '../../modules/price' import { showModal } from '../../modules/modal' import { participateInSale, oldParticipateInSale } from '../../modules/sale' @@ -22,9 +24,7 @@ const mapStateToProps: MapStateToProps<*, *, *> = (state: Object) => ({ neoPrice: getNEOPrice(state), gasPrice: getGASPrice(state), tokenBalances: getTokenBalances(state), - currencyCode: getCurrency(state), - networks: getNetworks(), - allTokens: getAllTokens(state) + networks: getNetworks() }) const actionCreators = { @@ -33,13 +33,18 @@ const actionCreators = { showSuccessNotification, showModal, participateInSale, - oldParticipateInSale, - setUserGeneratedTokens + oldParticipateInSale } const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) +const mapActionsToProps = (actions) => ({ + setUserGeneratedTokens: (tokens) => actions.request({ tokens }) +}) + export default compose( connect(mapStateToProps, mapDispatchToProps), - withNetworkData() + withNetworkData(), + withCurrencyData('currencyCode'), + withActions(updateSettingsActions, mapActionsToProps) )(WalletInfo) diff --git a/app/core/deprecated.js b/app/core/deprecated.js index 2d0ca9226..c92abadf8 100644 --- a/app/core/deprecated.js +++ b/app/core/deprecated.js @@ -1,14 +1,18 @@ // @flow import { get } from 'lodash' +import blockHeightActions from '../actions/blockHeightActions' import { ID as NETWORK_ID } from '../actions/networkActions' +import { ID as SETTINGS_ID } from '../actions/settingsActions' import { getNetworks } from '../core/networks' const PREFIX = 'api' -// TODO: Module functions like `doClaimNotify` should be refactored into function props exposed by -// higher-order components. Data normally provided by functions like `getNetwork` can instead -// be retrieved via the `withData` higher-order component. +// TODO: Module functions that inspect state such as `doClaimNotify` should be refactored into +// higher-order components that expose function props for performing these actions. Data that +// is normally provided by functions like `getNetwork` can instead be retrieved via the +// `withData` higher-order component. + export const getNetworkId = (state: Object) => { return get(state, `${PREFIX}.${NETWORK_ID}.data`) } @@ -26,3 +30,17 @@ export const getNetworkById = (networkId: string) => { const networkItem = networks.find(({ id, value }) => id === networkId) || networks[0] return networkItem.network } + +export const getCurrency = (state: Object) => { + return get(state, `${PREFIX}.${SETTINGS_ID}.data.currency`) +} + +export const getTokensForNetwork = (state: Object) => { + const selectedNetworkId = getNetworkId(state) + const allTokens = get(state, `${PREFIX}.${SETTINGS_ID}.data.tokens`) + return allTokens.filter(({ networkId }) => networkId === selectedNetworkId) +} + +export const syncBlockHeight = (state: Object) => { + return blockHeightActions.request({ networkId: getNetworkId(state) }) +} diff --git a/app/core/nep5.js b/app/core/nep5.js new file mode 100644 index 000000000..a6b7cac16 --- /dev/null +++ b/app/core/nep5.js @@ -0,0 +1,30 @@ +// @flow +import { map } from 'lodash' + +import { toBigNumber } from './math' +import { COIN_DECIMAL_LENGTH } from './formatters' +import { TOKENS, TOKENS_TEST, MAIN_NETWORK_ID, TEST_NETWORK_ID } from './constants' + +export const adjustDecimalAmountForTokenTransfer = (value: string): string => + toBigNumber(value).times(10 ** COIN_DECIMAL_LENGTH).round().toNumber() + +const getTokenEntry = ((): Function => { + let id = 1 + + return (symbol: string, scriptHash: string, networkId: string) => ({ + id: `${id++}`, + symbol, + scriptHash, + networkId, + isUserGenerated: false + }) +})() + +export const getDefaultTokens = (): Array<TokenItemType> => { + const tokens = [] + + tokens.push(...map(TOKENS, (scriptHash, symbol) => getTokenEntry(symbol, scriptHash, MAIN_NETWORK_ID))) + tokens.push(...map(TOKENS_TEST, (scriptHash, symbol) => getTokenEntry(symbol, scriptHash, TEST_NETWORK_ID))) + + return tokens +} diff --git a/app/core/storage.js b/app/core/storage.js index 84d40a475..6f9ace188 100644 --- a/app/core/storage.js +++ b/app/core/storage.js @@ -1,9 +1,13 @@ import storage from 'electron-json-storage' +import promisify from 'es6-promisify' + +const get = promisify(storage.get, storage) +const set = promisify(storage.set, storage) export const getStorage = async (key) => { - return storage.get(key) + return get(key) } export const setStorage = async (key, value) => { - return storage.set(key, value) + return set(key, value) } diff --git a/app/hocs/api/progressStrategies/alreadyLoadedStrategy.js b/app/hocs/api/progressStrategies/alreadyLoadedStrategy.js new file mode 100644 index 000000000..eff9c8111 --- /dev/null +++ b/app/hocs/api/progressStrategies/alreadyLoadedStrategy.js @@ -0,0 +1,22 @@ +// @flow +import { some, every } from 'lodash' + +import { LOADING, LOADED, FAILED, type ProgressState } from '../../../values/state' + +function anyFailed (actionStates: Array<Object>): boolean { + return some(actionStates, (actionState) => actionState.state === FAILED) +} + +function alreadyLoaded (actionStates: Array<Object>): boolean { + return every(actionStates, (actionState) => actionState.loadedCount > 0) +} + +export default function alreadyLoadedStrategy (actions: Array<Object>): ProgressState { + if (anyFailed(actions)) { + return FAILED + } else if (alreadyLoaded(actions)) { + return LOADED + } else { + return LOADING + } +} diff --git a/app/hocs/api/progressStrategies/defaultStrategy.js b/app/hocs/api/progressStrategies/defaultStrategy.js new file mode 100644 index 000000000..15fdb4b69 --- /dev/null +++ b/app/hocs/api/progressStrategies/defaultStrategy.js @@ -0,0 +1,22 @@ +// @flow +import { some, every } from 'lodash' + +import { LOADING, LOADED, FAILED, type ProgressState } from '../../../values/state' + +function anyFailed (actionStates: Array<Object>): boolean { + return some(actionStates, (actionState) => actionState.state === FAILED) +} + +function allLoaded (actionStates: Array<Object>): boolean { + return every(actionStates, (actionState) => actionState.state === LOADED) +} + +export default function defaultStrategy (actions: Array<Object>): ProgressState { + if (anyFailed(actions)) { + return FAILED + } else if (allLoaded(actions)) { + return LOADED + } else { + return LOADING + } +} diff --git a/app/hocs/api/withData.js b/app/hocs/api/withData.js index 70e39c5f2..39d5c542e 100644 --- a/app/hocs/api/withData.js +++ b/app/hocs/api/withData.js @@ -5,6 +5,10 @@ import { compose, setDisplayName, wrapDisplayName } from 'recompose' import { type Data, type Actions, type ActionStateMap } from '../../values/api' +type Options = { + prefix: string +} + const mapRequestDataToProps: Function = (data: Data): any => data const mapBatchDataToProps: Function = (state: Object, id: string, mapping: ActionStateMap, prefix: string): Object => { @@ -23,9 +27,13 @@ const mapDataToProps: Function = (state: Object, id: string, prefix: string): Ob const defaultMapper = mapRequestDataToProps -export default function withData (actions: Actions, mapper: Function = defaultMapper, prefix: string = 'api'): Class<React.Component<*>> { - const mapStateToProps: MapStateToProps<*, *, *> = (state: Object): Object => { - return mapper(mapDataToProps(state, actions.id, prefix)) +export default function withData ( + actions: Actions, + mapper: Function = defaultMapper, + { prefix = 'api' }: Options = {} +): Class<React.Component<*>> { + const mapStateToProps: MapStateToProps<*, *, *> = (state: Object, ownProps: Object): Object => { + return mapper(mapDataToProps(state, actions.id, prefix), ownProps) } return (Component: Class<React.Component<*>>) => { diff --git a/app/hocs/api/withProgressComponents.js b/app/hocs/api/withProgressComponents.js index afcd47a46..5bf50a346 100644 --- a/app/hocs/api/withProgressComponents.js +++ b/app/hocs/api/withProgressComponents.js @@ -1,9 +1,10 @@ // @flow import React from 'react' +import { omit } from 'lodash' import { compose, setDisplayName, wrapDisplayName } from 'recompose' +import defaultStrategy from './progressStrategies/defaultStrategy' import withProgressProp from './withProgressProp' -import withoutProps from '../withoutProps' import { type Actions } from '../../values/api' import { type ProgressState } from '../../values/state' @@ -15,22 +16,32 @@ type Mapping = { [key: ProgressState]: Class<React.Component<*>> } +type Options = { + strategy?: (Array<Object>) => ProgressState, + prefix?: string, + propName?: string +} + const PROGRESS_PROP: string = '__progress__' -export default function withProgressComponents (actions: Actions, mapping: Mapping = {}, propName: string = PROGRESS_PROP) { +export default function withProgressComponents ( + actions: Actions, + mapping: Mapping = {}, + { strategy = defaultStrategy, prefix = 'api', propName = PROGRESS_PROP }: Options = {} +) { return (Component: Class<React.Component<*>>): Class<React.Component<*>> => { class ComponentWithProgressComponents extends React.Component<Props> { static displayName = 'ComponentWithProgressComponents' render = () => { const MappedComponent = mapping[this.props[propName]] || Component - const WrappedComponent = withoutProps(propName)(MappedComponent) - return <WrappedComponent {...this.props} /> + const passDownProps = omit(this.props, propName) + return <MappedComponent {...passDownProps} /> } } return compose( - withProgressProp(actions), + withProgressProp(actions, { strategy, prefix, propName }), setDisplayName(wrapDisplayName(Component, 'withProgressComponents')) )(ComponentWithProgressComponents) } diff --git a/app/hocs/api/withProgressProp.js b/app/hocs/api/withProgressProp.js index 5fdfcb590..71a64f876 100644 --- a/app/hocs/api/withProgressProp.js +++ b/app/hocs/api/withProgressProp.js @@ -1,19 +1,35 @@ // @flow -import { get } from 'lodash' +import { get, map, castArray } from 'lodash' import { connect, type MapStateToProps } from 'react-redux' import { compose, setDisplayName, wrapDisplayName } from 'recompose' -import { type Actions } from '../../values/api' -import { INITIAL, type ProgressState } from '../../values/state' +import defaultStrategy from './progressStrategies/defaultStrategy' +import { type Actions, type ActionState } from '../../values/api' +import { type ProgressState } from '../../values/state' const PROGRESS_PROP: string = '__progress__' -export default function withProgressProp (actions: Actions, propName: string = PROGRESS_PROP) { - const mapProgressToProps = (progressState: ProgressState) => ({ [propName]: progressState }) +type Options = { + strategy?: (Array<Object>) => ProgressState, + prefix?: string, + propName?: string +} + +export default function withProgressProp ( + actions: Actions, + { strategy = defaultStrategy, prefix = 'api', propName = PROGRESS_PROP }: Options = {} +) { + const mapProgressToProps = (actionStates: Array<ActionState>) => ({ [propName]: strategy(actionStates) }) const mapStateToProps: MapStateToProps<*, *, *> = (state: Object): Object => { - const progressState = get(state, `api.${actions.id}.state`) || INITIAL - return mapProgressToProps(progressState) + const actionState = get(state, `${prefix}.${actions.id}`) + + // TODO: this doesn't account for batch within a batch, need to make this recursive + const actionStates: Array<ActionState> = actionState.batch + ? map(actionState.mapping, (key) => get(state, `${prefix}.${key}`)) + : castArray(actionState) + + return mapProgressToProps(actionStates) } return (Component: Class<React.Component<*>>) => { diff --git a/app/hocs/withCurrencyData.js b/app/hocs/withCurrencyData.js new file mode 100644 index 000000000..1dc7dc7c7 --- /dev/null +++ b/app/hocs/withCurrencyData.js @@ -0,0 +1,8 @@ +// @flow +import withData from './api/withData' +import settingsActions from '../actions/settingsActions' + +export default function withCurrencyData (key: string = 'currency') { + const mapSettingsDataToProps = (settings) => ({ [key]: settings.currency }) + return withData(settingsActions, mapSettingsDataToProps) +} diff --git a/app/hocs/withExplorerData.js b/app/hocs/withExplorerData.js new file mode 100644 index 000000000..71c59f683 --- /dev/null +++ b/app/hocs/withExplorerData.js @@ -0,0 +1,8 @@ +// @flow +import withData from './api/withData' +import settingsActions from '../actions/settingsActions' + +export default function withExplorerData (key: string = 'explorer') { + const mapSettingsDataToProps = (settings) => ({ [key]: settings.blockExplorer }) + return withData(settingsActions, mapSettingsDataToProps) +} diff --git a/app/hocs/withFilteredTokensData.js b/app/hocs/withFilteredTokensData.js new file mode 100644 index 000000000..cebc767bd --- /dev/null +++ b/app/hocs/withFilteredTokensData.js @@ -0,0 +1,17 @@ +// @flow +import { compose } from 'recompose' + +import withData from './api/withData' +import withNetworkData from './withNetworkData' +import settingsActions from '../actions/settingsActions' + +export default function withFilteredTokensData (key: string = 'tokens') { + const mapSettingsDataToProps = (settings, props: Object) => ({ + [key]: settings.tokens.filter(({ networkId }) => networkId === props.networkId) + }) + + return compose( + withNetworkData(), + withData(settingsActions, mapSettingsDataToProps) + ) +} diff --git a/app/hocs/withNetworkData.js b/app/hocs/withNetworkData.js index 7bb4b6887..4c58cceca 100644 --- a/app/hocs/withNetworkData.js +++ b/app/hocs/withNetworkData.js @@ -1,5 +1,4 @@ // @flow -// import { connect, type MapStateToProps } from 'react-redux' import { compose, mapProps } from 'recompose' import withData from './api/withData' diff --git a/app/hocs/withTokensData.js b/app/hocs/withTokensData.js new file mode 100644 index 000000000..b346f8cf7 --- /dev/null +++ b/app/hocs/withTokensData.js @@ -0,0 +1,7 @@ +// @flow +import withData from './api/withData' +import settingsActions from '../actions/settingsActions' + +export default function withTokensData (key: string = 'tokens') { + return withData(settingsActions, (settings) => ({ [key]: settings.tokens })) +} diff --git a/app/modules/api/request.js b/app/modules/api/request.js index 6dc834a2c..10acbe1ae 100644 --- a/app/modules/api/request.js +++ b/app/modules/api/request.js @@ -22,6 +22,7 @@ type State = { batch: false, state: ProgressState, rollbackState: ProgressState | null, + loadedCount: number, data: Data, error: Error } @@ -30,6 +31,7 @@ const initialState: State = { batch: false, state: INITIAL, rollbackState: null, + loadedCount: 0, data: null, error: null } @@ -39,7 +41,7 @@ function reduceRequest (state: State = initialState, actionState: ActionState): case ACTION_REQUEST: return { ...state, state: LOADING, rollbackState: state.state } case ACTION_SUCCESS: - return { ...state, state: LOADED, rollbackState: LOADED, data: actionState.payload } + return { ...state, state: LOADED, rollbackState: LOADED, data: actionState.payload, loadedCount: state.loadedCount + 1 } case ACTION_FAILURE: return { ...state, state: FAILED, rollbackState: FAILED, error: actionState.payload } case ACTION_RESET: diff --git a/app/modules/index.js b/app/modules/index.js index f022b5512..968d9c6e8 100644 --- a/app/modules/index.js +++ b/app/modules/index.js @@ -4,7 +4,6 @@ import api from './api' import account from './account' import generateWallet from './generateWallet' import transactions from './transactions' -import metadata from './metadata' import wallet from './wallet' import claim from './claim' import dashboard from './dashboard' @@ -21,7 +20,6 @@ export default combineReducers({ wallet, transactions, dashboard, - metadata, claim, notifications, modal, diff --git a/app/modules/metadata.js b/app/modules/metadata.js index f4c2b36a0..87e72d4ef 100644 --- a/app/modules/metadata.js +++ b/app/modules/metadata.js @@ -1,109 +1,15 @@ // @flow -import { createSelector } from 'reselect' -import { isNil } from 'lodash' import axios from 'axios' import { api } from 'neon-js' -import storage from 'electron-json-storage' -import { showWarningNotification, showErrorNotification } from './notifications' -import { setCurrency } from './price' - -import { getNetwork, getNetworkId } from '../core/deprecated' -import { - EXPLORERS, - NEON_WALLET_RELEASE_LINK, - NOTIFICATION_POSITIONS, - TOKENS, - TOKENS_TEST, - MAIN_NETWORK_ID, - TEST_NETWORK_ID -} from '../core/constants' +import { showWarningNotification } from './notifications' import asyncWrap from '../core/asyncHelper' - +import { getNetwork } from '../core/deprecated' +import { NEON_WALLET_RELEASE_LINK, NOTIFICATION_POSITIONS } from '../core/constants' import { version } from '../../package.json' -// Constants -export const SET_HEIGHT = 'SET_HEIGHT' -export const SET_EXPLORER = 'SET_EXPLORER' -export const SET_USER_GENERATED_TOKENS = 'SET_USER_GENERATED_TOKENS' - // Actions -export const setBlockHeight = (blockHeight: number) => ({ - type: SET_HEIGHT, - payload: { blockHeight } -}) - -export const setBlockExplorer = (blockExplorer: ExplorerType) => async ( - dispatch: DispatchType -) => { - storage.get('settings', (errorReading, settingsObj) => { - if (errorReading) { - dispatch( - showErrorNotification({ message: 'error grabbing data from storage' }) - ) - } - storage.set( - 'settings', - { - ...settingsObj, - blockExplorer - }, - saveError => { - if (saveError) { - dispatch( - showErrorNotification({ - message: 'error saving new block explorer in storage' - }) - ) - } - } - ) - }) - return dispatch({ - type: SET_EXPLORER, - payload: { blockExplorer } - }) -} - -export const setUserGeneratedTokens = (tokens: Array<TokenItemType>) => async ( - dispatch: DispatchType -) => { - storage.get('settings', (errorReading, settingsObj) => { - if (errorReading) { - dispatch( - showErrorNotification({ message: 'error grabbing data from storage' }) - ) - } - const userGeneratedTokens = tokens.filter( - (token: TokenItemType) => token.isUserGenerated - ) - storage.set( - 'settings', - { - ...settingsObj, - tokens: userGeneratedTokens - }, - saveError => { - if (saveError) { - dispatch( - showErrorNotification({ - message: 'error saving new tokens in storage' - }) - ) - } - } - ) - }) - return dispatch({ - type: SET_USER_GENERATED_TOKENS, - payload: { tokens } - }) -} - -export const checkVersion = () => async ( - dispatch: DispatchType, - getState: GetStateType -) => { +export const checkVersion = () => async (dispatch: DispatchType, getState: GetStateType) => { const state = getState() const net = getNetwork(state) const apiEndpoint = api.neonDB.getAPIEndpoint(net) @@ -125,103 +31,3 @@ export const checkVersion = () => async ( ) } } - -export const initSettings = () => async (dispatch: DispatchType) => { - // eslint-disable-next-line - storage.get('settings', (error, settings) => { - if (!isNil(settings.blockExplorer)) { - dispatch(setBlockExplorer(settings.blockExplorer)) - } - - if (!isNil(settings.currency)) { - dispatch(setCurrency(settings.currency)) - } - - if (!isNil(settings.tokens)) { - dispatch(setUserGeneratedTokens(settings.tokens)) - } - }) -} - -export const syncBlockHeight = (net: NetworkType) => async ( - dispatch: DispatchType -) => { - const [_err, blockHeight] = await asyncWrap(api.neonDB.getWalletDBHeight(net)) // eslint-disable-line - return dispatch(setBlockHeight(blockHeight)) -} - -// state getters -export const getBlockHeight = (state: Object) => state.metadata.blockHeight -export const getBlockExplorer = (state: Object) => state.metadata.blockExplorer -export const getAllTokens = (state: Object) => state.metadata.tokens - -// computed state getters - -export const getTokensForNetwork = createSelector( - getAllTokens, - getNetworkId, - (allTokens, selectedNetworkId) => - allTokens.filter(({ networkId }) => networkId === selectedNetworkId) -) - -const generatePredfinedTokens = (): Array<TokenItemType> => { - const tokens = [] - let id = 1 - - const getTokenEntry = ( - scriptHash: string, - networkId: string - ) => ({ - id: `${id++}`, - scriptHash, - networkId, - isUserGenerated: false - }) - - Object.keys(TOKENS).forEach(symbol => { - const scriptHash = TOKENS[symbol] - tokens.push(getTokenEntry(scriptHash, MAIN_NETWORK_ID)) - }) - - Object.keys(TOKENS_TEST).forEach(symbol => { - const scriptHash = TOKENS_TEST[symbol] - tokens.push(getTokenEntry(scriptHash, TEST_NETWORK_ID)) - }) - - return tokens -} - -const initialState = { - blockHeight: 0, - tokens: generatePredfinedTokens(), - blockExplorer: EXPLORERS.NEO_TRACKER -} - -export default (state: Object = initialState, action: ReduxAction) => { - switch (action.type) { - case SET_HEIGHT: - const { blockHeight } = action.payload - return { - ...state, - blockHeight - } - case SET_EXPLORER: - const { blockExplorer } = action.payload - return { - ...state, - blockExplorer - } - case SET_USER_GENERATED_TOKENS: - const { tokens } = action.payload - return { - ...state, - tokens: [ - // keep existing non user generated tokens - ...state.tokens.filter(token => !token.isUserGenerated), - ...tokens.filter(token => token.isUserGenerated) - ] - } - default: - return state - } -} diff --git a/app/modules/price.js b/app/modules/price.js index 5d852c38f..63c9a99cd 100644 --- a/app/modules/price.js +++ b/app/modules/price.js @@ -1,17 +1,13 @@ // @flow import { api } from 'neon-js' -import storage from 'electron-json-storage' - -import { showErrorNotification } from './notifications' import asyncWrap from '../core/asyncHelper' -import { DEFAULT_CURRENCY_CODE } from '../core/constants' +import { getCurrency } from '../core/deprecated' // Constants export const SET_NEO_PRICE = 'SET_NEO_PRICE' export const SET_GAS_PRICE = 'SET_GAS_PRICE' export const RESET_PRICE = 'RESET_PRICE' -export const SET_CURRENCY = 'SET_CURRENCY' // Actions export const setNEOPrice = (NEO: string) => ({ @@ -24,38 +20,6 @@ export const setGASPrice = (GAS: string) => ({ payload: { GAS } }) -export const setCurrency = (currency: string) => async ( - dispatch: DispatchType -) => { - storage.get('settings', (errorReading, settingsObj) => { - if (errorReading) { - dispatch( - showErrorNotification({ message: 'error grabbing data from storage' }) - ) - } - storage.set( - 'settings', - { - ...settingsObj, - currency - }, - saveError => { - if (saveError) { - dispatch( - showErrorNotification({ - message: 'error saving new currency in storage' - }) - ) - } - } - ) - }) - return dispatch({ - type: SET_CURRENCY, - payload: { currency } - }) -} - export function resetPrice () { return { type: RESET_PRICE @@ -89,12 +53,10 @@ export const getGasMarketPriceUSD = () => async ( // state getters export const getNEOPrice = (state: Object) => state.price.NEO export const getGASPrice = (state: Object) => state.price.GAS -export const getCurrency = (state: Object) => state.price.currency const initialState = { NEO: 0, - GAS: 0, - currency: DEFAULT_CURRENCY_CODE + GAS: 0 } export default (state: Object = initialState, action: Object) => { @@ -111,14 +73,6 @@ export default (state: Object = initialState, action: Object) => { ...state, GAS } - case SET_CURRENCY: - const { currency } = action.payload - return { - ...state, - NEO: 0, - GAS: 0, - currency - } case RESET_PRICE: return { ...state, diff --git a/app/modules/wallet.js b/app/modules/wallet.js index cd2d95f17..e7c534ea0 100644 --- a/app/modules/wallet.js +++ b/app/modules/wallet.js @@ -4,13 +4,12 @@ import { isNil } from 'lodash' import { syncTransactionHistory } from './transactions' import { syncAvailableClaim } from './claim' -import { syncBlockHeight, getTokensForNetwork } from './metadata' import { LOGOUT, getAddress } from './account' import { getMarketPriceUSD, getGasMarketPriceUSD } from './price' import { showErrorNotification } from './notifications' import { ASSETS } from '../core/constants' -import { getNetwork } from '../core/deprecated' +import { getNetwork, getTokensForNetwork, syncBlockHeight } from '../core/deprecated' import asyncWrap from '../core/asyncHelper' import { getTokenBalancesMap } from '../core/wallet' import { COIN_DECIMAL_LENGTH } from '../core/formatters' @@ -93,7 +92,7 @@ export const loadWalletData = (silent: boolean = true) => async ( } dispatch(syncTransactionHistory(net, address)) dispatch(syncAvailableClaim(net, address)) - dispatch(syncBlockHeight(net)) + dispatch(syncBlockHeight(state)) dispatch(getMarketPriceUSD()) dispatch(getGasMarketPriceUSD()) await Promise.all([ diff --git a/app/sagas/batchSaga.js b/app/sagas/batchSaga.js index 52e4fbc93..fdc638956 100644 --- a/app/sagas/batchSaga.js +++ b/app/sagas/batchSaga.js @@ -75,6 +75,7 @@ export default function * batchSaga (state: Object, actionState: ActionState): S yield put(sagaActions.success()) } } catch (err) { + console.error(`${id} batch action failed.`, err) yield put(sagaActions.failure(err.message)) } diff --git a/app/sagas/requestSaga.js b/app/sagas/requestSaga.js index 7b5c997cc..01ee69019 100644 --- a/app/sagas/requestSaga.js +++ b/app/sagas/requestSaga.js @@ -30,6 +30,7 @@ function createSagaActions (meta: ActionMeta): SagaActions { const result = yield call(fn, state) yield put(actions.success(result)) } catch (err) { + console.error(`${meta.id} request action failed.`, err) yield put(actions.failure(err.message)) } } diff --git a/package.json b/package.json index be441b54a..b662632a9 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "electron-json-storage": "4.0.2", "electron-save-file": "1.0.2", "elliptic": "6.4.0", + "es6-promisify": "^5.0.0", "file-loader": "1.1.5", "fs": "0.0.1-security", "global": "4.3.2", diff --git a/yarn.lock b/yarn.lock index 148ba13d6..da3cd8d54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3345,10 +3345,16 @@ es6-map@^0.1.3: es6-symbol "~3.1.1" event-emitter "~0.3.5" -es6-promise@^4.0.5: +es6-promise@^4.0.3, es6-promise@^4.0.5: version "4.2.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.2.tgz#f722d7769af88bd33bc13ec6605e1f92966b82d9" +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + dependencies: + es6-promise "^4.0.3" + es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"