Normalize Swap Reducer (#443)
This commit is contained in:
parent
d3210ebc8a
commit
72e30643a9
|
@ -2,58 +2,24 @@ import * as interfaces from './actionTypes';
|
|||
import { TypeKeys } from './constants';
|
||||
|
||||
export type TChangeStepSwap = typeof changeStepSwap;
|
||||
export function changeStepSwap(
|
||||
payload: number
|
||||
): interfaces.ChangeStepSwapAction {
|
||||
export function changeStepSwap(payload: number): interfaces.ChangeStepSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_STEP,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TOriginKindSwap = typeof originKindSwap;
|
||||
export function originKindSwap(
|
||||
payload: string
|
||||
): interfaces.OriginKindSwapAction {
|
||||
export type TInitSwap = typeof initSwap;
|
||||
export function initSwap(payload: interfaces.SwapInputs): interfaces.InitSwap {
|
||||
return {
|
||||
type: TypeKeys.SWAP_ORIGIN_KIND,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TDestinationKindSwap = typeof destinationKindSwap;
|
||||
export function destinationKindSwap(
|
||||
payload: string
|
||||
): interfaces.DestinationKindSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_DESTINATION_KIND,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TOriginAmountSwap = typeof originAmountSwap;
|
||||
export function originAmountSwap(
|
||||
payload?: number | null
|
||||
): interfaces.OriginAmountSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_ORIGIN_AMOUNT,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TDestinationAmountSwap = typeof destinationAmountSwap;
|
||||
export function destinationAmountSwap(
|
||||
payload?: number | null
|
||||
): interfaces.DestinationAmountSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_DESTINATION_AMOUNT,
|
||||
type: TypeKeys.SWAP_INIT,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TLoadBityRatesSucceededSwap = typeof loadBityRatesSucceededSwap;
|
||||
export function loadBityRatesSucceededSwap(
|
||||
payload: interfaces.Pairs
|
||||
payload: interfaces.ApiResponse
|
||||
): interfaces.LoadBityRatesSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_BITY_RATES_SUCCEEDED,
|
||||
|
@ -62,9 +28,7 @@ export function loadBityRatesSucceededSwap(
|
|||
}
|
||||
|
||||
export type TDestinationAddressSwap = typeof destinationAddressSwap;
|
||||
export function destinationAddressSwap(
|
||||
payload?: string
|
||||
): interfaces.DestinationAddressSwapAction {
|
||||
export function destinationAddressSwap(payload?: string): interfaces.DestinationAddressSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_DESTINATION_ADDRESS,
|
||||
payload
|
||||
|
@ -93,9 +57,7 @@ export function stopLoadBityRatesSwap(): interfaces.StopLoadBityRatesSwapAction
|
|||
}
|
||||
|
||||
export type TOrderTimeSwap = typeof orderTimeSwap;
|
||||
export function orderTimeSwap(
|
||||
payload: number
|
||||
): interfaces.OrderSwapTimeSwapAction {
|
||||
export function orderTimeSwap(payload: number): interfaces.OrderSwapTimeSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_ORDER_TIME,
|
||||
payload
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { TypeKeys } from './constants';
|
||||
|
||||
export interface Pairs {
|
||||
ETHBTC: number;
|
||||
ETHREP: number;
|
||||
|
@ -6,29 +7,38 @@ export interface Pairs {
|
|||
BTCREP: number;
|
||||
}
|
||||
|
||||
export interface OriginKindSwapAction {
|
||||
type: TypeKeys.SWAP_ORIGIN_KIND;
|
||||
payload: string;
|
||||
export interface SwapInput {
|
||||
id: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface DestinationKindSwapAction {
|
||||
type: TypeKeys.SWAP_DESTINATION_KIND;
|
||||
payload: string;
|
||||
export interface SwapInputs {
|
||||
origin: SwapInput;
|
||||
destination: SwapInput;
|
||||
}
|
||||
|
||||
export interface OriginAmountSwapAction {
|
||||
type: TypeKeys.SWAP_ORIGIN_AMOUNT;
|
||||
payload?: number | null;
|
||||
export interface InitSwap {
|
||||
type: TypeKeys.SWAP_INIT;
|
||||
payload: SwapInputs;
|
||||
}
|
||||
|
||||
export interface DestinationAmountSwapAction {
|
||||
type: TypeKeys.SWAP_DESTINATION_AMOUNT;
|
||||
payload?: number | null;
|
||||
export interface Option {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ApiResponseObj {
|
||||
id: string;
|
||||
options: Option[];
|
||||
rate: number;
|
||||
}
|
||||
|
||||
export interface ApiResponse {
|
||||
[name: string]: ApiResponseObj;
|
||||
}
|
||||
|
||||
export interface LoadBityRatesSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_BITY_RATES_SUCCEEDED;
|
||||
payload: Pairs;
|
||||
payload: ApiResponse;
|
||||
}
|
||||
|
||||
export interface DestinationAddressSwapAction {
|
||||
|
@ -135,10 +145,7 @@ export interface StopPollBityOrderStatusAction {
|
|||
/*** Action Type Union ***/
|
||||
export type SwapAction =
|
||||
| ChangeStepSwapAction
|
||||
| OriginKindSwapAction
|
||||
| DestinationKindSwapAction
|
||||
| OriginAmountSwapAction
|
||||
| DestinationAmountSwapAction
|
||||
| InitSwap
|
||||
| LoadBityRatesSucceededSwapAction
|
||||
| DestinationAddressSwapAction
|
||||
| RestartSwapAction
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
export enum TypeKeys {
|
||||
SWAP_STEP = 'SWAP_STEP',
|
||||
SWAP_ORIGIN_KIND = 'SWAP_ORIGIN_KIND',
|
||||
SWAP_DESTINATION_KIND = 'SWAP_DESTINATION_KIND',
|
||||
SWAP_ORIGIN_AMOUNT = 'SWAP_ORIGIN_AMOUNT',
|
||||
SWAP_DESTINATION_AMOUNT = 'SWAP_DESTINATION_AMOUNT',
|
||||
SWAP_INIT = 'SWAP_INIT',
|
||||
SWAP_LOAD_BITY_RATES_SUCCEEDED = 'SWAP_LOAD_BITY_RATES_SUCCEEDED',
|
||||
SWAP_DESTINATION_ADDRESS = 'SWAP_DESTINATION_ADDRESS',
|
||||
SWAP_RESTART = 'SWAP_RESTART',
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
import bityConfig from 'config/bity';
|
||||
import { checkHttpStatus, parseJSON } from './utils';
|
||||
import bityConfig, { WhitelistedCoins } from 'config/bity';
|
||||
import { checkHttpStatus, parseJSON, filter } from './utils';
|
||||
|
||||
const isCryptoPair = (from: string, to: string, arr: WhitelistedCoins[]) => {
|
||||
return filter(from, arr) && filter(to, arr);
|
||||
};
|
||||
|
||||
export function getAllRates() {
|
||||
const mappedRates = {};
|
||||
return _getAllRates().then(bityRates => {
|
||||
bityRates.objects.forEach(each => {
|
||||
const pairName = each.pair;
|
||||
mappedRates[pairName] = parseFloat(each.rate_we_sell);
|
||||
const from = { id: pairName.substring(0, 3) };
|
||||
const to = { id: pairName.substring(3, 6) };
|
||||
// Check if rate exists= && check if the pair only crypto to crypto, not crypto to fiat, or any other combination
|
||||
if (parseFloat(each.rate_we_sell) && isCryptoPair(from.id, to.id, ['BTC', 'ETH', 'REP'])) {
|
||||
mappedRates[pairName] = {
|
||||
id: pairName,
|
||||
options: [from, to],
|
||||
rate: parseFloat(each.rate_we_sell)
|
||||
};
|
||||
}
|
||||
});
|
||||
return mappedRates;
|
||||
});
|
||||
}
|
||||
|
||||
export function postOrder(
|
||||
amount: number,
|
||||
destAddress: string,
|
||||
mode: number,
|
||||
pair: string
|
||||
) {
|
||||
export function postOrder(amount: number, destAddress: string, mode: number, pair: string) {
|
||||
return fetch(`${bityConfig.serverURL}/order`, {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
import { indexOf } from 'lodash';
|
||||
|
||||
export const filter = (i: any, arr: any[]) => {
|
||||
return -1 !== indexOf(arr, i) ? true : false;
|
||||
};
|
||||
|
||||
export function checkHttpStatus(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
|
|
|
@ -31,7 +31,6 @@ export default class SimpleButton extends Component<Props, {}> {
|
|||
|
||||
public render() {
|
||||
const { loading, disabled, loadingText, text, onClick } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
|
@ -39,13 +38,13 @@ export default class SimpleButton extends Component<Props, {}> {
|
|||
disabled={loading || disabled}
|
||||
className={this.computedClass()}
|
||||
>
|
||||
{loading
|
||||
? <div>
|
||||
<Spinner /> {loadingText || text}
|
||||
</div>
|
||||
: <div>
|
||||
{text}
|
||||
</div>}
|
||||
{loading ? (
|
||||
<div>
|
||||
<Spinner /> {loadingText || text}
|
||||
</div>
|
||||
) : (
|
||||
<div>{text}</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { BTCTxExplorer, ETHTxExplorer } from './data';
|
||||
|
||||
type SupportedDestinationKind = 'ETH' | 'BTC' | 'REP';
|
||||
export type WhitelistedCoins = 'ETH' | 'BTC' | 'REP';
|
||||
|
||||
const serverURL = 'https://bity.myetherapi.com';
|
||||
const bityURL = 'https://bity.com/api';
|
||||
|
@ -16,19 +16,13 @@ const buffers = {
|
|||
};
|
||||
|
||||
// rate must be BTC[KIND]
|
||||
export function generateKindMin(
|
||||
BTCKINDRate: number,
|
||||
kind: SupportedDestinationKind
|
||||
): number {
|
||||
export function generateKindMin(BTCKINDRate: number, kind: WhitelistedCoins): number {
|
||||
const kindMinVal = BTCKINDRate * BTCMin;
|
||||
return kindMinVal + kindMinVal * buffers[kind];
|
||||
}
|
||||
|
||||
// rate must be BTC[KIND]
|
||||
export function generateKindMax(
|
||||
BTCKINDRate: number,
|
||||
kind: SupportedDestinationKind
|
||||
): number {
|
||||
export function generateKindMax(BTCKINDRate: number, kind: WhitelistedCoins): number {
|
||||
const kindMax = BTCKINDRate * BTCMax;
|
||||
return kindMax - kindMax * buffers[kind];
|
||||
}
|
||||
|
|
|
@ -3,29 +3,25 @@ import React, { Component } from 'react';
|
|||
|
||||
interface Props {
|
||||
paymentAddress: string | null;
|
||||
amount: number | null;
|
||||
destinationAmount: number;
|
||||
}
|
||||
|
||||
export default class BitcoinQR extends Component<Props, {}> {
|
||||
public render() {
|
||||
const { paymentAddress, amount } = this.props;
|
||||
const { paymentAddress, destinationAmount } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<section className="row block swap-address text-center">
|
||||
<label> Your Address </label>
|
||||
<div className="qr-code">
|
||||
<QRCode value={`bitcoin:${paymentAddress}amount=${amount}`} />
|
||||
<QRCode value={`bitcoin:${paymentAddress}amount=${destinationAmount}`} />
|
||||
</div>
|
||||
<br />
|
||||
<p className="text-danger">
|
||||
Orders that take too long will have to be processed manually &
|
||||
and may delay the amount of time it takes to receive your coins.
|
||||
Orders that take too long will have to be processed manually & and may delay the
|
||||
amount of time it takes to receive your coins.
|
||||
<br />
|
||||
<a
|
||||
href="https://shapeshift.io/#/btcfee"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<a href="https://shapeshift.io/#/btcfee" target="_blank" rel="noopener">
|
||||
Please use the recommended TX fees seen here.
|
||||
</a>
|
||||
</p>
|
||||
|
|
|
@ -1,267 +1,218 @@
|
|||
import { TShowNotification } from 'actions/notifications';
|
||||
import {
|
||||
TChangeStepSwap,
|
||||
TDestinationAmountSwap,
|
||||
TDestinationKindSwap,
|
||||
TOriginAmountSwap,
|
||||
TOriginKindSwap
|
||||
} from 'actions/swap';
|
||||
import { TChangeStepSwap, TInitSwap } from 'actions/swap';
|
||||
import { NormalizedBityRates, NormalizedOptions, SwapInput } from 'reducers/swap/types';
|
||||
import SimpleButton from 'components/ui/SimpleButton';
|
||||
import bityConfig, { generateKindMax, generateKindMin } from 'config/bity';
|
||||
import bityConfig, { generateKindMax, generateKindMin, WhitelistedCoins } from 'config/bity';
|
||||
import React, { Component } from 'react';
|
||||
import translate from 'translations';
|
||||
import { combineAndUpper, toFixedIfLarger } from 'utils/formatters';
|
||||
import './CurrencySwap.scss';
|
||||
import { combineAndUpper } from 'utils/formatters';
|
||||
import { Dropdown } from 'components/ui';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { without, intersection } from 'lodash';
|
||||
import './CurrencySwap.scss';
|
||||
|
||||
export interface StateProps {
|
||||
bityRates: any;
|
||||
originAmount: number | null;
|
||||
destinationAmount: number | null;
|
||||
originKind: string;
|
||||
destinationKind: string;
|
||||
destinationKindOptions: string[];
|
||||
originKindOptions: string[];
|
||||
bityRates: NormalizedBityRates;
|
||||
options: NormalizedOptions;
|
||||
}
|
||||
|
||||
export interface ActionProps {
|
||||
showNotification: TShowNotification;
|
||||
changeStepSwap: TChangeStepSwap;
|
||||
originKindSwap: TOriginKindSwap;
|
||||
destinationKindSwap: TDestinationKindSwap;
|
||||
originAmountSwap: TOriginAmountSwap;
|
||||
destinationAmountSwap: TDestinationAmountSwap;
|
||||
initSwap: TInitSwap;
|
||||
}
|
||||
|
||||
interface State {
|
||||
disabled: boolean;
|
||||
showedMinMaxError: boolean;
|
||||
origin: SwapInput;
|
||||
destination: SwapInput;
|
||||
originKindOptions: WhitelistedCoins[];
|
||||
destinationKindOptions: WhitelistedCoins[];
|
||||
originErr: string;
|
||||
destinationErr: string;
|
||||
}
|
||||
|
||||
export default class CurrencySwap extends Component<
|
||||
StateProps & ActionProps,
|
||||
State
|
||||
> {
|
||||
type Props = StateProps & ActionProps;
|
||||
|
||||
export default class CurrencySwap extends Component<Props, State> {
|
||||
public state = {
|
||||
disabled: true,
|
||||
showedMinMaxError: false,
|
||||
origin: { id: 'BTC', amount: NaN } as SwapInput,
|
||||
destination: { id: 'ETH', amount: NaN } as SwapInput,
|
||||
originKindOptions: ['BTC', 'ETH'] as WhitelistedCoins[],
|
||||
destinationKindOptions: ['ETH'] as WhitelistedCoins[],
|
||||
originErr: '',
|
||||
destinationErr: ''
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(newProps) {
|
||||
const {
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmount
|
||||
} = newProps;
|
||||
if (
|
||||
originKind !== this.props.originKind ||
|
||||
destinationKind !== this.props.destinationKind
|
||||
) {
|
||||
this.setDisabled(
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmount
|
||||
public componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
const { origin, destination } = this.state;
|
||||
const { options } = this.props;
|
||||
if (origin !== prevState.origin) {
|
||||
this.setDisabled(origin, destination);
|
||||
}
|
||||
if (options.allIds !== prevProps.options.allIds) {
|
||||
const originKindOptions: WhitelistedCoins[] = intersection<any>(
|
||||
options.allIds,
|
||||
this.state.originKindOptions
|
||||
);
|
||||
const destinationKindOptions: WhitelistedCoins[] = without<any>(options.allIds, origin.id);
|
||||
this.setState({
|
||||
originKindOptions,
|
||||
destinationKindOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public isMinMaxValid = (amount, kind) => {
|
||||
let bityMin;
|
||||
let bityMax;
|
||||
public getMinMax = (kind: WhitelistedCoins) => {
|
||||
let min;
|
||||
let max;
|
||||
if (kind !== 'BTC') {
|
||||
const bityPairRate = this.props.bityRates['BTC' + kind];
|
||||
bityMin = generateKindMin(bityPairRate, kind);
|
||||
bityMax = generateKindMax(bityPairRate, kind);
|
||||
const bityPairRate = this.props.bityRates.byId['BTC' + kind].rate;
|
||||
min = generateKindMin(bityPairRate, kind);
|
||||
max = generateKindMax(bityPairRate, kind);
|
||||
} else {
|
||||
bityMin = bityConfig.BTCMin;
|
||||
bityMax = bityConfig.BTCMax;
|
||||
min = bityConfig.BTCMin;
|
||||
max = bityConfig.BTCMax;
|
||||
}
|
||||
const higherThanMin = amount >= bityMin;
|
||||
const lowerThanMax = amount <= bityMax;
|
||||
return { min, max };
|
||||
};
|
||||
|
||||
public isMinMaxValid = (amount: number, kind: WhitelistedCoins) => {
|
||||
const rate = this.getMinMax(kind);
|
||||
const higherThanMin = amount >= rate.min;
|
||||
const lowerThanMax = amount <= rate.max;
|
||||
return higherThanMin && lowerThanMax;
|
||||
};
|
||||
|
||||
public isDisabled = (originAmount, originKind, destinationAmount) => {
|
||||
const hasOriginAmountAndDestinationAmount =
|
||||
originAmount && destinationAmount;
|
||||
const minMaxIsValid = this.isMinMaxValid(originAmount, originKind);
|
||||
return !(hasOriginAmountAndDestinationAmount && minMaxIsValid);
|
||||
};
|
||||
public setDisabled(origin: SwapInput, destination: SwapInput) {
|
||||
const amountsValid = origin.amount && destination.amount;
|
||||
const minMaxValid = this.isMinMaxValid(origin.amount, origin.id);
|
||||
|
||||
public setDisabled(
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmount
|
||||
) {
|
||||
const disabled = this.isDisabled(
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationAmount
|
||||
);
|
||||
const disabled = !(amountsValid && minMaxValid);
|
||||
|
||||
if (disabled && originAmount) {
|
||||
const { bityRates } = this.props;
|
||||
const ETHMin = generateKindMin(bityRates.BTCETH, 'ETH');
|
||||
const ETHMax = generateKindMax(bityRates.BTCETH, 'ETH');
|
||||
const REPMin = generateKindMin(bityRates.BTCREP, 'REP');
|
||||
const createErrString = (kind: WhitelistedCoins, amount: number) => {
|
||||
const rate = this.getMinMax(kind);
|
||||
let errString;
|
||||
if (amount > rate.max) {
|
||||
errString = `Maximum ${rate.max} ${kind}`;
|
||||
} else {
|
||||
errString = `Minimum ${rate.min} ${kind}`;
|
||||
}
|
||||
return errString;
|
||||
};
|
||||
|
||||
const getRates = kind => {
|
||||
let minAmount;
|
||||
let maxAmount;
|
||||
switch (kind) {
|
||||
case 'BTC':
|
||||
minAmount = toFixedIfLarger(bityConfig.BTCMin, 3);
|
||||
maxAmount = toFixedIfLarger(bityConfig.BTCMax, 3);
|
||||
break;
|
||||
case 'ETH':
|
||||
minAmount = toFixedIfLarger(ETHMin, 3);
|
||||
maxAmount = toFixedIfLarger(ETHMax, 3);
|
||||
break;
|
||||
case 'REP':
|
||||
minAmount = toFixedIfLarger(REPMin, 3);
|
||||
break;
|
||||
default:
|
||||
if (this.state.showedMinMaxError) {
|
||||
this.setState(
|
||||
{
|
||||
showedMinMaxError: true
|
||||
},
|
||||
() => {
|
||||
this.props.showNotification(
|
||||
'danger',
|
||||
"Couldn't get match currency kind. Something went terribly wrong",
|
||||
10000
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return { minAmount, maxAmount };
|
||||
};
|
||||
const showError = disabled && amountsValid;
|
||||
const originErr = showError ? createErrString(origin.id, origin.amount) : '';
|
||||
const destinationErr = showError ? createErrString(destination.id, destination.amount) : '';
|
||||
|
||||
const createErrString = (kind, amount, rate) => {
|
||||
let errString;
|
||||
if (amount > rate.maxAmount) {
|
||||
errString = `Maximum ${kind} is ${rate.maxAmount} ${kind}`;
|
||||
} else {
|
||||
errString = `Minimum ${kind} is ${rate.minAmount} ${kind}`;
|
||||
}
|
||||
|
||||
return errString;
|
||||
};
|
||||
const originRate = getRates(originKind);
|
||||
const destinationRate = getRates(destinationKind);
|
||||
const originErr = createErrString(originKind, originAmount, originRate);
|
||||
const destinationErr = createErrString(
|
||||
destinationKind,
|
||||
destinationAmount,
|
||||
destinationRate
|
||||
);
|
||||
|
||||
this.setState({
|
||||
originErr,
|
||||
destinationErr,
|
||||
disabled: true
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
originErr: '',
|
||||
destinationErr: '',
|
||||
disabled
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
disabled,
|
||||
originErr,
|
||||
destinationErr
|
||||
});
|
||||
}
|
||||
|
||||
public onClickStartSwap = () => {
|
||||
this.props.changeStepSwap(2);
|
||||
const { origin, destination } = this.state;
|
||||
const { changeStepSwap, initSwap } = this.props;
|
||||
initSwap({ origin, destination });
|
||||
changeStepSwap(2);
|
||||
};
|
||||
|
||||
public setOriginAndDestinationToNull = () => {
|
||||
this.props.originAmountSwap(null);
|
||||
this.props.destinationAmountSwap(null);
|
||||
this.setDisabled(
|
||||
null,
|
||||
this.props.originKind,
|
||||
this.props.destinationKind,
|
||||
null
|
||||
);
|
||||
public setOriginAndDestinationToInitialVal = () => {
|
||||
this.setState({
|
||||
origin: { ...this.state.origin, amount: NaN },
|
||||
destination: { ...this.state.destination, amount: NaN }
|
||||
});
|
||||
};
|
||||
|
||||
public onChangeOriginAmount = (
|
||||
event: React.SyntheticEvent<HTMLInputElement>
|
||||
) => {
|
||||
const { destinationKind, originKind } = this.props;
|
||||
const amount = (event.target as HTMLInputElement).value;
|
||||
const originAmountAsNumber = parseFloat(amount);
|
||||
if (originAmountAsNumber || originAmountAsNumber === 0) {
|
||||
const pairName = combineAndUpper(originKind, destinationKind);
|
||||
const bityRate = this.props.bityRates[pairName];
|
||||
this.props.originAmountSwap(originAmountAsNumber);
|
||||
const destinationAmount = originAmountAsNumber * bityRate;
|
||||
this.props.destinationAmountSwap(destinationAmount);
|
||||
this.setDisabled(
|
||||
originAmountAsNumber,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmount
|
||||
);
|
||||
public updateOriginAmount = (origin: SwapInput, destination: SwapInput, amount: number) => {
|
||||
if (amount || amount === 0) {
|
||||
const pairName = combineAndUpper(origin.id, destination.id);
|
||||
const bityRate = this.props.bityRates.byId[pairName].rate;
|
||||
const destinationAmount = amount * bityRate;
|
||||
this.setState({
|
||||
origin: { ...this.state.origin, amount },
|
||||
destination: { ...this.state.destination, amount: destinationAmount }
|
||||
});
|
||||
} else {
|
||||
this.setOriginAndDestinationToNull();
|
||||
this.setOriginAndDestinationToInitialVal();
|
||||
}
|
||||
};
|
||||
|
||||
public onChangeDestinationAmount = (
|
||||
event: React.SyntheticEvent<HTMLInputElement>
|
||||
) => {
|
||||
const { destinationKind, originKind } = this.props;
|
||||
const amount = (event.target as HTMLInputElement).value;
|
||||
const destinationAmountAsNumber = parseFloat(amount);
|
||||
if (destinationAmountAsNumber || destinationAmountAsNumber === 0) {
|
||||
this.props.destinationAmountSwap(destinationAmountAsNumber);
|
||||
const pairNameReversed = combineAndUpper(destinationKind, originKind);
|
||||
const bityRate = this.props.bityRates[pairNameReversed];
|
||||
const originAmount = destinationAmountAsNumber * bityRate;
|
||||
this.props.originAmountSwap(originAmount);
|
||||
this.setDisabled(
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmountAsNumber
|
||||
);
|
||||
public updateDestinationAmount = (origin: SwapInput, destination: SwapInput, amount: number) => {
|
||||
if (amount || amount === 0) {
|
||||
const pairNameReversed = combineAndUpper(destination.id, origin.id);
|
||||
const bityRate = this.props.bityRates.byId[pairNameReversed].rate;
|
||||
const originAmount = amount * bityRate;
|
||||
this.setState({
|
||||
origin: { ...this.state.origin, amount: originAmount },
|
||||
destination: {
|
||||
...this.state.destination,
|
||||
amount
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setOriginAndDestinationToNull();
|
||||
this.setOriginAndDestinationToInitialVal();
|
||||
}
|
||||
};
|
||||
|
||||
public onChangeAmount = (event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const type = (event.target as HTMLInputElement).id;
|
||||
const { origin, destination } = this.state;
|
||||
const amount = parseFloat((event.target as HTMLInputElement).value);
|
||||
type === 'origin-swap-input'
|
||||
? this.updateOriginAmount(origin, destination, amount)
|
||||
: this.updateDestinationAmount(origin, destination, amount);
|
||||
};
|
||||
|
||||
public onChangeOriginKind = (newOption: WhitelistedCoins) => {
|
||||
const { origin, destination, destinationKindOptions } = this.state;
|
||||
const newDestinationAmount = () => {
|
||||
const pairName = combineAndUpper(destination.id, origin.id);
|
||||
const bityRate = this.props.bityRates.byId[pairName].rate;
|
||||
return bityRate * origin.amount;
|
||||
};
|
||||
this.setState({
|
||||
origin: { ...origin, id: newOption },
|
||||
destination: {
|
||||
id: newOption === destination.id ? origin.id : destination.id,
|
||||
amount: newDestinationAmount() ? newDestinationAmount() : destination.amount
|
||||
},
|
||||
destinationKindOptions: without([...destinationKindOptions, origin.id], newOption)
|
||||
});
|
||||
};
|
||||
|
||||
public onChangeDestinationKind = (newOption: WhitelistedCoins) => {
|
||||
const { origin, destination } = this.state;
|
||||
const newOriginAmount = () => {
|
||||
const pairName = combineAndUpper(newOption, origin.id);
|
||||
const bityRate = this.props.bityRates.byId[pairName].rate;
|
||||
return bityRate * destination.amount;
|
||||
};
|
||||
this.setState({
|
||||
origin: {
|
||||
...origin,
|
||||
amount: newOriginAmount() ? newOriginAmount() : origin.amount
|
||||
},
|
||||
destination: { ...destination, id: newOption }
|
||||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { bityRates } = this.props;
|
||||
const {
|
||||
originAmount,
|
||||
destinationAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationKindOptions,
|
||||
origin,
|
||||
destination,
|
||||
originKindOptions,
|
||||
bityRates
|
||||
} = this.props;
|
||||
destinationKindOptions,
|
||||
originErr,
|
||||
destinationErr
|
||||
} = this.state;
|
||||
|
||||
const { originErr, destinationErr } = this.state;
|
||||
|
||||
const OriginKindDropDown = Dropdown as new () => Dropdown<
|
||||
typeof originKind
|
||||
>;
|
||||
const DestinationKindDropDown = Dropdown as new () => Dropdown<
|
||||
typeof destinationKind
|
||||
>;
|
||||
const pairName = combineAndUpper(originKind, destinationKind);
|
||||
const bityLoaded = bityRates[pairName];
|
||||
const OriginKindDropDown = Dropdown as new () => Dropdown<any>;
|
||||
const DestinationKindDropDown = Dropdown as new () => Dropdown<typeof destination.id>;
|
||||
const pairName = combineAndUpper(origin.id, destination.id);
|
||||
const bityLoaded = bityRates.byId[pairName] ? bityRates.byId[pairName].id : false;
|
||||
return (
|
||||
<article className="CurrencySwap">
|
||||
<h1 className="CurrencySwap-title">{translate('SWAP_init_1')}</h1>
|
||||
|
@ -270,25 +221,23 @@ export default class CurrencySwap extends Component<
|
|||
<div className="CurrencySwap-input-group">
|
||||
<span className="CurrencySwap-error-message">{originErr}</span>
|
||||
<input
|
||||
id="origin-swap-input"
|
||||
className={`CurrencySwap-input form-control ${
|
||||
String(originAmount) !== '' &&
|
||||
this.isMinMaxValid(originAmount, originKind)
|
||||
String(origin.amount) !== '' && this.isMinMaxValid(origin.amount, origin.id)
|
||||
? 'is-valid'
|
||||
: 'is-invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
value={originAmount || originAmount === 0 ? originAmount : ''}
|
||||
onChange={this.onChangeOriginAmount}
|
||||
value={isNaN(origin.amount) ? '' : origin.amount}
|
||||
onChange={this.onChangeAmount}
|
||||
/>
|
||||
<div className="CurrencySwap-dropdown">
|
||||
<OriginKindDropDown
|
||||
ariaLabel={`change origin kind. current origin kind ${
|
||||
originKind
|
||||
}`}
|
||||
ariaLabel={`change origin kind. current origin kind ${origin.id}`}
|
||||
options={originKindOptions}
|
||||
value={originKind}
|
||||
onChange={this.props.originKindSwap}
|
||||
value={origin.id}
|
||||
onChange={this.onChangeOriginKind}
|
||||
size="smr"
|
||||
color="default"
|
||||
/>
|
||||
|
@ -296,33 +245,25 @@ export default class CurrencySwap extends Component<
|
|||
</div>
|
||||
<h1 className="CurrencySwap-divider">{translate('SWAP_init_2')}</h1>
|
||||
<div className="CurrencySwap-input-group">
|
||||
<span className="CurrencySwap-error-message">
|
||||
{destinationErr}
|
||||
</span>
|
||||
<span className="CurrencySwap-error-message">{destinationErr}</span>
|
||||
<input
|
||||
id="destination-swap-input"
|
||||
className={`CurrencySwap-input form-control ${
|
||||
String(destinationAmount) !== '' &&
|
||||
this.isMinMaxValid(originAmount, originKind)
|
||||
String(destination.amount) !== '' && this.isMinMaxValid(origin.amount, origin.id)
|
||||
? 'is-valid'
|
||||
: 'is-invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
value={
|
||||
destinationAmount || destinationAmount === 0
|
||||
? destinationAmount
|
||||
: ''
|
||||
}
|
||||
onChange={this.onChangeDestinationAmount}
|
||||
value={isNaN(destination.amount) ? '' : destination.amount}
|
||||
onChange={this.onChangeAmount}
|
||||
/>
|
||||
<div className="CurrencySwap-dropdown">
|
||||
<DestinationKindDropDown
|
||||
ariaLabel={`change destination kind. current destination kind ${
|
||||
destinationKind
|
||||
}`}
|
||||
ariaLabel={`change destination kind. current destination kind ${destination.id}`}
|
||||
options={destinationKindOptions}
|
||||
value={destinationKind}
|
||||
onChange={this.props.destinationKindSwap}
|
||||
value={destination.id}
|
||||
onChange={this.onChangeDestinationKind}
|
||||
size="smr"
|
||||
color="default"
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Pairs } from 'actions/swap';
|
||||
import { NormalizedBityRate } from 'reducers/swap/types';
|
||||
import bityLogoWhite from 'assets/images/logo-bity-white.svg';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { bityReferralURL } from 'config/data';
|
||||
|
@ -7,13 +7,18 @@ import translate from 'translations';
|
|||
import { toFixedIfLarger } from 'utils/formatters';
|
||||
import './CurrentRates.scss';
|
||||
|
||||
interface Props {
|
||||
[id: string]: NormalizedBityRate;
|
||||
}
|
||||
|
||||
interface State {
|
||||
ETHBTCAmount: number;
|
||||
ETHREPAmount: number;
|
||||
BTCETHAmount: number;
|
||||
BTCREPAmount: number;
|
||||
}
|
||||
export default class CurrentRates extends Component<Pairs, State> {
|
||||
|
||||
export default class CurrentRates extends Component<Props, State> {
|
||||
public state = {
|
||||
ETHBTCAmount: 1,
|
||||
ETHREPAmount: 1,
|
||||
|
@ -32,7 +37,7 @@ export default class CurrentRates extends Component<Pairs, State> {
|
|||
public buildPairRate = (origin: string, destination: string) => {
|
||||
const pair = origin + destination;
|
||||
const statePair = this.state[pair + 'Amount'];
|
||||
const propsPair = this.props[pair];
|
||||
const propsPair = this.props[pair] ? this.props[pair].rate : null;
|
||||
return (
|
||||
<div className="SwapRates-panel-rate">
|
||||
{propsPair ? (
|
||||
|
@ -44,9 +49,7 @@ export default class CurrentRates extends Component<Pairs, State> {
|
|||
name={pair + 'Amount'}
|
||||
/>
|
||||
<span className="SwapRates-panel-rate-amount">
|
||||
{` ${origin} = ${toFixedIfLarger(statePair * propsPair, 6)} ${
|
||||
destination
|
||||
}`}
|
||||
{` ${origin} = ${toFixedIfLarger(statePair * propsPair, 6)} ${destination}`}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -71,11 +74,7 @@ export default class CurrentRates extends Component<Pairs, State> {
|
|||
{this.buildPairRate('BTC', 'ETH')}
|
||||
{this.buildPairRate('BTC', 'REP')}
|
||||
</div>
|
||||
<a
|
||||
className="SwapRates-panel-logo"
|
||||
href={bityReferralURL}
|
||||
target="_blank"
|
||||
>
|
||||
<a className="SwapRates-panel-logo" href={bityReferralURL} target="_blank">
|
||||
<img src={bityLogoWhite} width={120} height={49} />
|
||||
</a>
|
||||
</section>
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
TStopOrderTimerSwap,
|
||||
TStopPollBityOrderStatus
|
||||
} from 'actions/swap';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import React, { Component } from 'react';
|
||||
import BitcoinQR from './BitcoinQR';
|
||||
import PaymentInfo from './PaymentInfo';
|
||||
|
@ -13,10 +14,8 @@ import SwapProgress from './SwapProgress';
|
|||
|
||||
interface ReduxStateProps {
|
||||
destinationAddress: string;
|
||||
destinationKind: string;
|
||||
originKind: string;
|
||||
originAmount: number | null;
|
||||
destinationAmount: number | null;
|
||||
origin: SwapInput;
|
||||
destination: SwapInput;
|
||||
reference: string;
|
||||
secondsRemaining: number | null;
|
||||
paymentAddress: string | null;
|
||||
|
@ -33,10 +32,7 @@ interface ReduxActionProps {
|
|||
showNotification: TShowNotification;
|
||||
}
|
||||
|
||||
export default class PartThree extends Component<
|
||||
ReduxActionProps & ReduxStateProps,
|
||||
{}
|
||||
> {
|
||||
export default class PartThree extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
||||
public componentDidMount() {
|
||||
this.props.startPollBityOrderStatus();
|
||||
this.props.startOrderTimerSwap();
|
||||
|
@ -50,21 +46,19 @@ export default class PartThree extends Component<
|
|||
public render() {
|
||||
const {
|
||||
// STATE
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
origin,
|
||||
destination,
|
||||
paymentAddress,
|
||||
orderStatus,
|
||||
destinationAddress,
|
||||
outputTx,
|
||||
destinationAmount,
|
||||
// ACTIONS
|
||||
showNotification
|
||||
} = this.props;
|
||||
|
||||
const SwapProgressProps = {
|
||||
originKind,
|
||||
destinationKind,
|
||||
originId: origin.id,
|
||||
destinationId: destination.id,
|
||||
orderStatus,
|
||||
showNotification,
|
||||
destinationAddress,
|
||||
|
@ -72,22 +66,20 @@ export default class PartThree extends Component<
|
|||
};
|
||||
|
||||
const PaymentInfoProps = {
|
||||
originKind,
|
||||
originAmount,
|
||||
origin,
|
||||
paymentAddress
|
||||
};
|
||||
|
||||
const BitcoinQRProps = {
|
||||
paymentAddress,
|
||||
amount: destinationAmount
|
||||
destinationAmount: destination.amount
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SwapProgress {...SwapProgressProps} />
|
||||
<PaymentInfo {...PaymentInfoProps} />
|
||||
{orderStatus === 'OPEN' &&
|
||||
originKind === 'BTC' && <BitcoinQR {...BitcoinQRProps} />}
|
||||
{orderStatus === 'OPEN' && origin.id === 'BTC' && <BitcoinQR {...BitcoinQRProps} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import React, { Component } from 'react';
|
||||
import translate from 'translations';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import './PaymentInfo.scss';
|
||||
|
||||
export interface Props {
|
||||
originKind: string;
|
||||
originAmount: number | null;
|
||||
origin: SwapInput;
|
||||
paymentAddress: string | null;
|
||||
}
|
||||
|
||||
export default class PaymentInfo extends Component<Props, {}> {
|
||||
public render() {
|
||||
const { origin } = this.props;
|
||||
return (
|
||||
<section className="SwapPayment">
|
||||
<h1>
|
||||
<span>{translate('SWAP_order_CTA')}</span>
|
||||
<strong>
|
||||
{' '}
|
||||
{this.props.originAmount} {this.props.originKind}
|
||||
{origin.amount} {origin.id}
|
||||
</strong>
|
||||
<span> {translate('SENDModal_Content_2')}</span>
|
||||
<input
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
TDestinationAddressSwap,
|
||||
TStopLoadBityRatesSwap
|
||||
} from 'actions/swap';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import classnames from 'classnames';
|
||||
import SimpleButton from 'components/ui/SimpleButton';
|
||||
import { donationAddressMap } from 'config/data';
|
||||
|
@ -14,10 +15,9 @@ import { combineAndUpper } from 'utils/formatters';
|
|||
import './ReceivingAddress.scss';
|
||||
|
||||
export interface StateProps {
|
||||
origin: SwapInput;
|
||||
destinationId: string;
|
||||
isPostingOrder: boolean;
|
||||
originAmount: number | null;
|
||||
originKind: string;
|
||||
destinationKind: string;
|
||||
destinationAddress: string;
|
||||
}
|
||||
|
||||
|
@ -28,33 +28,29 @@ export interface ActionProps {
|
|||
bityOrderCreateRequestedSwap: TBityOrderCreateRequestedSwap;
|
||||
}
|
||||
|
||||
export default class ReceivingAddress extends Component<
|
||||
StateProps & ActionProps,
|
||||
{}
|
||||
> {
|
||||
public onChangeDestinationAddress = (
|
||||
event: React.SyntheticEvent<HTMLInputElement>
|
||||
) => {
|
||||
export default class ReceivingAddress extends Component<StateProps & ActionProps, {}> {
|
||||
public onChangeDestinationAddress = (event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const value = (event.target as HTMLInputElement).value;
|
||||
this.props.destinationAddressSwap(value);
|
||||
};
|
||||
|
||||
public onClickPartTwoComplete = () => {
|
||||
if (!this.props.originAmount) {
|
||||
const { origin, destinationId } = this.props;
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
this.props.bityOrderCreateRequestedSwap(
|
||||
this.props.originAmount,
|
||||
origin.amount,
|
||||
this.props.destinationAddress,
|
||||
combineAndUpper(this.props.originKind, this.props.destinationKind)
|
||||
combineAndUpper(origin.id, destinationId)
|
||||
);
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { destinationKind, destinationAddress, isPostingOrder } = this.props;
|
||||
const { destinationId, destinationAddress, isPostingOrder } = this.props;
|
||||
let validAddress;
|
||||
// TODO - find better pattern here once currencies move beyond BTC, ETH, REP
|
||||
if (this.props.destinationKind === 'BTC') {
|
||||
if (destinationId === 'BTC') {
|
||||
validAddress = isValidBTCAddress(destinationAddress);
|
||||
} else {
|
||||
validAddress = isValidETHAddress(destinationAddress);
|
||||
|
@ -73,7 +69,7 @@ export default class ReceivingAddress extends Component<
|
|||
<div className="col-sm-8 col-sm-offset-2 col-xs-12">
|
||||
<label className="SwapAddress-address">
|
||||
<h4 className="SwapAddress-address-label">
|
||||
{translate('SWAP_rec_add')} ({destinationKind})
|
||||
{translate('SWAP_rec_add')} ({destinationId})
|
||||
</h4>
|
||||
|
||||
<input
|
||||
|
@ -81,7 +77,7 @@ export default class ReceivingAddress extends Component<
|
|||
type="text"
|
||||
value={destinationAddress}
|
||||
onChange={this.onChangeDestinationAddress}
|
||||
placeholder={donationAddressMap[destinationKind]}
|
||||
placeholder={donationAddressMap[destinationId]}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { RestartSwapAction } from 'actions/swap';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import React, { Component } from 'react';
|
||||
import translate from 'translations';
|
||||
import { toFixedIfLarger } from 'utils/formatters';
|
||||
|
@ -6,10 +7,8 @@ import './SwapInfoHeader.scss';
|
|||
import SwapInfoHeaderTitle from './SwapInfoHeaderTitle';
|
||||
|
||||
export interface SwapInfoHeaderProps {
|
||||
originAmount: number | null;
|
||||
originKind: string;
|
||||
destinationKind: string;
|
||||
destinationAmount: number | null;
|
||||
origin: SwapInput;
|
||||
destination: SwapInput;
|
||||
reference: string;
|
||||
secondsRemaining: number | null;
|
||||
restartSwap(): RestartSwapAction;
|
||||
|
@ -17,10 +16,11 @@ export interface SwapInfoHeaderProps {
|
|||
|
||||
export default class SwapInfoHeader extends Component<SwapInfoHeaderProps, {}> {
|
||||
public computedOriginDestinationRatio = () => {
|
||||
if (!this.props.originAmount || !this.props.destinationAmount) {
|
||||
const { origin, destination } = this.props;
|
||||
if (!origin.amount || !destination.amount) {
|
||||
return;
|
||||
}
|
||||
return this.props.destinationAmount / this.props.originAmount;
|
||||
return destination.amount / origin.amount;
|
||||
};
|
||||
|
||||
public isExpanded = () => {
|
||||
|
@ -51,14 +51,7 @@ export default class SwapInfoHeader extends Component<SwapInfoHeaderProps, {}> {
|
|||
|
||||
public render() {
|
||||
const computedOriginDestinationRatio = this.computedOriginDestinationRatio();
|
||||
const {
|
||||
reference,
|
||||
originAmount,
|
||||
destinationAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
restartSwap
|
||||
} = this.props;
|
||||
const { reference, origin, destination, restartSwap } = this.props;
|
||||
return (
|
||||
<div className="SwapInfo">
|
||||
<SwapInfoHeaderTitle restartSwap={restartSwap} />
|
||||
|
@ -66,12 +59,8 @@ export default class SwapInfoHeader extends Component<SwapInfoHeaderProps, {}> {
|
|||
{/*Amount to send*/}
|
||||
{!this.isExpanded() && (
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">{` ${originAmount} ${
|
||||
originKind
|
||||
}`}</h3>
|
||||
<p className="SwapInfo-details-block-label">
|
||||
{translate('SEND_amount')}
|
||||
</p>
|
||||
<h3 className="SwapInfo-details-block-value">{` ${origin.amount} ${origin.id}`}</h3>
|
||||
<p className="SwapInfo-details-block-label">{translate('SEND_amount')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -79,45 +68,33 @@ export default class SwapInfoHeader extends Component<SwapInfoHeaderProps, {}> {
|
|||
{this.isExpanded() && (
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">{reference}</h3>
|
||||
<p className="SwapInfo-details-block-label">
|
||||
{translate('SWAP_ref_num')}
|
||||
</p>
|
||||
<p className="SwapInfo-details-block-label">{translate('SWAP_ref_num')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/*Time remaining*/}
|
||||
{this.isExpanded() && (
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">
|
||||
{this.formattedTime()}
|
||||
</h3>
|
||||
<p className="SwapInfo-details-block-label">
|
||||
{translate('SWAP_time')}
|
||||
</p>
|
||||
<h3 className="SwapInfo-details-block-value">{this.formattedTime()}</h3>
|
||||
<p className="SwapInfo-details-block-label">{translate('SWAP_time')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/*Amount to Receive*/}
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">
|
||||
{` ${destinationAmount} ${destinationKind}`}
|
||||
{` ${destination.amount} ${destination.id}`}
|
||||
</h3>
|
||||
<p className="SwapInfo-details-block-label">
|
||||
{translate('SWAP_rec_amt')}
|
||||
</p>
|
||||
<p className="SwapInfo-details-block-label">{translate('SWAP_rec_amt')}</p>
|
||||
</div>
|
||||
|
||||
{/*Your rate*/}
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">
|
||||
{`${computedOriginDestinationRatio &&
|
||||
toFixedIfLarger(computedOriginDestinationRatio)} ${
|
||||
destinationKind
|
||||
}/${originKind}`}
|
||||
toFixedIfLarger(computedOriginDestinationRatio)} ${destination.id}/${origin.id}`}
|
||||
</h3>
|
||||
<p className="SwapInfo-details-block-label">
|
||||
{translate('SWAP_your_rate')}
|
||||
</p>
|
||||
<p className="SwapInfo-details-block-label">{translate('SWAP_your_rate')}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -5,10 +5,10 @@ import translate, { translateRaw } from 'translations';
|
|||
import './SwapProgress.scss';
|
||||
|
||||
export interface Props {
|
||||
destinationKind: string;
|
||||
destinationId: string;
|
||||
originId: string;
|
||||
destinationAddress: string;
|
||||
outputTx: string;
|
||||
originKind: string;
|
||||
orderStatus: string | null;
|
||||
// actions
|
||||
showNotification: TShowNotification;
|
||||
|
@ -28,12 +28,7 @@ export default class SwapProgress extends Component<Props, State> {
|
|||
|
||||
public showSwapNotification = () => {
|
||||
const { hasShownViewTx } = this.state;
|
||||
const {
|
||||
destinationKind,
|
||||
outputTx,
|
||||
showNotification,
|
||||
orderStatus
|
||||
} = this.props;
|
||||
const { destinationId, outputTx, showNotification, orderStatus } = this.props;
|
||||
|
||||
if (orderStatus === 'FILL') {
|
||||
if (!hasShownViewTx) {
|
||||
|
@ -41,7 +36,7 @@ export default class SwapProgress extends Component<Props, State> {
|
|||
let link;
|
||||
const notificationMessage = translateRaw('SUCCESS_3') + outputTx;
|
||||
// everything but BTC is a token
|
||||
if (destinationKind !== 'BTC') {
|
||||
if (destinationId !== 'BTC') {
|
||||
link = bityConfig.ETHTxExplorer(outputTx);
|
||||
linkElement = (
|
||||
<a href={link} target="_blank" rel="noopener">
|
||||
|
@ -97,22 +92,22 @@ export default class SwapProgress extends Component<Props, State> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { destinationKind, originKind } = this.props;
|
||||
const numberOfConfirmations = originKind === 'BTC' ? '3' : '10';
|
||||
const { originId, destinationId } = this.props;
|
||||
const numberOfConfirmations = originId === 'BTC' ? '3' : '10';
|
||||
const steps = [
|
||||
// 1
|
||||
translate('SWAP_progress_1'),
|
||||
// 2
|
||||
<span key="1">
|
||||
{translate('SWAP_progress_2')} {originKind}...
|
||||
{translate('SWAP_progress_2')} {originId}...
|
||||
</span>,
|
||||
// 3
|
||||
<span key="2">
|
||||
{originKind} {translate('SWAP_progress_3')}
|
||||
{originId} {translate('SWAP_progress_3')}
|
||||
</span>,
|
||||
// 4 TODO: Translate me
|
||||
<span key="3">
|
||||
Sending your {destinationKind}
|
||||
Sending your {destinationId}
|
||||
<br />
|
||||
<small>Waiting for {numberOfConfirmations} confirmations...</small>
|
||||
</span>,
|
||||
|
@ -128,9 +123,7 @@ export default class SwapProgress extends Component<Props, State> {
|
|||
return (
|
||||
<div key={idx} className={this.computedClass(idx + 1)}>
|
||||
<div className={`SwapProgress-item-circle position-${idx + 1}`}>
|
||||
<span className="SwapProgress-item-circle-number">
|
||||
{idx + 1}
|
||||
</span>
|
||||
<span className="SwapProgress-item-circle-number">{idx + 1}</span>
|
||||
</div>
|
||||
<p className="SwapProgress-item-text">{text}</p>
|
||||
</div>
|
||||
|
|
|
@ -1,30 +1,21 @@
|
|||
import { showNotification as dShowNotification, TShowNotification } from 'actions/notifications';
|
||||
import {
|
||||
showNotification as dShowNotification,
|
||||
TShowNotification
|
||||
} from 'actions/notifications';
|
||||
import {
|
||||
initSwap as dInitSwap,
|
||||
bityOrderCreateRequestedSwap as dBityOrderCreateRequestedSwap,
|
||||
changeStepSwap as dChangeStepSwap,
|
||||
destinationAddressSwap as dDestinationAddressSwap,
|
||||
destinationAmountSwap as dDestinationAmountSwap,
|
||||
destinationKindSwap as dDestinationKindSwap,
|
||||
loadBityRatesRequestedSwap as dLoadBityRatesRequestedSwap,
|
||||
originAmountSwap as dOriginAmountSwap,
|
||||
originKindSwap as dOriginKindSwap,
|
||||
restartSwap as dRestartSwap,
|
||||
startOrderTimerSwap as dStartOrderTimerSwap,
|
||||
startPollBityOrderStatus as dStartPollBityOrderStatus,
|
||||
stopLoadBityRatesSwap as dStopLoadBityRatesSwap,
|
||||
stopOrderTimerSwap as dStopOrderTimerSwap,
|
||||
stopPollBityOrderStatus as dStopPollBityOrderStatus,
|
||||
TInitSwap,
|
||||
TBityOrderCreateRequestedSwap,
|
||||
TChangeStepSwap,
|
||||
TDestinationAddressSwap,
|
||||
TDestinationAmountSwap,
|
||||
TDestinationKindSwap,
|
||||
TLoadBityRatesRequestedSwap,
|
||||
TOriginAmountSwap,
|
||||
TOriginKindSwap,
|
||||
TRestartSwap,
|
||||
TStartOrderTimerSwap,
|
||||
TStartPollBityOrderStatus,
|
||||
|
@ -32,6 +23,7 @@ import {
|
|||
TStopOrderTimerSwap,
|
||||
TStopPollBityOrderStatus
|
||||
} from 'actions/swap';
|
||||
import { SwapInput, NormalizedOptions, NormalizedBityRates } from 'reducers/swap/types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
@ -43,14 +35,11 @@ import SwapInfoHeader from './components/SwapInfoHeader';
|
|||
import TabSection from 'containers/TabSection';
|
||||
|
||||
interface ReduxStateProps {
|
||||
originAmount: number | null;
|
||||
destinationAmount: number | null;
|
||||
originKind: string;
|
||||
destinationKind: string;
|
||||
destinationKindOptions: string[];
|
||||
originKindOptions: string[];
|
||||
step: number;
|
||||
bityRates: any;
|
||||
origin: SwapInput;
|
||||
destination: SwapInput;
|
||||
bityRates: NormalizedBityRates;
|
||||
options: NormalizedOptions;
|
||||
bityOrder: any;
|
||||
destinationAddress: string;
|
||||
isFetchingRates: boolean | null;
|
||||
|
@ -63,10 +52,6 @@ interface ReduxStateProps {
|
|||
|
||||
interface ReduxActionProps {
|
||||
changeStepSwap: TChangeStepSwap;
|
||||
originKindSwap: TOriginKindSwap;
|
||||
destinationKindSwap: TDestinationKindSwap;
|
||||
originAmountSwap: TOriginAmountSwap;
|
||||
destinationAmountSwap: TDestinationAmountSwap;
|
||||
loadBityRatesRequestedSwap: TLoadBityRatesRequestedSwap;
|
||||
destinationAddressSwap: TDestinationAddressSwap;
|
||||
restartSwap: TRestartSwap;
|
||||
|
@ -77,11 +62,11 @@ interface ReduxActionProps {
|
|||
stopPollBityOrderStatus: TStopPollBityOrderStatus;
|
||||
showNotification: TShowNotification;
|
||||
startOrderTimerSwap: TStartOrderTimerSwap;
|
||||
initSwap: TInitSwap;
|
||||
}
|
||||
|
||||
class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
||||
public componentDidMount() {
|
||||
// TODO: Use `isFetchingRates` to show a loader
|
||||
this.props.loadBityRatesRequestedSwap();
|
||||
}
|
||||
|
||||
|
@ -93,12 +78,9 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
|||
const {
|
||||
// STATE
|
||||
bityRates,
|
||||
originAmount,
|
||||
destinationAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationKindOptions,
|
||||
originKindOptions,
|
||||
options,
|
||||
origin,
|
||||
destination,
|
||||
destinationAddress,
|
||||
step,
|
||||
bityOrder,
|
||||
|
@ -108,13 +90,10 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
|||
isPostingOrder,
|
||||
outputTx,
|
||||
// ACTIONS
|
||||
initSwap,
|
||||
restartSwap,
|
||||
stopLoadBityRatesSwap,
|
||||
changeStepSwap,
|
||||
originKindSwap,
|
||||
destinationKindSwap,
|
||||
originAmountSwap,
|
||||
destinationAmountSwap,
|
||||
destinationAddressSwap,
|
||||
bityOrderCreateRequestedSwap,
|
||||
showNotification,
|
||||
|
@ -128,9 +107,8 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
|||
|
||||
const ReceivingAddressProps = {
|
||||
isPostingOrder,
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
origin,
|
||||
destinationId: destination.id,
|
||||
destinationAddressSwap,
|
||||
destinationAddress,
|
||||
stopLoadBityRatesSwap,
|
||||
|
@ -139,38 +117,24 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
|||
};
|
||||
|
||||
const SwapInfoHeaderProps = {
|
||||
origin,
|
||||
destination,
|
||||
reference,
|
||||
secondsRemaining,
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmount,
|
||||
restartSwap,
|
||||
orderStatus
|
||||
};
|
||||
|
||||
const { ETHBTC, ETHREP, BTCETH, BTCREP } = bityRates;
|
||||
const CurrentRatesProps = { ETHBTC, ETHREP, BTCETH, BTCREP };
|
||||
|
||||
const CurrencySwapProps = {
|
||||
showNotification,
|
||||
bityRates,
|
||||
originAmount,
|
||||
destinationAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationKindOptions,
|
||||
originKindOptions,
|
||||
originKindSwap,
|
||||
destinationKindSwap,
|
||||
originAmountSwap,
|
||||
destinationAmountSwap,
|
||||
options,
|
||||
initSwap,
|
||||
changeStepSwap
|
||||
};
|
||||
|
||||
const PaymentInfoProps = {
|
||||
originKind,
|
||||
originAmount,
|
||||
origin,
|
||||
paymentAddress
|
||||
};
|
||||
|
||||
|
@ -187,13 +151,14 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
|||
outputTx
|
||||
};
|
||||
|
||||
const { ETHBTC, ETHREP, BTCETH, BTCREP } = bityRates.byId;
|
||||
const CurrentRatesProps = { ETHBTC, ETHREP, BTCETH, BTCREP };
|
||||
|
||||
return (
|
||||
<TabSection>
|
||||
<section className="Tab-content swap-tab">
|
||||
{step === 1 && <CurrentRates {...CurrentRatesProps} />}
|
||||
{(step === 2 || step === 3) && (
|
||||
<SwapInfoHeader {...SwapInfoHeaderProps} />
|
||||
)}
|
||||
{(step === 2 || step === 3) && <SwapInfoHeader {...SwapInfoHeaderProps} />}
|
||||
|
||||
<main className="Tab-content-pane">
|
||||
{step === 1 && <CurrencySwap {...CurrencySwapProps} />}
|
||||
|
@ -208,14 +173,11 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps, {}> {
|
|||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
originAmount: state.swap.originAmount,
|
||||
destinationAmount: state.swap.destinationAmount,
|
||||
originKind: state.swap.originKind,
|
||||
destinationKind: state.swap.destinationKind,
|
||||
destinationKindOptions: state.swap.destinationKindOptions,
|
||||
originKindOptions: state.swap.originKindOptions,
|
||||
step: state.swap.step,
|
||||
origin: state.swap.origin,
|
||||
destination: state.swap.destination,
|
||||
bityRates: state.swap.bityRates,
|
||||
options: state.swap.options,
|
||||
bityOrder: state.swap.bityOrder,
|
||||
destinationAddress: state.swap.destinationAddress,
|
||||
isFetchingRates: state.swap.isFetchingRates,
|
||||
|
@ -228,14 +190,11 @@ function mapStateToProps(state: AppState) {
|
|||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
bityOrderCreateRequestedSwap: dBityOrderCreateRequestedSwap,
|
||||
changeStepSwap: dChangeStepSwap,
|
||||
destinationAddressSwap: dDestinationAddressSwap,
|
||||
destinationAmountSwap: dDestinationAmountSwap,
|
||||
destinationKindSwap: dDestinationKindSwap,
|
||||
initSwap: dInitSwap,
|
||||
bityOrderCreateRequestedSwap: dBityOrderCreateRequestedSwap,
|
||||
loadBityRatesRequestedSwap: dLoadBityRatesRequestedSwap,
|
||||
originAmountSwap: dOriginAmountSwap,
|
||||
originKindSwap: dOriginKindSwap,
|
||||
destinationAddressSwap: dDestinationAddressSwap,
|
||||
restartSwap: dRestartSwap,
|
||||
startOrderTimerSwap: dStartOrderTimerSwap,
|
||||
startPollBityOrderStatus: dStartPollBityOrderStatus,
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import without from 'lodash/without';
|
||||
import { combineAndUpper } from 'utils/formatters';
|
||||
import { ALL_CRYPTO_KIND_OPTIONS } from '.';
|
||||
|
||||
export const buildDestinationAmount = (
|
||||
originAmount,
|
||||
originKind,
|
||||
destinationKind,
|
||||
bityRates
|
||||
) => {
|
||||
const pairName = combineAndUpper(originKind, destinationKind);
|
||||
const bityRate = bityRates[pairName];
|
||||
return originAmount !== null ? originAmount * bityRate : null;
|
||||
};
|
||||
|
||||
export const buildDestinationKind = (
|
||||
originKind: string,
|
||||
destinationKind: string
|
||||
): string => {
|
||||
if (originKind === destinationKind) {
|
||||
return without(ALL_CRYPTO_KIND_OPTIONS, originKind)[0];
|
||||
} else {
|
||||
return destinationKind;
|
||||
}
|
||||
};
|
||||
|
||||
export const buildOriginKind = (
|
||||
originKind: string,
|
||||
destinationKind: string
|
||||
): string => {
|
||||
if (originKind === destinationKind) {
|
||||
return without(ALL_CRYPTO_KIND_OPTIONS, destinationKind)[0];
|
||||
} else {
|
||||
return originKind;
|
||||
}
|
||||
};
|
|
@ -1,24 +1,15 @@
|
|||
import * as actionTypes from 'actions/swap';
|
||||
import * as stateTypes from './types';
|
||||
import * as schema from './schema';
|
||||
import { TypeKeys } from 'actions/swap/constants';
|
||||
import without from 'lodash/without';
|
||||
import {
|
||||
buildDestinationAmount,
|
||||
buildDestinationKind,
|
||||
buildOriginKind
|
||||
} from './helpers';
|
||||
export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP'];
|
||||
const DEFAULT_ORIGIN_KIND = 'BTC';
|
||||
const DEFAULT_DESTINATION_KIND = 'ETH';
|
||||
import { normalize } from 'normalizr';
|
||||
|
||||
export interface State {
|
||||
originAmount: number | null;
|
||||
destinationAmount: number | null;
|
||||
originKind: string;
|
||||
destinationKind: string;
|
||||
destinationKindOptions: string[];
|
||||
originKindOptions: string[];
|
||||
step: number;
|
||||
bityRates: any;
|
||||
origin: stateTypes.SwapInput;
|
||||
destination: stateTypes.SwapInput;
|
||||
options: stateTypes.NormalizedOptions;
|
||||
bityRates: stateTypes.NormalizedBityRates;
|
||||
bityOrder: any;
|
||||
destinationAddress: string;
|
||||
isFetchingRates: boolean | null;
|
||||
|
@ -33,14 +24,17 @@ export interface State {
|
|||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
originAmount: null,
|
||||
destinationAmount: null,
|
||||
originKind: DEFAULT_ORIGIN_KIND,
|
||||
destinationKind: DEFAULT_DESTINATION_KIND,
|
||||
destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, DEFAULT_ORIGIN_KIND),
|
||||
originKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, 'REP'),
|
||||
step: 1,
|
||||
bityRates: {},
|
||||
origin: { id: 'BTC', amount: NaN },
|
||||
destination: { id: 'ETH', amount: NaN },
|
||||
options: {
|
||||
byId: {},
|
||||
allIds: []
|
||||
},
|
||||
bityRates: {
|
||||
byId: {},
|
||||
allIds: []
|
||||
},
|
||||
destinationAddress: '',
|
||||
bityOrder: {},
|
||||
isFetchingRates: null,
|
||||
|
@ -54,76 +48,29 @@ export const INITIAL_STATE: State = {
|
|||
orderId: null
|
||||
};
|
||||
|
||||
function handleSwapOriginKind(
|
||||
state: State,
|
||||
action: actionTypes.OriginKindSwapAction
|
||||
) {
|
||||
const newDestinationKind = buildDestinationKind(
|
||||
action.payload,
|
||||
state.destinationKind
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
originKind: action.payload,
|
||||
destinationKind: newDestinationKind,
|
||||
destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, action.payload),
|
||||
destinationAmount: buildDestinationAmount(
|
||||
state.originAmount,
|
||||
action.payload,
|
||||
newDestinationKind,
|
||||
state.bityRates
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function handleSwapDestinationKind(
|
||||
state: State,
|
||||
action: actionTypes.DestinationKindSwapAction
|
||||
) {
|
||||
const newOriginKind = buildOriginKind(state.originKind, action.payload);
|
||||
return {
|
||||
...state,
|
||||
originKind: newOriginKind,
|
||||
destinationKind: action.payload,
|
||||
destinationAmount: buildDestinationAmount(
|
||||
state.originAmount,
|
||||
state.originKind,
|
||||
action.payload,
|
||||
state.bityRates
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
export function swap(
|
||||
state: State = INITIAL_STATE,
|
||||
action: actionTypes.SwapAction
|
||||
) {
|
||||
export function swap(state: State = INITIAL_STATE, action: actionTypes.SwapAction) {
|
||||
switch (action.type) {
|
||||
case TypeKeys.SWAP_ORIGIN_KIND: {
|
||||
return handleSwapOriginKind(state, action);
|
||||
}
|
||||
case TypeKeys.SWAP_DESTINATION_KIND: {
|
||||
return handleSwapDestinationKind(state, action);
|
||||
}
|
||||
case TypeKeys.SWAP_ORIGIN_AMOUNT:
|
||||
return {
|
||||
...state,
|
||||
originAmount: action.payload
|
||||
};
|
||||
case TypeKeys.SWAP_DESTINATION_AMOUNT:
|
||||
return {
|
||||
...state,
|
||||
destinationAmount: action.payload
|
||||
};
|
||||
case TypeKeys.SWAP_LOAD_BITY_RATES_SUCCEEDED:
|
||||
const { payload } = action;
|
||||
return {
|
||||
...state,
|
||||
bityRates: {
|
||||
...state.bityRates,
|
||||
...action.payload
|
||||
byId: normalize(payload, [schema.bityRate]).entities.bityRates,
|
||||
allIds: schema.allIds(normalize(payload, [schema.bityRate]).entities.bityRates)
|
||||
},
|
||||
options: {
|
||||
byId: normalize(payload, [schema.bityRate]).entities.options,
|
||||
allIds: schema.allIds(normalize(payload, [schema.bityRate]).entities.options)
|
||||
},
|
||||
isFetchingRates: false
|
||||
};
|
||||
case TypeKeys.SWAP_INIT: {
|
||||
return {
|
||||
...state,
|
||||
origin: action.payload.origin,
|
||||
destination: action.payload.destination
|
||||
};
|
||||
}
|
||||
case TypeKeys.SWAP_STEP: {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { schema } from 'normalizr';
|
||||
|
||||
export const allIds = (byIds: { [name: string]: {} }) => {
|
||||
return Object.keys(byIds);
|
||||
};
|
||||
export const option = new schema.Entity('options');
|
||||
export const bityRate = new schema.Entity('bityRates', {
|
||||
options: [option]
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import { Option } from 'actions/swap/actionTypes';
|
||||
import { WhitelistedCoins } from 'config/bity';
|
||||
|
||||
export interface SwapInput {
|
||||
id: WhitelistedCoins;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface NormalizedBityRate {
|
||||
id: number;
|
||||
options: WhitelistedCoins[];
|
||||
rate: number;
|
||||
}
|
||||
|
||||
export interface NormalizedBityRates {
|
||||
byId: { [id: string]: NormalizedBityRate };
|
||||
allIds: string[];
|
||||
}
|
||||
|
||||
export interface NormalizedOptions {
|
||||
byId: { [id: string]: Option };
|
||||
allIds: string[];
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
import throttle from 'lodash/throttle';
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
import { INITIAL_STATE as configInitialState } from 'reducers/config';
|
||||
import { INITIAL_STATE as customTokensInitialState } from 'reducers/customTokens';
|
||||
import { INITIAL_STATE as swapInitialState } from 'reducers/swap';
|
||||
import { State as ConfigState, INITIAL_STATE as configInitialState } from 'reducers/config';
|
||||
import {
|
||||
State as CustomTokenState,
|
||||
INITIAL_STATE as customTokensInitialState
|
||||
} from 'reducers/customTokens';
|
||||
import { State as SwapState, INITIAL_STATE as swapInitialState } from 'reducers/swap';
|
||||
import { applyMiddleware, createStore } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage';
|
||||
import RootReducer from './reducers';
|
||||
import { State as ConfigState } from './reducers/config';
|
||||
import { State as CustomTokenState } from './reducers/customTokens';
|
||||
import { State as SwapState } from './reducers/swap';
|
||||
import promiseMiddleware from 'redux-promise-middleware';
|
||||
import { getNodeConfigFromId } from 'utils/node';
|
||||
|
||||
|
@ -54,13 +54,9 @@ const configureStore = () => {
|
|||
}
|
||||
: { ...swapInitialState };
|
||||
|
||||
const localCustomTokens = loadStatePropertyOrEmptyObject<CustomTokenState>(
|
||||
'customTokens'
|
||||
);
|
||||
const localCustomTokens = loadStatePropertyOrEmptyObject<CustomTokenState>('customTokens');
|
||||
|
||||
const savedConfigState = loadStatePropertyOrEmptyObject<ConfigState>(
|
||||
'config'
|
||||
);
|
||||
const savedConfigState = loadStatePropertyOrEmptyObject<ConfigState>('config');
|
||||
|
||||
// If they have a saved node, make sure we assign that too. The node selected
|
||||
// isn't serializable, so we have to assign it here.
|
||||
|
@ -90,8 +86,7 @@ const configureStore = () => {
|
|||
// if 'web3' has persisted as node selection, reset to app default
|
||||
// necessary because web3 is only initialized as a node upon MetaMask / Mist unlock
|
||||
if (persistedInitialState.config.nodeSelection === 'web3') {
|
||||
persistedInitialState.config.nodeSelection =
|
||||
configInitialState.nodeSelection;
|
||||
persistedInitialState.config.nodeSelection = configInitialState.nodeSelection;
|
||||
}
|
||||
|
||||
store = createStore(RootReducer, persistedInitialState, middleware);
|
||||
|
@ -112,7 +107,11 @@ const configureStore = () => {
|
|||
customNodes: state.config.customNodes,
|
||||
customNetworks: state.config.customNetworks
|
||||
},
|
||||
swap: { ...state.swap, bityRates: {} },
|
||||
swap: {
|
||||
...state.swap,
|
||||
options: {},
|
||||
bityRates: {}
|
||||
},
|
||||
customTokens: state.customTokens
|
||||
});
|
||||
}),
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"jsonschema": "1.2.0",
|
||||
"lodash": "4.17.4",
|
||||
"moment": "2.19.3",
|
||||
"normalizr": "3.2.4",
|
||||
"qrcode": "1.0.0",
|
||||
"qrcode.react": "0.7.2",
|
||||
"query-string": "5.0.1",
|
||||
|
@ -113,7 +114,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"freezer": "webpack --config=./webpack_config/webpack.freezer.js && node ./dist/freezer.js",
|
||||
"freezer:validate": "npm run freezer -- --validate",
|
||||
"freezer:validate": "npm run freezer -- --validate",
|
||||
"db": "nodemon ./db",
|
||||
"build": "webpack --config webpack_config/webpack.prod.js",
|
||||
"prebuild": "check-node-version --package",
|
||||
|
|
|
@ -4,31 +4,12 @@ import Adapter from 'enzyme-adapter-react-16';
|
|||
import Swap from 'containers/Tabs/Swap';
|
||||
import shallowWithStore from '../utils/shallowWithStore';
|
||||
import { createMockStore } from 'redux-test-utils';
|
||||
import { INITIAL_STATE } from 'reducers/swap';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
it('render snapshot', () => {
|
||||
const testState = {
|
||||
swap: {
|
||||
originAmount: {},
|
||||
destinationAmount: {},
|
||||
originKind: {},
|
||||
destinationKind: {},
|
||||
destinationKindOptions: {},
|
||||
originKindOptions: {},
|
||||
step: {},
|
||||
bityRates: {},
|
||||
bityOrder: {},
|
||||
destinationAddress: {},
|
||||
isFetchingRates: {},
|
||||
secondsRemaining: {},
|
||||
outputTx: {},
|
||||
isPostingOrder: {},
|
||||
orderStatus: {},
|
||||
paymentAddress: {}
|
||||
}
|
||||
};
|
||||
const store = createMockStore(testState);
|
||||
const store = createMockStore({ swap: INITIAL_STATE });
|
||||
const component = shallowWithStore(<Swap />, store);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
|
|
|
@ -4,32 +4,46 @@ exports[`render snapshot 1`] = `
|
|||
<Swap
|
||||
bityOrder={Object {}}
|
||||
bityOrderCreateRequestedSwap={[Function]}
|
||||
bityRates={Object {}}
|
||||
bityRates={
|
||||
Object {
|
||||
"allIds": Array [],
|
||||
"byId": Object {},
|
||||
}
|
||||
}
|
||||
changeStepSwap={[Function]}
|
||||
destinationAddress={Object {}}
|
||||
destination={
|
||||
Object {
|
||||
"amount": NaN,
|
||||
"id": "ETH",
|
||||
}
|
||||
}
|
||||
destinationAddress=""
|
||||
destinationAddressSwap={[Function]}
|
||||
destinationAmount={Object {}}
|
||||
destinationAmountSwap={[Function]}
|
||||
destinationKind={Object {}}
|
||||
destinationKindOptions={Object {}}
|
||||
destinationKindSwap={[Function]}
|
||||
isFetchingRates={Object {}}
|
||||
isPostingOrder={Object {}}
|
||||
initSwap={[Function]}
|
||||
isFetchingRates={null}
|
||||
isPostingOrder={false}
|
||||
loadBityRatesRequestedSwap={[Function]}
|
||||
orderStatus={Object {}}
|
||||
originAmount={Object {}}
|
||||
originAmountSwap={[Function]}
|
||||
originKind={Object {}}
|
||||
originKindOptions={Object {}}
|
||||
originKindSwap={[Function]}
|
||||
outputTx={Object {}}
|
||||
paymentAddress={Object {}}
|
||||
options={
|
||||
Object {
|
||||
"allIds": Array [],
|
||||
"byId": Object {},
|
||||
}
|
||||
}
|
||||
orderStatus={null}
|
||||
origin={
|
||||
Object {
|
||||
"amount": NaN,
|
||||
"id": "BTC",
|
||||
}
|
||||
}
|
||||
outputTx={null}
|
||||
paymentAddress={null}
|
||||
restartSwap={[Function]}
|
||||
secondsRemaining={Object {}}
|
||||
secondsRemaining={null}
|
||||
showNotification={[Function]}
|
||||
startOrderTimerSwap={[Function]}
|
||||
startPollBityOrderStatus={[Function]}
|
||||
step={Object {}}
|
||||
step={1}
|
||||
stopLoadBityRatesSwap={[Function]}
|
||||
stopOrderTimerSwap={[Function]}
|
||||
stopPollBityOrderStatus={[Function]}
|
||||
|
|
|
@ -1,95 +1,36 @@
|
|||
import { swap, INITIAL_STATE, ALL_CRYPTO_KIND_OPTIONS } from 'reducers/swap';
|
||||
import {
|
||||
buildDestinationAmount,
|
||||
buildDestinationKind,
|
||||
buildOriginKind
|
||||
} from 'reducers/swap/helpers';
|
||||
import { swap, INITIAL_STATE } from 'reducers/swap';
|
||||
import * as swapActions from 'actions/swap';
|
||||
import without from 'lodash/without';
|
||||
import { NormalizedBityRates, NormalizedOptions } from 'reducers/swap/types';
|
||||
import { normalize } from 'normalizr';
|
||||
import * as schema from 'reducers/swap/schema';
|
||||
|
||||
describe('swap reducer', () => {
|
||||
it('should handle SWAP_ORIGIN_KIND', () => {
|
||||
const newOriginKind = 'ETH';
|
||||
const newDestinationKind = buildDestinationKind(
|
||||
newOriginKind,
|
||||
INITIAL_STATE.destinationKind
|
||||
);
|
||||
const fakeBityRates = {
|
||||
BTCETH: 10,
|
||||
ETHBTC: 0.01
|
||||
};
|
||||
expect(swap(undefined, swapActions.originKindSwap(newOriginKind))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
originKind: newOriginKind,
|
||||
destinationKind: newDestinationKind,
|
||||
destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, newOriginKind),
|
||||
destinationAmount: buildDestinationAmount(
|
||||
INITIAL_STATE.originAmount,
|
||||
newOriginKind,
|
||||
newDestinationKind,
|
||||
fakeBityRates
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle SWAP_DESTINATION_KIND', () => {
|
||||
const newDestinationKind = 'REP';
|
||||
const newOriginKind = buildOriginKind(
|
||||
INITIAL_STATE.originKind,
|
||||
newDestinationKind
|
||||
);
|
||||
const fakeBityRates = {
|
||||
BTCETH: 10,
|
||||
ETHBTC: 0.01
|
||||
};
|
||||
expect(
|
||||
swap(undefined, swapActions.destinationKindSwap(newDestinationKind))
|
||||
).toEqual({
|
||||
...INITIAL_STATE,
|
||||
destinationKind: newDestinationKind,
|
||||
destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, newOriginKind),
|
||||
destinationAmount: buildDestinationAmount(
|
||||
INITIAL_STATE.originAmount,
|
||||
newOriginKind,
|
||||
newDestinationKind,
|
||||
fakeBityRates
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle SWAP_ORIGIN_AMOUNT', () => {
|
||||
const originAmount = 2;
|
||||
expect(swap(undefined, swapActions.originAmountSwap(originAmount))).toEqual(
|
||||
{
|
||||
...INITIAL_STATE,
|
||||
originAmount
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle SWAP_DESTINATION_AMOUNT', () => {
|
||||
const destinationAmount = 2;
|
||||
expect(
|
||||
swap(undefined, swapActions.destinationAmountSwap(destinationAmount))
|
||||
).toEqual({
|
||||
...INITIAL_STATE,
|
||||
destinationAmount
|
||||
});
|
||||
});
|
||||
|
||||
const apiResponse = {
|
||||
BTCETH: {
|
||||
id: 'BTCETH',
|
||||
options: [{ id: 'BTC' }, { id: 'ETH' }],
|
||||
rate: 23.27855114
|
||||
},
|
||||
ETHBTC: {
|
||||
id: 'ETHBTC',
|
||||
options: [{ id: 'ETH' }, { id: 'BTC' }],
|
||||
rate: 0.042958
|
||||
}
|
||||
};
|
||||
const normalizedbityRates: NormalizedBityRates = {
|
||||
byId: normalize(apiResponse, [schema.bityRate]).entities.bityRates,
|
||||
allIds: schema.allIds(normalize(apiResponse, [schema.bityRate]).entities.bityRates)
|
||||
};
|
||||
const normalizedOptions: NormalizedOptions = {
|
||||
byId: normalize(apiResponse, [schema.bityRate]).entities.options,
|
||||
allIds: schema.allIds(normalize(apiResponse, [schema.bityRate]).entities.options)
|
||||
};
|
||||
it('should handle SWAP_LOAD_BITY_RATES_SUCCEEDED', () => {
|
||||
const bityRates = {
|
||||
BTCETH: 0.01,
|
||||
ETHREP: 10,
|
||||
ETHBTC: 0,
|
||||
BTCREP: 0
|
||||
};
|
||||
expect(
|
||||
swap(undefined, swapActions.loadBityRatesSucceededSwap(bityRates))
|
||||
).toEqual({
|
||||
expect(swap(undefined, swapActions.loadBityRatesSucceededSwap(apiResponse))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
isFetchingRates: false,
|
||||
bityRates
|
||||
bityRates: normalizedbityRates,
|
||||
options: normalizedOptions
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -103,31 +44,26 @@ describe('swap reducer', () => {
|
|||
|
||||
it('should handle SWAP_DESTINATION_ADDRESS', () => {
|
||||
const destinationAddress = '341a0sdf83';
|
||||
expect(
|
||||
swap(undefined, swapActions.destinationAddressSwap(destinationAddress))
|
||||
).toEqual({
|
||||
expect(swap(undefined, swapActions.destinationAddressSwap(destinationAddress))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
destinationAddress
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle SWAP_RESTART', () => {
|
||||
const bityRates = {
|
||||
BTCETH: 0.01,
|
||||
ETHREP: 10
|
||||
};
|
||||
expect(
|
||||
swap(
|
||||
{
|
||||
...INITIAL_STATE,
|
||||
bityRates,
|
||||
originAmount: 1
|
||||
bityRates: normalizedbityRates,
|
||||
origin: { id: 'BTC', amount: 1 },
|
||||
destination: { id: 'ETH', amount: 3 }
|
||||
},
|
||||
swapActions.restartSwap()
|
||||
)
|
||||
).toEqual({
|
||||
...INITIAL_STATE,
|
||||
bityRates
|
||||
bityRates: normalizedbityRates
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -174,9 +110,7 @@ describe('swap reducer', () => {
|
|||
id: 'id'
|
||||
};
|
||||
|
||||
expect(
|
||||
swap(undefined, swapActions.bityOrderCreateSucceededSwap(mockedBityOrder))
|
||||
).toEqual({
|
||||
expect(swap(undefined, swapActions.bityOrderCreateSucceededSwap(mockedBityOrder))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
bityOrder: {
|
||||
...mockedBityOrder
|
||||
|
@ -210,9 +144,7 @@ describe('swap reducer', () => {
|
|||
status: 'status'
|
||||
};
|
||||
|
||||
expect(
|
||||
swap(undefined, swapActions.orderStatusSucceededSwap(mockedBityResponse))
|
||||
).toEqual({
|
||||
expect(swap(undefined, swapActions.orderStatusSucceededSwap(mockedBityResponse))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
outputTx: mockedBityResponse.output.reference,
|
||||
orderStatus: mockedBityResponse.output.status
|
||||
|
@ -221,9 +153,7 @@ describe('swap reducer', () => {
|
|||
|
||||
it('should handle SWAP_ORDER_TIME', () => {
|
||||
const secondsRemaining = 300;
|
||||
expect(
|
||||
swap(undefined, swapActions.orderTimeSwap(secondsRemaining))
|
||||
).toEqual({
|
||||
expect(swap(undefined, swapActions.orderTimeSwap(secondsRemaining))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
secondsRemaining
|
||||
});
|
||||
|
|
|
@ -4,21 +4,22 @@ import { getAllRates } from 'api/bity';
|
|||
import { delay } from 'redux-saga';
|
||||
import { call, cancel, fork, put, take, takeLatest } from 'redux-saga/effects';
|
||||
import { createMockTask } from 'redux-saga/utils';
|
||||
import { Pairs } from 'actions/swap/actionTypes';
|
||||
import {
|
||||
loadBityRates,
|
||||
handleBityRates,
|
||||
getBityRatesSaga
|
||||
} from 'sagas/swap/rates';
|
||||
import { loadBityRates, handleBityRates, getBityRatesSaga } from 'sagas/swap/rates';
|
||||
|
||||
describe('loadBityRates*', () => {
|
||||
const gen1 = loadBityRates();
|
||||
const gen2 = loadBityRates();
|
||||
const rates: Pairs = {
|
||||
ETHBTC: 1,
|
||||
ETHREP: 2,
|
||||
BTCETH: 3,
|
||||
BTCREP: 4
|
||||
const apiResponse = {
|
||||
BTCETH: {
|
||||
id: 'BTCETH',
|
||||
options: [{ id: 'BTC' }, { id: 'ETH' }],
|
||||
rate: 23.27855114
|
||||
},
|
||||
ETHBTC: {
|
||||
id: 'ETHBTC',
|
||||
options: [{ id: 'ETH' }, { id: 'BTC' }],
|
||||
rate: 0.042958
|
||||
}
|
||||
};
|
||||
let random;
|
||||
|
||||
|
@ -36,9 +37,7 @@ describe('loadBityRates*', () => {
|
|||
});
|
||||
|
||||
it('should put loadBityRatesSucceededSwap', () => {
|
||||
expect(gen1.next(rates).value).toEqual(
|
||||
put(loadBityRatesSucceededSwap(rates))
|
||||
);
|
||||
expect(gen1.next(apiResponse).value).toEqual(put(loadBityRatesSucceededSwap(apiResponse)));
|
||||
});
|
||||
|
||||
it('should call delay for 5 seconds', () => {
|
||||
|
@ -48,9 +47,7 @@ describe('loadBityRates*', () => {
|
|||
it('should handle an exception', () => {
|
||||
const err = { message: 'error' };
|
||||
gen2.next();
|
||||
expect((gen2 as any).throw(err).value).toEqual(
|
||||
put(showNotification('danger', err.message))
|
||||
);
|
||||
expect((gen2 as any).throw(err).value).toEqual(put(showNotification('danger', err.message)));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -79,8 +76,6 @@ describe('getBityRatesSaga*', () => {
|
|||
const gen = getBityRatesSaga();
|
||||
|
||||
it('should takeLatest SWAP_LOAD_RATES_REQUESTED', () => {
|
||||
expect(gen.next().value).toEqual(
|
||||
takeLatest('SWAP_LOAD_BITY_RATES_REQUESTED', handleBityRates)
|
||||
);
|
||||
expect(gen.next().value).toEqual(takeLatest('SWAP_LOAD_BITY_RATES_REQUESTED', handleBityRates));
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue