Token Balance retry button & equivalent values fix (#1453)

* Add refresh button to token balances error. Refactor actions a bit.

* Fix assertion error from bn on rates
This commit is contained in:
William O'Beirne 2018-04-05 17:19:14 -04:00 committed by Daniel Ternyak
parent c9676cac62
commit c3d1e4e3af
11 changed files with 82 additions and 27 deletions

View File

@ -160,9 +160,16 @@ export function setWalletConfig(config: WalletConfig): types.SetWalletConfigActi
}; };
} }
export type TSetAccountBalance = typeof setAccountBalance; export type TRefreshAccountBalance = typeof refreshAccountBalance;
export function setAccountBalance(): types.SetAccountBalanceAction { export function refreshAccountBalance(): types.RefreshAccountBalanceAction {
return { return {
type: TypeKeys.WALLET_SET_ACCOUNT_BALANCE type: TypeKeys.WALLET_REFRESH_ACCOUNT_BALANCE
};
}
export type TRefreshTokenBalances = typeof refreshTokenBalances;
export function refreshTokenBalances(): types.RefreshTokenBalancesAction {
return {
type: TypeKeys.WALLET_REFRESH_TOKEN_BALANCES
}; };
} }

View File

@ -125,8 +125,12 @@ export interface SetPasswordPendingAction {
type: TypeKeys.WALLET_SET_PASSWORD_PENDING; type: TypeKeys.WALLET_SET_PASSWORD_PENDING;
} }
export interface SetAccountBalanceAction { export interface RefreshAccountBalanceAction {
type: TypeKeys.WALLET_SET_ACCOUNT_BALANCE; type: TypeKeys.WALLET_REFRESH_ACCOUNT_BALANCE;
}
export interface RefreshTokenBalancesAction {
type: TypeKeys.WALLET_REFRESH_TOKEN_BALANCES;
} }
/*** Union Type ***/ /*** Union Type ***/
@ -148,4 +152,5 @@ export type WalletAction =
| SetWalletTokensAction | SetWalletTokensAction
| SetWalletConfigAction | SetWalletConfigAction
| SetPasswordPendingAction | SetPasswordPendingAction
| SetAccountBalanceAction; | RefreshAccountBalanceAction
| RefreshTokenBalancesAction;

View File

@ -20,5 +20,6 @@ export enum TypeKeys {
WALLET_SET_CONFIG = 'WALLET_SET_CONFIG', WALLET_SET_CONFIG = 'WALLET_SET_CONFIG',
WALLET_RESET = 'WALLET_RESET', WALLET_RESET = 'WALLET_RESET',
WALLET_SET_PASSWORD_PENDING = 'WALLET_SET_PASSWORD_PENDING', WALLET_SET_PASSWORD_PENDING = 'WALLET_SET_PASSWORD_PENDING',
WALLET_SET_ACCOUNT_BALANCE = 'WALLET_SET_ACCOUNT_BALANCE' WALLET_REFRESH_ACCOUNT_BALANCE = 'WALLET_REFRESH_ACCOUNT_BALANCE',
WALLET_REFRESH_TOKEN_BALANCES = 'WALLET_REFRESH_TOKEN_BALANCES'
} }

View File

@ -58,24 +58,39 @@ export interface CCResponse {
[symbol: string]: ISymbol; [symbol: string]: ISymbol;
} }
interface IRates extends ISymbol { interface IRatesResponse {
Response?: 'Error'; [key: string]: number;
}
interface IRatesError {
Response: 'Error';
} }
export const fetchRates = (symbols: string[] = []): Promise<CCResponse> => export const fetchRates = (symbols: string[] = []): Promise<CCResponse> =>
fetch(CCRates(symbols)) fetch(CCRates(symbols))
.then(response => handleJSONResponse(response, ERROR_MESSAGE)) .then(response => handleJSONResponse(response, ERROR_MESSAGE))
.then((rates: IRates) => { .then((rates: IRatesResponse | IRatesError) => {
// API errors come as 200s, so check the json for error // API errors come as 200s, so check the json for error
if (rates.Response && rates.Response === 'Error') { if ((rates as IRatesError).Response === 'Error') {
throw new Error('Failed to fetch rates'); throw new Error('Failed to fetch rates');
} }
return rates;
})
.then((rates: IRatesResponse) => {
// Sometimes the API erroneously gives tokens an extremely high value,
// like 10000000 ETH to 1 token. Filter those out. If that ever turns
// out to be true, we should all go home.
return Object.keys(rates).reduce((filteredRates: IRatesResponse, key) => {
if (rates[key] > 0.000001) {
filteredRates[key] = rates[key];
}
return filteredRates;
}, {});
})
.then((rates: IRatesResponse) => {
// All currencies are in ETH right now. We'll do token -> eth -> value to // All currencies are in ETH right now. We'll do token -> eth -> value to
// do it all in one request // do it all in one request to their respective rates via ETH.
// to their respective rates via ETH.
return symbols.reduce( return symbols.reduce(
(eqRates, sym: keyof ISymbol) => { (eqRates, sym) => {
if (rates[sym]) { if (rates[sym]) {
eqRates[sym] = rateSymbols.symbols.all.reduce( eqRates[sym] = rateSymbols.symbols.all.reduce(
(symRates, rateSym) => { (symRates, rateSym) => {

View File

@ -9,7 +9,7 @@ import Spinner from 'components/ui/Spinner';
import { getNetworkConfig, getOffline } from 'selectors/config'; import { getNetworkConfig, getOffline } from 'selectors/config';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { NetworkConfig } from 'types/network'; import { NetworkConfig } from 'types/network';
import { TSetAccountBalance, setAccountBalance } from 'actions/wallet'; import { TRefreshAccountBalance, refreshAccountBalance } from 'actions/wallet';
import './AccountInfo.scss'; import './AccountInfo.scss';
interface OwnProps { interface OwnProps {
@ -30,7 +30,7 @@ interface State {
} }
interface DispatchProps { interface DispatchProps {
setAccountBalance: TSetAccountBalance; refreshAccountBalance: TRefreshAccountBalance;
} }
type Props = OwnProps & StateProps & DispatchProps; type Props = OwnProps & StateProps & DispatchProps;
@ -170,7 +170,7 @@ class AccountInfo extends React.Component<Props, State> {
!isOffline && ( !isOffline && (
<button <button
className="AccountInfo-section-refresh" className="AccountInfo-section-refresh"
onClick={this.props.setAccountBalance} onClick={this.props.refreshAccountBalance}
> >
<i className="fa fa-refresh" /> <i className="fa fa-refresh" />
</button> </button>
@ -214,5 +214,5 @@ function mapStateToProps(state: AppState): StateProps {
isOffline: getOffline(state) isOffline: getOffline(state)
}; };
} }
const mapDispatchToProps: DispatchProps = { setAccountBalance }; const mapDispatchToProps: DispatchProps = { refreshAccountBalance };
export default connect(mapStateToProps, mapDispatchToProps)(AccountInfo); export default connect(mapStateToProps, mapDispatchToProps)(AccountInfo);

View File

@ -33,6 +33,19 @@
margin-top: $space; margin-top: $space;
} }
&-error {
color: $brand-danger;
text-align: center;
&-message {
margin-top: 0;
}
.fa {
margin-left: $space-md;
}
}
&-buttons { &-buttons {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -11,7 +11,9 @@ import {
scanWalletForTokens, scanWalletForTokens,
TScanWalletForTokens, TScanWalletForTokens,
setWalletTokens, setWalletTokens,
TSetWalletTokens TSetWalletTokens,
refreshTokenBalances,
TRefreshTokenBalances
} from 'actions/wallet'; } from 'actions/wallet';
import { getAllTokens, getOffline } from 'selectors/config'; import { getAllTokens, getOffline } from 'selectors/config';
import { getTokenBalances, getWalletInst, getWalletConfig, TokenBalance } from 'selectors/wallet'; import { getTokenBalances, getWalletInst, getWalletConfig, TokenBalance } from 'selectors/wallet';
@ -36,6 +38,7 @@ interface ActionProps {
removeCustomToken: TRemoveCustomToken; removeCustomToken: TRemoveCustomToken;
scanWalletForTokens: TScanWalletForTokens; scanWalletForTokens: TScanWalletForTokens;
setWalletTokens: TSetWalletTokens; setWalletTokens: TSetWalletTokens;
refreshTokenBalances: TRefreshTokenBalances;
} }
type Props = StateProps & ActionProps; type Props = StateProps & ActionProps;
@ -56,12 +59,18 @@ class TokenBalances extends React.Component<Props> {
let content; let content;
if (isOffline) { if (isOffline) {
content = ( content = (
<div className="TokenBalances-offline well well-sm"> <div className="TokenBalances-offline well well-sm">{translate('SCAN_TOKENS_OFFLINE')}</div>
Token balances are unavailable offline
</div>
); );
} else if (tokensError) { } else if (tokensError) {
content = <h5>{tokensError}</h5>; content = (
<div className="TokenBalances-error well well-md">
<h5 className="TokenBalances-error-message">{tokensError}</h5>
<button onClick={this.props.refreshTokenBalances} className="btn btn-default btn-sm">
{translate('X_TRY_AGAIN')}
<i className="fa fa-refresh" />
</button>
</div>
);
} else if (isTokensLoading) { } else if (isTokensLoading) {
content = ( content = (
<div className="TokenBalances-loader"> <div className="TokenBalances-loader">
@ -126,5 +135,6 @@ export default connect(mapStateToProps, {
addCustomToken, addCustomToken,
removeCustomToken, removeCustomToken,
scanWalletForTokens, scanWalletForTokens,
setWalletTokens setWalletTokens,
refreshTokenBalances
})(TokenBalances); })(TokenBalances);

View File

@ -104,7 +104,7 @@ function setTokenBalanceRejected(state: State): State {
return { return {
...state, ...state,
isTokensLoading: false, isTokensLoading: false,
tokensError: 'Failed to fetch token value' tokensError: translateRaw('SCAN_TOKENS_FAIL')
}; };
} }

View File

@ -9,6 +9,7 @@ export function* fetchRatesSaga(action: FetchCCRatesRequested): SagaIterator {
const rates: CCResponse = yield call(fetchRates, action.payload); const rates: CCResponse = yield call(fetchRates, action.payload);
yield put(fetchCCRatesSucceeded(rates)); yield put(fetchCCRatesSucceeded(rates));
} catch (e) { } catch (e) {
console.error('Failed to fetch rates:', e);
yield put(fetchCCRatesFailed()); yield put(fetchCCRatesFailed());
return; return;
} }

View File

@ -320,7 +320,8 @@ export default function* walletSaga(): SagaIterator {
takeEvery(TypeKeys.WALLET_SCAN_WALLET_FOR_TOKENS, scanWalletForTokens), takeEvery(TypeKeys.WALLET_SCAN_WALLET_FOR_TOKENS, scanWalletForTokens),
takeEvery(TypeKeys.WALLET_SET_WALLET_TOKENS, handleSetWalletTokens), takeEvery(TypeKeys.WALLET_SET_WALLET_TOKENS, handleSetWalletTokens),
takeEvery(TypeKeys.WALLET_SET_TOKEN_BALANCE_PENDING, updateTokenBalance), takeEvery(TypeKeys.WALLET_SET_TOKEN_BALANCE_PENDING, updateTokenBalance),
takeEvery(TypeKeys.WALLET_SET_ACCOUNT_BALANCE, updateAccountBalance), takeEvery(TypeKeys.WALLET_REFRESH_ACCOUNT_BALANCE, updateAccountBalance),
takeEvery(TypeKeys.WALLET_REFRESH_TOKEN_BALANCES, updateTokenBalances),
// Foreign actions // Foreign actions
takeEvery(ConfigTypeKeys.CONFIG_TOGGLE_OFFLINE, updateBalances), takeEvery(ConfigTypeKeys.CONFIG_TOGGLE_OFFLINE, updateBalances),
takeEvery(CustomTokenTypeKeys.CUSTOM_TOKEN_ADD, handleCustomTokenAdd) takeEvery(CustomTokenTypeKeys.CUSTOM_TOKEN_ADD, handleCustomTokenAdd)

View File

@ -42,6 +42,7 @@
"X_PRIVKEY2": "Private Key", "X_PRIVKEY2": "Private Key",
"X_PRIVKEYDESC": "This is the unencrypted text version of your private key, meaning no password is necessary. If someone were to find your unencrypted private key, they could access your wallet without a password. For this reason, encrypted versions are typically recommended. ", "X_PRIVKEYDESC": "This is the unencrypted text version of your private key, meaning no password is necessary. If someone were to find your unencrypted private key, they could access your wallet without a password. For this reason, encrypted versions are typically recommended. ",
"X_SAVE": "Save ", "X_SAVE": "Save ",
"X_TRY_AGAIN": "Try again",
"CX_WARNING_1": "Make sure you have **external backups** of any wallets you store here. Many things could happen that would cause you to lose the data in this Chrome Extension, including uninstalling and reinstalling the extension. This extension is a way to easily access your wallets, **not** a way to back them up. ", "CX_WARNING_1": "Make sure you have **external backups** of any wallets you store here. Many things could happen that would cause you to lose the data in this Chrome Extension, including uninstalling and reinstalling the extension. This extension is a way to easily access your wallets, **not** a way to back them up. ",
"SIDEBAR_ACCOUNTADDR": "Account Address ", "SIDEBAR_ACCOUNTADDR": "Account Address ",
"SIDEBAR_ACCOUNTBAL": "Account Balance ", "SIDEBAR_ACCOUNTBAL": "Account Balance ",
@ -103,6 +104,7 @@
"SCAN_TOKENS": "Scan For Tokens", "SCAN_TOKENS": "Scan For Tokens",
"SCAN_TOKENS_FAIL": "Failed to fetch token values", "SCAN_TOKENS_FAIL": "Failed to fetch token values",
"SCAN_TOKENS_FAIL_NO_TOKENS": "No tokens found", "SCAN_TOKENS_FAIL_NO_TOKENS": "No tokens found",
"SCAN_TOKENS_OFFLINE": "Token balances are unavailable offline",
"SEND_GAS": "Gas ", "SEND_GAS": "Gas ",
"SEND_TRANSFERTOTAL": "Send Entire Balance ", "SEND_TRANSFERTOTAL": "Send Entire Balance ",
"SEND_GENERATE": "Generate Transaction ", "SEND_GENERATE": "Generate Transaction ",