From f3b85b2aaef7b419f785490f0b7188d0d7f7b019 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Tue, 12 Sep 2017 15:15:23 -0700 Subject: [PATCH] Alerting and UX Improvements. (#185) * Remove unused imports. * Create and use .toPrecision forwarding method for `Unit` * Error handling when unlocking trezor devices. * Use translateRaw to fulfill string req; * - Refactor rates actions and action creators to use standard network request state pattern (REQUESTED / SUCCE - Only Request Rates once AccountInfo Component has mounted, instead of upon saga instantiation (uneeded overhead). This allows also us to issue subsequent fiat rates requests to update the "equivalent values" should the users session persist. - Show '???' as account balance when balance is null - Wallet initial state with balance as null instead of 0. We don't actually know what the balance is, and we shouldn't have 0 as a default as this may confuse users and doesn't accurately reflect their balance. * - Display 'No rates were loaded.' in EquivalentValues when rates are null, instead of nothing. - Remove unneeded imports. * Remove unneeded imports and reformat. * Fix error messaging (show error message instead of error Object) * remove console.log * inform flow how silly it is being * fix wallet test to reflect balance being null by default * figure out way to have flow understand that rates will not be undefined * open external links in new tab * handle case where balance is null in equivalanet values --- common/actions/rates.js | 22 +++++++++--- .../components/BalanceSidebar/AccountInfo.jsx | 10 +++--- .../BalanceSidebar/EquivalentValues.jsx | 36 ++++++++++--------- common/components/BalanceSidebar/Promos.jsx | 1 + common/components/BalanceSidebar/index.jsx | 24 ++++++++++--- .../DeterministicWalletsModal.jsx | 2 -- common/reducers/rates.js | 11 +++--- common/reducers/wallet.js | 6 ++-- common/sagas/rates.js | 24 ++++++------- common/sagas/swap/rates.js | 2 +- spec/reducers/wallet.spec.js | 2 +- 11 files changed, 87 insertions(+), 53 deletions(-) diff --git a/common/actions/rates.js b/common/actions/rates.js index fd62b6d0..2255c832 100644 --- a/common/actions/rates.js +++ b/common/actions/rates.js @@ -1,17 +1,29 @@ // @flow +export type FiatRequestedRatesAction = { + type: 'RATES_FIAT_REQUESTED' +}; + +export function fiatRequestedRates() { + return { + type: 'RATES_FIAT_REQUESTED' + }; +} + /*** Set rates ***/ -export type SetRatesAction = { - type: 'RATES_SET', +export type FiatSucceededRatesAction = { + type: 'RATES_FIAT_SUCCEEDED', payload: { [string]: number } }; -export function setRates(payload: { [string]: number }): SetRatesAction { +export function fiatSucceededRates(payload: { + [string]: number +}): FiatSucceededRatesAction { return { - type: 'RATES_SET', + type: 'RATES_FIAT_SUCCEEDED', payload }; } /*** Union Type ***/ -export type RatesAction = SetRatesAction; +export type RatesAction = FiatSucceededRatesAction | FiatRequestedRatesAction; diff --git a/common/components/BalanceSidebar/AccountInfo.jsx b/common/components/BalanceSidebar/AccountInfo.jsx index e23a6d9a..efbd28f0 100644 --- a/common/components/BalanceSidebar/AccountInfo.jsx +++ b/common/components/BalanceSidebar/AccountInfo.jsx @@ -7,11 +7,13 @@ import { formatNumber } from 'utils/formatters'; import type { IWallet } from 'libs/wallet'; import type { NetworkConfig } from 'config/data'; import { Ether } from 'libs/units'; +import type { FiatRequestedRatesAction } from 'actions/rates'; type Props = { balance: Ether, wallet: IWallet, - network: NetworkConfig + network: NetworkConfig, + fiatRequestedRates: () => FiatRequestedRatesAction }; export default class AccountInfo extends React.Component { @@ -23,6 +25,7 @@ export default class AccountInfo extends React.Component { }; componentDidMount() { + this.props.fiatRequestedRates(); this.props.wallet.getAddress().then(addr => { this.setState({ address: addr }); }); @@ -67,11 +70,10 @@ export default class AccountInfo extends React.Component { {this.state.showLongBalance - ? balance.toString() - : formatNumber(balance.amount)} + ? balance ? balance.toString() : '???' + : balance ? formatNumber(balance.amount) : '???'} {` ${network.name}`} diff --git a/common/components/BalanceSidebar/EquivalentValues.jsx b/common/components/BalanceSidebar/EquivalentValues.jsx index afec1345..3c9ffaec 100644 --- a/common/components/BalanceSidebar/EquivalentValues.jsx +++ b/common/components/BalanceSidebar/EquivalentValues.jsx @@ -2,16 +2,14 @@ import './EquivalentValues.scss'; import React from 'react'; import translate from 'translations'; -import { Link } from 'react-router'; import { formatNumber } from 'utils/formatters'; -import type Big from 'bignumber.js'; import { Ether } from 'libs/units'; const ratesKeys = ['BTC', 'REP', 'EUR', 'USD', 'GBP', 'CHF']; type Props = { - balance: Ether, - rates: { [string]: number } + balance: ?Ether, + rates: ?{ [string]: number } }; export default class EquivalentValues extends React.Component { @@ -27,19 +25,23 @@ export default class EquivalentValues extends React.Component { ); diff --git a/common/components/BalanceSidebar/Promos.jsx b/common/components/BalanceSidebar/Promos.jsx index 4e1b467a..d2334fff 100644 --- a/common/components/BalanceSidebar/Promos.jsx +++ b/common/components/BalanceSidebar/Promos.jsx @@ -66,6 +66,7 @@ export default class Promos extends React.Component { ? diff --git a/common/components/BalanceSidebar/index.jsx b/common/components/BalanceSidebar/index.jsx index 1c3e9848..5801619a 100644 --- a/common/components/BalanceSidebar/index.jsx +++ b/common/components/BalanceSidebar/index.jsx @@ -9,6 +9,8 @@ import type { TokenBalance } from 'selectors/wallet'; import { getNetworkConfig } from 'selectors/config'; import * as customTokenActions from 'actions/customTokens'; import { showNotification } from 'actions/notifications'; +import { fiatRequestedRates } from 'actions/rates'; +import type { FiatRequestedRatesAction } from 'actions/rates'; import AccountInfo from './AccountInfo'; import Promos from './Promos'; @@ -24,14 +26,22 @@ type Props = { rates: { [string]: number }, showNotification: Function, addCustomToken: typeof customTokenActions.addCustomToken, - removeCustomToken: typeof customTokenActions.removeCustomToken + removeCustomToken: typeof customTokenActions.removeCustomToken, + fiatRequestedRates: () => FiatRequestedRatesAction }; export class BalanceSidebar extends React.Component { props: Props; render() { - const { wallet, balance, network, tokenBalances, rates } = this.props; + const { + wallet, + balance, + network, + tokenBalances, + rates, + fiatRequestedRates + } = this.props; if (!wallet) { return null; } @@ -40,7 +50,12 @@ export class BalanceSidebar extends React.Component { { name: 'Account Info', content: ( - + ) }, { @@ -91,5 +106,6 @@ function mapStateToProps(state: State) { export default connect(mapStateToProps, { ...customTokenActions, - showNotification + showNotification, + fiatRequestedRates })(BalanceSidebar); diff --git a/common/components/WalletDecrypt/DeterministicWalletsModal.jsx b/common/components/WalletDecrypt/DeterministicWalletsModal.jsx index 06c94429..3fd7b6eb 100644 --- a/common/components/WalletDecrypt/DeterministicWalletsModal.jsx +++ b/common/components/WalletDecrypt/DeterministicWalletsModal.jsx @@ -7,11 +7,9 @@ import { getDeterministicWallets, setDesiredToken } from 'actions/deterministicWallets'; -import { toUnit } from 'libs/units'; import { getNetworkConfig } from 'selectors/config'; import { getTokens } from 'selectors/wallet'; import { isValidPath } from 'libs/validators'; - import type { DeterministicWalletData, GetDeterministicWalletsArgs, diff --git a/common/reducers/rates.js b/common/reducers/rates.js index b555afbf..d0203d18 100644 --- a/common/reducers/rates.js +++ b/common/reducers/rates.js @@ -1,5 +1,5 @@ // @flow -import type { SetRatesAction, RatesAction } from 'actions/rates'; +import type { FiatSucceededRatesAction, RatesAction } from 'actions/rates'; // SYMBOL -> PRICE TO BUY 1 ETH export type State = { @@ -8,7 +8,10 @@ export type State = { export const INITIAL_STATE: State = {}; -function setRates(state: State, action: SetRatesAction): State { +function fiatSucceededRates( + state: State, + action: FiatSucceededRatesAction +): State { return action.payload; } @@ -17,8 +20,8 @@ export function rates( action: RatesAction ): State { switch (action.type) { - case 'RATES_SET': - return setRates(state, action); + case 'RATES_FIAT_SUCCEEDED': + return fiatSucceededRates(state, action); default: return state; } diff --git a/common/reducers/wallet.js b/common/reducers/wallet.js index 4a9c8221..2ba14684 100644 --- a/common/reducers/wallet.js +++ b/common/reducers/wallet.js @@ -14,7 +14,7 @@ import { Ether } from 'libs/units'; export type State = { inst: ?IWallet, // in ETH - balance: Ether, + balance: ?Ether, tokens: { [string]: Big }, @@ -23,14 +23,14 @@ export type State = { export const INITIAL_STATE: State = { inst: null, - balance: new Ether(0), + balance: null, tokens: {}, isBroadcasting: false, transactions: [] }; function setWallet(state: State, action: SetWalletAction): State { - return { ...state, inst: action.payload, balance: new Ether(0), tokens: {} }; + return { ...state, inst: action.payload, balance: null, tokens: {} }; } function setBalance(state: State, action: SetBalanceAction): State { diff --git a/common/sagas/rates.js b/common/sagas/rates.js index 250bf1d0..4d483b38 100644 --- a/common/sagas/rates.js +++ b/common/sagas/rates.js @@ -1,28 +1,28 @@ // @flow -import { put, call } from 'redux-saga/effects'; - +import { put, call, takeLatest } from 'redux-saga/effects'; import { handleJSONResponse } from 'api/utils'; - -import { setRates } from 'actions/rates'; -import { showNotification } from 'actions/notifications'; - +import { fiatSucceededRates } from 'actions/rates'; import type { Yield, Return, Next } from 'sagas/types'; const symbols = ['USD', 'EUR', 'GBP', 'BTC', 'CHF', 'REP']; const symbolsURL = symbols.join(','); +// TODO - internationalize +const ERROR_MESSAGE = 'Could not fetch rate data.'; const fetchRates = () => fetch( `https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=${symbolsURL}` - ).then(response => - handleJSONResponse(response, 'Could not fetch rate data.') - ); + ).then(response => handleJSONResponse(response, ERROR_MESSAGE)); -export default function* ratesSaga(): Generator { +export function* handleRatesRequest(): Generator { try { const rates = yield call(fetchRates); - yield put(setRates(rates)); + yield put(fiatSucceededRates(rates)); } catch (error) { - yield put(showNotification('danger', error)); + yield put({ type: 'RATES_FIAT_FAILED', payload: error }); } } + +export default function* ratesSaga(): Generator { + yield takeLatest('RATES_FIAT_REQUESTED', handleRatesRequest); +} diff --git a/common/sagas/swap/rates.js b/common/sagas/swap/rates.js index 7b79f430..414944f9 100644 --- a/common/sagas/swap/rates.js +++ b/common/sagas/swap/rates.js @@ -15,7 +15,7 @@ export function* loadBityRates(_action?: any): Generator { const data = yield call(getAllRates); yield put(loadBityRatesSucceededSwap(data)); } catch (error) { - yield put(yield showNotification('danger', error)); + yield put(yield showNotification('danger', error.message)); } yield call(delay, 5000); } diff --git a/spec/reducers/wallet.spec.js b/spec/reducers/wallet.spec.js index cec0d2e1..db00bdf5 100644 --- a/spec/reducers/wallet.spec.js +++ b/spec/reducers/wallet.spec.js @@ -13,7 +13,7 @@ describe('wallet reducer', () => { expect(wallet(undefined, walletActions.setWallet(walletInstance))).toEqual({ ...INITIAL_STATE, inst: walletInstance, - balance: new Ether(0), + balance: null, tokens: {} }); });