Swap Part 4 (#101)
### Re-implements: * min/max validators on initial currency swap selection * polling of order status * timer that persists across refreshes via localStorage (computed based on `createdTime` and `validFor` amount) * swap persists across refreshes once order is created. * various type refactors ### New additions: * *SimpleButton* (can be PRd separately on request) * clear loading state after order create (via SimpleButton and font-awesome) * buffers for non-BTC swaps (bity does not actually accept 0.01 BTC worth of ETH as they claim they do in their JSON response, so a magic number of 10% is added to the minimum).
This commit is contained in:
parent
e3505fd958
commit
a66337ac0a
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
/*** Shared types ***/
|
/*** Shared types ***/
|
||||||
export type NOTIFICATION_LEVEL = 'danger' | 'warning' | 'success' | 'info';
|
export type NOTIFICATION_LEVEL = 'danger' | 'warning' | 'success' | 'info';
|
||||||
|
export type INFINITY = 'infinity';
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
level: NOTIFICATION_LEVEL,
|
level: NOTIFICATION_LEVEL,
|
||||||
msg: string,
|
msg: string,
|
||||||
duration?: number
|
duration?: number | INFINITY
|
||||||
};
|
};
|
||||||
|
|
||||||
/*** Show Notification ***/
|
/*** Show Notification ***/
|
||||||
|
|
|
@ -1,10 +1,27 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type {
|
||||||
/*** Change Step ***/
|
OriginKindSwapAction,
|
||||||
export type ChangeStepSwapAction = {
|
DestinationKindSwapAction,
|
||||||
type: 'SWAP_STEP',
|
OriginAmountSwapAction,
|
||||||
value: number
|
DestinationAmountSwapAction,
|
||||||
};
|
LoadBityRatesSucceededSwapAction,
|
||||||
|
DestinationAddressSwapAction,
|
||||||
|
BityOrderCreateSucceededSwapAction,
|
||||||
|
BityOrderCreateRequestedSwapAction,
|
||||||
|
OrderStatusSucceededSwapAction,
|
||||||
|
ChangeStepSwapAction,
|
||||||
|
Pairs,
|
||||||
|
RestartSwapAction,
|
||||||
|
LoadBityRatesRequestedSwapAction,
|
||||||
|
StopLoadBityRatesSwapAction,
|
||||||
|
BityOrderResponse,
|
||||||
|
BityOrderPostResponse,
|
||||||
|
OrderStatusRequestedSwapAction,
|
||||||
|
StopOrderTimerSwapAction,
|
||||||
|
StartOrderTimerSwapAction,
|
||||||
|
StartPollBityOrderStatusAction,
|
||||||
|
StopPollBityOrderStatusAction
|
||||||
|
} from './swapTypes';
|
||||||
|
|
||||||
export function changeStepSwap(value: number): ChangeStepSwapAction {
|
export function changeStepSwap(value: number): ChangeStepSwapAction {
|
||||||
return {
|
return {
|
||||||
|
@ -13,25 +30,6 @@ export function changeStepSwap(value: number): ChangeStepSwapAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Change Reference Number ***/
|
|
||||||
export type ReferenceNumberSwapAction = {
|
|
||||||
type: 'SWAP_REFERENCE_NUMBER',
|
|
||||||
value: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export function referenceNumberSwap(value: string): ReferenceNumberSwapAction {
|
|
||||||
return {
|
|
||||||
type: 'SWAP_REFERENCE_NUMBER',
|
|
||||||
value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Change Origin Kind ***/
|
|
||||||
export type OriginKindSwapAction = {
|
|
||||||
type: 'SWAP_ORIGIN_KIND',
|
|
||||||
value: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export function originKindSwap(value: string): OriginKindSwapAction {
|
export function originKindSwap(value: string): OriginKindSwapAction {
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_ORIGIN_KIND',
|
type: 'SWAP_ORIGIN_KIND',
|
||||||
|
@ -39,12 +37,6 @@ export function originKindSwap(value: string): OriginKindSwapAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Change Destination Kind ***/
|
|
||||||
export type DestinationKindSwapAction = {
|
|
||||||
type: 'SWAP_DESTINATION_KIND',
|
|
||||||
value: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export function destinationKindSwap(value: string): DestinationKindSwapAction {
|
export function destinationKindSwap(value: string): DestinationKindSwapAction {
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_DESTINATION_KIND',
|
type: 'SWAP_DESTINATION_KIND',
|
||||||
|
@ -52,12 +44,6 @@ export function destinationKindSwap(value: string): DestinationKindSwapAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Change Origin Amount ***/
|
|
||||||
export type OriginAmountSwapAction = {
|
|
||||||
type: 'SWAP_ORIGIN_AMOUNT',
|
|
||||||
value: ?number
|
|
||||||
};
|
|
||||||
|
|
||||||
export function originAmountSwap(value: ?number): OriginAmountSwapAction {
|
export function originAmountSwap(value: ?number): OriginAmountSwapAction {
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_ORIGIN_AMOUNT',
|
type: 'SWAP_ORIGIN_AMOUNT',
|
||||||
|
@ -65,12 +51,6 @@ export function originAmountSwap(value: ?number): OriginAmountSwapAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Change Destination Amount ***/
|
|
||||||
export type DestinationAmountSwapAction = {
|
|
||||||
type: 'SWAP_DESTINATION_AMOUNT',
|
|
||||||
value: ?number
|
|
||||||
};
|
|
||||||
|
|
||||||
export function destinationAmountSwap(
|
export function destinationAmountSwap(
|
||||||
value: ?number
|
value: ?number
|
||||||
): DestinationAmountSwapAction {
|
): DestinationAmountSwapAction {
|
||||||
|
@ -80,32 +60,15 @@ export function destinationAmountSwap(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Update Bity Rates ***/
|
export function loadBityRatesSucceededSwap(
|
||||||
export type Pairs = {
|
|
||||||
ETHBTC: number,
|
|
||||||
ETHREP: number,
|
|
||||||
BTCETH: number,
|
|
||||||
BTCREP: number
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BityRatesSwapAction = {
|
|
||||||
type: 'SWAP_UPDATE_BITY_RATES',
|
|
||||||
value: Pairs
|
value: Pairs
|
||||||
};
|
): LoadBityRatesSucceededSwapAction {
|
||||||
|
|
||||||
export function updateBityRatesSwap(value: Pairs): BityRatesSwapAction {
|
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_UPDATE_BITY_RATES',
|
type: 'SWAP_LOAD_BITY_RATES_SUCCEEDED',
|
||||||
value
|
value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Change Destination Address ***/
|
|
||||||
export type DestinationAddressSwapAction = {
|
|
||||||
type: 'SWAP_DESTINATION_ADDRESS',
|
|
||||||
value: ?string
|
|
||||||
};
|
|
||||||
|
|
||||||
export function destinationAddressSwap(
|
export function destinationAddressSwap(
|
||||||
value: ?string
|
value: ?string
|
||||||
): DestinationAddressSwapAction {
|
): DestinationAddressSwapAction {
|
||||||
|
@ -115,49 +78,92 @@ export function destinationAddressSwap(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Restart ***/
|
|
||||||
export type RestartSwapAction = {
|
|
||||||
type: 'SWAP_RESTART'
|
|
||||||
};
|
|
||||||
|
|
||||||
export function restartSwap(): RestartSwapAction {
|
export function restartSwap(): RestartSwapAction {
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_RESTART'
|
type: 'SWAP_RESTART'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Load Bity Rates ***/
|
export function loadBityRatesRequestedSwap(): LoadBityRatesRequestedSwapAction {
|
||||||
export type LoadBityRatesSwapAction = {
|
|
||||||
type: 'SWAP_LOAD_BITY_RATES'
|
|
||||||
};
|
|
||||||
|
|
||||||
export function loadBityRatesSwap(): LoadBityRatesSwapAction {
|
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_LOAD_BITY_RATES'
|
type: 'SWAP_LOAD_BITY_RATES_REQUESTED'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Stop Loading Bity Rates ***/
|
|
||||||
export type StopLoadBityRatesSwapAction = {
|
|
||||||
type: 'SWAP_STOP_LOAD_BITY_RATES'
|
|
||||||
};
|
|
||||||
|
|
||||||
export function stopLoadBityRatesSwap(): StopLoadBityRatesSwapAction {
|
export function stopLoadBityRatesSwap(): StopLoadBityRatesSwapAction {
|
||||||
return {
|
return {
|
||||||
type: 'SWAP_STOP_LOAD_BITY_RATES'
|
type: 'SWAP_STOP_LOAD_BITY_RATES'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Action Type Union ***/
|
export function orderTimeSwap(value: number) {
|
||||||
export type SwapAction =
|
return {
|
||||||
| ChangeStepSwapAction
|
type: 'SWAP_ORDER_TIME',
|
||||||
| ReferenceNumberSwapAction
|
value
|
||||||
| OriginKindSwapAction
|
};
|
||||||
| DestinationKindSwapAction
|
}
|
||||||
| OriginAmountSwapAction
|
|
||||||
| DestinationAmountSwapAction
|
export function bityOrderCreateSucceededSwap(
|
||||||
| BityRatesSwapAction
|
payload: BityOrderPostResponse
|
||||||
| DestinationAddressSwapAction
|
): BityOrderCreateSucceededSwapAction {
|
||||||
| RestartSwapAction
|
return {
|
||||||
| LoadBityRatesSwapAction
|
type: 'SWAP_BITY_ORDER_CREATE_SUCCEEDED',
|
||||||
| StopLoadBityRatesSwapAction;
|
payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bityOrderCreateRequestedSwap(
|
||||||
|
amount: number,
|
||||||
|
destinationAddress: string,
|
||||||
|
pair: string,
|
||||||
|
mode: number = 0
|
||||||
|
): BityOrderCreateRequestedSwapAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_ORDER_CREATE_REQUESTED',
|
||||||
|
payload: {
|
||||||
|
amount,
|
||||||
|
destinationAddress,
|
||||||
|
pair,
|
||||||
|
mode
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderStatusSucceededSwap(
|
||||||
|
payload: BityOrderResponse
|
||||||
|
): OrderStatusSucceededSwapAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_BITY_ORDER_STATUS_SUCCEEDED',
|
||||||
|
payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderStatusRequestedSwap(): OrderStatusRequestedSwapAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_BITY_ORDER_STATUS_REQUESTED'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startOrderTimerSwap(): StartOrderTimerSwapAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_ORDER_START_TIMER'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopOrderTimerSwap(): StopOrderTimerSwapAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_ORDER_STOP_TIMER'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startPollBityOrderStatus(): StartPollBityOrderStatusAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_START_POLL_BITY_ORDER_STATUS'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopPollBityOrderStatus(): StopPollBityOrderStatusAction {
|
||||||
|
return {
|
||||||
|
type: 'SWAP_STOP_POLL_BITY_ORDER_STATUS'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
export type Pairs = {
|
||||||
|
ETHBTC: number,
|
||||||
|
ETHREP: number,
|
||||||
|
BTCETH: number,
|
||||||
|
BTCREP: number
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OriginKindSwapAction = {
|
||||||
|
type: 'SWAP_ORIGIN_KIND',
|
||||||
|
value: string
|
||||||
|
};
|
||||||
|
export type DestinationKindSwapAction = {
|
||||||
|
type: 'SWAP_DESTINATION_KIND',
|
||||||
|
value: string
|
||||||
|
};
|
||||||
|
export type OriginAmountSwapAction = {
|
||||||
|
type: 'SWAP_ORIGIN_AMOUNT',
|
||||||
|
value: ?number
|
||||||
|
};
|
||||||
|
export type DestinationAmountSwapAction = {
|
||||||
|
type: 'SWAP_DESTINATION_AMOUNT',
|
||||||
|
value: ?number
|
||||||
|
};
|
||||||
|
export type LoadBityRatesSucceededSwapAction = {
|
||||||
|
type: 'SWAP_LOAD_BITY_RATES_SUCCEEDED',
|
||||||
|
value: Pairs
|
||||||
|
};
|
||||||
|
export type DestinationAddressSwapAction = {
|
||||||
|
type: 'SWAP_DESTINATION_ADDRESS',
|
||||||
|
value: ?number
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RestartSwapAction = {
|
||||||
|
type: 'SWAP_RESTART'
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoadBityRatesRequestedSwapAction = {
|
||||||
|
type: 'SWAP_LOAD_BITY_RATES_REQUESTED'
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChangeStepSwapAction = {
|
||||||
|
type: 'SWAP_STEP',
|
||||||
|
value: number
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StopLoadBityRatesSwapAction = {
|
||||||
|
type: 'SWAP_STOP_LOAD_BITY_RATES'
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BityOrderCreateRequestedSwapAction = {
|
||||||
|
type: 'SWAP_ORDER_CREATE_REQUESTED',
|
||||||
|
payload: {
|
||||||
|
amount: number,
|
||||||
|
destinationAddress: string,
|
||||||
|
pair: string,
|
||||||
|
mode: number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type BityOrderInput = {
|
||||||
|
amount: string
|
||||||
|
};
|
||||||
|
|
||||||
|
type BityOrderOutput = {
|
||||||
|
amount: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BityOrderResponse = {
|
||||||
|
status: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BityOrderPostResponse = BityOrderResponse & {
|
||||||
|
payment_address: string,
|
||||||
|
status: string,
|
||||||
|
input: BityOrderInput,
|
||||||
|
output: BityOrderOutput,
|
||||||
|
timestamp_created: string,
|
||||||
|
validFor: number
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BityOrderCreateSucceededSwapAction = {
|
||||||
|
type: 'SWAP_BITY_ORDER_CREATE_SUCCEEDED',
|
||||||
|
payload: BityOrderPostResponse
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderStatusRequestedSwapAction = {
|
||||||
|
type: 'SWAP_BITY_ORDER_STATUS_REQUESTED',
|
||||||
|
payload: BityOrderResponse
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderStatusSucceededSwapAction = {
|
||||||
|
type: 'SWAP_BITY_ORDER_STATUS_SUCCEEDED',
|
||||||
|
payload: BityOrderResponse
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StartOrderTimerSwapAction = {
|
||||||
|
type: 'SWAP_ORDER_START_TIMER'
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StopOrderTimerSwapAction = {
|
||||||
|
type: 'SWAP_ORDER_STOP_TIMER'
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StartPollBityOrderStatusAction = {
|
||||||
|
type: 'SWAP_START_POLL_BITY_ORDER_STATUS'
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StopPollBityOrderStatusAction = {
|
||||||
|
type: 'SWAP_STOP_POLL_BITY_ORDER_STATUS'
|
||||||
|
};
|
||||||
|
|
||||||
|
/*** Action Type Union ***/
|
||||||
|
export type SwapAction =
|
||||||
|
| ChangeStepSwapAction
|
||||||
|
| OriginKindSwapAction
|
||||||
|
| DestinationKindSwapAction
|
||||||
|
| OriginAmountSwapAction
|
||||||
|
| DestinationAmountSwapAction
|
||||||
|
| LoadBityRatesSucceededSwapAction
|
||||||
|
| DestinationAddressSwapAction
|
||||||
|
| RestartSwapAction
|
||||||
|
| LoadBityRatesRequestedSwapAction
|
||||||
|
| StopLoadBityRatesSwapAction
|
||||||
|
| BityOrderCreateRequestedSwapAction
|
||||||
|
| BityOrderCreateSucceededSwapAction
|
||||||
|
| BityOrderResponse
|
||||||
|
| OrderStatusSucceededSwapAction
|
||||||
|
| StartPollBityOrderStatusAction;
|
|
@ -1,38 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
import bityConfig from 'config/bity';
|
import bityConfig from 'config/bity';
|
||||||
import {combineAndUpper} from 'utils/formatters'
|
import { checkHttpStatus, parseJSON } from './utils';
|
||||||
|
import { combineAndUpper } from 'utils/formatters';
|
||||||
|
|
||||||
|
function findRateFromBityRateList(rateObjects, pairName: string) {
|
||||||
function findRateFromBityRateList(rateObjects, pairName) {
|
|
||||||
return rateObjects.find(x => x.pair === pairName);
|
return rateObjects.find(x => x.pair === pairName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME better types
|
function _getRate(bityRates, originKind: string, destinationKind: string) {
|
||||||
function _getRate(bityRates, origin: string, destination: string) {
|
const pairName = combineAndUpper(originKind, destinationKind);
|
||||||
const pairName = combineAndUpper(origin, destination);
|
|
||||||
const rateObjects = bityRates.objects;
|
const rateObjects = bityRates.objects;
|
||||||
return findRateFromBityRateList(rateObjects, pairName);
|
return findRateFromBityRateList(rateObjects, pairName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gives you multiple rates from Bitys API without making multiple API calls
|
|
||||||
* @param arrayOfOriginAndDestinationDicts - [{origin: 'BTC', destination: 'ETH'}, {origin: 'BTC', destination: 'REP}]
|
|
||||||
*/
|
|
||||||
function getMultipleRates(arrayOfOriginAndDestinationDicts) {
|
|
||||||
const mappedRates = {};
|
|
||||||
return _getAllRates().then(bityRates => {
|
|
||||||
arrayOfOriginAndDestinationDicts.forEach(each => {
|
|
||||||
const origin = each.origin;
|
|
||||||
const destination = each.destination;
|
|
||||||
const pairName = combineAndUpper(origin, destination);
|
|
||||||
const rate = _getRate(bityRates, origin, destination);
|
|
||||||
mappedRates[pairName] = parseFloat(rate.rate_we_sell);
|
|
||||||
});
|
|
||||||
return mappedRates;
|
|
||||||
});
|
|
||||||
// TODO - catch errors
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAllRates() {
|
export function getAllRates() {
|
||||||
const mappedRates = {};
|
const mappedRates = {};
|
||||||
return _getAllRates().then(bityRates => {
|
return _getAllRates().then(bityRates => {
|
||||||
|
@ -42,11 +22,44 @@ export function getAllRates() {
|
||||||
});
|
});
|
||||||
return mappedRates;
|
return mappedRates;
|
||||||
});
|
});
|
||||||
// TODO - catch errors
|
}
|
||||||
|
|
||||||
|
export function postOrder(
|
||||||
|
amount: number,
|
||||||
|
destAddress: string,
|
||||||
|
mode: number,
|
||||||
|
pair: string
|
||||||
|
) {
|
||||||
|
return fetch(`${bityConfig.serverURL}/order`, {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify({
|
||||||
|
amount,
|
||||||
|
destAddress,
|
||||||
|
mode,
|
||||||
|
pair
|
||||||
|
}),
|
||||||
|
headers: bityConfig.postConfig.headers
|
||||||
|
})
|
||||||
|
.then(checkHttpStatus)
|
||||||
|
.then(parseJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderStatus(orderid: string) {
|
||||||
|
return fetch(`${bityConfig.serverURL}/status`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
orderid
|
||||||
|
}),
|
||||||
|
headers: bityConfig.postConfig.headers
|
||||||
|
})
|
||||||
|
.then(checkHttpStatus)
|
||||||
|
.then(parseJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getAllRates() {
|
function _getAllRates() {
|
||||||
return fetch(`${bityConfig.bityAPI}/v1/rate2/`).then(r => r.json());
|
return fetch(`${bityConfig.bityAPI}/v1/rate2/`)
|
||||||
|
.then(checkHttpStatus)
|
||||||
|
.then(parseJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestStatus() {}
|
function requestOrderStatus() {}
|
||||||
|
|
|
@ -1,117 +1,13 @@
|
||||||
// Request utils,
|
export function checkHttpStatus(response) {
|
||||||
// feel free to replace with your code
|
if (response.status >= 200 && response.status < 300) {
|
||||||
// (get, post are used in ApiServices)
|
return response;
|
||||||
|
|
||||||
import { getLocalToken } from 'api/AuthSvc';
|
|
||||||
import config from 'config';
|
|
||||||
|
|
||||||
window.BASE_API = config.BASE_API;
|
|
||||||
|
|
||||||
function requestWrapper(method) {
|
|
||||||
return async function(url, data = null, params = {}) {
|
|
||||||
if (method === 'GET') {
|
|
||||||
// is it a GET?
|
|
||||||
// GET doesn't have data
|
|
||||||
params = data;
|
|
||||||
data = null;
|
|
||||||
} else if (data === Object(data)) {
|
|
||||||
// (data === Object(data)) === _.isObject(data)
|
|
||||||
data = JSON.stringify(data);
|
|
||||||
} else {
|
|
||||||
throw new Error(`XHR invalid, check ${method} on ${url}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// default params for fetch = method + (Content-Type)
|
|
||||||
let defaults = {
|
|
||||||
method: method,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json; charset=UTF-8'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// check that req url is relative and request was sent to our domain
|
|
||||||
if (url.match(/^https?:\/\//gi) > -1) {
|
|
||||||
let token = getLocalToken();
|
|
||||||
if (token) {
|
|
||||||
defaults.headers['Authorization'] = `JWT ${token}`;
|
|
||||||
}
|
|
||||||
url = window.BASE_API + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
defaults.body = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
let paramsObj = {
|
|
||||||
...defaults,
|
|
||||||
headers: { ...params, ...defaults.headers }
|
|
||||||
};
|
|
||||||
|
|
||||||
return await fetch(url, paramsObj).then(parseJSON).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// middlewares
|
|
||||||
// parse fetch json, add ok property and return request result
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. parse response
|
|
||||||
* 2. add "ok" property to result
|
|
||||||
* 3. return request result
|
|
||||||
* @param {Object} res - response from server
|
|
||||||
* @return {Object} response result with "ok" property
|
|
||||||
*/
|
|
||||||
async function parseJSON(res) {
|
|
||||||
let json;
|
|
||||||
try {
|
|
||||||
json = await res.json();
|
|
||||||
} catch (e) {
|
|
||||||
return { data: {}, ok: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
// simplest validation ever, ahah :)
|
|
||||||
if (!res.ok) {
|
|
||||||
return { data: json, ok: false };
|
|
||||||
}
|
|
||||||
// resultOK - is a function with side effects
|
|
||||||
// It removes ok property from result object
|
|
||||||
return { data: json, ok: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const get = requestWrapper('GET');
|
|
||||||
export const post = requestWrapper('POST');
|
|
||||||
export const put = requestWrapper('PUT');
|
|
||||||
export const patch = requestWrapper('PATCH');
|
|
||||||
export const del = requestWrapper('DELETE');
|
|
||||||
|
|
||||||
// USAGE:
|
|
||||||
// get('https://www.google.com', {
|
|
||||||
// Authorization: 'JWT LOL',
|
|
||||||
// headers: {
|
|
||||||
// 'Content-Type': 'text/html'
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// FUNCTION WITH SIDE-EFFECTS
|
|
||||||
/**
|
|
||||||
* `parseJSON()` adds property "ok"
|
|
||||||
* that identicates that response is OK
|
|
||||||
*
|
|
||||||
* `resultOK`removes result.ok from result and returns "ok" property
|
|
||||||
* It widely used in `/actions/*`
|
|
||||||
* for choosing action to dispatch after request to API
|
|
||||||
*
|
|
||||||
* @param {Object} result - response result that
|
|
||||||
* @return {bool} - indicates was request successful or not
|
|
||||||
*/
|
|
||||||
export function resultOK(result) {
|
|
||||||
if (result) {
|
|
||||||
let ok = result.ok;
|
|
||||||
delete result.ok;
|
|
||||||
return ok; //look at parseJSON
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
let error = new Error(response.statusText);
|
||||||
|
error.response = response;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseJSON(response) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
|
@ -74,10 +74,11 @@ export default class TabsOptions extends Component {
|
||||||
<ul className="Navigation-links">
|
<ul className="Navigation-links">
|
||||||
{tabs.map((object, i) => {
|
{tabs.map((object, i) => {
|
||||||
// if the window pathname is the same or similar to the tab objects name, set the active toggle
|
// if the window pathname is the same or similar to the tab objects name, set the active toggle
|
||||||
const activeOrNot = location.pathname === object.link ||
|
const activeOrNot =
|
||||||
|
location.pathname === object.link ||
|
||||||
location.pathname.substring(1) === object.link
|
location.pathname.substring(1) === object.link
|
||||||
? 'is-active'
|
? 'is-active'
|
||||||
: '';
|
: '';
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className={'Navigation-links-item'}
|
className={'Navigation-links-item'}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
// @flow
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
const DEFAULT_BUTTON_TYPE = 'primary';
|
||||||
|
const DEFAULT_BUTTON_SIZE = 'lg';
|
||||||
|
|
||||||
|
const Spinner = () => {
|
||||||
|
return <i className="fa fa-spinner fa-spin fa-fw" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ButtonType =
|
||||||
|
| 'default'
|
||||||
|
| 'primary'
|
||||||
|
| 'success'
|
||||||
|
| 'info'
|
||||||
|
| 'warning'
|
||||||
|
| 'danger';
|
||||||
|
type ButtonSize = 'lg' | 'sm' | 'xs';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClick: () => any,
|
||||||
|
text: string,
|
||||||
|
loading?: boolean,
|
||||||
|
disabled?: boolean,
|
||||||
|
loadingText?: string,
|
||||||
|
size?: ButtonSize,
|
||||||
|
type?: ButtonType
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SimpleButton extends Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
computedClass = () => {
|
||||||
|
return `btn btn-${this.props.size || DEFAULT_BUTTON_TYPE} btn-${this.props
|
||||||
|
.type || DEFAULT_BUTTON_SIZE}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { loading, disabled, loadingText, text, onClick } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={loading || disabled}
|
||||||
|
className={this.computedClass()}
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <div>
|
||||||
|
<Spinner />
|
||||||
|
{` ${loadingText || text}`}
|
||||||
|
</div>
|
||||||
|
: <div>
|
||||||
|
{text}
|
||||||
|
</div>}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,39 @@
|
||||||
export default {
|
export default {
|
||||||
serverURL: 'https://bity.myetherapi.com',
|
serverURL: 'https://bity.myetherapi.com',
|
||||||
bityAPI: 'https://bity.com/api',
|
bityAPI: 'https://bity.com/api',
|
||||||
decimals: 6,
|
|
||||||
ethExplorer: 'https://etherscan.io/tx/[[txHash]]',
|
ethExplorer: 'https://etherscan.io/tx/[[txHash]]',
|
||||||
btcExplorer: 'https://blockchain.info/tx/[[txHash]]',
|
btcExplorer: 'https://blockchain.info/tx/[[txHash]]',
|
||||||
validStatus: ['RCVE', 'FILL', 'CONF', 'EXEC'],
|
validStatus: ['RCVE', 'FILL', 'CONF', 'EXEC'],
|
||||||
invalidStatus: ['CANC'],
|
invalidStatus: ['CANC'],
|
||||||
mainPairs: ['REP', 'ETH'],
|
// while Bity is supposedly OK with any order that is at least 0.01 BTC Worth, the order will fail if you send 0.01 BTC worth of ETH.
|
||||||
min: 0.01,
|
// This is a bad magic number, but will suffice for now
|
||||||
max: 3,
|
ETHBuffer: 0.1, // percent higher/lower than 0.01 BTC worth
|
||||||
priceLoaded: false,
|
REPBuffer: 0.2, // percent higher/lower than 0.01 BTC worth
|
||||||
|
BTCMin: 0.01,
|
||||||
|
BTCMax: 3,
|
||||||
|
ETHMin: function(BTCETHRate: number) {
|
||||||
|
const ETHMin = BTCETHRate * this.BTCMin;
|
||||||
|
const ETHMinWithPadding = ETHMin + ETHMin * this.ETHBuffer;
|
||||||
|
return ETHMinWithPadding;
|
||||||
|
},
|
||||||
|
ETHMax: function(BTCETHRate: number) {
|
||||||
|
const ETHMax = BTCETHRate * this.BTCMax;
|
||||||
|
const ETHMaxWithPadding = ETHMax - ETHMax * this.ETHBuffer;
|
||||||
|
return ETHMaxWithPadding;
|
||||||
|
},
|
||||||
|
REPMin: function(BTCREPRate: number) {
|
||||||
|
const REPMin = BTCREPRate * this.BTCMin;
|
||||||
|
const REPMinWithPadding = REPMin + REPMin * this.REPBuffer;
|
||||||
|
return REPMinWithPadding;
|
||||||
|
},
|
||||||
|
REPMax: function(BTCREPRate: number) {
|
||||||
|
const REPMax = BTCREPRate * this.BTCMax;
|
||||||
|
const REPMaxWithPadding = REPMax - REPMax * this.ETHBuffer;
|
||||||
|
return REPMaxWithPadding;
|
||||||
|
},
|
||||||
postConfig: {
|
postConfig: {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json; charse:UTF-8'
|
'Content-Type': 'application/json; charset:UTF-8'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import translate from 'translations';
|
|
||||||
import { combineAndUpper } from 'utils/formatters';
|
|
||||||
import SimpleDropDown from 'components/ui/SimpleDropdown';
|
|
||||||
import type {
|
|
||||||
OriginKindSwapAction,
|
|
||||||
DestinationKindSwapAction,
|
|
||||||
OriginAmountSwapAction,
|
|
||||||
DestinationAmountSwapAction,
|
|
||||||
ChangeStepSwapAction
|
|
||||||
} from 'actions/swap';
|
|
||||||
|
|
||||||
export type StateProps = {
|
|
||||||
bityRates: {},
|
|
||||||
originAmount: ?number,
|
|
||||||
destinationAmount: ?number,
|
|
||||||
originKind: string,
|
|
||||||
destinationKind: string,
|
|
||||||
destinationKindOptions: String[],
|
|
||||||
originKindOptions: String[]
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ActionProps = {
|
|
||||||
originKindSwap: (value: string) => OriginKindSwapAction,
|
|
||||||
destinationKindSwap: (value: string) => DestinationKindSwapAction,
|
|
||||||
originAmountSwap: (value: ?number) => OriginAmountSwapAction,
|
|
||||||
destinationAmountSwap: (value: ?number) => DestinationAmountSwapAction,
|
|
||||||
changeStepSwap: () => ChangeStepSwapAction
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class CurrencySwap extends Component {
|
|
||||||
props: StateProps & ActionProps;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
disabled: false
|
|
||||||
};
|
|
||||||
|
|
||||||
onClickStartSwap = () => {
|
|
||||||
this.props.changeStepSwap(2);
|
|
||||||
};
|
|
||||||
|
|
||||||
setOriginAndDestinationToNull = () => {
|
|
||||||
this.props.originAmountSwap(null);
|
|
||||||
this.props.destinationAmountSwap(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
onChangeOriginAmount = (event: SyntheticInputEvent) => {
|
|
||||||
const amount = event.target.value;
|
|
||||||
let originAmountAsNumber = parseFloat(amount);
|
|
||||||
if (originAmountAsNumber) {
|
|
||||||
let pairName = combineAndUpper(
|
|
||||||
this.props.originKind,
|
|
||||||
this.props.destinationKind
|
|
||||||
);
|
|
||||||
let bityRate = this.props.bityRates[pairName];
|
|
||||||
this.props.originAmountSwap(originAmountAsNumber);
|
|
||||||
this.props.destinationAmountSwap(originAmountAsNumber * bityRate);
|
|
||||||
} else {
|
|
||||||
this.setOriginAndDestinationToNull();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onChangeDestinationAmount = (event: SyntheticInputEvent) => {
|
|
||||||
const amount = event.target.value;
|
|
||||||
let destinationAmountAsNumber = parseFloat(amount);
|
|
||||||
if (destinationAmountAsNumber) {
|
|
||||||
this.props.destinationAmountSwap(destinationAmountAsNumber);
|
|
||||||
let pairName = combineAndUpper(
|
|
||||||
this.props.destinationKind,
|
|
||||||
this.props.originKind
|
|
||||||
);
|
|
||||||
let bityRate = this.props.bityRates[pairName];
|
|
||||||
this.props.originAmountSwap(destinationAmountAsNumber * bityRate);
|
|
||||||
} else {
|
|
||||||
this.setOriginAndDestinationToNull();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onChangeDestinationKind = (event: SyntheticInputEvent) => {
|
|
||||||
let newDestinationKind = event.target.value;
|
|
||||||
this.props.destinationKindSwap(newDestinationKind);
|
|
||||||
};
|
|
||||||
|
|
||||||
onChangeOriginKind = (event: SyntheticInputEvent) => {
|
|
||||||
let newOriginKind = event.target.value;
|
|
||||||
this.props.originKindSwap(newOriginKind);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
originAmount,
|
|
||||||
destinationAmount,
|
|
||||||
originKind,
|
|
||||||
destinationKind,
|
|
||||||
destinationKindOptions,
|
|
||||||
originKindOptions
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article className="swap-panel">
|
|
||||||
<h1>
|
|
||||||
{translate('SWAP_init_1')}
|
|
||||||
</h1>
|
|
||||||
<input
|
|
||||||
className={`form-control ${this.props.originAmount !== '' &&
|
|
||||||
this.props.originAmount > 0
|
|
||||||
? 'is-valid'
|
|
||||||
: 'is-invalid'}`}
|
|
||||||
type="number"
|
|
||||||
placeholder="Amount"
|
|
||||||
value={originAmount || ''}
|
|
||||||
onChange={this.onChangeOriginAmount}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SimpleDropDown
|
|
||||||
value={originKind}
|
|
||||||
onChange={this.onChangeOriginKind.bind(this)}
|
|
||||||
options={originKindOptions}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
{translate('SWAP_init_2')}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className={`form-control ${this.props.destinationAmount !== '' &&
|
|
||||||
this.props.destinationAmount > 0
|
|
||||||
? 'is-valid'
|
|
||||||
: 'is-invalid'}`}
|
|
||||||
type="number"
|
|
||||||
placeholder="Amount"
|
|
||||||
value={destinationAmount || ''}
|
|
||||||
onChange={this.onChangeDestinationAmount}
|
|
||||||
/>
|
|
||||||
<SimpleDropDown
|
|
||||||
value={destinationKind}
|
|
||||||
onChange={this.onChangeDestinationKind}
|
|
||||||
options={destinationKindOptions}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="col-xs-12 clearfix text-center">
|
|
||||||
<button
|
|
||||||
disabled={this.state.disabled}
|
|
||||||
onClick={this.onClickStartSwap}
|
|
||||||
className="btn btn-info btn-lg"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{translate('SWAP_init_CTA')}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import translate from 'translations';
|
||||||
|
import { combineAndUpper } from 'utils/formatters';
|
||||||
|
import SimpleDropDown from 'components/ui/SimpleDropdown';
|
||||||
|
import SimpleButton from 'components/ui/SimpleButton';
|
||||||
|
import type {
|
||||||
|
OriginKindSwapAction,
|
||||||
|
DestinationKindSwapAction,
|
||||||
|
OriginAmountSwapAction,
|
||||||
|
DestinationAmountSwapAction,
|
||||||
|
ChangeStepSwapAction
|
||||||
|
} from 'actions/swapTypes';
|
||||||
|
import bityConfig from 'config/bity';
|
||||||
|
import { toFixedIfLarger } from 'utils/formatters';
|
||||||
|
|
||||||
|
export type StateProps = {
|
||||||
|
bityRates: {},
|
||||||
|
originAmount: ?number,
|
||||||
|
destinationAmount: ?number,
|
||||||
|
originKind: string,
|
||||||
|
destinationKind: string,
|
||||||
|
destinationKindOptions: String[],
|
||||||
|
originKindOptions: String[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActionProps = {
|
||||||
|
originKindSwap: (value: string) => OriginKindSwapAction,
|
||||||
|
destinationKindSwap: (value: string) => DestinationKindSwapAction,
|
||||||
|
originAmountSwap: (value: ?number) => OriginAmountSwapAction,
|
||||||
|
destinationAmountSwap: (value: ?number) => DestinationAmountSwapAction,
|
||||||
|
changeStepSwap: () => ChangeStepSwapAction,
|
||||||
|
showNotification: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class CurrencySwap extends Component {
|
||||||
|
props: StateProps & ActionProps;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
disabled: true,
|
||||||
|
showedMinMaxError: false
|
||||||
|
};
|
||||||
|
|
||||||
|
isMinMaxValid = (amount, kind) => {
|
||||||
|
let bityMin;
|
||||||
|
let bityMax;
|
||||||
|
if (kind !== 'BTC') {
|
||||||
|
const bityPairRate = this.props.bityRates['BTC' + kind];
|
||||||
|
bityMin = bityConfig[kind + 'Min'](bityPairRate);
|
||||||
|
bityMax = bityConfig[kind + 'Max'](bityPairRate);
|
||||||
|
} else {
|
||||||
|
bityMin = bityConfig.BTCMin;
|
||||||
|
bityMax = bityConfig.BTCMax;
|
||||||
|
}
|
||||||
|
let higherThanMin = amount >= bityMin;
|
||||||
|
let lowerThanMax = amount <= bityMax;
|
||||||
|
return higherThanMin && lowerThanMax;
|
||||||
|
};
|
||||||
|
|
||||||
|
isDisabled = (originAmount, originKind, destinationAmount) => {
|
||||||
|
const hasOriginAmountAndDestinationAmount =
|
||||||
|
originAmount && destinationAmount;
|
||||||
|
const minMaxIsValid = this.isMinMaxValid(originAmount, originKind);
|
||||||
|
return !(hasOriginAmountAndDestinationAmount && minMaxIsValid);
|
||||||
|
};
|
||||||
|
|
||||||
|
setDisabled(originAmount, originKind, destinationAmount) {
|
||||||
|
const disabled = this.isDisabled(
|
||||||
|
originAmount,
|
||||||
|
originKind,
|
||||||
|
destinationAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
if (disabled && originAmount && !this.state.showedMinMaxError) {
|
||||||
|
const { bityRates } = this.props;
|
||||||
|
const ETHMin = bityConfig.ETHMin(bityRates.BTCETH);
|
||||||
|
const ETHMax = bityConfig.ETHMax(bityRates.BTCETH);
|
||||||
|
const REPMin = bityConfig.REPMax(bityRates.BTCREP);
|
||||||
|
|
||||||
|
const notificationMessage = `
|
||||||
|
Minimum amount ${bityConfig.BTCMin} BTC,
|
||||||
|
${toFixedIfLarger(ETHMin, 3)} ETH.
|
||||||
|
Max amount ${bityConfig.BTCMax} BTC,
|
||||||
|
${toFixedIfLarger(ETHMax, 3)} ETH, or
|
||||||
|
${toFixedIfLarger(REPMin, 3)} REP
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
disabled: disabled,
|
||||||
|
showedMinMaxError: true
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.props.showNotification('danger', notificationMessage, 10000);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
disabled: disabled
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickStartSwap = () => {
|
||||||
|
this.props.changeStepSwap(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
setOriginAndDestinationToNull = () => {
|
||||||
|
this.props.originAmountSwap(null);
|
||||||
|
this.props.destinationAmountSwap(null);
|
||||||
|
this.setDisabled(null, this.props.originKind, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeOriginAmount = (event: SyntheticInputEvent) => {
|
||||||
|
const { destinationKind, originKind } = this.props;
|
||||||
|
const amount = event.target.value;
|
||||||
|
let originAmountAsNumber = parseFloat(amount);
|
||||||
|
if (originAmountAsNumber || originAmountAsNumber === 0) {
|
||||||
|
let pairName = combineAndUpper(originKind, destinationKind);
|
||||||
|
let bityRate = this.props.bityRates[pairName];
|
||||||
|
this.props.originAmountSwap(originAmountAsNumber);
|
||||||
|
let destinationAmount = originAmountAsNumber * bityRate;
|
||||||
|
this.props.destinationAmountSwap(destinationAmount);
|
||||||
|
this.setDisabled(originAmountAsNumber, originKind, destinationAmount);
|
||||||
|
} else {
|
||||||
|
this.setOriginAndDestinationToNull();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeDestinationAmount = (event: SyntheticInputEvent) => {
|
||||||
|
const { destinationKind, originKind } = this.props;
|
||||||
|
const amount = event.target.value;
|
||||||
|
let destinationAmountAsNumber = parseFloat(amount);
|
||||||
|
if (destinationAmountAsNumber || destinationAmountAsNumber === 0) {
|
||||||
|
this.props.destinationAmountSwap(destinationAmountAsNumber);
|
||||||
|
let pairNameReversed = combineAndUpper(destinationKind, originKind);
|
||||||
|
let bityRate = this.props.bityRates[pairNameReversed];
|
||||||
|
let originAmount = destinationAmountAsNumber * bityRate;
|
||||||
|
this.props.originAmountSwap(originAmount, originKind);
|
||||||
|
this.setDisabled(originAmount, originKind, destinationAmountAsNumber);
|
||||||
|
} else {
|
||||||
|
this.setOriginAndDestinationToNull();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeDestinationKind = (event: SyntheticInputEvent) => {
|
||||||
|
let newDestinationKind = event.target.value;
|
||||||
|
this.props.destinationKindSwap(newDestinationKind);
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeOriginKind = (event: SyntheticInputEvent) => {
|
||||||
|
let newOriginKind = event.target.value;
|
||||||
|
this.props.originKindSwap(newOriginKind);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
originAmount,
|
||||||
|
destinationAmount,
|
||||||
|
originKind,
|
||||||
|
destinationKind,
|
||||||
|
destinationKindOptions,
|
||||||
|
originKindOptions
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="swap-panel">
|
||||||
|
<h1>
|
||||||
|
{translate('SWAP_init_1')}
|
||||||
|
</h1>
|
||||||
|
<input
|
||||||
|
className={`form-control ${originAmount !== '' &&
|
||||||
|
this.isMinMaxValid(originAmount, originKind)
|
||||||
|
? 'is-valid'
|
||||||
|
: 'is-invalid'}`}
|
||||||
|
type="number"
|
||||||
|
placeholder="Amount"
|
||||||
|
value={
|
||||||
|
parseFloat(originAmount) === 0 ? originAmount : originAmount || ''
|
||||||
|
}
|
||||||
|
onChange={this.onChangeOriginAmount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SimpleDropDown
|
||||||
|
value={originKind}
|
||||||
|
onChange={this.onChangeOriginKind.bind(this)}
|
||||||
|
options={originKindOptions}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
{translate('SWAP_init_2')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className={`form-control ${destinationAmount !== '' &&
|
||||||
|
this.isMinMaxValid(originAmount, originKind)
|
||||||
|
? 'is-valid'
|
||||||
|
: 'is-invalid'}`}
|
||||||
|
type="number"
|
||||||
|
placeholder="Amount"
|
||||||
|
value={
|
||||||
|
parseFloat(destinationAmount) === 0
|
||||||
|
? destinationAmount
|
||||||
|
: destinationAmount || ''
|
||||||
|
}
|
||||||
|
onChange={this.onChangeDestinationAmount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SimpleDropDown
|
||||||
|
value={destinationKind}
|
||||||
|
onChange={this.onChangeDestinationKind}
|
||||||
|
options={destinationKindOptions}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="col-xs-12 clearfix text-center">
|
||||||
|
<SimpleButton
|
||||||
|
onClick={this.onClickStartSwap}
|
||||||
|
text={translate('SWAP_init_CTA')}
|
||||||
|
disabled={this.state.disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
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';
|
||||||
import type { Pairs } from 'actions/swap';
|
import type { Pairs } from 'actions/swapTypes';
|
||||||
import { bityReferralURL } from 'config/data';
|
import { bityReferralURL } from 'config/data';
|
||||||
import bityLogoWhite from 'assets/images/logo-bity-white.svg';
|
import bityLogoWhite from 'assets/images/logo-bity-white.svg';
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import type {
|
||||||
|
LoadBityRatesRequestedSwapAction,
|
||||||
|
RestartSwapAction,
|
||||||
|
StopLoadBityRatesSwapAction
|
||||||
|
} from 'actions/swap';
|
||||||
|
import SwapProgress from './SwapProgress';
|
||||||
|
import PaymentInfo from './PaymentInfo';
|
||||||
|
|
||||||
|
type ReduxStateProps = {
|
||||||
|
destinationAddress: string,
|
||||||
|
destinationKind: string,
|
||||||
|
originKind: string,
|
||||||
|
originAmount: ?number,
|
||||||
|
destinationAmount: ?number,
|
||||||
|
isPostingOrder: boolean,
|
||||||
|
reference: string,
|
||||||
|
secondsRemaining: ?number,
|
||||||
|
paymentAddress: ?string,
|
||||||
|
orderStatus: ?string
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReduxActionProps = {
|
||||||
|
loadBityRatesRequestedSwap: () => LoadBityRatesRequestedSwapAction,
|
||||||
|
restartSwap: () => RestartSwapAction,
|
||||||
|
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
|
||||||
|
startOrderTimerSwap: Function,
|
||||||
|
startPollBityOrderStatus: Function,
|
||||||
|
stopOrderTimerSwap: Function,
|
||||||
|
stopPollBityOrderStatus: Function,
|
||||||
|
showNotification: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class PartThree extends Component {
|
||||||
|
props: ReduxActionProps & ReduxStateProps;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.startPollBityOrderStatus();
|
||||||
|
this.props.startOrderTimerSwap();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.stopOrderTimerSwap();
|
||||||
|
this.props.stopPollBityOrderStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let {
|
||||||
|
// STATE
|
||||||
|
originAmount,
|
||||||
|
originKind,
|
||||||
|
destinationKind,
|
||||||
|
paymentAddress,
|
||||||
|
orderStatus,
|
||||||
|
destinationAddress,
|
||||||
|
outputTx,
|
||||||
|
// ACTIONS
|
||||||
|
showNotification
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
let SwapProgressProps = {
|
||||||
|
originKind,
|
||||||
|
destinationKind,
|
||||||
|
orderStatus,
|
||||||
|
showNotification,
|
||||||
|
destinationAddress,
|
||||||
|
outputTx
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaymentInfoProps = {
|
||||||
|
originKind,
|
||||||
|
originAmount,
|
||||||
|
paymentAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SwapProgress {...SwapProgressProps} />
|
||||||
|
<PaymentInfo {...PaymentInfoProps} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import translate from 'translations';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
originKind: string,
|
||||||
|
originAmount: string,
|
||||||
|
paymentAddress: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class PaymentInfo extends Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<section className="row text-center">
|
||||||
|
<h1>
|
||||||
|
<span>
|
||||||
|
{translate('SWAP_order_CTA')}
|
||||||
|
</span>
|
||||||
|
<strong>
|
||||||
|
{this.props.originAmount} {this.props.originKind}{' '}
|
||||||
|
</strong>
|
||||||
|
<span>
|
||||||
|
{translate('SENDModal_Content_2')}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<strong className="mono text-primary">
|
||||||
|
{this.props.paymentAddress}
|
||||||
|
</strong>
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,13 +4,18 @@ import type {
|
||||||
DestinationAddressSwapAction,
|
DestinationAddressSwapAction,
|
||||||
ChangeStepSwapAction,
|
ChangeStepSwapAction,
|
||||||
StopLoadBityRatesSwapAction,
|
StopLoadBityRatesSwapAction,
|
||||||
ReferenceNumberSwapAction
|
BityOrderCreateRequestedSwapAction
|
||||||
} from 'actions/swap';
|
} from 'actions/swapTypes';
|
||||||
import { donationAddressMap } from 'config/data';
|
import { donationAddressMap } from 'config/data';
|
||||||
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
|
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
|
import { combineAndUpper } from 'utils/formatters';
|
||||||
|
import SimpleButton from 'components/ui/SimpleButton';
|
||||||
|
|
||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
|
isPostingOrder: boolean,
|
||||||
|
originAmount: number,
|
||||||
|
originKind: string,
|
||||||
destinationKind: string,
|
destinationKind: string,
|
||||||
destinationAddress: string
|
destinationAddress: string
|
||||||
};
|
};
|
||||||
|
@ -19,7 +24,12 @@ export type ActionProps = {
|
||||||
destinationAddressSwap: (value: ?string) => DestinationAddressSwapAction,
|
destinationAddressSwap: (value: ?string) => DestinationAddressSwapAction,
|
||||||
changeStepSwap: (value: number) => ChangeStepSwapAction,
|
changeStepSwap: (value: number) => ChangeStepSwapAction,
|
||||||
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
|
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
|
||||||
referenceNumberSwap: (value: string) => ReferenceNumberSwapAction
|
bityOrderCreateRequestedSwap: (
|
||||||
|
amount: number,
|
||||||
|
destinationAddress: string,
|
||||||
|
pair: string,
|
||||||
|
mode: ?number
|
||||||
|
) => BityOrderCreateRequestedSwapAction
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ReceivingAddress extends Component {
|
export default class ReceivingAddress extends Component {
|
||||||
|
@ -31,14 +41,15 @@ export default class ReceivingAddress extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickPartTwoComplete = () => {
|
onClickPartTwoComplete = () => {
|
||||||
this.props.stopLoadBityRatesSwap();
|
this.props.bityOrderCreateRequestedSwap(
|
||||||
// temporarily here for testing purposes. will live in saga
|
this.props.originAmount,
|
||||||
this.props.referenceNumberSwap('');
|
this.props.destinationAddress,
|
||||||
this.props.changeStepSwap(3);
|
combineAndUpper(this.props.originKind, this.props.destinationKind)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { destinationKind, destinationAddress } = this.props;
|
const { destinationKind, 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 (this.props.destinationKind === 'BTC') {
|
||||||
|
@ -72,15 +83,12 @@ export default class ReceivingAddress extends Component {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="row text-center">
|
<section className="row text-center">
|
||||||
<button
|
<SimpleButton
|
||||||
disabled={!validAddress}
|
text={translate('SWAP_start_CTA')}
|
||||||
onClick={this.onClickPartTwoComplete}
|
onClick={this.onClickPartTwoComplete}
|
||||||
className="btn btn-primary btn-lg"
|
disabled={!validAddress}
|
||||||
>
|
loading={isPostingOrder}
|
||||||
<span>
|
/>
|
||||||
{translate('SWAP_start_CTA')}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
|
@ -1,139 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { toFixedIfLarger } from 'utils/formatters';
|
|
||||||
import translate from 'translations';
|
|
||||||
import type { RestartSwapAction } from 'actions/swap';
|
|
||||||
import bityLogo from 'assets/images/logo-bity.svg';
|
|
||||||
import { bityReferralURL } from 'config/data';
|
|
||||||
|
|
||||||
export type StateProps = {
|
|
||||||
timeRemaining: string,
|
|
||||||
originAmount: number,
|
|
||||||
originKind: string,
|
|
||||||
destinationKind: string,
|
|
||||||
destinationAmount: number,
|
|
||||||
referenceNumber: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ActionProps = {
|
|
||||||
restartSwap: () => RestartSwapAction
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class SwapInfoHeader extends Component {
|
|
||||||
props: StateProps & ActionProps;
|
|
||||||
|
|
||||||
computedOriginDestinationRatio = () => {
|
|
||||||
return toFixedIfLarger(
|
|
||||||
this.props.destinationAmount / this.props.originAmount,
|
|
||||||
6
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
isExpanded = () => {
|
|
||||||
const { referenceNumber, timeRemaining, restartSwap } = this.props;
|
|
||||||
return referenceNumber && timeRemaining && restartSwap;
|
|
||||||
};
|
|
||||||
|
|
||||||
computedClass = () => {
|
|
||||||
if (this.isExpanded()) {
|
|
||||||
return 'col-sm-3 order-info';
|
|
||||||
} else {
|
|
||||||
return 'col-sm-4 order-info';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
referenceNumber,
|
|
||||||
timeRemaining,
|
|
||||||
originAmount,
|
|
||||||
destinationAmount,
|
|
||||||
originKind,
|
|
||||||
destinationKind,
|
|
||||||
restartSwap
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<section className="row text-center">
|
|
||||||
<div className="col-xs-3 text-left">
|
|
||||||
<button className="btn btn-danger btn-xs" onClick={restartSwap}>
|
|
||||||
Start New Swap
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<h5 className="col-xs-6">
|
|
||||||
{translate('SWAP_information')}
|
|
||||||
</h5>
|
|
||||||
<div className="col-xs-3">
|
|
||||||
<a
|
|
||||||
className="link"
|
|
||||||
href={bityReferralURL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="pull-right"
|
|
||||||
src={bityLogo}
|
|
||||||
width={100}
|
|
||||||
height={38}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="row order-info-wrap">
|
|
||||||
{/*Amount to send */}
|
|
||||||
{!this.isExpanded() &&
|
|
||||||
<div className={this.computedClass()}>
|
|
||||||
<h4>
|
|
||||||
{` ${toFixedIfLarger(originAmount, 6)} ${originKind}`}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{translate('SEND_amount')}
|
|
||||||
</p>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{/* Reference Number*/}
|
|
||||||
{this.isExpanded() &&
|
|
||||||
<div className={this.computedClass()}>
|
|
||||||
<h4>
|
|
||||||
{referenceNumber}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{translate('SWAP_ref_num')}
|
|
||||||
</p>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{/*Time remaining*/}
|
|
||||||
{this.isExpanded() &&
|
|
||||||
<div className={this.computedClass()}>
|
|
||||||
<h4>
|
|
||||||
{timeRemaining}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{translate('SWAP_time')}
|
|
||||||
</p>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{/*Amount to Receive*/}
|
|
||||||
<div className={this.computedClass()}>
|
|
||||||
<h4>
|
|
||||||
{` ${toFixedIfLarger(destinationAmount, 6)} ${destinationKind}`}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{translate('SWAP_rec_amt')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/*Your rate*/}
|
|
||||||
<div className={this.computedClass()}>
|
|
||||||
<h4>
|
|
||||||
{` ${this.computedOriginDestinationRatio()} ${originKind}/${destinationKind} `}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{translate('SWAP_your_rate')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
// @flow
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import translate from 'translations';
|
||||||
|
import type { RestartSwapAction } from 'actions/swapTypes';
|
||||||
|
import bityLogo from 'assets/images/logo-bity.svg';
|
||||||
|
import { bityReferralURL } from 'config/data';
|
||||||
|
import { toFixedIfLarger } from 'utils/formatters';
|
||||||
|
|
||||||
|
export type SwapInfoHeaderTitleProps = {
|
||||||
|
restartSwap: () => RestartSwapAction
|
||||||
|
};
|
||||||
|
|
||||||
|
class SwapInfoHeaderTitle extends Component {
|
||||||
|
props: SwapInfoHeaderTitleProps;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<section className="row text-center">
|
||||||
|
<div className="col-xs-3 text-left">
|
||||||
|
<button
|
||||||
|
className="btn btn-danger btn-xs"
|
||||||
|
onClick={this.props.restartSwap}
|
||||||
|
>
|
||||||
|
Start New Swap
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h5 className="col-xs-6">
|
||||||
|
{translate('SWAP_information')}
|
||||||
|
</h5>
|
||||||
|
<div className="col-xs-3">
|
||||||
|
<a
|
||||||
|
className="link"
|
||||||
|
href={bityReferralURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="pull-right"
|
||||||
|
src={bityLogo}
|
||||||
|
width={100}
|
||||||
|
height={38}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwapInfoHeaderProps = {
|
||||||
|
originAmount: number,
|
||||||
|
originKind: string,
|
||||||
|
destinationKind: string,
|
||||||
|
destinationAmount: number,
|
||||||
|
reference: string,
|
||||||
|
secondsRemaining: ?number,
|
||||||
|
restartSwap: () => RestartSwapAction
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SwapInfoHeader extends Component {
|
||||||
|
props: SwapInfoHeaderProps;
|
||||||
|
|
||||||
|
computedOriginDestinationRatio = () => {
|
||||||
|
return this.props.destinationAmount / this.props.originAmount;
|
||||||
|
};
|
||||||
|
|
||||||
|
isExpanded = () => {
|
||||||
|
const { reference, restartSwap } = this.props;
|
||||||
|
return reference && restartSwap;
|
||||||
|
};
|
||||||
|
|
||||||
|
computedClass = () => {
|
||||||
|
if (this.isExpanded()) {
|
||||||
|
return 'col-sm-3 order-info';
|
||||||
|
} else {
|
||||||
|
return 'col-sm-4 order-info';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
formattedTime = () => {
|
||||||
|
const { secondsRemaining } = this.props;
|
||||||
|
if (secondsRemaining || secondsRemaining === 0) {
|
||||||
|
let minutes = Math.floor(secondsRemaining / 60);
|
||||||
|
let seconds = secondsRemaining - minutes * 60;
|
||||||
|
minutes = minutes < 10 ? '0' + minutes : minutes;
|
||||||
|
seconds = seconds < 10 ? '0' + seconds : seconds;
|
||||||
|
return minutes + ':' + seconds;
|
||||||
|
} else {
|
||||||
|
throw Error('secondsRemaining must be a number');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
reference,
|
||||||
|
originAmount,
|
||||||
|
destinationAmount,
|
||||||
|
originKind,
|
||||||
|
destinationKind,
|
||||||
|
restartSwap
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SwapInfoHeaderTitle restartSwap={restartSwap} />
|
||||||
|
<section className="row order-info-wrap">
|
||||||
|
{/*Amount to send*/}
|
||||||
|
{!this.isExpanded() &&
|
||||||
|
<div className={this.computedClass()}>
|
||||||
|
<h4>
|
||||||
|
{` ${originAmount} ${originKind}`}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{translate('SEND_amount')}
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
{/*Reference Number*/}
|
||||||
|
{this.isExpanded() &&
|
||||||
|
<div className={this.computedClass()}>
|
||||||
|
<h4>
|
||||||
|
{reference}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{translate('SWAP_ref_num')}
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
{/*Time remaining*/}
|
||||||
|
{this.isExpanded() &&
|
||||||
|
<div className={this.computedClass()}>
|
||||||
|
<h4>
|
||||||
|
{this.formattedTime()}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{translate('SWAP_time')}
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
{/*Amount to Receive*/}
|
||||||
|
<div className={this.computedClass()}>
|
||||||
|
<h4>
|
||||||
|
{` ${destinationAmount} ${destinationKind}`}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{translate('SWAP_rec_amt')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/*Your rate*/}
|
||||||
|
<div className={this.computedClass()}>
|
||||||
|
<h4>
|
||||||
|
{` ${toFixedIfLarger(
|
||||||
|
this.computedOriginDestinationRatio()
|
||||||
|
)} ${originKind}/${destinationKind} `}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{translate('SWAP_your_rate')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,79 +0,0 @@
|
||||||
//flow
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import translate from 'translations';
|
|
||||||
|
|
||||||
export type StateProps = {
|
|
||||||
numberOfConfirmations: number,
|
|
||||||
destinationKind: string,
|
|
||||||
originKind: string,
|
|
||||||
orderStep: number
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class SwapProgress extends Component {
|
|
||||||
props: StateProps;
|
|
||||||
|
|
||||||
computedClass(i: number) {
|
|
||||||
const { orderStep } = this.props;
|
|
||||||
let cssClass = 'progress-item';
|
|
||||||
if (orderStep > i) {
|
|
||||||
cssClass += ' progress-true';
|
|
||||||
} else if (i === orderStep) {
|
|
||||||
cssClass += ' progress-active';
|
|
||||||
}
|
|
||||||
return cssClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { numberOfConfirmations, destinationKind, originKind } = this.props;
|
|
||||||
return (
|
|
||||||
<section className="row swap-progress">
|
|
||||||
<div className="sep" />
|
|
||||||
|
|
||||||
<div className={this.computedClass(1)}>
|
|
||||||
<div className="progress-circle">
|
|
||||||
<i>1</i>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{translate('SWAP_progress_1')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={this.computedClass(2)}>
|
|
||||||
<div className="progress-circle">
|
|
||||||
<i>2</i>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<span>{translate('SWAP_progress_2')}</span>
|
|
||||||
{originKind}...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={this.computedClass(3)}>
|
|
||||||
<div className="progress-circle">
|
|
||||||
<i>3</i>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{originKind} <span>{translate('SWAP_progress_3')}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={this.computedClass(4)}>
|
|
||||||
<div className="progress-circle">
|
|
||||||
<i>4</i>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<span>Sending your </span>
|
|
||||||
{destinationKind}
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
Waiting for {numberOfConfirmations} confirmations...
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={this.computedClass(5)}>
|
|
||||||
<div className="progress-circle">
|
|
||||||
<i>5</i>
|
|
||||||
</div>
|
|
||||||
<p>Order Complete</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
//flow
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import translate from 'translations';
|
||||||
|
import bityConfig from 'config/bity';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
destinationKind: string,
|
||||||
|
destinationAddress: string,
|
||||||
|
outputTx: string,
|
||||||
|
originKind: string,
|
||||||
|
orderStatus: string,
|
||||||
|
// actions
|
||||||
|
showNotification: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SwapProgress extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
hasShownViewTx: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.showNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification = () => {
|
||||||
|
const { hasShownViewTx } = this.state;
|
||||||
|
const {
|
||||||
|
destinationKind,
|
||||||
|
outputTx,
|
||||||
|
showNotification,
|
||||||
|
orderStatus
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (orderStatus === 'FILL') {
|
||||||
|
if (!hasShownViewTx) {
|
||||||
|
let linkElement;
|
||||||
|
let link;
|
||||||
|
// everything but BTC is a token
|
||||||
|
if (destinationKind !== 'BTC') {
|
||||||
|
link = bityConfig.ethExplorer.replace('[[txHash]]', outputTx);
|
||||||
|
linkElement = `<a href="${link}" target='_blank' rel='noopener'> View your transaction </a>`;
|
||||||
|
// BTC uses a different explorer
|
||||||
|
} else {
|
||||||
|
link = bityConfig.btcExplorer.replace('[[txHash]]', outputTx);
|
||||||
|
linkElement = `<a href="${link}" target='_blank' rel='noopener'> View your transaction </a>`;
|
||||||
|
}
|
||||||
|
this.setState({ hasShownViewTx: true }, () => {
|
||||||
|
showNotification('success', linkElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
computedClass = (step: number) => {
|
||||||
|
const { orderStatus } = this.props;
|
||||||
|
|
||||||
|
let cssClass = 'progress-item';
|
||||||
|
|
||||||
|
switch (orderStatus) {
|
||||||
|
case 'OPEN':
|
||||||
|
if (step < 2) {
|
||||||
|
return cssClass + ' progress-true';
|
||||||
|
} else if (step === 2) {
|
||||||
|
return cssClass + ' progress-active';
|
||||||
|
} else {
|
||||||
|
return cssClass;
|
||||||
|
}
|
||||||
|
case 'RCVE':
|
||||||
|
if (step < 4) {
|
||||||
|
return cssClass + ' progress-true';
|
||||||
|
} else if (step === 4) {
|
||||||
|
return cssClass + ' progress-active';
|
||||||
|
} else {
|
||||||
|
return cssClass;
|
||||||
|
}
|
||||||
|
case 'FILL':
|
||||||
|
cssClass += ' progress-true';
|
||||||
|
return cssClass;
|
||||||
|
case 'CANC':
|
||||||
|
return cssClass;
|
||||||
|
default:
|
||||||
|
return cssClass;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { destinationKind, originKind } = this.props;
|
||||||
|
|
||||||
|
const numberOfConfirmations = originKind === 'BTC' ? '3' : '10';
|
||||||
|
return (
|
||||||
|
<section className="row swap-progress">
|
||||||
|
<div className="sep" />
|
||||||
|
|
||||||
|
<div className={this.computedClass(1)}>
|
||||||
|
<div className="progress-circle">
|
||||||
|
<i>1</i>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{translate('SWAP_progress_1')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={this.computedClass(2)}>
|
||||||
|
<div className="progress-circle">
|
||||||
|
<i>2</i>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<span>{translate('SWAP_progress_2')}</span>
|
||||||
|
{originKind}...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={this.computedClass(3)}>
|
||||||
|
<div className="progress-circle">
|
||||||
|
<i>3</i>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{originKind} <span>{translate('SWAP_progress_3')}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={this.computedClass(4)}>
|
||||||
|
<div className="progress-circle">
|
||||||
|
<i>4</i>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<span>Sending your </span>
|
||||||
|
{destinationKind}
|
||||||
|
<br />
|
||||||
|
<small>
|
||||||
|
Waiting for {numberOfConfirmations} confirmations...
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={this.computedClass(5)}>
|
||||||
|
<div className="progress-circle">
|
||||||
|
<i>5</i>
|
||||||
|
</div>
|
||||||
|
<p>Order Complete</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { showNotification } from 'actions/notifications';
|
||||||
import * as swapActions from 'actions/swap';
|
import * as swapActions from 'actions/swap';
|
||||||
import type {
|
import type {
|
||||||
ChangeStepSwapAction,
|
ChangeStepSwapAction,
|
||||||
|
@ -7,16 +8,20 @@ import type {
|
||||||
DestinationKindSwapAction,
|
DestinationKindSwapAction,
|
||||||
OriginAmountSwapAction,
|
OriginAmountSwapAction,
|
||||||
DestinationAmountSwapAction,
|
DestinationAmountSwapAction,
|
||||||
LoadBityRatesSwapAction,
|
LoadBityRatesRequestedSwapAction,
|
||||||
DestinationAddressSwapAction,
|
DestinationAddressSwapAction,
|
||||||
RestartSwapAction,
|
RestartSwapAction,
|
||||||
StopLoadBityRatesSwapAction
|
StopLoadBityRatesSwapAction,
|
||||||
|
BityOrderCreateRequestedSwapAction,
|
||||||
|
StartPollBityOrderStatusAction,
|
||||||
|
StopOrderTimerSwapAction,
|
||||||
|
StopPollBityOrderStatusAction
|
||||||
} from 'actions/swap';
|
} from 'actions/swap';
|
||||||
import CurrencySwap from './components/CurrencySwap';
|
import CurrencySwap from './components/CurrencySwap';
|
||||||
import CurrentRates from './components/CurrentRates';
|
import CurrentRates from './components/CurrentRates';
|
||||||
import ReceivingAddress from './components/ReceivingAddress';
|
import ReceivingAddress from './components/ReceivingAddress';
|
||||||
import SwapInfoHeader from './components/SwapInfoHeader';
|
import SwapInfoHeader from './components/SwapInfoHeader';
|
||||||
import SwapProgress from './components/SwapProgress';
|
import PartThree from './components/PartThree';
|
||||||
|
|
||||||
type ReduxStateProps = {
|
type ReduxStateProps = {
|
||||||
step: string,
|
step: string,
|
||||||
|
@ -25,16 +30,16 @@ type ReduxStateProps = {
|
||||||
originKind: string,
|
originKind: string,
|
||||||
destinationKindOptions: String[],
|
destinationKindOptions: String[],
|
||||||
originKindOptions: String[],
|
originKindOptions: String[],
|
||||||
bityRates: boolean,
|
bityRates: {},
|
||||||
originAmount: ?number,
|
originAmount: ?number,
|
||||||
destinationAmount: ?number,
|
destinationAmount: ?number,
|
||||||
|
isPostingOrder: boolean,
|
||||||
isFetchingRates: boolean,
|
isFetchingRates: boolean,
|
||||||
// PART 3
|
bityOrder: {},
|
||||||
referenceNumber: string,
|
secondsRemaining: ?number,
|
||||||
timeRemaining: string,
|
paymentAddress: ?string,
|
||||||
numberOfConfirmation: number,
|
orderStatus: ?string,
|
||||||
orderStep: number,
|
outputTx: ?string
|
||||||
orderStarted: boolean
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReduxActionProps = {
|
type ReduxActionProps = {
|
||||||
|
@ -43,12 +48,19 @@ type ReduxActionProps = {
|
||||||
destinationKindSwap: (value: string) => DestinationKindSwapAction,
|
destinationKindSwap: (value: string) => DestinationKindSwapAction,
|
||||||
originAmountSwap: (value: ?number) => OriginAmountSwapAction,
|
originAmountSwap: (value: ?number) => OriginAmountSwapAction,
|
||||||
destinationAmountSwap: (value: ?number) => DestinationAmountSwapAction,
|
destinationAmountSwap: (value: ?number) => DestinationAmountSwapAction,
|
||||||
loadBityRatesSwap: () => LoadBityRatesSwapAction,
|
loadBityRatesRequestedSwap: () => LoadBityRatesRequestedSwapAction,
|
||||||
destinationAddressSwap: (value: ?string) => DestinationAddressSwapAction,
|
destinationAddressSwap: (value: ?string) => DestinationAddressSwapAction,
|
||||||
restartSwap: () => RestartSwapAction,
|
restartSwap: () => RestartSwapAction,
|
||||||
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
|
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
|
||||||
// PART 3 (IGNORE FOR NOW)
|
bityOrderCreateRequestedSwap: (
|
||||||
referenceNumberSwap: typeof swapActions.referenceNumberSwap
|
amount: number,
|
||||||
|
destinationAddress: string,
|
||||||
|
pair: string,
|
||||||
|
mode: number
|
||||||
|
) => BityOrderCreateRequestedSwapAction,
|
||||||
|
startPollBityOrderStatus: () => StartPollBityOrderStatusAction,
|
||||||
|
stopOrderTimerSwap: () => StopOrderTimerSwapAction,
|
||||||
|
stopPollBityOrderStatus: () => StopPollBityOrderStatusAction
|
||||||
};
|
};
|
||||||
|
|
||||||
class Swap extends Component {
|
class Swap extends Component {
|
||||||
|
@ -56,7 +68,7 @@ class Swap extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// TODO: Use `isFetchingRates` to show a loader
|
// TODO: Use `isFetchingRates` to show a loader
|
||||||
this.props.loadBityRatesSwap();
|
this.props.loadBityRatesRequestedSwap();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -75,10 +87,12 @@ class Swap extends Component {
|
||||||
originKindOptions,
|
originKindOptions,
|
||||||
destinationAddress,
|
destinationAddress,
|
||||||
step,
|
step,
|
||||||
referenceNumber,
|
bityOrder,
|
||||||
timeRemaining,
|
secondsRemaining,
|
||||||
numberOfConfirmations,
|
paymentAddress,
|
||||||
orderStep,
|
orderStatus,
|
||||||
|
isPostingOrder,
|
||||||
|
outputTx,
|
||||||
// ACTIONS
|
// ACTIONS
|
||||||
restartSwap,
|
restartSwap,
|
||||||
stopLoadBityRatesSwap,
|
stopLoadBityRatesSwap,
|
||||||
|
@ -88,35 +102,44 @@ class Swap extends Component {
|
||||||
originAmountSwap,
|
originAmountSwap,
|
||||||
destinationAmountSwap,
|
destinationAmountSwap,
|
||||||
destinationAddressSwap,
|
destinationAddressSwap,
|
||||||
referenceNumberSwap
|
bityOrderCreateRequestedSwap,
|
||||||
|
showNotification,
|
||||||
|
startOrderTimerSwap,
|
||||||
|
startPollBityOrderStatus,
|
||||||
|
stopOrderTimerSwap,
|
||||||
|
stopPollBityOrderStatus
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const { reference } = bityOrder;
|
||||||
|
|
||||||
let ReceivingAddressProps = {
|
let ReceivingAddressProps = {
|
||||||
|
isPostingOrder,
|
||||||
|
originAmount,
|
||||||
|
originKind,
|
||||||
destinationKind,
|
destinationKind,
|
||||||
destinationAddressSwap,
|
destinationAddressSwap,
|
||||||
destinationAddress,
|
destinationAddress,
|
||||||
stopLoadBityRatesSwap,
|
stopLoadBityRatesSwap,
|
||||||
changeStepSwap,
|
changeStepSwap,
|
||||||
referenceNumberSwap
|
bityOrderCreateRequestedSwap
|
||||||
};
|
};
|
||||||
|
|
||||||
let SwapInfoHeaderProps = {
|
let SwapInfoHeaderProps = {
|
||||||
referenceNumber,
|
reference,
|
||||||
timeRemaining,
|
secondsRemaining,
|
||||||
originAmount,
|
originAmount,
|
||||||
originKind,
|
originKind,
|
||||||
destinationKind,
|
destinationKind,
|
||||||
destinationAmount,
|
destinationAmount,
|
||||||
restartSwap,
|
restartSwap,
|
||||||
numberOfConfirmations,
|
orderStatus
|
||||||
orderStep
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { ETHBTC, ETHREP, BTCETH, BTCREP } = bityRates;
|
const { ETHBTC, ETHREP, BTCETH, BTCREP } = bityRates;
|
||||||
|
|
||||||
const CurrentRatesProps = { ETHBTC, ETHREP, BTCETH, BTCREP };
|
const CurrentRatesProps = { ETHBTC, ETHREP, BTCETH, BTCREP };
|
||||||
|
|
||||||
const CurrencySwapProps = {
|
const CurrencySwapProps = {
|
||||||
|
showNotification,
|
||||||
bityRates,
|
bityRates,
|
||||||
originAmount,
|
originAmount,
|
||||||
destinationAmount,
|
destinationAmount,
|
||||||
|
@ -131,6 +154,25 @@ class Swap extends Component {
|
||||||
changeStepSwap
|
changeStepSwap
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PaymentInfoProps = {
|
||||||
|
originKind,
|
||||||
|
originAmount,
|
||||||
|
paymentAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
const PartThreeProps = {
|
||||||
|
...SwapInfoHeaderProps,
|
||||||
|
...PaymentInfoProps,
|
||||||
|
reference,
|
||||||
|
startOrderTimerSwap,
|
||||||
|
startPollBityOrderStatus,
|
||||||
|
stopOrderTimerSwap,
|
||||||
|
stopPollBityOrderStatus,
|
||||||
|
showNotification,
|
||||||
|
destinationAddress,
|
||||||
|
outputTx
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="container" style={{ minHeight: '50%' }}>
|
<section className="container" style={{ minHeight: '50%' }}>
|
||||||
<div className="tab-content">
|
<div className="tab-content">
|
||||||
|
@ -143,7 +185,7 @@ class Swap extends Component {
|
||||||
{(step === 2 || step === 3) &&
|
{(step === 2 || step === 3) &&
|
||||||
<SwapInfoHeader {...SwapInfoHeaderProps} />}
|
<SwapInfoHeader {...SwapInfoHeaderProps} />}
|
||||||
{step === 2 && <ReceivingAddress {...ReceivingAddressProps} />}
|
{step === 2 && <ReceivingAddress {...ReceivingAddressProps} />}
|
||||||
{step === 3 && <SwapProgress {...SwapInfoHeaderProps} />}
|
{step === 3 && <PartThree {...PartThreeProps} />}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -153,6 +195,10 @@ class Swap extends Component {
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
|
outputTx: state.swap.outputTx,
|
||||||
|
isPostingOrder: state.swap.isPostingOrder,
|
||||||
|
orderStatus: state.swap.orderStatus,
|
||||||
|
paymentAddress: state.swap.paymentAddress,
|
||||||
step: state.swap.step,
|
step: state.swap.step,
|
||||||
destinationAddress: state.swap.destinationAddress,
|
destinationAddress: state.swap.destinationAddress,
|
||||||
originAmount: state.swap.originAmount,
|
originAmount: state.swap.originAmount,
|
||||||
|
@ -162,13 +208,12 @@ function mapStateToProps(state) {
|
||||||
destinationKindOptions: state.swap.destinationKindOptions,
|
destinationKindOptions: state.swap.destinationKindOptions,
|
||||||
originKindOptions: state.swap.originKindOptions,
|
originKindOptions: state.swap.originKindOptions,
|
||||||
bityRates: state.swap.bityRates,
|
bityRates: state.swap.bityRates,
|
||||||
referenceNumber: state.swap.referenceNumber,
|
bityOrder: state.swap.bityOrder,
|
||||||
timeRemaining: state.swap.timeRemaining,
|
secondsRemaining: state.swap.secondsRemaining,
|
||||||
numberOfConfirmations: state.swap.numberOfConfirmations,
|
|
||||||
orderStep: state.swap.orderStep,
|
|
||||||
orderStarted: state.swap.orderStarted,
|
|
||||||
isFetchingRates: state.swap.isFetchingRates
|
isFetchingRates: state.swap.isFetchingRates
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, swapActions)(Swap);
|
export default connect(mapStateToProps, { ...swapActions, showNotification })(
|
||||||
|
Swap
|
||||||
|
);
|
|
@ -1,5 +1,6 @@
|
||||||
// Application styles must come first in order, to allow for overrides
|
// Application styles must come first in order, to allow for overrides
|
||||||
import 'assets/styles/etherwallet-master.less';
|
import 'assets/styles/etherwallet-master.less';
|
||||||
|
import 'font-awesome/scss/font-awesome.scss';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { combineAndUpper } from 'utils/formatters';
|
import { combineAndUpper } from 'utils/formatters';
|
||||||
import type { SwapAction } from 'actions/swap';
|
import type { SwapAction } from 'actions/swapTypes';
|
||||||
|
import without from 'lodash/without';
|
||||||
export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP'];
|
export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP'];
|
||||||
|
const DEFAULT_ORIGIN_KIND = 'BTC';
|
||||||
|
const DEFAULT_DESTINATION_KIND = 'ETH';
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
originAmount: number,
|
originAmount: ?number,
|
||||||
destinationAmount: number,
|
destinationAmount: ?number,
|
||||||
originKind: string,
|
originKind: string,
|
||||||
destinationKind: string,
|
destinationKind: string,
|
||||||
destinationKindOptions: Array<string>,
|
destinationKindOptions: Array<string>,
|
||||||
|
@ -14,32 +16,37 @@ type State = {
|
||||||
step: number,
|
step: number,
|
||||||
bityRates: Object,
|
bityRates: Object,
|
||||||
destinationAddress: string,
|
destinationAddress: string,
|
||||||
referenceNumber: string,
|
isFetchingRates: ?boolean,
|
||||||
timeRemaining: string,
|
secondsRemaining: ?number,
|
||||||
numberOfConfirmations: ?number,
|
outputTx: ?string,
|
||||||
orderStep: ?number,
|
isPostingOrder: ?boolean,
|
||||||
isFetchingRates: boolean
|
orderStatus: ?string,
|
||||||
|
orderTimestampCreatedISOString: ?string,
|
||||||
|
paymentAddress: ?string,
|
||||||
|
validFor: ?number,
|
||||||
|
orderId: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INITIAL_STATE: State = {
|
export const INITIAL_STATE: State = {
|
||||||
originAmount: 0,
|
originAmount: null,
|
||||||
destinationAmount: 0,
|
destinationAmount: null,
|
||||||
originKind: 'BTC',
|
originKind: DEFAULT_ORIGIN_KIND,
|
||||||
destinationKind: 'ETH',
|
destinationKind: DEFAULT_DESTINATION_KIND,
|
||||||
destinationKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(element => {
|
destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, DEFAULT_ORIGIN_KIND),
|
||||||
return element !== 'BTC';
|
originKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, 'REP'),
|
||||||
}),
|
|
||||||
originKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(element => {
|
|
||||||
return element !== 'REP';
|
|
||||||
}),
|
|
||||||
step: 1,
|
step: 1,
|
||||||
bityRates: {},
|
bityRates: {},
|
||||||
destinationAddress: '',
|
destinationAddress: '',
|
||||||
referenceNumber: '',
|
bityOrder: {},
|
||||||
timeRemaining: '',
|
isFetchingRates: null,
|
||||||
numberOfConfirmations: null,
|
secondsRemaining: null,
|
||||||
orderStep: null,
|
outputTx: null,
|
||||||
isFetchingRates: false
|
isPostingOrder: false,
|
||||||
|
orderStatus: null,
|
||||||
|
orderTimestampCreatedISOString: null,
|
||||||
|
paymentAddress: null,
|
||||||
|
validFor: null,
|
||||||
|
orderId: null
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildDestinationAmount = (
|
const buildDestinationAmount = (
|
||||||
|
@ -50,7 +57,7 @@ const buildDestinationAmount = (
|
||||||
) => {
|
) => {
|
||||||
let pairName = combineAndUpper(originKind, destinationKind);
|
let pairName = combineAndUpper(originKind, destinationKind);
|
||||||
let bityRate = bityRates[pairName];
|
let bityRate = bityRates[pairName];
|
||||||
return originAmount * bityRate;
|
return originAmount ? originAmount * bityRate : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildDestinationKind = (
|
const buildDestinationKind = (
|
||||||
|
@ -58,7 +65,7 @@ const buildDestinationKind = (
|
||||||
destinationKind: string
|
destinationKind: string
|
||||||
): string => {
|
): string => {
|
||||||
if (originKind === destinationKind) {
|
if (originKind === destinationKind) {
|
||||||
return ALL_CRYPTO_KIND_OPTIONS.filter(element => element !== originKind)[0];
|
return without(ALL_CRYPTO_KIND_OPTIONS, originKind)[0];
|
||||||
} else {
|
} else {
|
||||||
return destinationKind;
|
return destinationKind;
|
||||||
}
|
}
|
||||||
|
@ -75,10 +82,7 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
|
||||||
...state,
|
...state,
|
||||||
originKind: action.value,
|
originKind: action.value,
|
||||||
destinationKind: newDestinationKind,
|
destinationKind: newDestinationKind,
|
||||||
destinationKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(element => {
|
destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, action.value),
|
||||||
// $FlowFixMe
|
|
||||||
return element !== action.value;
|
|
||||||
}),
|
|
||||||
destinationAmount: buildDestinationAmount(
|
destinationAmount: buildDestinationAmount(
|
||||||
state.originAmount,
|
state.originAmount,
|
||||||
action.value,
|
action.value,
|
||||||
|
@ -109,7 +113,7 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
|
||||||
...state,
|
...state,
|
||||||
destinationAmount: action.value
|
destinationAmount: action.value
|
||||||
};
|
};
|
||||||
case 'SWAP_UPDATE_BITY_RATES':
|
case 'SWAP_LOAD_BITY_RATES_SUCCEEDED':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
bityRates: {
|
bityRates: {
|
||||||
|
@ -134,16 +138,48 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
|
||||||
...INITIAL_STATE,
|
...INITIAL_STATE,
|
||||||
bityRates: state.bityRates
|
bityRates: state.bityRates
|
||||||
};
|
};
|
||||||
case 'SWAP_REFERENCE_NUMBER':
|
case 'SWAP_ORDER_CREATE_REQUESTED':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
referenceNumber: '2341asdfads',
|
isPostingOrder: true
|
||||||
timeRemaining: '2:30',
|
};
|
||||||
numberOfConfirmations: 3,
|
case 'SWAP_ORDER_CREATE_FAILED':
|
||||||
orderStep: 2
|
return {
|
||||||
|
...state,
|
||||||
|
isPostingOrder: false
|
||||||
|
};
|
||||||
|
case 'SWAP_BITY_ORDER_CREATE_SUCCEEDED':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
bityOrder: {
|
||||||
|
...action.payload
|
||||||
|
},
|
||||||
|
isPostingOrder: false,
|
||||||
|
originAmount: parseFloat(action.payload.input.amount),
|
||||||
|
destinationAmount: parseFloat(action.payload.output.amount),
|
||||||
|
secondsRemaining: action.payload.validFor, // will get update
|
||||||
|
validFor: action.payload.validFor, // to build from local storage
|
||||||
|
orderTimestampCreatedISOString: action.payload.timestamp_created,
|
||||||
|
paymentAddress: action.payload.payment_address,
|
||||||
|
orderStatus: action.payload.status,
|
||||||
|
orderId: action.payload.id
|
||||||
|
};
|
||||||
|
case 'SWAP_BITY_ORDER_STATUS_SUCCEEDED':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
outputTx: action.payload.output.reference,
|
||||||
|
orderStatus:
|
||||||
|
action.payload.output.status === 'FILL'
|
||||||
|
? action.payload.output.status
|
||||||
|
: action.payload.input.status
|
||||||
|
};
|
||||||
|
case 'SWAP_ORDER_TIME':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
secondsRemaining: action.value
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'SWAP_LOAD_BITY_RATES':
|
case 'SWAP_LOAD_BITY_RATES_REQUESTED':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isFetchingRates: true
|
isFetchingRates: true
|
||||||
|
@ -156,7 +192,6 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
(action: empty);
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,23 @@
|
||||||
import bity from './bity';
|
import {
|
||||||
|
postBityOrderSaga,
|
||||||
|
bityTimeRemaining,
|
||||||
|
pollBityOrderStatusSaga
|
||||||
|
} from './swap/orders';
|
||||||
|
import { getBityRatesSaga } from './swap/rates';
|
||||||
import contracts from './contracts';
|
import contracts from './contracts';
|
||||||
import ens from './ens';
|
import ens from './ens';
|
||||||
import notifications from './notifications';
|
import notifications from './notifications';
|
||||||
import rates from './rates';
|
import rates from './rates';
|
||||||
import wallet from './wallet';
|
import wallet from './wallet';
|
||||||
|
|
||||||
export default { bity, contracts, ens, notifications, rates, wallet };
|
export default {
|
||||||
|
bityTimeRemaining,
|
||||||
|
postBityOrderSaga,
|
||||||
|
pollBityOrderStatusSaga,
|
||||||
|
getBityRatesSaga,
|
||||||
|
contracts,
|
||||||
|
ens,
|
||||||
|
notifications,
|
||||||
|
rates,
|
||||||
|
wallet
|
||||||
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ function* handleNotification(action?: ShowNotificationAction) {
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
const { duration } = action.payload;
|
const { duration } = action.payload;
|
||||||
// show forever
|
// show forever
|
||||||
if (duration === 0) {
|
if (duration === 0 || duration === 'infinity') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
import { showNotification } from 'actions/notifications';
|
||||||
|
import { delay } from 'redux-saga';
|
||||||
|
import { postOrder, getOrderStatus } from 'api/bity';
|
||||||
|
import {
|
||||||
|
call,
|
||||||
|
put,
|
||||||
|
fork,
|
||||||
|
take,
|
||||||
|
cancel,
|
||||||
|
select,
|
||||||
|
cancelled,
|
||||||
|
takeEvery,
|
||||||
|
Effect
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
import {
|
||||||
|
orderTimeSwap,
|
||||||
|
bityOrderCreateSucceededSwap,
|
||||||
|
stopLoadBityRatesSwap,
|
||||||
|
changeStepSwap,
|
||||||
|
orderStatusRequestedSwap,
|
||||||
|
orderStatusSucceededSwap,
|
||||||
|
startOrderTimerSwap,
|
||||||
|
startPollBityOrderStatus,
|
||||||
|
stopPollBityOrderStatus
|
||||||
|
} from 'actions/swap';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export const getSwap = state => state.swap;
|
||||||
|
const ONE_SECOND = 1000;
|
||||||
|
const TEN_SECONDS = ONE_SECOND * 10;
|
||||||
|
const BITY_TIMEOUT_MESSAGE = `
|
||||||
|
Time has run out.
|
||||||
|
If you have already sent, please wait 1 hour.
|
||||||
|
If your order has not be processed after 1 hour,
|
||||||
|
please press the orange 'Issue with your Swap?' button.
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function* pollBityOrderStatus(): Generator<Effect, void, any> {
|
||||||
|
try {
|
||||||
|
let swap = yield select(getSwap);
|
||||||
|
while (true) {
|
||||||
|
yield put(orderStatusRequestedSwap());
|
||||||
|
const orderStatus = yield call(getOrderStatus, swap.orderId);
|
||||||
|
if (orderStatus.error) {
|
||||||
|
yield put(
|
||||||
|
showNotification(
|
||||||
|
'danger',
|
||||||
|
`Bity Error: ${orderStatus.msg}`,
|
||||||
|
TEN_SECONDS
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
yield put(orderStatusSucceededSwap(orderStatus.data));
|
||||||
|
yield call(delay, ONE_SECOND * 5);
|
||||||
|
swap = yield select(getSwap);
|
||||||
|
if (swap === 'CANC') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (yield cancelled()) {
|
||||||
|
// TODO - implement request cancel if needed
|
||||||
|
// yield put(actions.requestFailure('Request cancelled!'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* pollBityOrderStatusSaga(): Generator<Effect, void, any> {
|
||||||
|
while (yield take('SWAP_START_POLL_BITY_ORDER_STATUS')) {
|
||||||
|
// starts the task in the background
|
||||||
|
const pollBityOrderStatusTask = yield fork(pollBityOrderStatus);
|
||||||
|
// wait for the user to get to point where refresh is no longer needed
|
||||||
|
yield take('SWAP_STOP_POLL_BITY_ORDER_STATUS');
|
||||||
|
// cancel the background task
|
||||||
|
// this will cause the forked loadBityRates task to jump into its finally block
|
||||||
|
yield cancel(pollBityOrderStatusTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* postBityOrderCreate(action) {
|
||||||
|
const payload = action.payload;
|
||||||
|
try {
|
||||||
|
yield put(stopLoadBityRatesSwap());
|
||||||
|
const order = yield call(
|
||||||
|
postOrder,
|
||||||
|
payload.amount,
|
||||||
|
payload.destAddress,
|
||||||
|
payload.mode,
|
||||||
|
payload.pair
|
||||||
|
);
|
||||||
|
if (order.error) {
|
||||||
|
// TODO - handle better / like existing site?
|
||||||
|
yield put(
|
||||||
|
showNotification('danger', `Bity Error: ${order.msg}`, TEN_SECONDS)
|
||||||
|
);
|
||||||
|
yield put({ type: 'SWAP_ORDER_CREATE_FAILED' });
|
||||||
|
} else {
|
||||||
|
yield put(bityOrderCreateSucceededSwap(order.data));
|
||||||
|
yield put(changeStepSwap(3));
|
||||||
|
// start countdown
|
||||||
|
yield put(startOrderTimerSwap());
|
||||||
|
// start bity order status polling
|
||||||
|
yield put(startPollBityOrderStatus());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const message =
|
||||||
|
'Connection Error. Please check the developer console for more details and/or contact support';
|
||||||
|
yield put(showNotification('danger', message, TEN_SECONDS));
|
||||||
|
yield put({ type: 'SWAP_ORDER_CREATE_FAILED' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* postBityOrderSaga(): Generator<Effect, void, any> {
|
||||||
|
yield takeEvery('SWAP_ORDER_CREATE_REQUESTED', postBityOrderCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* bityTimeRemaining() {
|
||||||
|
while (yield take('SWAP_ORDER_START_TIMER')) {
|
||||||
|
let hasShownNotification = false;
|
||||||
|
while (true) {
|
||||||
|
yield call(delay, ONE_SECOND);
|
||||||
|
const swap = yield select(getSwap);
|
||||||
|
// if (swap.bityOrder.status === 'OPEN') {
|
||||||
|
const createdTimeStampMoment = moment(
|
||||||
|
swap.orderTimestampCreatedISOString
|
||||||
|
);
|
||||||
|
let validUntil = moment(createdTimeStampMoment).add(swap.validFor, 's');
|
||||||
|
let now = moment();
|
||||||
|
if (validUntil.isAfter(now)) {
|
||||||
|
let duration = moment.duration(validUntil.diff(now));
|
||||||
|
let seconds = duration.asSeconds();
|
||||||
|
yield put(orderTimeSwap(parseInt(seconds)));
|
||||||
|
// TODO (!Important) - check orderStatus here and stop polling / show notifications based on status
|
||||||
|
} else {
|
||||||
|
switch (swap.orderStatus) {
|
||||||
|
case 'OPEN':
|
||||||
|
yield put(orderTimeSwap(0));
|
||||||
|
yield put(stopPollBityOrderStatus());
|
||||||
|
yield put({ type: 'SWAP_STOP_LOAD_BITY_RATES' });
|
||||||
|
|
||||||
|
if (!hasShownNotification) {
|
||||||
|
hasShownNotification = true;
|
||||||
|
yield put(
|
||||||
|
showNotification('danger', BITY_TIMEOUT_MESSAGE, 'infinity')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'CANC':
|
||||||
|
yield put(orderTimeSwap(0));
|
||||||
|
yield put(stopPollBityOrderStatus());
|
||||||
|
yield put({ type: 'SWAP_STOP_LOAD_BITY_RATES' });
|
||||||
|
if (!hasShownNotification) {
|
||||||
|
hasShownNotification = true;
|
||||||
|
yield put(
|
||||||
|
showNotification('danger', BITY_TIMEOUT_MESSAGE, 'infinity')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'RCVE':
|
||||||
|
yield put(orderTimeSwap(0));
|
||||||
|
if (!hasShownNotification) {
|
||||||
|
hasShownNotification = true;
|
||||||
|
yield put(
|
||||||
|
showNotification('warning', BITY_TIMEOUT_MESSAGE, 'infinity')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'FILL':
|
||||||
|
yield put(orderTimeSwap(0));
|
||||||
|
yield put(stopPollBityOrderStatus());
|
||||||
|
yield put({ type: 'SWAP_STOP_LOAD_BITY_RATES' });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,26 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { call, put, fork, take, cancel, cancelled } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
import type { Effect } from 'redux-saga/effects';
|
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import { updateBityRatesSwap } from 'actions/swap';
|
|
||||||
import { getAllRates } from 'api/bity';
|
import { getAllRates } from 'api/bity';
|
||||||
|
import {
|
||||||
|
call,
|
||||||
|
put,
|
||||||
|
fork,
|
||||||
|
take,
|
||||||
|
cancel,
|
||||||
|
cancelled,
|
||||||
|
takeLatest
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
import type { Effect } from 'redux-saga/effects';
|
||||||
|
import { loadBityRatesSucceededSwap } from 'actions/swap';
|
||||||
|
|
||||||
export function* loadBityRates(_action?: any): Generator<Effect, void, any> {
|
export function* loadBityRates(_action?: any): Generator<Effect, void, any> {
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
// TODO - yield put(actions.requestStart()) if we want to display swap refresh status
|
// TODO - BITY_RATE_REQUESTED
|
||||||
// network request
|
// network request
|
||||||
const data = yield call(getAllRates);
|
const data = yield call(getAllRates);
|
||||||
// action
|
// action
|
||||||
yield put(updateBityRatesSwap(data));
|
yield put(loadBityRatesSucceededSwap(data));
|
||||||
// wait 5 seconds before refreshing rates
|
// wait 5 seconds before refreshing rates
|
||||||
yield call(delay, 5000);
|
yield call(delay, 5000);
|
||||||
}
|
}
|
||||||
|
@ -25,11 +32,10 @@ export function* loadBityRates(_action?: any): Generator<Effect, void, any> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function* bitySaga(): Generator<Effect, void, any> {
|
export function* getBityRatesSaga(): Generator<Effect, void, any> {
|
||||||
while (yield take('SWAP_LOAD_BITY_RATES')) {
|
while (yield take('SWAP_LOAD_BITY_RATES_REQUESTED')) {
|
||||||
// starts the task in the background
|
// starts the task in the background
|
||||||
const loadBityRatesTask = yield fork(loadBityRates);
|
const loadBityRatesTask = yield fork(loadBityRates);
|
||||||
|
|
||||||
// wait for the user to get to point where refresh is no longer needed
|
// wait for the user to get to point where refresh is no longer needed
|
||||||
yield take('SWAP_STOP_LOAD_BITY_RATES');
|
yield take('SWAP_STOP_LOAD_BITY_RATES');
|
||||||
// cancel the background task
|
// cancel the background task
|
|
@ -8,6 +8,7 @@ import createSagaMiddleware from 'redux-saga';
|
||||||
import sagas from './sagas';
|
import sagas from './sagas';
|
||||||
import { INITIAL_STATE as configInitialState } from 'reducers/config';
|
import { INITIAL_STATE as configInitialState } from 'reducers/config';
|
||||||
import { INITIAL_STATE as customTokensInitialState } from 'reducers/customTokens';
|
import { INITIAL_STATE as customTokensInitialState } from 'reducers/customTokens';
|
||||||
|
import { INITIAL_STATE as swapInitialState } from 'reducers/swap';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
import Perf from 'react-addons-perf';
|
import Perf from 'react-addons-perf';
|
||||||
|
@ -37,7 +38,15 @@ const configureStore = () => {
|
||||||
...configInitialState,
|
...configInitialState,
|
||||||
...loadStatePropertyOrEmptyObject('config')
|
...loadStatePropertyOrEmptyObject('config')
|
||||||
},
|
},
|
||||||
customTokens: (loadState() || {}).customTokens || customTokensInitialState
|
customTokens: (loadState() || {}).customTokens || customTokensInitialState,
|
||||||
|
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
|
||||||
|
swap:
|
||||||
|
loadStatePropertyOrEmptyObject('swap').step === 3
|
||||||
|
? {
|
||||||
|
...swapInitialState,
|
||||||
|
...loadStatePropertyOrEmptyObject('swap')
|
||||||
|
}
|
||||||
|
: { ...swapInitialState }
|
||||||
};
|
};
|
||||||
|
|
||||||
store = createStore(RootReducer, persistedInitialState, middleware);
|
store = createStore(RootReducer, persistedInitialState, middleware);
|
||||||
|
@ -53,6 +62,7 @@ const configureStore = () => {
|
||||||
config: {
|
config: {
|
||||||
languageSelection: store.getState().config.languageSelection
|
languageSelection: store.getState().config.languageSelection
|
||||||
},
|
},
|
||||||
|
swap: store.getState().swap,
|
||||||
customTokens: store.getState().customTokens
|
customTokens: store.getState().customTokens
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -4252,6 +4252,11 @@
|
||||||
"integrity": "sha1-1M2yQw3uGjWZ8Otv5VEUbjAnJWo=",
|
"integrity": "sha1-1M2yQw3uGjWZ8Otv5VEUbjAnJWo=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"font-awesome": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||||
|
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
|
||||||
|
},
|
||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||||
|
@ -8224,6 +8229,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
|
||||||
|
"integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
|
||||||
|
},
|
||||||
"mozjpeg": {
|
"mozjpeg": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/mozjpeg/-/mozjpeg-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/mozjpeg/-/mozjpeg-4.1.1.tgz",
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"big.js": "^3.1.3",
|
"big.js": "^3.1.3",
|
||||||
"ethereum-blockies": "https://github.com/MyEtherWallet/blockies.git",
|
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
|
||||||
"ethereumjs-util": "^5.1.2",
|
"ethereumjs-util": "^5.1.2",
|
||||||
"ethereumjs-wallet": "^0.6.0",
|
"ethereumjs-wallet": "^0.6.0",
|
||||||
|
"font-awesome": "^4.7.0",
|
||||||
"idna-uts46": "^1.1.0",
|
"idna-uts46": "^1.1.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
|
"moment": "^2.18.1",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.5.8",
|
||||||
"qrcode": "^0.8.2",
|
"qrcode": "^0.8.2",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
|
|
|
@ -40,10 +40,6 @@ module.exports = {
|
||||||
loaders: ['babel-loader'],
|
loaders: ['babel-loader'],
|
||||||
exclude: [/node_modules\/(?!ethereum-blockies|idna-uts46)/]
|
exclude: [/node_modules\/(?!ethereum-blockies|idna-uts46)/]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.(ico|webp|eot|otf|ttf|woff|woff2)(\?.*)?$/,
|
|
||||||
loader: 'file-loader?limit=100000'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
test: /\.(gif|png|jpe?g|svg)$/i,
|
test: /\.(gif|png|jpe?g|svg)$/i,
|
||||||
loaders: [
|
loaders: [
|
||||||
|
|
Loading…
Reference in New Issue