diff --git a/common/actions/notifications.js b/common/actions/notifications.js
index 8f0a8cdc..a1df1a50 100644
--- a/common/actions/notifications.js
+++ b/common/actions/notifications.js
@@ -2,11 +2,12 @@
/*** Shared types ***/
export type NOTIFICATION_LEVEL = 'danger' | 'warning' | 'success' | 'info';
+export type INFINITY = 'infinity';
export type Notification = {
level: NOTIFICATION_LEVEL,
msg: string,
- duration?: number
+ duration?: number | INFINITY
};
/*** Show Notification ***/
diff --git a/common/actions/swap.js b/common/actions/swap.js
index 309a51bd..a9027669 100644
--- a/common/actions/swap.js
+++ b/common/actions/swap.js
@@ -1,10 +1,27 @@
// @flow
-
-/*** Change Step ***/
-export type ChangeStepSwapAction = {
- type: 'SWAP_STEP',
- value: number
-};
+import type {
+ OriginKindSwapAction,
+ DestinationKindSwapAction,
+ OriginAmountSwapAction,
+ 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 {
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 {
return {
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 {
return {
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 {
return {
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(
value: ?number
): DestinationAmountSwapAction {
@@ -80,32 +60,15 @@ export function destinationAmountSwap(
};
}
-/*** Update Bity Rates ***/
-export type Pairs = {
- ETHBTC: number,
- ETHREP: number,
- BTCETH: number,
- BTCREP: number
-};
-
-export type BityRatesSwapAction = {
- type: 'SWAP_UPDATE_BITY_RATES',
+export function loadBityRatesSucceededSwap(
value: Pairs
-};
-
-export function updateBityRatesSwap(value: Pairs): BityRatesSwapAction {
+): LoadBityRatesSucceededSwapAction {
return {
- type: 'SWAP_UPDATE_BITY_RATES',
+ type: 'SWAP_LOAD_BITY_RATES_SUCCEEDED',
value
};
}
-/*** Change Destination Address ***/
-export type DestinationAddressSwapAction = {
- type: 'SWAP_DESTINATION_ADDRESS',
- value: ?string
-};
-
export function destinationAddressSwap(
value: ?string
): DestinationAddressSwapAction {
@@ -115,49 +78,92 @@ export function destinationAddressSwap(
};
}
-/*** Restart ***/
-export type RestartSwapAction = {
- type: 'SWAP_RESTART'
-};
-
export function restartSwap(): RestartSwapAction {
return {
type: 'SWAP_RESTART'
};
}
-/*** Load Bity Rates ***/
-export type LoadBityRatesSwapAction = {
- type: 'SWAP_LOAD_BITY_RATES'
-};
-
-export function loadBityRatesSwap(): LoadBityRatesSwapAction {
+export function loadBityRatesRequestedSwap(): LoadBityRatesRequestedSwapAction {
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 {
return {
type: 'SWAP_STOP_LOAD_BITY_RATES'
};
}
-/*** Action Type Union ***/
-export type SwapAction =
- | ChangeStepSwapAction
- | ReferenceNumberSwapAction
- | OriginKindSwapAction
- | DestinationKindSwapAction
- | OriginAmountSwapAction
- | DestinationAmountSwapAction
- | BityRatesSwapAction
- | DestinationAddressSwapAction
- | RestartSwapAction
- | LoadBityRatesSwapAction
- | StopLoadBityRatesSwapAction;
+export function orderTimeSwap(value: number) {
+ return {
+ type: 'SWAP_ORDER_TIME',
+ value
+ };
+}
+
+export function bityOrderCreateSucceededSwap(
+ payload: BityOrderPostResponse
+): BityOrderCreateSucceededSwapAction {
+ return {
+ type: 'SWAP_BITY_ORDER_CREATE_SUCCEEDED',
+ 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'
+ };
+}
diff --git a/common/actions/swapTypes.js b/common/actions/swapTypes.js
new file mode 100644
index 00000000..c9001734
--- /dev/null
+++ b/common/actions/swapTypes.js
@@ -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;
diff --git a/common/api/bity.js b/common/api/bity.js
index 90e73f83..c2da47c4 100644
--- a/common/api/bity.js
+++ b/common/api/bity.js
@@ -1,38 +1,18 @@
// @flow
import bityConfig from 'config/bity';
-import {combineAndUpper} from 'utils/formatters'
+import { checkHttpStatus, parseJSON } from './utils';
+import { combineAndUpper } from 'utils/formatters';
-
-function findRateFromBityRateList(rateObjects, pairName) {
+function findRateFromBityRateList(rateObjects, pairName: string) {
return rateObjects.find(x => x.pair === pairName);
}
-// FIXME better types
-function _getRate(bityRates, origin: string, destination: string) {
- const pairName = combineAndUpper(origin, destination);
+function _getRate(bityRates, originKind: string, destinationKind: string) {
+ const pairName = combineAndUpper(originKind, destinationKind);
const rateObjects = bityRates.objects;
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() {
const mappedRates = {};
return _getAllRates().then(bityRates => {
@@ -42,11 +22,44 @@ export function getAllRates() {
});
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() {
- return fetch(`${bityConfig.bityAPI}/v1/rate2/`).then(r => r.json());
+ return fetch(`${bityConfig.bityAPI}/v1/rate2/`)
+ .then(checkHttpStatus)
+ .then(parseJSON);
}
-function requestStatus() {}
+function requestOrderStatus() {}
diff --git a/common/api/utils.js b/common/api/utils.js
index f97d1908..bac14eb5 100644
--- a/common/api/utils.js
+++ b/common/api/utils.js
@@ -1,117 +1,13 @@
-// Request utils,
-// feel free to replace with your code
-// (get, post are used in ApiServices)
-
-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
+export function checkHttpStatus(response) {
+ if (response.status >= 200 && response.status < 300) {
+ return response;
} else {
- return false;
+ let error = new Error(response.statusText);
+ error.response = response;
+ throw error;
}
}
+
+export function parseJSON(response) {
+ return response.json();
+}
diff --git a/common/components/Header/components/Navigation.jsx b/common/components/Header/components/Navigation.jsx
index a51c3a00..b704f421 100644
--- a/common/components/Header/components/Navigation.jsx
+++ b/common/components/Header/components/Navigation.jsx
@@ -74,10 +74,11 @@ export default class TabsOptions extends Component {
{tabs.map((object, i) => {
// 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
- ? 'is-active'
- : '';
+ ? 'is-active'
+ : '';
return (
{
+ return ;
+};
+
+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 (
+
+
+ {loading
+ ?
+
+ {` ${loadingText || text}`}
+
+ :
+ {text}
+
}
+
+
+ );
+ }
+}
diff --git a/common/components/ui/SimpleDropdown.js b/common/components/ui/SimpleDropdown.jsx
similarity index 100%
rename from common/components/ui/SimpleDropdown.js
rename to common/components/ui/SimpleDropdown.jsx
diff --git a/common/config/bity.js b/common/config/bity.js
index 5bd49720..3aeeb40b 100644
--- a/common/config/bity.js
+++ b/common/config/bity.js
@@ -1,18 +1,39 @@
export default {
serverURL: 'https://bity.myetherapi.com',
bityAPI: 'https://bity.com/api',
- decimals: 6,
ethExplorer: 'https://etherscan.io/tx/[[txHash]]',
btcExplorer: 'https://blockchain.info/tx/[[txHash]]',
validStatus: ['RCVE', 'FILL', 'CONF', 'EXEC'],
invalidStatus: ['CANC'],
- mainPairs: ['REP', 'ETH'],
- min: 0.01,
- max: 3,
- priceLoaded: false,
+ // 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.
+ // This is a bad magic number, but will suffice for now
+ ETHBuffer: 0.1, // percent higher/lower than 0.01 BTC worth
+ 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: {
headers: {
- 'Content-Type': 'application/json; charse:UTF-8'
+ 'Content-Type': 'application/json; charset:UTF-8'
}
}
};
diff --git a/common/containers/Tabs/Swap/components/CurrencySwap.js b/common/containers/Tabs/Swap/components/CurrencySwap.js
deleted file mode 100644
index 3638ac18..00000000
--- a/common/containers/Tabs/Swap/components/CurrencySwap.js
+++ /dev/null
@@ -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 (
-
-
- {translate('SWAP_init_1')}
-
- 0
- ? 'is-valid'
- : 'is-invalid'}`}
- type="number"
- placeholder="Amount"
- value={originAmount || ''}
- onChange={this.onChangeOriginAmount}
- />
-
-
-
-
- {translate('SWAP_init_2')}
-
-
- 0
- ? 'is-valid'
- : 'is-invalid'}`}
- type="number"
- placeholder="Amount"
- value={destinationAmount || ''}
- onChange={this.onChangeDestinationAmount}
- />
-
-
-
-
-
- {translate('SWAP_init_CTA')}
-
-
-
-
- );
- }
-}
diff --git a/common/containers/Tabs/Swap/components/CurrencySwap.jsx b/common/containers/Tabs/Swap/components/CurrencySwap.jsx
new file mode 100644
index 00000000..edf1f8c8
--- /dev/null
+++ b/common/containers/Tabs/Swap/components/CurrencySwap.jsx
@@ -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 (
+
+
+ {translate('SWAP_init_1')}
+
+
+
+
+
+
+ {translate('SWAP_init_2')}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/Swap/components/CurrentRates.js b/common/containers/Tabs/Swap/components/CurrentRates.jsx
similarity index 98%
rename from common/containers/Tabs/Swap/components/CurrentRates.js
rename to common/containers/Tabs/Swap/components/CurrentRates.jsx
index b2dcc5f7..d9fcda20 100644
--- a/common/containers/Tabs/Swap/components/CurrentRates.js
+++ b/common/containers/Tabs/Swap/components/CurrentRates.jsx
@@ -2,7 +2,7 @@
import React, { Component } from 'react';
import translate from 'translations';
import { toFixedIfLarger } from 'utils/formatters';
-import type { Pairs } from 'actions/swap';
+import type { Pairs } from 'actions/swapTypes';
import { bityReferralURL } from 'config/data';
import bityLogoWhite from 'assets/images/logo-bity-white.svg';
diff --git a/common/containers/Tabs/Swap/components/PartThree.jsx b/common/containers/Tabs/Swap/components/PartThree.jsx
new file mode 100644
index 00000000..aa168634
--- /dev/null
+++ b/common/containers/Tabs/Swap/components/PartThree.jsx
@@ -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 (
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/Swap/components/PaymentInfo.jsx b/common/containers/Tabs/Swap/components/PaymentInfo.jsx
new file mode 100644
index 00000000..22c7f429
--- /dev/null
+++ b/common/containers/Tabs/Swap/components/PaymentInfo.jsx
@@ -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 (
+
+
+
+ {translate('SWAP_order_CTA')}
+
+
+ {this.props.originAmount} {this.props.originKind}{' '}
+
+
+ {translate('SENDModal_Content_2')}
+
+
+
+ {this.props.paymentAddress}
+
+
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/Swap/components/ReceivingAddress.js b/common/containers/Tabs/Swap/components/ReceivingAddress.jsx
similarity index 73%
rename from common/containers/Tabs/Swap/components/ReceivingAddress.js
rename to common/containers/Tabs/Swap/components/ReceivingAddress.jsx
index 7920d119..f72036be 100644
--- a/common/containers/Tabs/Swap/components/ReceivingAddress.js
+++ b/common/containers/Tabs/Swap/components/ReceivingAddress.jsx
@@ -4,13 +4,18 @@ import type {
DestinationAddressSwapAction,
ChangeStepSwapAction,
StopLoadBityRatesSwapAction,
- ReferenceNumberSwapAction
-} from 'actions/swap';
+ BityOrderCreateRequestedSwapAction
+} from 'actions/swapTypes';
import { donationAddressMap } from 'config/data';
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
import translate from 'translations';
+import { combineAndUpper } from 'utils/formatters';
+import SimpleButton from 'components/ui/SimpleButton';
export type StateProps = {
+ isPostingOrder: boolean,
+ originAmount: number,
+ originKind: string,
destinationKind: string,
destinationAddress: string
};
@@ -19,7 +24,12 @@ export type ActionProps = {
destinationAddressSwap: (value: ?string) => DestinationAddressSwapAction,
changeStepSwap: (value: number) => ChangeStepSwapAction,
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
- referenceNumberSwap: (value: string) => ReferenceNumberSwapAction
+ bityOrderCreateRequestedSwap: (
+ amount: number,
+ destinationAddress: string,
+ pair: string,
+ mode: ?number
+ ) => BityOrderCreateRequestedSwapAction
};
export default class ReceivingAddress extends Component {
@@ -31,14 +41,15 @@ export default class ReceivingAddress extends Component {
};
onClickPartTwoComplete = () => {
- this.props.stopLoadBityRatesSwap();
- // temporarily here for testing purposes. will live in saga
- this.props.referenceNumberSwap('');
- this.props.changeStepSwap(3);
+ this.props.bityOrderCreateRequestedSwap(
+ this.props.originAmount,
+ this.props.destinationAddress,
+ combineAndUpper(this.props.originKind, this.props.destinationKind)
+ );
};
render() {
- const { destinationKind, destinationAddress } = this.props;
+ const { destinationKind, destinationAddress, isPostingOrder } = this.props;
let validAddress;
// TODO - find better pattern here once currencies move beyond BTC, ETH, REP
if (this.props.destinationKind === 'BTC') {
@@ -72,15 +83,12 @@ export default class ReceivingAddress extends Component {
-
-
- {translate('SWAP_start_CTA')}
-
-
+ disabled={!validAddress}
+ loading={isPostingOrder}
+ />
diff --git a/common/containers/Tabs/Swap/components/SwapInfoHeader.js b/common/containers/Tabs/Swap/components/SwapInfoHeader.js
deleted file mode 100644
index 8c5c26e1..00000000
--- a/common/containers/Tabs/Swap/components/SwapInfoHeader.js
+++ /dev/null
@@ -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 (
-
-
-
-
- Start New Swap
-
-
-
- {translate('SWAP_information')}
-
-
-
-
- {/*Amount to send */}
- {!this.isExpanded() &&
-
-
- {` ${toFixedIfLarger(originAmount, 6)} ${originKind}`}
-
-
- {translate('SEND_amount')}
-
-
}
-
- {/* Reference Number*/}
- {this.isExpanded() &&
-
-
- {referenceNumber}
-
-
- {translate('SWAP_ref_num')}
-
-
}
-
- {/*Time remaining*/}
- {this.isExpanded() &&
-
-
- {timeRemaining}
-
-
- {translate('SWAP_time')}
-
-
}
-
- {/*Amount to Receive*/}
-
-
- {` ${toFixedIfLarger(destinationAmount, 6)} ${destinationKind}`}
-
-
- {translate('SWAP_rec_amt')}
-
-
-
- {/*Your rate*/}
-
-
- {` ${this.computedOriginDestinationRatio()} ${originKind}/${destinationKind} `}
-
-
- {translate('SWAP_your_rate')}
-
-
-
-
- );
- }
-}
diff --git a/common/containers/Tabs/Swap/components/SwapInfoHeader.jsx b/common/containers/Tabs/Swap/components/SwapInfoHeader.jsx
new file mode 100644
index 00000000..0dcbede2
--- /dev/null
+++ b/common/containers/Tabs/Swap/components/SwapInfoHeader.jsx
@@ -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 (
+
+
+
+ Start New Swap
+
+
+
+ {translate('SWAP_information')}
+
+
+
+ );
+ }
+}
+
+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 (
+
+
+
+ {/*Amount to send*/}
+ {!this.isExpanded() &&
+
+
+ {` ${originAmount} ${originKind}`}
+
+
+ {translate('SEND_amount')}
+
+
}
+
+ {/*Reference Number*/}
+ {this.isExpanded() &&
+
+
+ {reference}
+
+
+ {translate('SWAP_ref_num')}
+
+
}
+
+ {/*Time remaining*/}
+ {this.isExpanded() &&
+
+
+ {this.formattedTime()}
+
+
+ {translate('SWAP_time')}
+
+
}
+
+ {/*Amount to Receive*/}
+
+
+ {` ${destinationAmount} ${destinationKind}`}
+
+
+ {translate('SWAP_rec_amt')}
+
+
+
+ {/*Your rate*/}
+
+
+ {` ${toFixedIfLarger(
+ this.computedOriginDestinationRatio()
+ )} ${originKind}/${destinationKind} `}
+
+
+ {translate('SWAP_your_rate')}
+
+
+
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/Swap/components/SwapProgress.js b/common/containers/Tabs/Swap/components/SwapProgress.js
deleted file mode 100644
index 283abd39..00000000
--- a/common/containers/Tabs/Swap/components/SwapProgress.js
+++ /dev/null
@@ -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 (
-
-
-
-
-
- 1
-
-
- {translate('SWAP_progress_1')}
-
-
-
-
- 2
-
-
- {translate('SWAP_progress_2')}
- {originKind}...
-
-
-
-
- 3
-
-
- {originKind} {translate('SWAP_progress_3')}
-
-
-
-
- 4
-
-
- Sending your
- {destinationKind}
-
-
- Waiting for {numberOfConfirmations} confirmations...
-
-
-
-
-
- 5
-
-
Order Complete
-
-
- );
- }
-}
diff --git a/common/containers/Tabs/Swap/components/SwapProgress.jsx b/common/containers/Tabs/Swap/components/SwapProgress.jsx
new file mode 100644
index 00000000..28ee5912
--- /dev/null
+++ b/common/containers/Tabs/Swap/components/SwapProgress.jsx
@@ -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 = ` View your transaction `;
+ // BTC uses a different explorer
+ } else {
+ link = bityConfig.btcExplorer.replace('[[txHash]]', outputTx);
+ linkElement = ` View your transaction `;
+ }
+ 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 (
+
+
+
+
+
+ 1
+
+
+ {translate('SWAP_progress_1')}
+
+
+
+
+ 2
+
+
+ {translate('SWAP_progress_2')}
+ {originKind}...
+
+
+
+
+ 3
+
+
+ {originKind} {translate('SWAP_progress_3')}
+
+
+
+
+ 4
+
+
+ Sending your
+ {destinationKind}
+
+
+ Waiting for {numberOfConfirmations} confirmations...
+
+
+
+
+
+ 5
+
+
Order Complete
+
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/Swap/index.js b/common/containers/Tabs/Swap/index.jsx
similarity index 63%
rename from common/containers/Tabs/Swap/index.js
rename to common/containers/Tabs/Swap/index.jsx
index 489a0e4a..0a38b494 100644
--- a/common/containers/Tabs/Swap/index.js
+++ b/common/containers/Tabs/Swap/index.jsx
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
+import { showNotification } from 'actions/notifications';
import * as swapActions from 'actions/swap';
import type {
ChangeStepSwapAction,
@@ -7,16 +8,20 @@ import type {
DestinationKindSwapAction,
OriginAmountSwapAction,
DestinationAmountSwapAction,
- LoadBityRatesSwapAction,
+ LoadBityRatesRequestedSwapAction,
DestinationAddressSwapAction,
RestartSwapAction,
- StopLoadBityRatesSwapAction
+ StopLoadBityRatesSwapAction,
+ BityOrderCreateRequestedSwapAction,
+ StartPollBityOrderStatusAction,
+ StopOrderTimerSwapAction,
+ StopPollBityOrderStatusAction
} from 'actions/swap';
import CurrencySwap from './components/CurrencySwap';
import CurrentRates from './components/CurrentRates';
import ReceivingAddress from './components/ReceivingAddress';
import SwapInfoHeader from './components/SwapInfoHeader';
-import SwapProgress from './components/SwapProgress';
+import PartThree from './components/PartThree';
type ReduxStateProps = {
step: string,
@@ -25,16 +30,16 @@ type ReduxStateProps = {
originKind: string,
destinationKindOptions: String[],
originKindOptions: String[],
- bityRates: boolean,
+ bityRates: {},
originAmount: ?number,
destinationAmount: ?number,
+ isPostingOrder: boolean,
isFetchingRates: boolean,
- // PART 3
- referenceNumber: string,
- timeRemaining: string,
- numberOfConfirmation: number,
- orderStep: number,
- orderStarted: boolean
+ bityOrder: {},
+ secondsRemaining: ?number,
+ paymentAddress: ?string,
+ orderStatus: ?string,
+ outputTx: ?string
};
type ReduxActionProps = {
@@ -43,12 +48,19 @@ type ReduxActionProps = {
destinationKindSwap: (value: string) => DestinationKindSwapAction,
originAmountSwap: (value: ?number) => OriginAmountSwapAction,
destinationAmountSwap: (value: ?number) => DestinationAmountSwapAction,
- loadBityRatesSwap: () => LoadBityRatesSwapAction,
+ loadBityRatesRequestedSwap: () => LoadBityRatesRequestedSwapAction,
destinationAddressSwap: (value: ?string) => DestinationAddressSwapAction,
restartSwap: () => RestartSwapAction,
stopLoadBityRatesSwap: () => StopLoadBityRatesSwapAction,
- // PART 3 (IGNORE FOR NOW)
- referenceNumberSwap: typeof swapActions.referenceNumberSwap
+ bityOrderCreateRequestedSwap: (
+ amount: number,
+ destinationAddress: string,
+ pair: string,
+ mode: number
+ ) => BityOrderCreateRequestedSwapAction,
+ startPollBityOrderStatus: () => StartPollBityOrderStatusAction,
+ stopOrderTimerSwap: () => StopOrderTimerSwapAction,
+ stopPollBityOrderStatus: () => StopPollBityOrderStatusAction
};
class Swap extends Component {
@@ -56,7 +68,7 @@ class Swap extends Component {
componentDidMount() {
// TODO: Use `isFetchingRates` to show a loader
- this.props.loadBityRatesSwap();
+ this.props.loadBityRatesRequestedSwap();
}
componentWillUnmount() {
@@ -75,10 +87,12 @@ class Swap extends Component {
originKindOptions,
destinationAddress,
step,
- referenceNumber,
- timeRemaining,
- numberOfConfirmations,
- orderStep,
+ bityOrder,
+ secondsRemaining,
+ paymentAddress,
+ orderStatus,
+ isPostingOrder,
+ outputTx,
// ACTIONS
restartSwap,
stopLoadBityRatesSwap,
@@ -88,35 +102,44 @@ class Swap extends Component {
originAmountSwap,
destinationAmountSwap,
destinationAddressSwap,
- referenceNumberSwap
+ bityOrderCreateRequestedSwap,
+ showNotification,
+ startOrderTimerSwap,
+ startPollBityOrderStatus,
+ stopOrderTimerSwap,
+ stopPollBityOrderStatus
} = this.props;
+ const { reference } = bityOrder;
+
let ReceivingAddressProps = {
+ isPostingOrder,
+ originAmount,
+ originKind,
destinationKind,
destinationAddressSwap,
destinationAddress,
stopLoadBityRatesSwap,
changeStepSwap,
- referenceNumberSwap
+ bityOrderCreateRequestedSwap
};
let SwapInfoHeaderProps = {
- referenceNumber,
- timeRemaining,
+ reference,
+ secondsRemaining,
originAmount,
originKind,
destinationKind,
destinationAmount,
restartSwap,
- numberOfConfirmations,
- orderStep
+ orderStatus
};
const { ETHBTC, ETHREP, BTCETH, BTCREP } = bityRates;
-
const CurrentRatesProps = { ETHBTC, ETHREP, BTCETH, BTCREP };
const CurrencySwapProps = {
+ showNotification,
bityRates,
originAmount,
destinationAmount,
@@ -131,6 +154,25 @@ class Swap extends Component {
changeStepSwap
};
+ const PaymentInfoProps = {
+ originKind,
+ originAmount,
+ paymentAddress
+ };
+
+ const PartThreeProps = {
+ ...SwapInfoHeaderProps,
+ ...PaymentInfoProps,
+ reference,
+ startOrderTimerSwap,
+ startPollBityOrderStatus,
+ stopOrderTimerSwap,
+ stopPollBityOrderStatus,
+ showNotification,
+ destinationAddress,
+ outputTx
+ };
+
return (
@@ -143,7 +185,7 @@ class Swap extends Component {
{(step === 2 || step === 3) &&
}
{step === 2 &&
}
- {step === 3 &&
}
+ {step === 3 &&
}
@@ -153,6 +195,10 @@ class Swap extends Component {
function mapStateToProps(state) {
return {
+ outputTx: state.swap.outputTx,
+ isPostingOrder: state.swap.isPostingOrder,
+ orderStatus: state.swap.orderStatus,
+ paymentAddress: state.swap.paymentAddress,
step: state.swap.step,
destinationAddress: state.swap.destinationAddress,
originAmount: state.swap.originAmount,
@@ -162,13 +208,12 @@ function mapStateToProps(state) {
destinationKindOptions: state.swap.destinationKindOptions,
originKindOptions: state.swap.originKindOptions,
bityRates: state.swap.bityRates,
- referenceNumber: state.swap.referenceNumber,
- timeRemaining: state.swap.timeRemaining,
- numberOfConfirmations: state.swap.numberOfConfirmations,
- orderStep: state.swap.orderStep,
- orderStarted: state.swap.orderStarted,
+ bityOrder: state.swap.bityOrder,
+ secondsRemaining: state.swap.secondsRemaining,
isFetchingRates: state.swap.isFetchingRates
};
}
-export default connect(mapStateToProps, swapActions)(Swap);
+export default connect(mapStateToProps, { ...swapActions, showNotification })(
+ Swap
+);
diff --git a/common/index.jsx b/common/index.jsx
index 25f56526..1c5f8649 100644
--- a/common/index.jsx
+++ b/common/index.jsx
@@ -1,5 +1,6 @@
// Application styles must come first in order, to allow for overrides
import 'assets/styles/etherwallet-master.less';
+import 'font-awesome/scss/font-awesome.scss';
import React from 'react';
import { render } from 'react-dom';
diff --git a/common/reducers/swap.js b/common/reducers/swap.js
index 5048c22b..f3bf050f 100644
--- a/common/reducers/swap.js
+++ b/common/reducers/swap.js
@@ -1,12 +1,14 @@
// @flow
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'];
+const DEFAULT_ORIGIN_KIND = 'BTC';
+const DEFAULT_DESTINATION_KIND = 'ETH';
type State = {
- originAmount: number,
- destinationAmount: number,
+ originAmount: ?number,
+ destinationAmount: ?number,
originKind: string,
destinationKind: string,
destinationKindOptions: Array,
@@ -14,32 +16,37 @@ type State = {
step: number,
bityRates: Object,
destinationAddress: string,
- referenceNumber: string,
- timeRemaining: string,
- numberOfConfirmations: ?number,
- orderStep: ?number,
- isFetchingRates: boolean
+ isFetchingRates: ?boolean,
+ secondsRemaining: ?number,
+ outputTx: ?string,
+ isPostingOrder: ?boolean,
+ orderStatus: ?string,
+ orderTimestampCreatedISOString: ?string,
+ paymentAddress: ?string,
+ validFor: ?number,
+ orderId: string
};
export const INITIAL_STATE: State = {
- originAmount: 0,
- destinationAmount: 0,
- originKind: 'BTC',
- destinationKind: 'ETH',
- destinationKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(element => {
- return element !== 'BTC';
- }),
- originKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(element => {
- return element !== 'REP';
- }),
+ originAmount: null,
+ destinationAmount: null,
+ originKind: DEFAULT_ORIGIN_KIND,
+ destinationKind: DEFAULT_DESTINATION_KIND,
+ destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, DEFAULT_ORIGIN_KIND),
+ originKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, 'REP'),
step: 1,
bityRates: {},
destinationAddress: '',
- referenceNumber: '',
- timeRemaining: '',
- numberOfConfirmations: null,
- orderStep: null,
- isFetchingRates: false
+ bityOrder: {},
+ isFetchingRates: null,
+ secondsRemaining: null,
+ outputTx: null,
+ isPostingOrder: false,
+ orderStatus: null,
+ orderTimestampCreatedISOString: null,
+ paymentAddress: null,
+ validFor: null,
+ orderId: null
};
const buildDestinationAmount = (
@@ -50,7 +57,7 @@ const buildDestinationAmount = (
) => {
let pairName = combineAndUpper(originKind, destinationKind);
let bityRate = bityRates[pairName];
- return originAmount * bityRate;
+ return originAmount ? originAmount * bityRate : 0;
};
const buildDestinationKind = (
@@ -58,7 +65,7 @@ const buildDestinationKind = (
destinationKind: string
): string => {
if (originKind === destinationKind) {
- return ALL_CRYPTO_KIND_OPTIONS.filter(element => element !== originKind)[0];
+ return without(ALL_CRYPTO_KIND_OPTIONS, originKind)[0];
} else {
return destinationKind;
}
@@ -75,10 +82,7 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
...state,
originKind: action.value,
destinationKind: newDestinationKind,
- destinationKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(element => {
- // $FlowFixMe
- return element !== action.value;
- }),
+ destinationKindOptions: without(ALL_CRYPTO_KIND_OPTIONS, action.value),
destinationAmount: buildDestinationAmount(
state.originAmount,
action.value,
@@ -109,7 +113,7 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
...state,
destinationAmount: action.value
};
- case 'SWAP_UPDATE_BITY_RATES':
+ case 'SWAP_LOAD_BITY_RATES_SUCCEEDED':
return {
...state,
bityRates: {
@@ -134,16 +138,48 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
...INITIAL_STATE,
bityRates: state.bityRates
};
- case 'SWAP_REFERENCE_NUMBER':
+ case 'SWAP_ORDER_CREATE_REQUESTED':
return {
...state,
- referenceNumber: '2341asdfads',
- timeRemaining: '2:30',
- numberOfConfirmations: 3,
- orderStep: 2
+ isPostingOrder: true
+ };
+ case 'SWAP_ORDER_CREATE_FAILED':
+ 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 {
...state,
isFetchingRates: true
@@ -156,7 +192,6 @@ export function swap(state: State = INITIAL_STATE, action: SwapAction) {
};
default:
- (action: empty);
return state;
}
}
diff --git a/common/sagas/index.js b/common/sagas/index.js
index 1ad6edd4..6d130bb4 100644
--- a/common/sagas/index.js
+++ b/common/sagas/index.js
@@ -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 ens from './ens';
import notifications from './notifications';
import rates from './rates';
import wallet from './wallet';
-export default { bity, contracts, ens, notifications, rates, wallet };
+export default {
+ bityTimeRemaining,
+ postBityOrderSaga,
+ pollBityOrderStatusSaga,
+ getBityRatesSaga,
+ contracts,
+ ens,
+ notifications,
+ rates,
+ wallet
+};
diff --git a/common/sagas/notifications.js b/common/sagas/notifications.js
index 71056c1a..71140a65 100644
--- a/common/sagas/notifications.js
+++ b/common/sagas/notifications.js
@@ -9,7 +9,7 @@ function* handleNotification(action?: ShowNotificationAction) {
if (!action) return;
const { duration } = action.payload;
// show forever
- if (duration === 0) {
+ if (duration === 0 || duration === 'infinity') {
return;
}
diff --git a/common/sagas/swap/orders.js b/common/sagas/swap/orders.js
new file mode 100644
index 00000000..fbb27078
--- /dev/null
+++ b/common/sagas/swap/orders.js
@@ -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 {
+ 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 {
+ 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 {
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/common/sagas/bity.js b/common/sagas/swap/rates.js
similarity index 71%
rename from common/sagas/bity.js
rename to common/sagas/swap/rates.js
index 1ac4d36c..84d3c7e3 100644
--- a/common/sagas/bity.js
+++ b/common/sagas/swap/rates.js
@@ -1,19 +1,26 @@
// @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 { updateBityRatesSwap } from 'actions/swap';
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 {
try {
while (true) {
- // TODO - yield put(actions.requestStart()) if we want to display swap refresh status
+ // TODO - BITY_RATE_REQUESTED
// network request
const data = yield call(getAllRates);
// action
- yield put(updateBityRatesSwap(data));
+ yield put(loadBityRatesSucceededSwap(data));
// wait 5 seconds before refreshing rates
yield call(delay, 5000);
}
@@ -25,11 +32,10 @@ export function* loadBityRates(_action?: any): Generator {
}
}
-export default function* bitySaga(): Generator {
- while (yield take('SWAP_LOAD_BITY_RATES')) {
+export function* getBityRatesSaga(): Generator {
+ while (yield take('SWAP_LOAD_BITY_RATES_REQUESTED')) {
// starts the task in the background
const loadBityRatesTask = yield fork(loadBityRates);
-
// wait for the user to get to point where refresh is no longer needed
yield take('SWAP_STOP_LOAD_BITY_RATES');
// cancel the background task
diff --git a/common/store.js b/common/store.js
index f3d8500f..dd622573 100644
--- a/common/store.js
+++ b/common/store.js
@@ -8,6 +8,7 @@ import createSagaMiddleware from 'redux-saga';
import sagas from './sagas';
import { INITIAL_STATE as configInitialState } from 'reducers/config';
import { INITIAL_STATE as customTokensInitialState } from 'reducers/customTokens';
+import { INITIAL_STATE as swapInitialState } from 'reducers/swap';
import throttle from 'lodash/throttle';
import { composeWithDevTools } from 'redux-devtools-extension';
import Perf from 'react-addons-perf';
@@ -37,7 +38,15 @@ const configureStore = () => {
...configInitialState,
...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);
@@ -53,6 +62,7 @@ const configureStore = () => {
config: {
languageSelection: store.getState().config.languageSelection
},
+ swap: store.getState().swap,
customTokens: store.getState().customTokens
});
}),
diff --git a/package-lock.json b/package-lock.json
index 81033de3..9bbb601a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4252,6 +4252,11 @@
"integrity": "sha1-1M2yQw3uGjWZ8Otv5VEUbjAnJWo=",
"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": {
"version": "1.0.2",
"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": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/mozjpeg/-/mozjpeg-4.1.1.tgz",
diff --git a/package.json b/package.json
index e37aa245..446c89af 100644
--- a/package.json
+++ b/package.json
@@ -9,11 +9,13 @@
},
"dependencies": {
"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-wallet": "^0.6.0",
+ "font-awesome": "^4.7.0",
"idna-uts46": "^1.1.0",
"lodash": "^4.17.4",
+ "moment": "^2.18.1",
"prop-types": "^15.5.8",
"qrcode": "^0.8.2",
"react": "^15.4.2",
diff --git a/webpack_config/webpack.base.js b/webpack_config/webpack.base.js
index 89c38460..93ffd4d4 100644
--- a/webpack_config/webpack.base.js
+++ b/webpack_config/webpack.base.js
@@ -40,10 +40,6 @@ module.exports = {
loaders: ['babel-loader'],
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,
loaders: [