From 8bc1a348c75ab5091f0fcbe61ba3190a5febf62f Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 14 Nov 2017 22:51:09 -0700 Subject: [PATCH] Equivalent Values for Tokens (#366) * Add select with tokens. * Reformat state shape to allow multiple currency rates. Show those rates when selected. * Add loader --- common/actions/rates/actionCreators.ts | 4 +- common/actions/rates/actionPayloads.ts | 33 ++-- .../components/BalanceSidebar/AccountInfo.tsx | 3 - .../BalanceSidebar/EquivalentValues.scss | 5 + .../BalanceSidebar/EquivalentValues.tsx | 155 ++++++++++++++---- common/components/BalanceSidebar/index.tsx | 9 +- .../Tabs/Swap/components/SwapInfoHeader.tsx | 10 +- common/reducers/rates.ts | 15 +- 8 files changed, 167 insertions(+), 67 deletions(-) diff --git a/common/actions/rates/actionCreators.ts b/common/actions/rates/actionCreators.ts index 8725bf0b..3730d44c 100644 --- a/common/actions/rates/actionCreators.ts +++ b/common/actions/rates/actionCreators.ts @@ -3,9 +3,9 @@ import { TypeKeys } from './constants'; import { fetchRates } from './actionPayloads'; export type TFetchCCRates = typeof fetchCCRates; -export function fetchCCRates(): interfaces.FetchCCRates { +export function fetchCCRates(symbol: string): interfaces.FetchCCRates { return { type: TypeKeys.RATES_FETCH_CC, - payload: fetchRates() + payload: fetchRates(symbol) }; } diff --git a/common/actions/rates/actionPayloads.ts b/common/actions/rates/actionPayloads.ts index 715db807..aa4f9e50 100644 --- a/common/actions/rates/actionPayloads.ts +++ b/common/actions/rates/actionPayloads.ts @@ -1,22 +1,31 @@ import { handleJSONResponse } from 'api/utils'; -export const symbols = ['USD', 'EUR', 'GBP', 'BTC', 'CHF', 'REP']; -const symbolsURL = symbols.join(','); +export const rateSymbols = ['USD', 'EUR', 'GBP', 'BTC', 'CHF', 'REP', 'ETH']; +const rateSymbolsArg = rateSymbols.join(','); // TODO - internationalize const ERROR_MESSAGE = 'Could not fetch rate data.'; const CCApi = 'https://min-api.cryptocompare.com'; -const CCRates = CCSymbols => `${CCApi}/data/price?fsym=ETH&tsyms=${CCSymbols}`; +const CCRates = (symbol: string) => { + return `${CCApi}/data/price?fsym=${symbol}&tsyms=${rateSymbolsArg}`; +}; export interface CCResponse { - BTC: number; - EUR: number; - GBP: number; - CHF: number; - REP: number; + symbol: string; + rates: { + BTC: number; + EUR: number; + GBP: number; + CHF: number; + REP: number; + ETH: number; + }; } -export const fetchRates = (): Promise => - fetch(CCRates(symbolsURL)).then(response => - handleJSONResponse(response, ERROR_MESSAGE) - ); +export const fetchRates = (symbol: string): Promise => + fetch(CCRates(symbol)) + .then(response => handleJSONResponse(response, ERROR_MESSAGE)) + .then(rates => ({ + symbol, + rates + })); diff --git a/common/components/BalanceSidebar/AccountInfo.tsx b/common/components/BalanceSidebar/AccountInfo.tsx index 5971cf51..16edfc2c 100644 --- a/common/components/BalanceSidebar/AccountInfo.tsx +++ b/common/components/BalanceSidebar/AccountInfo.tsx @@ -1,4 +1,3 @@ -import { TFetchCCRates } from 'actions/rates'; import { Identicon, UnitDisplay } from 'components/ui'; import { NetworkConfig } from 'config/data'; import { IWallet, Balance } from 'libs/wallet'; @@ -11,7 +10,6 @@ interface Props { balance: Balance; wallet: IWallet; network: NetworkConfig; - fetchCCRates: TFetchCCRates; } interface State { @@ -32,7 +30,6 @@ export default class AccountInfo extends React.Component { } public componentDidMount() { - this.props.fetchCCRates(); this.setAddressFromWallet(); } diff --git a/common/components/BalanceSidebar/EquivalentValues.scss b/common/components/BalanceSidebar/EquivalentValues.scss index 9b47e328..095651cf 100644 --- a/common/components/BalanceSidebar/EquivalentValues.scss +++ b/common/components/BalanceSidebar/EquivalentValues.scss @@ -34,5 +34,10 @@ @include mono; } } + + &-loader { + padding: 25px 0; + text-align: center; + } } } diff --git a/common/components/BalanceSidebar/EquivalentValues.tsx b/common/components/BalanceSidebar/EquivalentValues.tsx index c8053a51..8555141c 100644 --- a/common/components/BalanceSidebar/EquivalentValues.tsx +++ b/common/components/BalanceSidebar/EquivalentValues.tsx @@ -1,56 +1,139 @@ -import React from 'react'; +import * as React from 'react'; import translate from 'translations'; -import './EquivalentValues.scss'; import { State } from 'reducers/rates'; -import { symbols } from 'actions/rates'; -import { UnitDisplay } from 'components/ui'; +import { rateSymbols, TFetchCCRates } from 'actions/rates'; +import { TokenBalance } from 'selectors/wallet'; import { Balance } from 'libs/wallet'; import Spinner from 'components/ui/Spinner'; +import UnitDisplay from 'components/ui/UnitDisplay'; +import './EquivalentValues.scss'; interface Props { - balance: Balance; - rates?: State['rates']; + balance?: Balance; + tokenBalances?: TokenBalance[]; + rates: State['rates']; ratesError?: State['ratesError']; + fetchCCRates: TFetchCCRates; } -export default class EquivalentValues extends React.Component { +interface CmpState { + currency: string; +} + +export default class EquivalentValues extends React.Component { + public state = { + currency: 'ETH' + }; + private balanceLookup = {}; + + public constructor(props) { + super(props); + this.makeBalanceLookup(props); + } + + public componentDidMount() { + this.props.fetchCCRates(this.state.currency); + } + + public componentWillReceiveProps(nextProps) { + const { balance, tokenBalances } = this.props; + if ( + nextProps.balance !== balance || + nextProps.tokenBalances !== tokenBalances + ) { + this.makeBalanceLookup(nextProps); + } + } + public render() { - const { balance, rates, ratesError } = this.props; + const { tokenBalances, rates, ratesError } = this.props; + const { currency } = this.state; + const balance = this.balanceLookup[currency]; + + let values; + if (balance && rates && rates[currency]) { + values = rateSymbols.map(key => { + if (!rates[currency][key] || key === currency) { + return null; + } + + return ( +
  • + + {key}: + {' '} + + {balance.isPending ? ( + + ) : ( + + )} + +
  • + ); + }); + } else if (ratesError) { + values =
    {ratesError}
    ; + } else { + values = ( +
    + +
    + ); + } return (
    -
    {translate('sidebar_Equiv')}
    - -
      - {rates - ? symbols.map(key => { - if (!rates[key]) { - return null; +
      + {translate('sidebar_Equiv')} for{' '} + +
      + +
        {values}
    ); } + + private changeCurrency = (ev: React.FormEvent) => { + const currency = ev.currentTarget.value; + this.setState({ currency }); + if (!this.props.rates || !this.props.rates[currency]) { + this.props.fetchCCRates(currency); + } + }; + + private makeBalanceLookup(props: Props) { + const tokenBalances = props.tokenBalances || []; + this.balanceLookup = tokenBalances.reduce( + (prev, tk) => { + return { + ...prev, + [tk.symbol]: tk.balance + }; + }, + { ETH: props.balance && props.balance.wei } + ); + } } diff --git a/common/components/BalanceSidebar/index.tsx b/common/components/BalanceSidebar/index.tsx index 06c8054b..c11e5fc9 100644 --- a/common/components/BalanceSidebar/index.tsx +++ b/common/components/BalanceSidebar/index.tsx @@ -66,12 +66,7 @@ export class BalanceSidebar extends React.Component { { name: 'Account Info', content: ( - + ) }, { @@ -94,8 +89,10 @@ export class BalanceSidebar extends React.Component { content: ( ) } diff --git a/common/containers/Tabs/Swap/components/SwapInfoHeader.tsx b/common/containers/Tabs/Swap/components/SwapInfoHeader.tsx index 0a057205..af9bcf91 100644 --- a/common/containers/Tabs/Swap/components/SwapInfoHeader.tsx +++ b/common/containers/Tabs/Swap/components/SwapInfoHeader.tsx @@ -66,7 +66,9 @@ export default class SwapInfoHeader extends Component { {/*Amount to send*/} {!this.isExpanded() && (
    -

    {` ${originAmount} ${originKind}`}

    +

    {` ${originAmount} ${ + originKind + }`}

    {translate('SEND_amount')}

    @@ -109,9 +111,9 @@ export default class SwapInfoHeader extends Component {

    {`${computedOriginDestinationRatio && - toFixedIfLarger( - computedOriginDestinationRatio - )} ${destinationKind}/${originKind}`} + toFixedIfLarger(computedOriginDestinationRatio)} ${ + destinationKind + }/${originKind}`}

    {translate('SWAP_your_rate')} diff --git a/common/reducers/rates.ts b/common/reducers/rates.ts index 5d0cb9fc..c45b26d8 100644 --- a/common/reducers/rates.ts +++ b/common/reducers/rates.ts @@ -1,20 +1,27 @@ import { FetchCCRatesSucceeded, RatesAction, CCResponse } from 'actions/rates'; import { TypeKeys } from 'actions/rates/constants'; -import { Optional } from 'utils/types'; // SYMBOL -> PRICE TO BUY 1 ETH export interface State { - rates?: Optional; + rates: { [symbol: string]: CCResponse['rates'] }; ratesError?: string | null; } -export const INITIAL_STATE: State = {}; +export const INITIAL_STATE: State = { + rates: {} +}; function fetchCCRatesSucceeded( state: State, action: FetchCCRatesSucceeded ): State { - return { ...state, rates: action.payload }; + return { + ...state, + rates: { + ...state.rates, + [action.payload.symbol]: action.payload.rates + } + }; } function fetchCCRatesFailed(state: State): State {