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