Redux promise middleware (v2) (#233)
* add redux-promise-middleware to package.json and update package-lock.json * intergrate redux-promise-middleware and simplify rates by replacing saga with promise * fix unrelated breaking test * -improve user messaging when network request fails. \n Clean up rates actions and reducers * Address tslint errors
This commit is contained in:
parent
3660260efb
commit
af84a589c5
|
@ -1,19 +1,11 @@
|
||||||
import * as interfaces from './actionTypes';
|
import * as interfaces from './actionTypes';
|
||||||
import { TypeKeys } from './constants';
|
import { TypeKeys } from './constants';
|
||||||
|
import { fetchRates, CCResponse } from './actionPayloads';
|
||||||
|
|
||||||
export type TFiatRequestedRates = typeof fiatRequestedRates;
|
export type TFetchCCRates = typeof fetchCCRates;
|
||||||
export function fiatRequestedRates(): interfaces.FiatRequestedRatesAction {
|
export function fetchCCRates(): interfaces.FetchCCRates {
|
||||||
return {
|
return {
|
||||||
type: TypeKeys.RATES_FIAT_REQUESTED
|
type: TypeKeys.RATES_FETCH_CC,
|
||||||
};
|
payload: fetchRates()
|
||||||
}
|
|
||||||
|
|
||||||
export type TFiatSucceededRates = typeof fiatSucceededRates;
|
|
||||||
export function fiatSucceededRates(payload: {
|
|
||||||
[key: string]: number;
|
|
||||||
}): interfaces.FiatSucceededRatesAction {
|
|
||||||
return {
|
|
||||||
type: TypeKeys.RATES_FIAT_SUCCEEDED,
|
|
||||||
payload
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { handleJSONResponse } from 'api/utils';
|
||||||
|
|
||||||
|
export const symbols = ['USD', 'EUR', 'GBP', 'BTC', 'CHF', 'REP'];
|
||||||
|
const symbolsURL = symbols.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}`;
|
||||||
|
|
||||||
|
export interface CCResponse {
|
||||||
|
BTC: number;
|
||||||
|
EUR: number;
|
||||||
|
GBP: number;
|
||||||
|
CHF: number;
|
||||||
|
REP: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchRates = (): Promise<CCResponse> =>
|
||||||
|
fetch(CCRates(symbolsURL)).then(response =>
|
||||||
|
handleJSONResponse(response, ERROR_MESSAGE)
|
||||||
|
);
|
|
@ -1,13 +1,23 @@
|
||||||
import { TypeKeys } from './constants';
|
import { TypeKeys } from './constants';
|
||||||
export interface FiatRequestedRatesAction {
|
import { CCResponse } from './actionPayloads';
|
||||||
type: TypeKeys.RATES_FIAT_REQUESTED;
|
|
||||||
|
export interface FetchCCRates {
|
||||||
|
type: TypeKeys.RATES_FETCH_CC;
|
||||||
|
payload: Promise<CCResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Set rates ***/
|
/*** Set rates ***/
|
||||||
export interface FiatSucceededRatesAction {
|
export interface FetchCCRatesSucceeded {
|
||||||
type: TypeKeys.RATES_FIAT_SUCCEEDED;
|
type: TypeKeys.RATES_FETCH_CC_SUCCEEDED;
|
||||||
payload: { [key: string]: number };
|
payload: CCResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchCCRatesFailed {
|
||||||
|
type: TypeKeys.RATES_FETCH_CC_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Union Type ***/
|
/*** Union Type ***/
|
||||||
export type RatesAction = FiatSucceededRatesAction | FiatRequestedRatesAction;
|
export type RatesAction =
|
||||||
|
| FetchCCRatesSucceeded
|
||||||
|
| FetchCCRates
|
||||||
|
| FetchCCRatesFailed;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export enum TypeKeys {
|
export enum TypeKeys {
|
||||||
RATES_FIAT_REQUESTED = 'RATES_FIAT_REQUESTED',
|
RATES_FETCH_CC = 'RATES_FETCH_CC',
|
||||||
RATES_FIAT_SUCCEEDED = 'RATES_FIAT_SUCCEEDED'
|
RATES_FETCH_CC_FAILED = 'RATES_FETCH_CC_FAILED',
|
||||||
|
RATES_FETCH_CC_SUCCEEDED = 'RATES_FETCH_CC_SUCCEEDED'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './actionCreators';
|
export * from './actionCreators';
|
||||||
export * from './actionTypes';
|
export * from './actionTypes';
|
||||||
|
export * from './actionPayloads';
|
||||||
|
|
|
@ -2,10 +2,7 @@ export function checkHttpStatus(response) {
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
const error = new Error(response.statusText);
|
return new Error(response.statusText);
|
||||||
// TODO: why assign response?
|
|
||||||
// error.response = response;
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +12,7 @@ export function parseJSON(response) {
|
||||||
|
|
||||||
export async function handleJSONResponse(response, errorMessage) {
|
export async function handleJSONResponse(response, errorMessage) {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const json = await response.json();
|
return await response.json();
|
||||||
return json;
|
|
||||||
}
|
}
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FiatRequestedRatesAction } from 'actions/rates';
|
import { TFetchCCRates } from 'actions/rates';
|
||||||
import { Identicon } from 'components/ui';
|
import { Identicon } from 'components/ui';
|
||||||
import { NetworkConfig } from 'config/data';
|
import { NetworkConfig } from 'config/data';
|
||||||
import { Ether } from 'libs/units';
|
import { Ether } from 'libs/units';
|
||||||
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
balance: Ether;
|
balance: Ether;
|
||||||
wallet: IWallet;
|
wallet: IWallet;
|
||||||
network: NetworkConfig;
|
network: NetworkConfig;
|
||||||
fiatRequestedRates(): FiatRequestedRatesAction;
|
fetchCCRates: TFetchCCRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -26,9 +26,9 @@ export default class AccountInfo extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.props.fiatRequestedRates();
|
this.props.fetchCCRates();
|
||||||
this.props.wallet.getAddress().then(addr => {
|
this.props.wallet.getAddress().then(address => {
|
||||||
this.setState({ address: addr });
|
this.setState({ address });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,9 +57,7 @@ export default class AccountInfo extends React.Component<Props, State> {
|
||||||
<div className="AccountInfo-address-icon">
|
<div className="AccountInfo-address-icon">
|
||||||
<Identicon address={address} size="100%" />
|
<Identicon address={address} size="100%" />
|
||||||
</div>
|
</div>
|
||||||
<div className="AccountInfo-address-addr">
|
<div className="AccountInfo-address-addr">{address}</div>
|
||||||
{address}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -82,26 +80,29 @@ export default class AccountInfo extends React.Component<Props, State> {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(!!blockExplorer || !!tokenExplorer) &&
|
{(!!blockExplorer || !!tokenExplorer) && (
|
||||||
<div className="AccountInfo-section">
|
<div className="AccountInfo-section">
|
||||||
<h5 className="AccountInfo-section-header">
|
<h5 className="AccountInfo-section-header">
|
||||||
{translate('sidebar_TransHistory')}
|
{translate('sidebar_TransHistory')}
|
||||||
</h5>
|
</h5>
|
||||||
<ul className="AccountInfo-list">
|
<ul className="AccountInfo-list">
|
||||||
{!!blockExplorer &&
|
{!!blockExplorer && (
|
||||||
<li className="AccountInfo-list-item">
|
<li className="AccountInfo-list-item">
|
||||||
<a href={blockExplorer.address(address)} target="_blank">
|
<a href={blockExplorer.address(address)} target="_blank">
|
||||||
{`${network.name} (${blockExplorer.name})`}
|
{`${network.name} (${blockExplorer.name})`}
|
||||||
</a>
|
</a>
|
||||||
</li>}
|
</li>
|
||||||
{!!tokenExplorer &&
|
)}
|
||||||
|
{!!tokenExplorer && (
|
||||||
<li className="AccountInfo-list-item">
|
<li className="AccountInfo-list-item">
|
||||||
<a href={tokenExplorer.address(address)} target="_blank">
|
<a href={tokenExplorer.address(address)} target="_blank">
|
||||||
{`Tokens (${tokenExplorer.name})`}
|
{`Tokens (${tokenExplorer.name})`}
|
||||||
</a>
|
</a>
|
||||||
</li>}
|
</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,26 @@ import React from 'react';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
import { formatNumber } from 'utils/formatters';
|
import { formatNumber } from 'utils/formatters';
|
||||||
import './EquivalentValues.scss';
|
import './EquivalentValues.scss';
|
||||||
|
import { State } from 'reducers/rates';
|
||||||
const ratesKeys = ['BTC', 'REP', 'EUR', 'USD', 'GBP', 'CHF'];
|
import { symbols } from 'actions/rates';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
balance?: Ether;
|
balance?: Ether;
|
||||||
rates?: { [key: string]: number };
|
rates?: State['rates'];
|
||||||
|
ratesError?: State['ratesError'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class EquivalentValues extends React.Component<Props, {}> {
|
export default class EquivalentValues extends React.Component<Props, {}> {
|
||||||
public render() {
|
public render() {
|
||||||
const { balance, rates } = this.props;
|
const { balance, rates, ratesError } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="EquivalentValues">
|
<div className="EquivalentValues">
|
||||||
<h5 className="EquivalentValues-title">
|
<h5 className="EquivalentValues-title">{translate('sidebar_Equiv')}</h5>
|
||||||
{translate('sidebar_Equiv')}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<ul className="EquivalentValues-values">
|
<ul className="EquivalentValues-values">
|
||||||
{rates
|
{rates
|
||||||
? ratesKeys.map(key => {
|
? symbols.map(key => {
|
||||||
if (!rates[key]) {
|
if (!rates[key]) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -33,14 +32,15 @@ export default class EquivalentValues extends React.Component<Props, {}> {
|
||||||
{key}:
|
{key}:
|
||||||
</span>
|
</span>
|
||||||
<span className="EquivalentValues-values-currency-value">
|
<span className="EquivalentValues-values-currency-value">
|
||||||
{' '}{balance
|
{' '}
|
||||||
|
{balance
|
||||||
? formatNumber(balance.amount.times(rates[key]))
|
? formatNumber(balance.amount.times(rates[key]))
|
||||||
: '???'}
|
: '???'}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: <h5>No rates were loaded.</h5>}
|
: ratesError && <h5>{ratesError}</h5>}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,10 +5,7 @@ import {
|
||||||
TRemoveCustomToken
|
TRemoveCustomToken
|
||||||
} from 'actions/customTokens';
|
} from 'actions/customTokens';
|
||||||
import { showNotification, TShowNotification } from 'actions/notifications';
|
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||||
import {
|
import { fetchCCRates as dFetchCCRates, TFetchCCRates } from 'actions/rates';
|
||||||
fiatRequestedRates as dFiatRequestedRates,
|
|
||||||
TFiatRequestedRates
|
|
||||||
} from 'actions/rates';
|
|
||||||
import { NetworkConfig } from 'config/data';
|
import { NetworkConfig } from 'config/data';
|
||||||
import { Ether } from 'libs/units';
|
import { Ether } from 'libs/units';
|
||||||
import { IWallet } from 'libs/wallet/IWallet';
|
import { IWallet } from 'libs/wallet/IWallet';
|
||||||
|
@ -25,17 +22,19 @@ import AccountInfo from './AccountInfo';
|
||||||
import EquivalentValues from './EquivalentValues';
|
import EquivalentValues from './EquivalentValues';
|
||||||
import Promos from './Promos';
|
import Promos from './Promos';
|
||||||
import TokenBalances from './TokenBalances';
|
import TokenBalances from './TokenBalances';
|
||||||
|
import { State } from 'reducers/rates';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
wallet: IWallet;
|
wallet: IWallet;
|
||||||
balance: Ether;
|
balance: Ether;
|
||||||
network: NetworkConfig;
|
network: NetworkConfig;
|
||||||
tokenBalances: TokenBalance[];
|
tokenBalances: TokenBalance[];
|
||||||
rates: { [key: string]: number };
|
rates: State['rates'];
|
||||||
|
ratesError: State['ratesError'];
|
||||||
showNotification: TShowNotification;
|
showNotification: TShowNotification;
|
||||||
addCustomToken: TAddCustomToken;
|
addCustomToken: TAddCustomToken;
|
||||||
removeCustomToken: TRemoveCustomToken;
|
removeCustomToken: TRemoveCustomToken;
|
||||||
fiatRequestedRates: TFiatRequestedRates;
|
fetchCCRates: TFetchCCRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Block {
|
interface Block {
|
||||||
|
@ -52,7 +51,8 @@ export class BalanceSidebar extends React.Component<Props, {}> {
|
||||||
network,
|
network,
|
||||||
tokenBalances,
|
tokenBalances,
|
||||||
rates,
|
rates,
|
||||||
fiatRequestedRates
|
ratesError,
|
||||||
|
fetchCCRates
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -66,7 +66,7 @@ export class BalanceSidebar extends React.Component<Props, {}> {
|
||||||
wallet={wallet}
|
wallet={wallet}
|
||||||
balance={balance}
|
balance={balance}
|
||||||
network={network}
|
network={network}
|
||||||
fiatRequestedRates={fiatRequestedRates}
|
fetchCCRates={fetchCCRates}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -87,20 +87,26 @@ export class BalanceSidebar extends React.Component<Props, {}> {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Equivalent Values',
|
name: 'Equivalent Values',
|
||||||
content: <EquivalentValues balance={balance} rates={rates} />
|
content: (
|
||||||
|
<EquivalentValues
|
||||||
|
balance={balance}
|
||||||
|
rates={rates}
|
||||||
|
ratesError={ratesError}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside>
|
<aside>
|
||||||
{blocks.map(block =>
|
{blocks.map(block => (
|
||||||
<section
|
<section
|
||||||
className={`Block ${block.isFullWidth ? 'is-full-width' : ''}`}
|
className={`Block ${block.isFullWidth ? 'is-full-width' : ''}`}
|
||||||
key={block.name}
|
key={block.name}
|
||||||
>
|
>
|
||||||
{block.content}
|
{block.content}
|
||||||
</section>
|
</section>
|
||||||
)}
|
))}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -112,7 +118,8 @@ function mapStateToProps(state: AppState) {
|
||||||
balance: state.wallet.balance,
|
balance: state.wallet.balance,
|
||||||
tokenBalances: getTokenBalances(state),
|
tokenBalances: getTokenBalances(state),
|
||||||
network: getNetworkConfig(state),
|
network: getNetworkConfig(state),
|
||||||
rates: state.rates
|
rates: state.rates.rates,
|
||||||
|
ratesError: state.rates.ratesError
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,5 +127,5 @@ export default connect(mapStateToProps, {
|
||||||
addCustomToken,
|
addCustomToken,
|
||||||
removeCustomToken,
|
removeCustomToken,
|
||||||
showNotification,
|
showNotification,
|
||||||
fiatRequestedRates: dFiatRequestedRates
|
fetchCCRates: dFetchCCRates
|
||||||
})(BalanceSidebar);
|
})(BalanceSidebar);
|
||||||
|
|
|
@ -104,8 +104,8 @@ export default class PaperWallet extends React.Component<Props, State> {
|
||||||
if (!this.props.wallet) {
|
if (!this.props.wallet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.props.wallet.getAddress().then(addr => {
|
this.props.wallet.getAddress().then(address => {
|
||||||
this.setState({ address: addr });
|
this.setState({ address });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ export default class DownloadWallet extends Component<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.props.wallet.getAddress().then(addr => {
|
this.props.wallet.getAddress().then(address => {
|
||||||
this.setState({ address: addr });
|
this.setState({ address });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,33 @@
|
||||||
import { FiatSucceededRatesAction, RatesAction } from 'actions/rates';
|
import {
|
||||||
|
FetchCCRatesSucceeded,
|
||||||
|
FetchCCRatesFailed,
|
||||||
|
RatesAction,
|
||||||
|
CCResponse
|
||||||
|
} from 'actions/rates';
|
||||||
import { TypeKeys } from 'actions/rates/constants';
|
import { TypeKeys } from 'actions/rates/constants';
|
||||||
|
import { Optional } from 'utils/types';
|
||||||
|
|
||||||
// SYMBOL -> PRICE TO BUY 1 ETH
|
// SYMBOL -> PRICE TO BUY 1 ETH
|
||||||
export interface State {
|
export interface State {
|
||||||
[key: string]: number;
|
rates?: Optional<CCResponse>;
|
||||||
|
ratesError?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INITIAL_STATE: State = {};
|
export const INITIAL_STATE: State = {};
|
||||||
|
|
||||||
function fiatSucceededRates(
|
function fetchCCRatesSucceeded(
|
||||||
state: State,
|
state: State,
|
||||||
action: FiatSucceededRatesAction
|
action: FetchCCRatesSucceeded
|
||||||
): State {
|
): State {
|
||||||
return action.payload;
|
return { ...state, rates: action.payload };
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchCCRatesFailed(state: State, action: FetchCCRatesFailed): State {
|
||||||
|
// TODO: Make library for error messages
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ratesError: 'Sorry. We were unable to fetch equivalent rates.'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rates(
|
export function rates(
|
||||||
|
@ -19,8 +35,10 @@ export function rates(
|
||||||
action: RatesAction
|
action: RatesAction
|
||||||
): State {
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case TypeKeys.RATES_FIAT_SUCCEEDED:
|
case TypeKeys.RATES_FETCH_CC_SUCCEEDED:
|
||||||
return fiatSucceededRates(state, action);
|
return fetchCCRatesSucceeded(state, action);
|
||||||
|
case TypeKeys.RATES_FETCH_CC_FAILED:
|
||||||
|
return fetchCCRatesFailed(state, action);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import handleConfigChanges from './config';
|
||||||
import contracts from './contracts';
|
import contracts from './contracts';
|
||||||
import deterministicWallets from './deterministicWallets';
|
import deterministicWallets from './deterministicWallets';
|
||||||
import notifications from './notifications';
|
import notifications from './notifications';
|
||||||
import rates from './rates';
|
|
||||||
import {
|
import {
|
||||||
bityTimeRemaining,
|
bityTimeRemaining,
|
||||||
pollBityOrderStatusSaga,
|
pollBityOrderStatusSaga,
|
||||||
|
@ -19,7 +18,6 @@ export default {
|
||||||
getBityRatesSaga,
|
getBityRatesSaga,
|
||||||
contracts,
|
contracts,
|
||||||
notifications,
|
notifications,
|
||||||
rates,
|
|
||||||
wallet,
|
wallet,
|
||||||
deterministicWallets
|
deterministicWallets
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { fiatSucceededRates } from 'actions/rates';
|
|
||||||
import { handleJSONResponse } from 'api/utils';
|
|
||||||
import { SagaIterator } from 'redux-saga';
|
|
||||||
import { call, put, takeLatest } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
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, ERROR_MESSAGE));
|
|
||||||
|
|
||||||
export function* handleRatesRequest(): SagaIterator {
|
|
||||||
try {
|
|
||||||
const rates = yield call(fetchRates);
|
|
||||||
yield put(fiatSucceededRates(rates));
|
|
||||||
} catch (error) {
|
|
||||||
yield put({ type: 'RATES_FIAT_FAILED', payload: error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function* ratesSaga(): SagaIterator {
|
|
||||||
yield takeLatest('RATES_FIAT_REQUESTED', handleRatesRequest);
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { Effect } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
export type Yield = Effect | {};
|
|
||||||
export type Return = void;
|
|
||||||
export type Next = any;
|
|
|
@ -8,14 +8,11 @@ import { applyMiddleware, createStore } from 'redux';
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
import createSagaMiddleware from 'redux-saga';
|
import createSagaMiddleware from 'redux-saga';
|
||||||
import {
|
import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage';
|
||||||
loadState,
|
|
||||||
loadStatePropertyOrEmptyObject,
|
|
||||||
saveState
|
|
||||||
} from 'utils/localStorage';
|
|
||||||
import RootReducer from './reducers';
|
import RootReducer from './reducers';
|
||||||
import { State as CustomTokenState } from './reducers/customTokens';
|
import { State as CustomTokenState } from './reducers/customTokens';
|
||||||
import { State as SwapState } from './reducers/swap';
|
import { State as SwapState } from './reducers/swap';
|
||||||
|
import promiseMiddleware from 'redux-promise-middleware';
|
||||||
|
|
||||||
import sagas from './sagas';
|
import sagas from './sagas';
|
||||||
|
|
||||||
|
@ -27,17 +24,26 @@ const configureStore = () => {
|
||||||
collapsed: true
|
collapsed: true
|
||||||
});
|
});
|
||||||
const sagaMiddleware = createSagaMiddleware();
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
const reduxPromiseMiddleWare = promiseMiddleware({
|
||||||
|
promiseTypeSuffixes: ['REQUESTED', 'SUCCEEDED', 'FAILED']
|
||||||
|
});
|
||||||
let middleware;
|
let middleware;
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
(window as MyWindow).Perf = Perf;
|
(window as MyWindow).Perf = Perf;
|
||||||
middleware = composeWithDevTools(
|
middleware = composeWithDevTools(
|
||||||
applyMiddleware(sagaMiddleware, logger, routerMiddleware(history as any))
|
applyMiddleware(
|
||||||
|
sagaMiddleware,
|
||||||
|
logger,
|
||||||
|
reduxPromiseMiddleWare,
|
||||||
|
routerMiddleware(history as any)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
middleware = applyMiddleware(
|
middleware = applyMiddleware(
|
||||||
sagaMiddleware,
|
sagaMiddleware,
|
||||||
|
reduxPromiseMiddleWare,
|
||||||
routerMiddleware(history as any)
|
routerMiddleware(history as any)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Maps interface keys to optional
|
||||||
|
export type Optional<T> = { [P in keyof T]?: T[P] };
|
||||||
|
// Maps interface keys to nullable
|
||||||
|
export type Nullable<T> = { [P in keyof T]: T[P] | null };
|
|
@ -145,6 +145,15 @@
|
||||||
"redux": "3.7.2"
|
"redux": "3.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/redux-promise-middleware": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/redux-promise-middleware/-/redux-promise-middleware-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-5GFSEoerhY5EAXgtc276k3TOq8HSY4vmqI0AinzpejJlgRHW5Aw6gl2wmy0cqxoPqkhrd8yWbs2uxRa5FOMdvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"redux": "3.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/redux-saga": {
|
"@types/redux-saga": {
|
||||||
"version": "0.10.5",
|
"version": "0.10.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/redux-saga/-/redux-saga-0.10.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/redux-saga/-/redux-saga-0.10.5.tgz",
|
||||||
|
@ -10878,6 +10887,11 @@
|
||||||
"deep-diff": "0.3.8"
|
"deep-diff": "0.3.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux-promise-middleware": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-promise-middleware/-/redux-promise-middleware-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-1B5eiSGbZIbTKutIwYBwvz8visA5Bdnqtlxm2UnVsl7pgyU7hc7wigBwj17cLOnvG6deO4yft9NXFjWpg7k7PQ=="
|
||||||
|
},
|
||||||
"redux-saga": {
|
"redux-saga": {
|
||||||
"version": "0.15.4",
|
"version": "0.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.15.4.tgz",
|
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.15.4.tgz",
|
||||||
|
|
13
package.json
13
package.json
|
@ -33,6 +33,7 @@
|
||||||
"redux": "^3.6.0",
|
"redux": "^3.6.0",
|
||||||
"redux-form": "^6.6.3",
|
"redux-form": "^6.6.3",
|
||||||
"redux-logger": "^3.0.1",
|
"redux-logger": "^3.0.1",
|
||||||
|
"redux-promise-middleware": "^4.4.1",
|
||||||
"redux-saga": "^0.15.3",
|
"redux-saga": "^0.15.3",
|
||||||
"scryptsy": "^2.0.0",
|
"scryptsy": "^2.0.0",
|
||||||
"store2": "^2.5.5",
|
"store2": "^2.5.5",
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
"@types/react-router-redux": "^4.0.50",
|
"@types/react-router-redux": "^4.0.50",
|
||||||
"@types/redux-form": "^7.0.5",
|
"@types/redux-form": "^7.0.5",
|
||||||
"@types/redux-logger": "^3.0.3",
|
"@types/redux-logger": "^3.0.3",
|
||||||
|
"@types/redux-promise-middleware": "0.0.8",
|
||||||
"@types/redux-saga": "^0.10.5",
|
"@types/redux-saga": "^0.10.5",
|
||||||
"@types/uuid": "^3.4.2",
|
"@types/uuid": "^3.4.2",
|
||||||
"@types/webpack-env": "^1.13.1",
|
"@types/webpack-env": "^1.13.1",
|
||||||
|
@ -107,8 +109,7 @@
|
||||||
"db": "nodemon ./db",
|
"db": "nodemon ./db",
|
||||||
"build": "webpack --config webpack_config/webpack.prod.js",
|
"build": "webpack --config webpack_config/webpack.prod.js",
|
||||||
"prebuild": "check-node-version --package",
|
"prebuild": "check-node-version --package",
|
||||||
"build:demo":
|
"build:demo": "BUILD_GH_PAGES=true webpack --config webpack_config/webpack.prod.js",
|
||||||
"BUILD_GH_PAGES=true webpack --config webpack_config/webpack.prod.js",
|
|
||||||
"prebuild:demo": "check-node-version --package",
|
"prebuild:demo": "check-node-version --package",
|
||||||
"test": "jest --config=jest_config/jest.config.json --coverage",
|
"test": "jest --config=jest_config/jest.config.json --coverage",
|
||||||
"pretest": "check-node-version --package",
|
"pretest": "check-node-version --package",
|
||||||
|
@ -116,14 +117,16 @@
|
||||||
"predev": "check-node-version --package",
|
"predev": "check-node-version --package",
|
||||||
"dev:https": "HTTPS=true node webpack_config/server.js",
|
"dev:https": "HTTPS=true node webpack_config/server.js",
|
||||||
"predev:https": "check-node-version --package",
|
"predev:https": "check-node-version --package",
|
||||||
"derivation-checker":
|
"derivation-checker": "webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js",
|
||||||
"webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js",
|
|
||||||
"tslint": "tslint --project . --exclude common/vendor/*",
|
"tslint": "tslint --project . --exclude common/vendor/*",
|
||||||
"postinstall": "webpack --config=./webpack_config/webpack.dll.js",
|
"postinstall": "webpack --config=./webpack_config/webpack.dll.js",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
"precommit": "lint-staged"
|
"precommit": "lint-staged"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx}": ["prettier --write --single-quote", "git add"]
|
"*.{ts,tsx}": [
|
||||||
|
"prettier --write --single-quote",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue