Merge pull request #21 from MyEtherWallet/swap_part_2

Swap Part 2
This commit is contained in:
Daniel Ternyak 2017-06-26 18:08:18 -05:00 committed by GitHub
commit 2acd78473d
16 changed files with 721 additions and 318 deletions

View File

@ -3,7 +3,9 @@ import {
SWAP_DESTINATION_KIND,
SWAP_ORIGIN_AMOUNT,
SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES
SWAP_UPDATE_BITY_RATES,
SWAP_PART_ONE_COMPLETE,
SWAP_RECEIVING_ADDRESS
} from './swapConstants';
export const originKindSwap = value => {
@ -40,3 +42,17 @@ export const updateBityRatesSwap = value => {
value
};
};
export const partOneCompleteSwap = (value: boolean) => {
return {
type: SWAP_PART_ONE_COMPLETE,
value
};
};
export const receivingAddressSwap = value => {
return {
type: SWAP_RECEIVING_ADDRESS,
value
};
};

View File

@ -3,3 +3,5 @@ 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_RECEIVING_ADDRESS = 'SWAP_RECEIVING_ADDRESS';

View File

@ -1,100 +1,202 @@
import React, {Component} from 'react';
import translate, {getTranslators} from 'translations';
import React, { Component } from 'react';
import translate, { getTranslators } from 'translations';
import { DONATION_ADDRESSES_MAP } from 'config/data';
export default class Footer extends Component {
render() {
const translators = getTranslators()
return (
<footer role="contentinfo" aria-label="footer">
<div className="container">
<section className="row">
<section className="row">
<div className="col-sm-3 footer-1">
<p aria-hidden="true">
<a href="/">
{/* TODO - don't hardcode image path*/}
<img src={'https://www.myetherwallet.com/images/logo-myetherwallet.svg'}
height="55px" width="auto" alt="Ether Wallet"/>
</a>
</p>
<p><span>{translate('FOOTER_1')}</span>
<span>{translate('FOOTER_1b')}</span>
<a aria-label="kvhnuke's github"
href="https://github.com/kvhnuke"
target="_blank">kvhnuke</a>
{' & '}
<a aria-label="tayvano's github"
href="https://github.com/tayvano"
target="_blank">tayvano</a>.
</p>
<br/>
</div>
<div className="col-sm-6 footer-2">
<h5><i aria-hidden="true">💝</i>{translate('FOOTER_2')}</h5>
<ul>
<li> ETH: <span
className="mono wrap">0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8</span>
</li>
<li> BTC: <span className="mono wrap">1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6</span></li>
</ul>
render() {
const translators = getTranslators();
return (
<footer role="contentinfo" aria-label="footer">
<div className="container">
<section className="row">
<section className="row">
<div className="col-sm-3 footer-1">
<p aria-hidden="true">
<a href="/">
{/* TODO - don't hardcode image path*/}
<img
src={
'https://www.myetherwallet.com/images/logo-myetherwallet.svg'
}
height="55px"
width="auto"
alt="Ether Wallet"
/>
</a>
</p>
<p>
<span>{translate('FOOTER_1')}</span>
<span>{translate('FOOTER_1b')}</span>
<a
aria-label="kvhnuke's github"
href="https://github.com/kvhnuke"
target="_blank"
>
kvhnuke
</a>
{' & '}
<a
aria-label="tayvano's github"
href="https://github.com/tayvano"
target="_blank"
>
tayvano
</a>.
</p>
<br />
</div>
<div className="col-sm-6 footer-2">
<h5><i aria-hidden="true">💝</i>{translate('FOOTER_2')}</h5>
<ul>
<li>
{' '}ETH:{' '}
<span className="mono wrap">
{DONATION_ADDRESSES_MAP.ETH}
</span>
</li>
<li>
{' '}BTC:{' '}
<span className="mono wrap">
{DONATION_ADDRESSES_MAP.BTC}
</span>
</li>
</ul>
<h5><i aria-hidden="true">👫</i>{translate('ADD_Warning_1')}</h5>
<p>Consider using our affiliate links to...</p>
<ul>
<li><a aria-label="Swap Ether or Bitcoin via Bity.com"
href="https://bity.com/af/jshkb37v" target="_blank">Swap ETH/BTC/EUR/CHF via
Bity.com</a></li>
<li><a href="https://www.ledgerwallet.com/r/fa4b?path=/products/" target="_blank">Buy
a
Ledger Nano S</a></li>
<li><a href="https://trezor.io/?a=myetherwallet.com" target="_blank">Buy a
TREZOR</a>
</li>
</ul>
<h5>
<i aria-hidden="true">👫</i>{translate('ADD_Warning_1')}
</h5>
<p>Consider using our affiliate links to...</p>
<ul>
<li>
<a
aria-label="Swap Ether or Bitcoin via Bity.com"
href="https://bity.com/af/jshkb37v"
target="_blank"
>
Swap ETH/BTC/EUR/CHF via
Bity.com
</a>
</li>
<li>
<a
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
target="_blank"
>
Buy
a
Ledger Nano S
</a>
</li>
<li>
<a
href="https://trezor.io/?a=myetherwallet.com"
target="_blank"
>
Buy a
TREZOR
</a>
</li>
</ul>
{!!translators.length &&
<h5> <i>🏅</i> <span>{translate('Translator_Desc')}</span></h5>
}
{!!translators.length &&
<ul>
<li>{translators.map(key => <span key={key}>{translate(key)}</span>)}</li>
</ul>
}
</div>
<div className="col-sm-3 footer-3">
<h5><i aria-hidden="true">🌎</i> On the Web</h5>
<ul>
<li><a aria-label="my ether wallet.com" href="https://www.MyEtherWallet.com"
target="_blank">www.MyEtherWallet.com</a></li>
<li><a aria-label="my ether wallet github"
href="https://github.com/kvhnuke/etherwallet"
target="_blank">Github: MyEtherWallet.com & CX</a></li>
<li><a aria-label="our organization on github"
href="https://github.com/MyEtherWallet"
target="_blank">Github: MyEtherWallet (Org)</a></li>
<li><a aria-label="join our slack" href="https://myetherwallet.herokuapp.com/"
target="_blank">Join Our Slack</a></li>
<li><a aria-label="twitter" href="https://twitter.com/myetherwallet"
target="_blank">Twitter</a>
</li>
<li><a aria-label="facebook" href="https://www.facebook.com/MyEtherWallet/"
target="_blank">Facebook</a></li>
</ul>
{!!translators.length &&
<h5>
{' '}<i>🏅</i> <span>{translate('Translator_Desc')}</span>
</h5>}
{!!translators.length &&
<ul>
<li>
{translators.map(key =>
<span key={key}>{translate(key)}</span>
)}
</li>
</ul>}
</div>
<div className="col-sm-3 footer-3">
<h5><i aria-hidden="true">🌎</i> On the Web</h5>
<ul>
<li>
<a
aria-label="my ether wallet.com"
href="https://www.MyEtherWallet.com"
target="_blank"
>
www.MyEtherWallet.com
</a>
</li>
<li>
<a
aria-label="my ether wallet github"
href="https://github.com/kvhnuke/etherwallet"
target="_blank"
>
Github: MyEtherWallet.com & CX
</a>
</li>
<li>
<a
aria-label="our organization on github"
href="https://github.com/MyEtherWallet"
target="_blank"
>
Github: MyEtherWallet (Org)
</a>
</li>
<li>
<a
aria-label="join our slack"
href="https://myetherwallet.herokuapp.com/"
target="_blank"
>
Join Our Slack
</a>
</li>
<li>
<a
aria-label="twitter"
href="https://twitter.com/myetherwallet"
target="_blank"
>
Twitter
</a>
</li>
<li>
<a
aria-label="facebook"
href="https://www.facebook.com/MyEtherWallet/"
target="_blank"
>
Facebook
</a>
</li>
</ul>
<h5><i aria-hidden="true">🙏</i> Support</h5>
<ul>
<li><a aria-label="email support at myetherwallet.com"
href="mailto:support@myetherwallet.com" target="_blank">Email</a></li>
<li><a aria-label="open a github issue"
href="https://github.com/kvhnuke/etherwallet/issues" target="_blank">Github
Issue</a></li>
</ul>
</div>
</section>
</section>
</div>
</footer>
)
}
<h5><i aria-hidden="true">🙏</i> Support</h5>
<ul>
<li>
<a
aria-label="email support at myetherwallet.com"
href="mailto:support@myetherwallet.com"
target="_blank"
>
Email
</a>
</li>
<li>
<a
aria-label="open a github issue"
href="https://github.com/kvhnuke/etherwallet/issues"
target="_blank"
>
Github
Issue
</a>
</li>
</ul>
</div>
</section>
</section>
</div>
</footer>
);
}
}

View File

@ -1,162 +1,168 @@
export const DONATION_ADDRESSES_MAP = {
BTC: '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6',
ETH: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
REP: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8'
};
export const languages = [
{
sign: 'en',
name: 'English'
},
{
sign: 'de',
name: 'Deutsch'
},
{
sign: 'el',
name: 'Ελληνικά'
},
{
sign: 'es',
name: 'Español'
},
{
sign: 'fi',
name: 'Suomi'
},
{
sign: 'fr',
name: 'Français'
},
{
sign: 'hu',
name: 'Magyar'
},
{
sign: 'id',
name: 'Indonesian'
},
{
sign: 'it',
name: 'Italiano'
},
{
sign: 'ja',
name: '日本語'
},
{
sign: 'nl',
name: 'Nederlands'
},
{
sign: 'no',
name: 'Norsk Bokmål'
},
{
sign: 'pl',
name: 'Polski'
},
{
sign: 'pt',
name: 'Português'
},
{
sign: 'ru',
name: 'Русский'
},
{
sign: 'ko',
name: 'Korean'
},
// {
// 'sign': 'sk',
// 'name': 'Slovenčina'
// },
// {
// 'sign': 'sl',
// 'name': 'Slovenščina'
// },
// {
// 'sign': 'sv',
// 'name': 'Svenska'
// },
{
sign: 'tr',
name: 'Türkçe'
},
{
sign: 'vi',
name: 'Tiếng Việt'
},
{
sign: 'zhcn',
name: '简体中文'
},
{
sign: 'zhtw',
name: '繁體中文'
}
{
sign: 'en',
name: 'English'
},
{
sign: 'de',
name: 'Deutsch'
},
{
sign: 'el',
name: 'Ελληνικά'
},
{
sign: 'es',
name: 'Español'
},
{
sign: 'fi',
name: 'Suomi'
},
{
sign: 'fr',
name: 'Français'
},
{
sign: 'hu',
name: 'Magyar'
},
{
sign: 'id',
name: 'Indonesian'
},
{
sign: 'it',
name: 'Italiano'
},
{
sign: 'ja',
name: '日本語'
},
{
sign: 'nl',
name: 'Nederlands'
},
{
sign: 'no',
name: 'Norsk Bokmål'
},
{
sign: 'pl',
name: 'Polski'
},
{
sign: 'pt',
name: 'Português'
},
{
sign: 'ru',
name: 'Русский'
},
{
sign: 'ko',
name: 'Korean'
},
// {
// 'sign': 'sk',
// 'name': 'Slovenčina'
// },
// {
// 'sign': 'sl',
// 'name': 'Slovenščina'
// },
// {
// 'sign': 'sv',
// 'name': 'Svenska'
// },
{
sign: 'tr',
name: 'Türkçe'
},
{
sign: 'vi',
name: 'Tiếng Việt'
},
{
sign: 'zhcn',
name: '简体中文'
},
{
sign: 'zhtw',
name: '繁體中文'
}
];
export const nodeList = [
{
name: 'ETH',
blockExplorerTX: 'https://etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://etherscan.io/address/[[address]]',
// 'type': nodes.nodeTypes.ETH,
eip155: true,
chainId: 1,
// 'tokenList': require('./tokens/ethTokens.json'),
// 'abiList': require('./abiDefinitions/ethAbi.json'),
estimateGas: true,
service: 'MyEtherWallet'
// 'lib': new nodes.customNode('https://api.myetherapi.com/eth', '')
},
{
name: 'ETH',
blockExplorerTX: 'https://etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://etherscan.io/address/[[address]]',
// 'type': nodes.nodeTypes.ETH,
eip155: true,
chainId: 1,
// 'tokenList': require('./tokens/ethTokens.json'),
// 'abiList': require('./abiDefinitions/ethAbi.json'),
estimateGas: false,
service: 'Etherscan.io'
// 'lib': require('./nodeHelpers/etherscan')
},
{
name: 'Ropsten',
// 'type': nodes.nodeTypes.Ropsten,
blockExplorerTX: 'https://ropsten.etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://ropsten.etherscan.io/address/[[address]]',
eip155: true,
chainId: 3,
// 'tokenList': require('./tokens/ropstenTokens.json'),
// 'abiList': require('./abiDefinitions/ropstenAbi.json'),
estimateGas: false,
service: 'MyEtherWallet'
// 'lib': new nodes.customNode('https://api.myetherapi.com/rop', '')
},
{
name: 'Kovan',
// 'type': nodes.nodeTypes.Kovan,
blockExplorerTX: 'https://kovan.etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://kovan.etherscan.io/address/[[address]]',
eip155: true,
chainId: 42,
// 'tokenList': require('./tokens/kovanTokens.json'),
// 'abiList': require('./abiDefinitions/kovanAbi.json'),
estimateGas: false,
service: 'Etherscan.io'
// 'lib': require('./nodeHelpers/etherscanKov')
},
{
name: 'ETC',
blockExplorerTX: 'https://gastracker.io/tx/[[txHash]]',
blockExplorerAddr: 'https://gastracker.io/addr/[[address]]',
// 'type': nodes.nodeTypes.ETC,
eip155: true,
chainId: 61,
// 'tokenList': require('./tokens/etcTokens.json'),
// 'abiList': require('./abiDefinitions/etcAbi.json'),
estimateGas: false,
service: 'Epool.io'
// 'lib': new nodes.customNode('https://mewapi.epool.io', '')
}
{
name: 'ETH',
blockExplorerTX: 'https://etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://etherscan.io/address/[[address]]',
// 'type': nodes.nodeTypes.ETH,
eip155: true,
chainId: 1,
// 'tokenList': require('./tokens/ethTokens.json'),
// 'abiList': require('./abiDefinitions/ethAbi.json'),
estimateGas: true,
service: 'MyEtherWallet'
// 'lib': new nodes.customNode('https://api.myetherapi.com/eth', '')
},
{
name: 'ETH',
blockExplorerTX: 'https://etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://etherscan.io/address/[[address]]',
// 'type': nodes.nodeTypes.ETH,
eip155: true,
chainId: 1,
// 'tokenList': require('./tokens/ethTokens.json'),
// 'abiList': require('./abiDefinitions/ethAbi.json'),
estimateGas: false,
service: 'Etherscan.io'
// 'lib': require('./nodeHelpers/etherscan')
},
{
name: 'Ropsten',
// 'type': nodes.nodeTypes.Ropsten,
blockExplorerTX: 'https://ropsten.etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://ropsten.etherscan.io/address/[[address]]',
eip155: true,
chainId: 3,
// 'tokenList': require('./tokens/ropstenTokens.json'),
// 'abiList': require('./abiDefinitions/ropstenAbi.json'),
estimateGas: false,
service: 'MyEtherWallet'
// 'lib': new nodes.customNode('https://api.myetherapi.com/rop', '')
},
{
name: 'Kovan',
// 'type': nodes.nodeTypes.Kovan,
blockExplorerTX: 'https://kovan.etherscan.io/tx/[[txHash]]',
blockExplorerAddr: 'https://kovan.etherscan.io/address/[[address]]',
eip155: true,
chainId: 42,
// 'tokenList': require('./tokens/kovanTokens.json'),
// 'abiList': require('./abiDefinitions/kovanAbi.json'),
estimateGas: false,
service: 'Etherscan.io'
// 'lib': require('./nodeHelpers/etherscanKov')
},
{
name: 'ETC',
blockExplorerTX: 'https://gastracker.io/tx/[[txHash]]',
blockExplorerAddr: 'https://gastracker.io/addr/[[address]]',
// 'type': nodes.nodeTypes.ETC,
eip155: true,
chainId: 61,
// 'tokenList': require('./tokens/etcTokens.json'),
// 'abiList': require('./abiDefinitions/etcAbi.json'),
estimateGas: false,
service: 'Epool.io'
// 'lib': new nodes.customNode('https://mewapi.epool.io', '')
}
];

View File

@ -31,9 +31,9 @@ class CoinTypeDropDown extends Component {
}
}
export default class WantToSwapMy extends Component {
constructor(props, context) {
super(props, context);
export default class CurrencySwap extends Component {
constructor(props) {
super(props);
}
static propTypes = {
@ -47,10 +47,13 @@ export default class WantToSwapMy extends Component {
originKindSwap: PropTypes.func,
destinationKindSwap: PropTypes.func,
originAmountSwap: PropTypes.func,
destinationAmountSwap: PropTypes.func
destinationAmountSwap: PropTypes.func,
partOneCompleteSwap: PropTypes.func
};
onClickStartSwap() {}
onClickStartSwap = () => {
this.props.partOneCompleteSwap(true);
};
onChangeOriginAmount = amount => {
let originAmountAsNumber = parseFloat(amount);

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import translate from 'translations';
import PropTypes from 'prop-types';
import { toFixedIfLarger } from 'utils/formatters';
export default class CurrentRates extends Component {
constructor(props) {
@ -48,10 +49,10 @@ export default class CurrentRates extends Component {
name="ETHBTCAmount"
/>
<span>
ETH = {(this.state.ETHBTCAmount * this.props.ETHBTC).toFixed(
{` ETH = ${toFixedIfLarger(
this.state.ETHBTCAmount * this.props.ETHBTC,
6
)}{' '}
BTC
)} BTC`}
</span>
</p>
<p className="mono">
@ -62,10 +63,10 @@ export default class CurrentRates extends Component {
name="ETHREPAmount"
/>
<span>
ETH = {(this.state.ETHREPAmount * this.props.ETHREP).toFixed(
{` ETH = ${toFixedIfLarger(
this.state.ETHREPAmount * this.props.ETHREP,
6
)}{' '}
REP
)} REP`}
</span>
</p>
</div>
@ -78,10 +79,10 @@ export default class CurrentRates extends Component {
name="BTCETHAmount"
/>
<span>
BTC = {(this.state.BTCETHAmount * this.props.BTCETH).toFixed(
{` BTC = ${toFixedIfLarger(
this.state.BTCETHAmount * this.props.BTCETH,
6
)}{' '}
ETH
)} ETH`}
</span>
</p>
<p className="mono">
@ -92,10 +93,10 @@ export default class CurrentRates extends Component {
name="BTCREPAmount"
/>
<span>
BTC = {(this.state.BTCREPAmount * this.props.BTCREP).toFixed(
{` BTC = ${toFixedIfLarger(
this.state.BTCREPAmount * this.props.BTCREP,
6
)}{' '}
REP
)} REP`}
</span>
</p>
</div>

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { DONATION_ADDRESSES_MAP } from 'config/data';
import Validator from 'libs/validator';
import translate from 'translations';
export default class ReceivingAddress extends Component {
constructor(props) {
super(props);
this.validator = new Validator();
this.state = {
validAddress: false
};
}
static propTypes = {
destinationKind: PropTypes.string.isRequired,
receivingAddressSwap: PropTypes.func.isRequired,
receivingAddress: PropTypes.string
};
onChangeReceivingAddress = event => {
const value = event.target.value;
this.props.receivingAddressSwap(value);
let validAddress;
// TODO - find better pattern here once currencies move beyond BTC, ETH, REP
if (this.props.destinationKind === 'BTC') {
validAddress = this.validator.isValidBTCAddress(value);
} else {
validAddress = this.validator.isValidETHAddress(value);
}
this.setState({ validAddress });
};
render() {
const { destinationKind, receivingAddress } = this.props;
const { validAddress } = this.state;
return (
<article className="swap-start">
<section className="swap-address block">
<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>
</label>
<input
className={`form-control ${validAddress
? 'is-valid'
: 'is-invalid'}`}
type="text"
value={receivingAddress}
onChange={this.onChangeReceivingAddress}
placeholder={DONATION_ADDRESSES_MAP[destinationKind]}
/>
</div>
</section>
<section className="row text-center">
<button disabled={!validAddress} className="btn btn-primary btn-lg">
<span>{translate('SWAP_start_CTA')}</span>
</button>
</section>
</section>
</article>
);
}
}

View File

@ -0,0 +1,79 @@
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,6 +1,9 @@
import React, { Component } from 'react';
import WantToSwapMy from './components/wantToSwapMy';
import CurrencySwap from './components/currencySwap';
import SwapInformation from './components/swapInformation';
import CurrentRates from './components/currentRates';
import ReceivingAddress from './components/receivingAddress';
import { connect } from 'react-redux';
import * as swapActions from 'actions/swap';
@ -18,14 +21,18 @@ class Swap extends Component {
originAmount: PropTypes.any,
destinationAmount: PropTypes.any,
originKind: PropTypes.string,
partOneComplete: PropTypes.bool,
destinationKind: PropTypes.string,
destinationKindOptions: PropTypes.array,
originKindOptions: PropTypes.array,
receivingAddress: PropTypes.string,
originKindSwap: PropTypes.func,
destinationKindSwap: PropTypes.func,
originAmountSwap: PropTypes.func,
destinationAmountSwap: PropTypes.func,
updateBityRatesSwap: PropTypes.func
updateBityRatesSwap: PropTypes.func,
partOneCompleteSwap: PropTypes.func,
receivingAddressSwap: PropTypes.func
};
componentDidMount() {
@ -55,7 +62,11 @@ class Swap extends Component {
originKindSwap,
destinationKindSwap,
originAmountSwap,
destinationAmountSwap
destinationAmountSwap,
partOneComplete,
partOneCompleteSwap,
receivingAddressSwap,
receivingAddress
} = this.props;
let wantToSwapMyProps = {
@ -69,15 +80,37 @@ class Swap extends Component {
originKindSwap,
destinationKindSwap,
originAmountSwap,
destinationAmountSwap
destinationAmountSwap,
partOneCompleteSwap
};
let yourInformationProps = {
originAmount,
destinationAmount,
originKind,
destinationKind
};
let yourReceivingProps = {
destinationKind,
receivingAddressSwap,
receivingAddress
};
return (
<section className="container" style={{ minHeight: '50%' }}>
<div className="tab-content">
<main className="tab-pane swap-tab">
<CurrentRates {...bityRates} />
<WantToSwapMy {...wantToSwapMyProps} />
{!partOneComplete &&
<div>
<CurrentRates {...bityRates} />
<CurrencySwap {...wantToSwapMyProps} />
</div>}
{partOneComplete &&
<div>
<SwapInformation {...yourInformationProps} />
<ReceivingAddress {...yourReceivingProps} />
</div>}
</main>
</div>
</section>
@ -87,6 +120,8 @@ class Swap extends Component {
function mapStateToProps(state) {
return {
receivingAddress: state.swap.receivingAddress,
partOneComplete: state.swap.partOneComplete,
originAmount: state.swap.originAmount,
destinationAmount: state.swap.destinationAmount,
originKind: state.swap.originKind,

View File

@ -1,54 +1,58 @@
import React from 'react'
import {render} from 'react-dom'
import {syncHistoryWithStore, routerMiddleware} from 'react-router-redux'
import {composeWithDevTools} from 'redux-devtools-extension'
import Perf from 'react-addons-perf'
import {createStore, applyMiddleware} from 'redux'
import RootReducer from './reducers'
import {Root} from 'components'
import {Routing, history} from './routing'
import {createLogger} from 'redux-logger'
import createSagaMiddleware from 'redux-saga'
import notificationsSaga from './sagas/notifications'
import React from 'react';
import { render } from 'react-dom';
import { syncHistoryWithStore, routerMiddleware } from 'react-router-redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import Perf from 'react-addons-perf';
import { createStore, applyMiddleware } from 'redux';
import RootReducer from './reducers';
import { Root } from 'components';
import { Routing, history } from './routing';
import { createLogger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import notificationsSaga from './sagas/notifications';
// application styles
import 'assets/styles/etherwallet-master.less'
import 'assets/styles/etherwallet-master.less';
const sagaMiddleware = createSagaMiddleware()
const sagaMiddleware = createSagaMiddleware();
const configureStore = () => {
let sagaApplied = applyMiddleware(sagaMiddleware);
let store;
let middleware;
let sagaApplied = applyMiddleware(sagaMiddleware);
let store;
let middleware;
if (process.env.NODE_ENV !== 'production') {
window.Perf = Perf;
sagaApplied = composeWithDevTools(sagaApplied);
const logger = createLogger({
collapsed: true
});
middleware = applyMiddleware(routerMiddleware(history), logger);
} else {
middleware = applyMiddleware(routerMiddleware(history));
}
if (process.env.NODE_ENV !== 'production') {
window.Perf = Perf;
sagaApplied = composeWithDevTools(sagaApplied);
const logger = createLogger({
collapsed: true
});
middleware = applyMiddleware(routerMiddleware(history), logger);
} else {
middleware = applyMiddleware(routerMiddleware(history));
}
store = createStore(RootReducer, sagaApplied, middleware);
sagaMiddleware.run(notificationsSaga)
return store
store = createStore(RootReducer, sagaApplied, middleware);
sagaMiddleware.run(notificationsSaga);
return store;
};
const renderRoot = (Root) => {
let store = configureStore();
let syncedHistory = syncHistoryWithStore(history, store);
render(
<Root key={Math.random()}
routes={Routing}
history={syncedHistory}
store={store}/>, document.getElementById('app'))
const renderRoot = Root => {
let store = configureStore();
let syncedHistory = syncHistoryWithStore(history, store);
render(
<Root
key={Math.random()}
routes={Routing}
history={syncedHistory}
store={store}
/>,
document.getElementById('app')
);
};
renderRoot(Root);
if (module.hot) {
module.hot.accept()
module.hot.accept();
}

16
common/libs/validator.js Normal file
View File

@ -0,0 +1,16 @@
import WalletAddressValidator from 'wallet-address-validator';
import ethUtil from 'ethereumjs-util';
export default class Validator {
isValidETHAddress = function(address) {
if (address && address === '0x0000000000000000000000000000000000000000')
return false;
if (address) {
return ethUtil.isValidAddress(address);
}
return false;
};
isValidBTCAddress = function(address) {
return WalletAddressValidator.validate(address, 'BTC');
};
}

View File

@ -3,15 +3,17 @@ import {
SWAP_DESTINATION_KIND,
SWAP_ORIGIN_AMOUNT,
SWAP_ORIGIN_KIND,
SWAP_UPDATE_BITY_RATES
SWAP_UPDATE_BITY_RATES,
SWAP_PART_ONE_COMPLETE,
SWAP_RECEIVING_ADDRESS
} from 'actions/swapConstants';
import { combineAndUpper } from 'api/bity';
export const ALL_CRYPTO_KIND_OPTIONS = ['BTC', 'ETH', 'REP'];
const initialState = {
originAmount: 0,
destinationAmount: 0,
originAmount: '',
destinationAmount: '',
originKind: 'BTC',
destinationKind: 'ETH',
destinationKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(
@ -20,7 +22,9 @@ const initialState = {
originKindOptions: ALL_CRYPTO_KIND_OPTIONS.filter(
element => element !== 'REP'
),
bityRates: {}
partOneComplete: false,
bityRates: {},
receivingAddress: ''
};
const buildDestinationAmount = (
@ -94,6 +98,16 @@ export function swap(state = initialState, action) {
...action.value
}
};
case SWAP_PART_ONE_COMPLETE:
return {
...state,
partOneComplete: action.value
};
case SWAP_RECEIVING_ADDRESS:
return {
...state,
receivingAddress: action.value
};
default:
return state;
}

View File

@ -0,0 +1,5 @@
//flow
export function toFixedIfLarger(number: number, fixedSize: number = 6): string {
return parseFloat(number.toFixed(fixedSize)).toString();
}

View File

@ -5,6 +5,7 @@
"description": "MyEtherWallet v4",
"dependencies": {
"axios": "^0.16.2",
"ethereumjs-util": "^5.1.2",
"lodash": "^4.17.4",
"prop-types": "^15.5.8",
"react": "^15.4.2",
@ -17,6 +18,7 @@
"redux-logger": "^3.0.1",
"redux-saga": "^0.15.3",
"store2": "^2.5.0",
"wallet-address-validator": "^0.1.0",
"whatwg-fetch": "^2.0.2"
},
"devDependencies": {

View File

@ -0,0 +1,34 @@
import Validator from '../../common/libs/validator';
import { DONATION_ADDRESSES_MAP } from '../../common/config/data';
describe('Validator', () => {
it('should validate correct BTC address as true', () => {
const validator = new Validator();
expect(
validator.isValidBTCAddress(DONATION_ADDRESSES_MAP.BTC)
).toBeTruthy();
});
it('should validate incorrect BTC address as false', () => {
const validator = new Validator();
expect(
validator.isValidBTCAddress(
'nonsense' + DONATION_ADDRESSES_MAP.BTC + 'nonsense'
)
).toBeFalsy();
});
it('should validate correct ETH address as true', () => {
const validator = new Validator();
expect(
validator.isValidETHAddress(DONATION_ADDRESSES_MAP.ETH)
).toBeTruthy();
});
it('should validate incorrect ETH address as false', () => {
const validator = new Validator();
expect(
validator.isValidETHAddress(
'nonsense' + DONATION_ADDRESSES_MAP.ETH + 'nonsense'
)
).toBeFalsy();
});
});

View File

@ -0,0 +1,17 @@
import { toFixedIfLarger } from '../../common/utils/formatters';
describe('toFixedIfLarger', () => {
it('should return same value if decimal isnt longer than default', () => {
const numExample = 7.002;
expect(toFixedIfLarger(numExample)).toEqual(String(numExample));
});
it('should return shortened value rounded up if decimal is longer than default', () => {
const numExample = 7.1234567;
expect(toFixedIfLarger(numExample)).toEqual(String(7.123457));
});
it('should return shortened value if decimal is longer than passed fixedSize', () => {
const numExample = 7.12345678;
expect(toFixedIfLarger(numExample, 2)).toEqual(String(7.12));
});
});