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
This commit is contained in:
Daniel Ternyak 2017-09-12 15:15:23 -07:00 committed by GitHub
parent f34811546a
commit f3b85b2aae
11 changed files with 87 additions and 53 deletions

View File

@ -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;

View File

@ -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 {
<span
className="AccountInfo-list-item-clickable mono wrap"
onClick={this.toggleShowLongBalance}
title={`${balance.toString()}`}
>
{this.state.showLongBalance
? balance.toString()
: formatNumber(balance.amount)}
? balance ? balance.toString() : '???'
: balance ? formatNumber(balance.amount) : '???'}
</span>
{` ${network.name}`}
</li>

View File

@ -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 {
</h5>
<ul className="EquivalentValues-values">
{ratesKeys.map(key => {
if (!rates[key]) return null;
return (
<li className="EquivalentValues-values-currency" key={key}>
<span className="EquivalentValues-values-currency-label">
{key}:
</span>
<span className="EquivalentValues-values-currency-value">
{' '}{formatNumber(balance.amount.times(rates[key]))}
</span>
</li>
);
})}
{rates
? ratesKeys.map(key => {
if (!rates[key]) return null;
return (
<li className="EquivalentValues-values-currency" key={key}>
<span className="EquivalentValues-values-currency-label">
{key}:
</span>
<span className="EquivalentValues-values-currency-value">
{' '}{balance
? formatNumber(balance.amount.times(rates[key]))
: '???'}
</span>
</li>
);
})
: <h5>No rates were loaded.</h5>}
</ul>
</div>
);

View File

@ -66,6 +66,7 @@ export default class Promos extends React.Component {
? <a
className="Promos-promo"
key={promo.href}
target="_blank"
href={promo.href}
style={{ backgroundColor: promo.color }}
>

View File

@ -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: (
<AccountInfo wallet={wallet} balance={balance} network={network} />
<AccountInfo
wallet={wallet}
balance={balance}
network={network}
fiatRequestedRates={fiatRequestedRates}
/>
)
},
{
@ -91,5 +106,6 @@ function mapStateToProps(state: State) {
export default connect(mapStateToProps, {
...customTokenActions,
showNotification
showNotification,
fiatRequestedRates
})(BalanceSidebar);

View File

@ -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,

View File

@ -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;
}

View File

@ -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 {

View File

@ -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<Yield, Return, Next> {
export function* handleRatesRequest(): Generator<Yield, Return, Next> {
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, Return, Next> {
yield takeLatest('RATES_FIAT_REQUESTED', handleRatesRequest);
}

View File

@ -15,7 +15,7 @@ export function* loadBityRates(_action?: any): Generator<Yield, Return, Next> {
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);
}

View File

@ -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: {}
});
});