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 { import {
SWAP_DESTINATION_AMOUNT, SWAP_DESTINATION_AMOUNT,
SWAP_DESTINATION_KIND, SWAP_DESTINATION_KIND,
SWAP_ORIGIN_AMOUNT, SWAP_ORIGIN_AMOUNT,
SWAP_ORIGIN_KIND, SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES, SWAP_UPDATE_BITY_RATES,
SWAP_PART_ONE_COMPLETE,
SWAP_PART_TWO_COMPLETE,
SWAP_DESTINATION_ADDRESS, SWAP_DESTINATION_ADDRESS,
SWAP_RESTART, SWAP_RESTART,
SWAP_LOAD_BITY_RATES, SWAP_LOAD_BITY_RATES,
SWAP_STOP_LOAD_BITY_RATES SWAP_STOP_LOAD_BITY_RATES,
SWAP_STEP,
SWAP_REFERENCE_NUMBER
} from './swapConstants'; } 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 { return {
type: SWAP_ORIGIN_KIND, type: SWAP_ORIGIN_KIND,
value value
}; };
}; };
export const destinationKindSwap = value => { export const destinationKindSwap = (
value: string
): swapTypes.DestinationKindSwapAction => {
return { return {
type: SWAP_DESTINATION_KIND, type: SWAP_DESTINATION_KIND,
value value
}; };
}; };
export const originAmountSwap = value => { export const originAmountSwap = (
value: ?number
): swapTypes.OriginAmountSwapAction => {
return { return {
type: SWAP_ORIGIN_AMOUNT, type: SWAP_ORIGIN_AMOUNT,
value value
}; };
}; };
export const destinationAmountSwap = value => { export const destinationAmountSwap = (
value: ?number
): swapTypes.DestinationAmountSwapAction => {
return { return {
type: SWAP_DESTINATION_AMOUNT, type: SWAP_DESTINATION_AMOUNT,
value value
}; };
}; };
export const updateBityRatesSwap = value => { export const updateBityRatesSwap = (
value: swapTypes.Pairs
): swapTypes.BityRatesSwapAction => {
return { return {
type: SWAP_UPDATE_BITY_RATES, type: SWAP_UPDATE_BITY_RATES,
value value
}; };
}; };
export const partOneCompleteSwap = (value: boolean) => { export const destinationAddressSwap = (
return { value: ?string
type: SWAP_PART_ONE_COMPLETE, ): swapTypes.DestinationAddressSwapAction => {
value
};
};
export const partTwoCompleteSwap = (value: boolean) => {
return {
type: SWAP_PART_TWO_COMPLETE,
value
};
};
export const destinationAddressSwap = value => {
return { return {
type: SWAP_DESTINATION_ADDRESS, type: SWAP_DESTINATION_ADDRESS,
value value
}; };
}; };
export const restartSwap = () => { export const restartSwap = (): swapTypes.RestartSwapAction => {
return { return {
type: SWAP_RESTART type: SWAP_RESTART
}; };
}; };
export const loadBityRates = () => { export const loadBityRatesSwap = (): swapTypes.LoadBityRatesSwapAction => {
return { return {
type: SWAP_LOAD_BITY_RATES type: SWAP_LOAD_BITY_RATES
}; };
}; };
export const stopLoadBityRates = () => { export const stopLoadBityRatesSwap = (): swapTypes.StopLoadBityRatesSwapAction => {
return { return {
type: SWAP_STOP_LOAD_BITY_RATES 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_ORIGIN_AMOUNT = 'SWAP_ORIGIN_AMOUNT';
export const SWAP_DESTINATION_AMOUNT = 'SWAP_DESTINATION_AMOUNT'; export const SWAP_DESTINATION_AMOUNT = 'SWAP_DESTINATION_AMOUNT';
export const SWAP_UPDATE_BITY_RATES = 'SWAP_UPDATE_BITY_RATES'; 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_DESTINATION_ADDRESS = 'SWAP_DESTINATION_ADDRESS';
export const SWAP_RESTART = 'SWAP_RESTART'; export const SWAP_RESTART = 'SWAP_RESTART';
export const SWAP_LOAD_BITY_RATES = 'SWAP_LOAD_BITY_RATES'; 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_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 // @flow
import bityConfig from 'config/bity'; 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) { function findRateFromBityRateList(rateObjects, pairName) {
return rateObjects.find(x => x.pair === 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 translate, { getTranslators } from 'translations';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config/data';
import logo from 'assets/images/logo-myetherwallet.svg'; import logo from 'assets/images/logo-myetherwallet.svg';
import { bityReferralURL } from 'config/data';
import './index.scss'; import './index.scss';
export default class Footer extends Component { export default class Footer extends Component {
@ -72,7 +72,7 @@ export default class Footer extends Component {
<li> <li>
<a <a
aria-label="Swap Ether or Bitcoin via Bity.com" aria-label="Swap Ether or Bitcoin via Bity.com"
href="https://bity.com/af/jshkb37v" href={bityReferralURL}
target="_blank" target="_blank"
> >
Swap ETH/BTC/EUR/CHF via Bity.com 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 gasPriceMaxGwei: 60
}; };
export const bityReferralURL = 'https://bity.com/af/jshkb37v';
export const languages = [ export const languages = [
{ {
sign: 'en', 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 React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import PropTypes from 'prop-types';
import { toFixedIfLarger } from 'utils/formatters'; 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 { export default class CurrentRates extends Component {
constructor(props) { props: Pairs;
super(props);
this.state = {
ETHBTCAmount: 1,
ETHREPAmount: 1,
BTCETHAmount: 1,
BTCREPAmount: 1
};
}
static propTypes = { state = {
ETHBTC: PropTypes.number, ETHBTCAmount: 1,
ETHREP: PropTypes.number, ETHREPAmount: 1,
BTCETH: PropTypes.number, BTCETHAmount: 1,
BTCREP: PropTypes.number BTCREPAmount: 1
}; };
onChange = event => { onChange = (event: SyntheticInputEvent) => {
const target = event.target; const target = event.target;
const value = target.value; const value = target.value;
const name = target.name; 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() { render() {
const { ETHBTC, ETHREP, BTCETH, BTCREP } = this.props;
return ( return (
<article className="swap-rates"> <article className="swap-rates">
<section className="row"> <section className="row">
@ -50,7 +46,7 @@ export default class CurrentRates extends Component {
/> />
<span> <span>
{` ETH = ${toFixedIfLarger( {` ETH = ${toFixedIfLarger(
this.state.ETHBTCAmount * this.props.ETHBTC, this.state.ETHBTCAmount * ETHBTC,
6 6
)} BTC`} )} BTC`}
</span> </span>
@ -64,7 +60,7 @@ export default class CurrentRates extends Component {
/> />
<span> <span>
{` ETH = ${toFixedIfLarger( {` ETH = ${toFixedIfLarger(
this.state.ETHREPAmount * this.props.ETHREP, this.state.ETHREPAmount * ETHREP,
6 6
)} REP`} )} REP`}
</span> </span>
@ -80,7 +76,7 @@ export default class CurrentRates extends Component {
/> />
<span> <span>
{` BTC = ${toFixedIfLarger( {` BTC = ${toFixedIfLarger(
this.state.BTCETHAmount * this.props.BTCETH, this.state.BTCETHAmount * BTCETH,
6 6
)} ETH`} )} ETH`}
</span> </span>
@ -94,22 +90,14 @@ export default class CurrentRates extends Component {
/> />
<span> <span>
{` BTC = ${toFixedIfLarger( {` BTC = ${toFixedIfLarger(
this.state.BTCREPAmount * this.props.BTCREP, this.state.BTCREPAmount * BTCREP,
6 6
)} REP`} )} REP`}
</span> </span>
</p> </p>
</div> </div>
<a <a className="link bity-logo" href={bityReferralURL} target="_blank">
className="link bity-logo" <img src={bityLogoWhite} width={120} height={49} />
href="https://bity.com/af/jshkb37v"
target="_blank"
>
<img
src={'https://www.myetherwallet.com/images/logo-bity-white.svg'}
width={120}
height={49}
/>
</a> </a>
</section> </section>
</article> </article>

View File

@ -1,18 +1,25 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import * as swapTypes from 'actions/swapTypes';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config/data';
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators'; import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
import translate from 'translations'; import translate from 'translations';
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 { export default class ReceivingAddress extends Component {
static propTypes = { props: StateProps & ActionProps;
destinationKind: PropTypes.string.isRequired,
destinationAddressSwap: PropTypes.func.isRequired,
destinationAddress: PropTypes.string,
partTwoCompleteSwap: PropTypes.func,
stopLoadBityRates: PropTypes.func
};
onChangeDestinationAddress = (event: SyntheticInputEvent) => { onChangeDestinationAddress = (event: SyntheticInputEvent) => {
const value = event.target.value; const value = event.target.value;
@ -20,8 +27,10 @@ export default class ReceivingAddress extends Component {
}; };
onClickPartTwoComplete = () => { onClickPartTwoComplete = () => {
this.props.stopLoadBityRates(); this.props.stopLoadBityRatesSwap();
this.props.partTwoCompleteSwap(true); // temporarily here for testing purposes. will live in saga
this.props.referenceNumberSwap('');
this.props.changeStepSwap(3);
}; };
render() { render() {
@ -40,8 +49,12 @@ export default class ReceivingAddress extends Component {
<section className="row"> <section className="row">
<div className="col-sm-8 col-sm-offset-2 col-xs-12"> <div className="col-sm-8 col-sm-offset-2 col-xs-12">
<label> <label>
<span>{translate('SWAP_rec_add')}</span> <span>
<strong> ({destinationKind})</strong> {translate('SWAP_rec_add')}
</span>
<strong>
{' '}({destinationKind})
</strong>
</label> </label>
<input <input
className={`form-control ${validAddress className={`form-control ${validAddress
@ -60,7 +73,9 @@ export default class ReceivingAddress extends Component {
onClick={this.onClickPartTwoComplete} onClick={this.onClickPartTwoComplete}
className="btn btn-primary btn-lg" className="btn btn-primary btn-lg"
> >
<span>{translate('SWAP_start_CTA')}</span> <span>
{translate('SWAP_start_CTA')}
</span>
</button> </button>
</section> </section>
</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 //flow
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import translate from 'translations'; import translate from 'translations';
export default class SwapProgress extends Component { export type StateProps = {
constructor(props) { numberOfConfirmations: number,
super(props); destinationKind: string,
} originKind: string,
orderStep: number
};
static propTypes = { export default class SwapProgress extends Component {
numberOfConfirmations: PropTypes.number.isRequired, props: StateProps;
destinationKind: PropTypes.string.isRequired,
originKind: PropTypes.string.isRequired,
activeStep: PropTypes.number.isRequired
};
computedClass(i: number) { computedClass(i: number) {
const { activeStep } = this.props; const { orderStep } = this.props;
let cssClass = 'progress-item'; let cssClass = 'progress-item';
if (activeStep > i) { if (orderStep > i) {
cssClass += ' progress-true'; cssClass += ' progress-true';
} else if (i === activeStep) { } else if (i === orderStep) {
cssClass += ' progress-active'; cssClass += ' progress-active';
} else {
cssClass += '';
} }
return cssClass; return cssClass;
} }
@ -35,25 +30,37 @@ export default class SwapProgress extends Component {
<div className="sep" /> <div className="sep" />
<div className={this.computedClass(1)}> <div className={this.computedClass(1)}>
<div className="progress-circle"><i>1</i></div> <div className="progress-circle">
<p>{translate('SWAP_progress_1')}</p> <i>1</i>
</div>
<p>
{translate('SWAP_progress_1')}
</p>
</div> </div>
<div className={this.computedClass(2)}> <div className={this.computedClass(2)}>
<div className="progress-circle"><i>2</i></div> <div className="progress-circle">
<i>2</i>
</div>
<p> <p>
<span>{translate('SWAP_progress_2')}</span>{originKind}... <span>{translate('SWAP_progress_2')}</span>
{originKind}...
</p> </p>
</div> </div>
<div className={this.computedClass(3)}> <div className={this.computedClass(3)}>
<div className="progress-circle"><i>3</i></div> <div className="progress-circle">
<i>3</i>
</div>
<p> <p>
{originKind} <span>{translate('SWAP_progress_3')}</span> {originKind} <span>{translate('SWAP_progress_3')}</span>
</p> </p>
</div> </div>
<div className={this.computedClass(4)}> <div className={this.computedClass(4)}>
<div className="progress-circle"><i>4</i></div> <div className="progress-circle">
<i>4</i>
</div>
<p> <p>
<span>Sending your </span>{destinationKind} <span>Sending your </span>
{destinationKind}
<br /> <br />
<small> <small>
Waiting for {numberOfConfirmations} confirmations... Waiting for {numberOfConfirmations} confirmations...
@ -61,7 +68,9 @@ export default class SwapProgress extends Component {
</p> </p>
</div> </div>
<div className={this.computedClass(5)}> <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> <p>Order Complete</p>
</div> </div>
</section> </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 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 { connect } from 'react-redux';
import * as swapActions from 'actions/swap'; 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 { class Swap extends Component {
constructor(props) { props: ReduxActionProps & ReduxStateProps;
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
};
componentDidMount() { componentDidMount() {
this.props.loadBityRates(); this.props.loadBityRatesSwap();
}
componentWillUnmount() {
this.props.stopLoadBityRatesSwap();
} }
render() { render() {
let { let {
// STATE
bityRates, bityRates,
originAmount, originAmount,
destinationAmount, destinationAmount,
@ -50,21 +65,50 @@ class Swap extends Component {
destinationKind, destinationKind,
destinationKindOptions, destinationKindOptions,
originKindOptions, originKindOptions,
destinationAddress,
step,
referenceNumber,
timeRemaining,
numberOfConfirmations,
orderStep,
// ACTIONS
restartSwap,
stopLoadBityRatesSwap,
changeStepSwap,
originKindSwap, originKindSwap,
destinationKindSwap, destinationKindSwap,
originAmountSwap, originAmountSwap,
destinationAmountSwap, destinationAmountSwap,
partOneComplete,
partOneCompleteSwap,
destinationAddressSwap, destinationAddressSwap,
destinationAddress, referenceNumberSwap
restartSwap,
partTwoCompleteSwap,
partTwoComplete,
stopLoadBityRates
} = this.props; } = 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, bityRates,
originAmount, originAmount,
destinationAmount, destinationAmount,
@ -76,62 +120,22 @@ class Swap extends Component {
destinationKindSwap, destinationKindSwap,
originAmountSwap, originAmountSwap,
destinationAmountSwap, destinationAmountSwap,
partOneCompleteSwap changeStepSwap
};
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
}; };
return ( return (
<section className="container" style={{ minHeight: '50%' }}> <section className="container" style={{ minHeight: '50%' }}>
<div className="tab-content"> <div className="tab-content">
<main className="tab-pane swap-tab"> <main className="tab-pane swap-tab">
{!partOneComplete && {step === 1 &&
!partTwoComplete &&
<div> <div>
<CurrentRates {...bityRates} /> <CurrentRates {...CurrentRatesProps} />
<CurrencySwap {...wantToSwapMyProps} /> <CurrencySwap {...CurrencySwapProps} />
</div>}
{partOneComplete &&
!partTwoComplete &&
<div>
<SwapInformation {...yourInformationProps} />
<ReceivingAddress {...receivingAddressProps} />
</div>}
{partOneComplete &&
partTwoComplete &&
<div>
<OnGoingSwapInformation {...onGoingSwapInformationProps} />
<SwapProgress {...onGoingSwapInformationProps} />
</div>} </div>}
{(step === 2 || step === 3) &&
<SwapInfoHeader {...SwapInfoHeaderProps} />}
{step === 2 && <ReceivingAddress {...ReceivingAddressProps} />}
{step === 3 && <SwapProgress {...SwapInfoHeaderProps} />}
</main> </main>
</div> </div>
</section> </section>
@ -141,16 +145,20 @@ class Swap extends Component {
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
partTwoComplete: state.swap.partTwoComplete, step: state.swap.step,
destinationAddress: state.swap.destinationAddress, destinationAddress: state.swap.destinationAddress,
partOneComplete: state.swap.partOneComplete,
originAmount: state.swap.originAmount, originAmount: state.swap.originAmount,
destinationAmount: state.swap.destinationAmount, destinationAmount: state.swap.destinationAmount,
originKind: state.swap.originKind, originKind: state.swap.originKind,
destinationKind: state.swap.destinationKind, destinationKind: state.swap.destinationKind,
destinationKindOptions: state.swap.destinationKindOptions, destinationKindOptions: state.swap.destinationKindOptions,
originKindOptions: state.swap.originKindOptions, originKindOptions: state.swap.originKindOptions,
bityRates: state.swap.bityRates bityRates: state.swap.bityRates,
referenceNumber: state.swap.referenceNumber,
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_AMOUNT,
SWAP_ORIGIN_KIND, SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES, SWAP_UPDATE_BITY_RATES,
SWAP_PART_ONE_COMPLETE,
SWAP_DESTINATION_ADDRESS, SWAP_DESTINATION_ADDRESS,
SWAP_RESTART, SWAP_RESTART,
SWAP_PART_TWO_COMPLETE SWAP_STEP,
SWAP_REFERENCE_NUMBER
} from 'actions/swapConstants'; } from 'actions/swapConstants';
import { combineAndUpper } from 'api/bity'; import { combineAndUpper } from 'utils/formatters';
export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP']; export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP'];
@ -24,10 +24,13 @@ const initialState = {
originKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter( originKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(
element => element !== 'REP' element => element !== 'REP'
), ),
partOneComplete: false, step: 1,
partTwoComplete: false,
bityRates: {}, bityRates: {},
destinationAddress: '' destinationAddress: '',
referenceNumber: '',
timeRemaining: '',
numberOfConfirmations: null,
orderStep: null
}; };
const buildDestinationAmount = ( const buildDestinationAmount = (
@ -101,16 +104,12 @@ export function swap(state = initialState, action) {
...action.value ...action.value
} }
}; };
case SWAP_PART_ONE_COMPLETE: case SWAP_STEP: {
return { return {
...state, ...state,
partOneComplete: action.value step: action.value
};
case SWAP_PART_TWO_COMPLETE:
return {
...state,
partTwoComplete: action.value
}; };
}
case SWAP_DESTINATION_ADDRESS: case SWAP_DESTINATION_ADDRESS:
return { return {
...state, ...state,
@ -122,6 +121,14 @@ export function swap(state = initialState, action) {
...initialState, ...initialState,
bityRates: state.bityRates bityRates: state.bityRates
}; };
case SWAP_REFERENCE_NUMBER:
return {
...state,
referenceNumber: '2341asdfads',
timeRemaining: '2:30',
numberOfConfirmations: 3,
orderStep: 2
};
default: default:
return state; return state;
} }

View File

@ -5,6 +5,10 @@ export function toFixedIfLarger(number: number, fixedSize: number = 6): string {
return parseFloat(number.toFixed(fixedSize)).toString(); 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 // Use in place of angular number filter
export function formatNumber(number: Big, digits: number = 3): string { export function formatNumber(number: Big, digits: number = 3): string {
let parts = number.toFixed(digits).split('.'); let parts = number.toFixed(digits).split('.');