Swap Cleanup (#68)

* add types for swap action creators (and separate out to separate module)
* DRY up swapInformation and onGoingSwapInformation -> SwapInfoHeader.
* stop bity rate loading when Swap will unmount
* split out SimpleDropdown to separate module
* various additional refactors
This commit is contained in:
Daniel Ternyak 2017-07-22 16:24:03 -05:00 committed by GitHub
parent 7d5e590c42
commit 0eae13f938
18 changed files with 646 additions and 536 deletions

View File

@ -1,86 +1,103 @@
// @flow
import {
SWAP_DESTINATION_AMOUNT,
SWAP_DESTINATION_KIND,
SWAP_ORIGIN_AMOUNT,
SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES,
SWAP_PART_ONE_COMPLETE,
SWAP_PART_TWO_COMPLETE,
SWAP_DESTINATION_ADDRESS,
SWAP_RESTART,
SWAP_LOAD_BITY_RATES,
SWAP_STOP_LOAD_BITY_RATES
SWAP_STOP_LOAD_BITY_RATES,
SWAP_STEP,
SWAP_REFERENCE_NUMBER
} from './swapConstants';
export const originKindSwap = value => {
import * as swapTypes from './swapTypes';
export function changeStepSwap(value: number): swapTypes.ChangeStepSwapAction {
return {
type: SWAP_STEP,
value
};
}
export function referenceNumberSwap(
value: string
): swapTypes.ReferenceNumberSwapAction {
return {
type: SWAP_REFERENCE_NUMBER,
value
};
}
export const originKindSwap = (
value: string
): swapTypes.OriginKindSwapAction => {
return {
type: SWAP_ORIGIN_KIND,
value
};
};
export const destinationKindSwap = value => {
export const destinationKindSwap = (
value: string
): swapTypes.DestinationKindSwapAction => {
return {
type: SWAP_DESTINATION_KIND,
value
};
};
export const originAmountSwap = value => {
export const originAmountSwap = (
value: ?number
): swapTypes.OriginAmountSwapAction => {
return {
type: SWAP_ORIGIN_AMOUNT,
value
};
};
export const destinationAmountSwap = value => {
export const destinationAmountSwap = (
value: ?number
): swapTypes.DestinationAmountSwapAction => {
return {
type: SWAP_DESTINATION_AMOUNT,
value
};
};
export const updateBityRatesSwap = value => {
export const updateBityRatesSwap = (
value: swapTypes.Pairs
): swapTypes.BityRatesSwapAction => {
return {
type: SWAP_UPDATE_BITY_RATES,
value
};
};
export const partOneCompleteSwap = (value: boolean) => {
return {
type: SWAP_PART_ONE_COMPLETE,
value
};
};
export const partTwoCompleteSwap = (value: boolean) => {
return {
type: SWAP_PART_TWO_COMPLETE,
value
};
};
export const destinationAddressSwap = value => {
export const destinationAddressSwap = (
value: ?string
): swapTypes.DestinationAddressSwapAction => {
return {
type: SWAP_DESTINATION_ADDRESS,
value
};
};
export const restartSwap = () => {
export const restartSwap = (): swapTypes.RestartSwapAction => {
return {
type: SWAP_RESTART
};
};
export const loadBityRates = () => {
export const loadBityRatesSwap = (): swapTypes.LoadBityRatesSwapAction => {
return {
type: SWAP_LOAD_BITY_RATES
};
};
export const stopLoadBityRates = () => {
export const stopLoadBityRatesSwap = (): swapTypes.StopLoadBityRatesSwapAction => {
return {
type: SWAP_STOP_LOAD_BITY_RATES
};

View File

@ -3,9 +3,9 @@ export const SWAP_DESTINATION_KIND = 'SWAP_DESTINATION_KIND';
export const SWAP_ORIGIN_AMOUNT = 'SWAP_ORIGIN_AMOUNT';
export const SWAP_DESTINATION_AMOUNT = 'SWAP_DESTINATION_AMOUNT';
export const SWAP_UPDATE_BITY_RATES = 'SWAP_UPDATE_BITY_RATES';
export const SWAP_PART_ONE_COMPLETE = 'SWAP_PART_ONE_COMPLETE';
export const SWAP_PART_TWO_COMPLETE = 'SWAP_PART_TWO_COMPLETE';
export const SWAP_DESTINATION_ADDRESS = 'SWAP_DESTINATION_ADDRESS';
export const SWAP_RESTART = 'SWAP_RESTART';
export const SWAP_LOAD_BITY_RATES = 'SWAP_LOAD_BITY_RATES';
export const SWAP_STOP_LOAD_BITY_RATES = 'SWAP_STOP_LOAD_BITY_RATES';
export const SWAP_STEP = 'SWAP_STEP';
export const SWAP_REFERENCE_NUMBER = 'SWAP_REFERENCE_NUMBER';

View File

@ -0,0 +1,66 @@
import {
SWAP_DESTINATION_AMOUNT,
SWAP_DESTINATION_KIND,
SWAP_ORIGIN_AMOUNT,
SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES,
SWAP_DESTINATION_ADDRESS,
SWAP_RESTART,
SWAP_LOAD_BITY_RATES,
SWAP_STOP_LOAD_BITY_RATES,
SWAP_STEP,
SWAP_REFERENCE_NUMBER
} from './swapConstants';
export type Pairs = {
ETHBTC: number,
ETHREP: number,
BTCETH: number,
BTCREP: number
};
export type ReferenceNumberSwapAction = {
type: SWAP_REFERENCE_NUMBER,
value: string
};
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 BityRatesSwapAction = {
type: SWAP_UPDATE_BITY_RATES,
value: Pairs
};
export type DestinationAddressSwapAction = {
type: SWAP_DESTINATION_ADDRESS,
value: ?number
};
export type RestartSwapAction = {
type: SWAP_RESTART
};
export type LoadBityRatesSwapAction = {
type: SWAP_LOAD_BITY_RATES
};
export type ChangeStepSwapAction = {
type: SWAP_STEP,
value: number
};
export type StopLoadBityRatesSwapAction = {
type: SWAP_STOP_LOAD_BITY_RATES
};

View File

@ -1,9 +1,7 @@
// @flow
import bityConfig from 'config/bity';
import {combineAndUpper} from 'utils/formatters'
export function combineAndUpper(...args: string[]) {
return args.reduce((acc, item) => acc.concat(item.toUpperCase()), '');
}
function findRateFromBityRateList(rateObjects, pairName) {
return rateObjects.find(x => x.pair === pairName);

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import translate, { getTranslators } from 'translations';
import { donationAddressMap } from 'config/data';
import logo from 'assets/images/logo-myetherwallet.svg';
import { bityReferralURL } from 'config/data';
import './index.scss';
export default class Footer extends Component {
@ -72,7 +72,7 @@ export default class Footer extends Component {
<li>
<a
aria-label="Swap Ether or Bitcoin via Bity.com"
href="https://bity.com/af/jshkb37v"
href={bityReferralURL}
target="_blank"
>
Swap ETH/BTC/EUR/CHF via Bity.com

View File

@ -0,0 +1,32 @@
// @flow
import React, { Component } from 'react';
type Props<T> = {
value?: T,
options: Array<T>,
onChange: (event: SyntheticInputEvent) => void
};
export default class SimpleDropDown<T: *> extends Component {
props: Props<T>;
render() {
return (
<span className="dropdown">
<select
value={this.props.value || this.props.options[0]}
className="btn btn-default dropdown-toggle"
onChange={this.props.onChange}
>
{this.props.options.map((obj, i) => {
return (
<option value={obj} key={i}>
{obj}
</option>
);
})}
</select>
</span>
);
}
}

View File

@ -14,6 +14,8 @@ export const gasPriceDefaults = {
gasPriceMaxGwei: 60
};
export const bityReferralURL = 'https://bity.com/af/jshkb37v';
export const languages = [
{
sign: 'en',

View File

@ -0,0 +1,151 @@
import React, { Component } from 'react';
import translate from 'translations';
import { combineAndUpper } from 'utils/formatters';
import * as swapTypes from 'actions/swapTypes';
import SimpleDropDown from 'components/ui/SimpleDropdown';
export type StateProps = {
bityRates: {},
originAmount: ?number,
destinationAmount: ?number,
originKind: string,
destinationKind: string,
destinationKindOptions: String[],
originKindOptions: String[]
};
export type ActionProps = {
originKindSwap: (value: string) => swapTypes.OriginKindSwapAction,
destinationKindSwap: (value: string) => swapTypes.DestinationKindSwapAction,
originAmountSwap: (value: ?number) => swapTypes.OriginAmountSwapAction,
destinationAmountSwap: (
value: ?number
) => swapTypes.DestinationAmountSwapAction,
changeStepSwap: () => swapTypes.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>
);
}
}

View File

@ -1,27 +1,22 @@
// @flow
import React, { Component } from 'react';
import translate from 'translations';
import PropTypes from 'prop-types';
import { toFixedIfLarger } from 'utils/formatters';
import { Pairs } from 'actions/swapTypes';
import { bityReferralURL } from 'config/data';
import bityLogoWhite from 'assets/images/logo-bity-white.svg';
export default class CurrentRates extends Component {
constructor(props) {
super(props);
this.state = {
ETHBTCAmount: 1,
ETHREPAmount: 1,
BTCETHAmount: 1,
BTCREPAmount: 1
};
}
props: Pairs;
static propTypes = {
ETHBTC: PropTypes.number,
ETHREP: PropTypes.number,
BTCETH: PropTypes.number,
BTCREP: PropTypes.number
state = {
ETHBTCAmount: 1,
ETHREPAmount: 1,
BTCETHAmount: 1,
BTCREPAmount: 1
};
onChange = event => {
onChange = (event: SyntheticInputEvent) => {
const target = event.target;
const value = target.value;
const name = target.name;
@ -30,8 +25,9 @@ export default class CurrentRates extends Component {
});
};
// TODO - A little code duplication here, but simple enough to where it doesn't seem worth the time to fix.
render() {
const { ETHBTC, ETHREP, BTCETH, BTCREP } = this.props;
return (
<article className="swap-rates">
<section className="row">
@ -50,7 +46,7 @@ export default class CurrentRates extends Component {
/>
<span>
{` ETH = ${toFixedIfLarger(
this.state.ETHBTCAmount * this.props.ETHBTC,
this.state.ETHBTCAmount * ETHBTC,
6
)} BTC`}
</span>
@ -64,7 +60,7 @@ export default class CurrentRates extends Component {
/>
<span>
{` ETH = ${toFixedIfLarger(
this.state.ETHREPAmount * this.props.ETHREP,
this.state.ETHREPAmount * ETHREP,
6
)} REP`}
</span>
@ -80,7 +76,7 @@ export default class CurrentRates extends Component {
/>
<span>
{` BTC = ${toFixedIfLarger(
this.state.BTCETHAmount * this.props.BTCETH,
this.state.BTCETHAmount * BTCETH,
6
)} ETH`}
</span>
@ -94,22 +90,14 @@ export default class CurrentRates extends Component {
/>
<span>
{` BTC = ${toFixedIfLarger(
this.state.BTCREPAmount * this.props.BTCREP,
this.state.BTCREPAmount * BTCREP,
6
)} REP`}
</span>
</p>
</div>
<a
className="link bity-logo"
href="https://bity.com/af/jshkb37v"
target="_blank"
>
<img
src={'https://www.myetherwallet.com/images/logo-bity-white.svg'}
width={120}
height={49}
/>
<a className="link bity-logo" href={bityReferralURL} target="_blank">
<img src={bityLogoWhite} width={120} height={49} />
</a>
</section>
</article>

View File

@ -1,18 +1,25 @@
// @flow
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as swapTypes from 'actions/swapTypes';
import { donationAddressMap } from 'config/data';
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
import translate from 'translations';
export type StateProps = {
destinationKind: string,
destinationAddress: string
};
export type ActionProps = {
destinationAddressSwap: (
value: ?string
) => swapTypes.DestinationAddressSwapAction,
changeStepSwap: (value: number) => swapTypes.ChangeStepSwapAction,
stopLoadBityRatesSwap: () => swapTypes.StopLoadBityRatesSwapAction
};
export default class ReceivingAddress extends Component {
static propTypes = {
destinationKind: PropTypes.string.isRequired,
destinationAddressSwap: PropTypes.func.isRequired,
destinationAddress: PropTypes.string,
partTwoCompleteSwap: PropTypes.func,
stopLoadBityRates: PropTypes.func
};
props: StateProps & ActionProps;
onChangeDestinationAddress = (event: SyntheticInputEvent) => {
const value = event.target.value;
@ -20,8 +27,10 @@ export default class ReceivingAddress extends Component {
};
onClickPartTwoComplete = () => {
this.props.stopLoadBityRates();
this.props.partTwoCompleteSwap(true);
this.props.stopLoadBityRatesSwap();
// temporarily here for testing purposes. will live in saga
this.props.referenceNumberSwap('');
this.props.changeStepSwap(3);
};
render() {
@ -40,8 +49,12 @@ export default class ReceivingAddress extends Component {
<section className="row">
<div className="col-sm-8 col-sm-offset-2 col-xs-12">
<label>
<span>{translate('SWAP_rec_add')}</span>
<strong> ({destinationKind})</strong>
<span>
{translate('SWAP_rec_add')}
</span>
<strong>
{' '}({destinationKind})
</strong>
</label>
<input
className={`form-control ${validAddress
@ -60,7 +73,9 @@ export default class ReceivingAddress extends Component {
onClick={this.onClickPartTwoComplete}
className="btn btn-primary btn-lg"
>
<span>{translate('SWAP_start_CTA')}</span>
<span>
{translate('SWAP_start_CTA')}
</span>
</button>
</section>
</section>

View File

@ -0,0 +1,142 @@
// @flow
import React, { Component } from 'react';
import { toFixedIfLarger } from 'utils/formatters';
import translate from 'translations';
import * as swapTypes from 'actions/swapTypes';
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: () => swapTypes.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>
{` ${toFixedIfLarger(
this.computedOriginDestinationRatio(),
6
)} ${originKind}/${destinationKind} `}
</h4>
<p>
{translate('SWAP_your_rate')}
</p>
</div>
</section>
</div>
);
}
}

View File

@ -1,29 +1,24 @@
//flow
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import translate from 'translations';
export default class SwapProgress extends Component {
constructor(props) {
super(props);
}
export type StateProps = {
numberOfConfirmations: number,
destinationKind: string,
originKind: string,
orderStep: number
};
static propTypes = {
numberOfConfirmations: PropTypes.number.isRequired,
destinationKind: PropTypes.string.isRequired,
originKind: PropTypes.string.isRequired,
activeStep: PropTypes.number.isRequired
};
export default class SwapProgress extends Component {
props: StateProps;
computedClass(i: number) {
const { activeStep } = this.props;
const { orderStep } = this.props;
let cssClass = 'progress-item';
if (activeStep > i) {
if (orderStep > i) {
cssClass += ' progress-true';
} else if (i === activeStep) {
} else if (i === orderStep) {
cssClass += ' progress-active';
} else {
cssClass += '';
}
return cssClass;
}
@ -35,25 +30,37 @@ export default class SwapProgress extends Component {
<div className="sep" />
<div className={this.computedClass(1)}>
<div className="progress-circle"><i>1</i></div>
<p>{translate('SWAP_progress_1')}</p>
<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>
<div className="progress-circle">
<i>2</i>
</div>
<p>
<span>{translate('SWAP_progress_2')}</span>{originKind}...
<span>{translate('SWAP_progress_2')}</span>
{originKind}...
</p>
</div>
<div className={this.computedClass(3)}>
<div className="progress-circle"><i>3</i></div>
<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>
<div className="progress-circle">
<i>4</i>
</div>
<p>
<span>Sending your </span>{destinationKind}
<span>Sending your </span>
{destinationKind}
<br />
<small>
Waiting for {numberOfConfirmations} confirmations...
@ -61,7 +68,9 @@ export default class SwapProgress extends Component {
</p>
</div>
<div className={this.computedClass(5)}>
<div className="progress-circle"><i>5</i></div>
<div className="progress-circle">
<i>5</i>
</div>
<p>Order Complete</p>
</div>
</section>

View File

@ -1,163 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import translate from 'translations';
import { combineAndUpper } from 'api/bity';
class CoinTypeDropDown extends Component {
constructor(props) {
super(props);
}
static propTypes = {
kind: PropTypes.any,
onChange: PropTypes.any,
kindOptions: PropTypes.any
};
render() {
return (
<span className="dropdown">
<select
value={this.props.kind}
className="btn btn-default"
onChange={this.props.onChange.bind(this)}
>
{this.props.kindOptions.map((obj, i) => {
return <option value={obj} key={i}>{obj}</option>;
})}
</select>
</span>
);
}
}
export default class CurrencySwap extends Component {
constructor(props) {
super(props);
this.state = {
disabled: false
};
}
static propTypes = {
bityRates: PropTypes.any,
originAmount: PropTypes.any,
destinationAmount: PropTypes.any,
originKind: PropTypes.string,
destinationKind: PropTypes.string,
destinationKindOptions: PropTypes.array,
originKindOptions: PropTypes.array,
originKindSwap: PropTypes.func,
destinationKindSwap: PropTypes.func,
originAmountSwap: PropTypes.func,
destinationAmountSwap: PropTypes.func,
partOneCompleteSwap: PropTypes.func
};
onClickStartSwap = () => {
this.props.partOneCompleteSwap(true);
};
onChangeOriginAmount = amount => {
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.props.originAmountSwap('');
this.props.destinationAmountSwap('');
}
};
onChangeDestinationAmount(amount) {
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.props.originAmountSwap('');
this.props.destinationAmountSwap('');
}
}
async onChangeDestinationKind(event) {
let newDestinationKind = event.target.value;
this.props.destinationKindSwap(newDestinationKind);
}
async onChangeOriginKind(event) {
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"
onChange={e => this.onChangeOriginAmount(e.target.value)}
value={originAmount}
/>
<CoinTypeDropDown
kind={originKind}
onChange={this.onChangeOriginKind.bind(this)}
kindOptions={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={e => this.onChangeDestinationAmount(e.target.value)}
/>
<CoinTypeDropDown
kind={destinationKind}
onChange={this.onChangeDestinationKind.bind(this)}
kindOptions={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>
);
}
}

View File

@ -1,87 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { toFixedIfLarger } from 'utils/formatters';
import translate from 'translations';
export default class OnGoingSwapInformation extends Component {
constructor(props) {
super(props);
}
static propTypes = {
referenceNumber: PropTypes.string.isRequired,
timeRemaining: PropTypes.any, // FIXME
originAmount: PropTypes.number.isRequired,
originKind: PropTypes.string.isRequired,
destinationKind: PropTypes.string.isRequired,
destinationAmount: PropTypes.number.isRequired,
restartSwap: PropTypes.func.isRequired
};
computedOriginDestinationRatio = () => {
return toFixedIfLarger(
this.props.destinationAmount / this.props.originAmount,
6
);
};
render() {
const {
referenceNumber,
timeRemaining,
originAmount,
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 ng-scope">{translate('SWAP_information')}</h5>
<div className="col-xs-3">
<a
className="link"
href="https://bity.com/af/jshkb37v"
target="_blank"
rel="noopener"
>
<img
className="pull-right"
src={'https://www.myetherwallet.com/images/logo-bity.svg'}
width={100}
height={38}
/>
</a>
</div>
</section>
<section className="row order-info-wrap">
<div className="col-sm-3 order-info">
<h4>{referenceNumber}</h4>
<p>{translate('SWAP_ref_num')}</p>
</div>
<div className="col-sm-3 order-info">
<h4>{timeRemaining}</h4>
<p>
{translate('SWAP_time')}
</p>
</div>
<div className="col-sm-3 order-info">
<h4>{originAmount} {originKind}</h4>
<p>{translate('SWAP_rec_amt')}</p>
</div>
<div className="col-sm-3 order-info">
<h4>
{`${this.computedOriginDestinationRatio()} ${destinationKind}/${originKind}`}
</h4>
<p>{translate('SWAP_your_rate')}</p>
</div>
</section>
</div>
);
}
}

View File

@ -1,79 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { toFixedIfLarger } from 'utils/formatters';
import translate from 'translations';
export default class SwapInformation extends Component {
constructor(props) {
super(props);
}
static propTypes = {
originAmount: PropTypes.number.isRequired,
destinationAmount: PropTypes.number.isRequired,
originKind: PropTypes.string.isRequired,
destinationKind: PropTypes.string.isRequired
};
computedOriginDestinationRatio = () => {
return this.props.destinationAmount / this.props.originAmount;
};
render() {
const {
originAmount,
originKind,
destinationAmount,
destinationKind
} = this.props;
return (
<article className="swap-start">
<section className="row">
<h5 className="col-xs-6 col-xs-offset-3">
{translate('SWAP_information')}
</h5>
<div className="col-xs-3">
<a
className="link"
href="https://bity.com/af/jshkb37v"
target="_blank"
rel="noopener"
>
{/* Todo - fix*/}
<img
className="pull-right"
src={'https://www.myetherwallet.com/images/logo-bity.svg'}
width={100}
height={38}
/>
</a>
</div>
</section>
<section className="order-info-wrap row">
<div className="col-sm-4 order-info">
<h4>
{` ${toFixedIfLarger(originAmount, 6)} ${originKind}`}
</h4>
<p>{translate('SEND_amount')}</p>
</div>
<div className="col-sm-4 order-info">
<h4>
{` ${toFixedIfLarger(destinationAmount, 6)} ${destinationKind}`}
</h4>
<p>{translate('SWAP_rec_amt')}</p>
</div>
<div className="col-sm-4 order-info">
<h4>
{` ${toFixedIfLarger(
this.computedOriginDestinationRatio(),
6
)} ${originKind}/${destinationKind} `}
</h4>
<p>{translate('SWAP_your_rate')}</p>
</div>
</section>
</article>
);
}
}

View File

@ -1,48 +1,63 @@
import React, { Component } from 'react';
import CurrencySwap from './components/currencySwap';
import SwapInformation from './components/swapInformation';
import CurrentRates from './components/currentRates';
import ReceivingAddress from './components/receivingAddress';
import SwapProgress from './components/swapProgress';
import OnGoingSwapInformation from './components/onGoingSwapInformation';
import { connect } from 'react-redux';
import * as swapActions from 'actions/swap';
import PropTypes from 'prop-types';
import * as swapTypes from 'actions/swapTypes';
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';
type ReduxStateProps = {
step: string,
destinationAddress: string,
destinationKind: string,
originKind: string,
destinationKindOptions: String[],
originKindOptions: String[],
bityRates: boolean,
originAmount: ?number,
destinationAmount: ?number,
// PART 3
referenceNumber: string,
timeRemaining: string,
numberOfConfirmation: number,
orderStep: number,
orderStarted: boolean
};
type ReduxActionProps = {
changeStepSwap: (value: number) => swapTypes.ChangeStepSwapAction,
originKindSwap: (value: string) => swapTypes.OriginKindSwapAction,
destinationKindSwap: (value: string) => swapTypes.DestinationKindSwapAction,
originAmountSwap: (value: ?number) => swapTypes.OriginAmountSwapAction,
destinationAmountSwap: (
value: ?number
) => swapTypes.DestinationAmountSwapAction,
loadBityRatesSwap: () => swapTypes.LoadBityRatesSwapAction,
destinationAddressSwap: (
value: ?string
) => swapTypes.DestinationAddressSwapAction,
restartSwap: () => swapTypes.RestartSwapAction,
stopLoadBityRatesSwap: () => swapTypes.StopLoadBityRatesSwapAction,
// PART 3 (IGNORE FOR NOW)
referenceNumberSwap: typeof swapActions.referenceNumberSwap
};
class Swap extends Component {
constructor(props) {
super(props);
}
static propTypes = {
bityRates: PropTypes.any,
originAmount: PropTypes.any,
destinationAmount: PropTypes.any,
originKind: PropTypes.string,
partOneComplete: PropTypes.bool,
destinationKind: PropTypes.string,
destinationKindOptions: PropTypes.array,
originKindOptions: PropTypes.array,
destinationAddress: PropTypes.string,
originKindSwap: PropTypes.func,
destinationKindSwap: PropTypes.func,
originAmountSwap: PropTypes.func,
destinationAmountSwap: PropTypes.func,
loadBityRates: PropTypes.func,
partOneCompleteSwap: PropTypes.func,
destinationAddressSwap: PropTypes.func,
restartSwap: PropTypes.func,
partTwoCompleteSwap: PropTypes.func,
partTwoComplete: PropTypes.bool,
stopLoadBityRates: PropTypes.func
};
props: ReduxActionProps & ReduxStateProps;
componentDidMount() {
this.props.loadBityRates();
this.props.loadBityRatesSwap();
}
componentWillUnmount() {
this.props.stopLoadBityRatesSwap();
}
render() {
let {
// STATE
bityRates,
originAmount,
destinationAmount,
@ -50,21 +65,50 @@ class Swap extends Component {
destinationKind,
destinationKindOptions,
originKindOptions,
destinationAddress,
step,
referenceNumber,
timeRemaining,
numberOfConfirmations,
orderStep,
// ACTIONS
restartSwap,
stopLoadBityRatesSwap,
changeStepSwap,
originKindSwap,
destinationKindSwap,
originAmountSwap,
destinationAmountSwap,
partOneComplete,
partOneCompleteSwap,
destinationAddressSwap,
destinationAddress,
restartSwap,
partTwoCompleteSwap,
partTwoComplete,
stopLoadBityRates
referenceNumberSwap
} = this.props;
let wantToSwapMyProps = {
let ReceivingAddressProps = {
destinationKind,
destinationAddressSwap,
destinationAddress,
stopLoadBityRatesSwap,
changeStepSwap,
referenceNumberSwap
};
let SwapInfoHeaderProps = {
referenceNumber,
timeRemaining,
originAmount,
originKind,
destinationKind,
destinationAmount,
restartSwap,
numberOfConfirmations,
orderStep
};
const { ETHBTC, ETHREP, BTCETH, BTCREP } = bityRates;
const CurrentRatesProps = { ETHBTC, ETHREP, BTCETH, BTCREP };
const CurrencySwapProps = {
bityRates,
originAmount,
destinationAmount,
@ -76,62 +120,22 @@ class Swap extends Component {
destinationKindSwap,
originAmountSwap,
destinationAmountSwap,
partOneCompleteSwap
};
let yourInformationProps = {
originAmount,
destinationAmount,
originKind,
destinationKind
};
let receivingAddressProps = {
destinationKind,
destinationAddressSwap,
destinationAddress,
partTwoCompleteSwap,
stopLoadBityRates
};
const referenceNumber = '2341asdfads';
const timeRemaining = '2:30';
let onGoingSwapInformationProps = {
// from bity
referenceNumber: referenceNumber,
timeRemaining: timeRemaining,
originAmount,
originKind,
destinationKind,
destinationAmount,
restartSwap,
numberOfConfirmations: 3,
activeStep: 2
changeStepSwap
};
return (
<section className="container" style={{ minHeight: '50%' }}>
<div className="tab-content">
<main className="tab-pane swap-tab">
{!partOneComplete &&
!partTwoComplete &&
{step === 1 &&
<div>
<CurrentRates {...bityRates} />
<CurrencySwap {...wantToSwapMyProps} />
</div>}
{partOneComplete &&
!partTwoComplete &&
<div>
<SwapInformation {...yourInformationProps} />
<ReceivingAddress {...receivingAddressProps} />
</div>}
{partOneComplete &&
partTwoComplete &&
<div>
<OnGoingSwapInformation {...onGoingSwapInformationProps} />
<SwapProgress {...onGoingSwapInformationProps} />
<CurrentRates {...CurrentRatesProps} />
<CurrencySwap {...CurrencySwapProps} />
</div>}
{(step === 2 || step === 3) &&
<SwapInfoHeader {...SwapInfoHeaderProps} />}
{step === 2 && <ReceivingAddress {...ReceivingAddressProps} />}
{step === 3 && <SwapProgress {...SwapInfoHeaderProps} />}
</main>
</div>
</section>
@ -141,16 +145,20 @@ class Swap extends Component {
function mapStateToProps(state) {
return {
partTwoComplete: state.swap.partTwoComplete,
step: state.swap.step,
destinationAddress: state.swap.destinationAddress,
partOneComplete: state.swap.partOneComplete,
originAmount: state.swap.originAmount,
destinationAmount: state.swap.destinationAmount,
originKind: state.swap.originKind,
destinationKind: state.swap.destinationKind,
destinationKindOptions: state.swap.destinationKindOptions,
originKindOptions: state.swap.originKindOptions,
bityRates: state.swap.bityRates
bityRates: state.swap.bityRates,
referenceNumber: state.swap.referenceNumber,
timeRemaining: state.swap.timeRemaining,
numberOfConfirmations: state.swap.numberOfConfirmations,
orderStep: state.swap.orderStep,
orderStarted: state.swap.orderStarted
};
}

View File

@ -4,12 +4,12 @@ import {
SWAP_ORIGIN_AMOUNT,
SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES,
SWAP_PART_ONE_COMPLETE,
SWAP_DESTINATION_ADDRESS,
SWAP_RESTART,
SWAP_PART_TWO_COMPLETE
SWAP_STEP,
SWAP_REFERENCE_NUMBER
} from 'actions/swapConstants';
import { combineAndUpper } from 'api/bity';
import { combineAndUpper } from 'utils/formatters';
export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP'];
@ -24,10 +24,13 @@ const initialState = {
originKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(
element => element !== 'REP'
),
partOneComplete: false,
partTwoComplete: false,
step: 1,
bityRates: {},
destinationAddress: ''
destinationAddress: '',
referenceNumber: '',
timeRemaining: '',
numberOfConfirmations: null,
orderStep: null
};
const buildDestinationAmount = (
@ -101,16 +104,12 @@ export function swap(state = initialState, action) {
...action.value
}
};
case SWAP_PART_ONE_COMPLETE:
case SWAP_STEP: {
return {
...state,
partOneComplete: action.value
};
case SWAP_PART_TWO_COMPLETE:
return {
...state,
partTwoComplete: action.value
step: action.value
};
}
case SWAP_DESTINATION_ADDRESS:
return {
...state,
@ -122,6 +121,14 @@ export function swap(state = initialState, action) {
...initialState,
bityRates: state.bityRates
};
case SWAP_REFERENCE_NUMBER:
return {
...state,
referenceNumber: '2341asdfads',
timeRemaining: '2:30',
numberOfConfirmations: 3,
orderStep: 2
};
default:
return state;
}

View File

@ -5,6 +5,10 @@ export function toFixedIfLarger(number: number, fixedSize: number = 6): string {
return parseFloat(number.toFixed(fixedSize)).toString();
}
export function combineAndUpper(...args: string[]) {
return args.reduce((acc, item) => acc.concat(item.toUpperCase()), '');
}
// Use in place of angular number filter
export function formatNumber(number: Big, digits: number = 3): string {
let parts = number.toFixed(digits).split('.');