merge develop into 'swap_part_3'

This commit is contained in:
Daniel Ternyak 2017-07-03 22:21:19 -05:00
commit 06ae70988a
40 changed files with 1529 additions and 438 deletions

View File

@ -1,9 +1,7 @@
{
"extends": ["eslint:recommended", "plugin:react/recommended"],
"parser": "babel-eslint",
"plugins": [
"react"
],
"plugins": ["react"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true,
@ -19,7 +17,7 @@
},
"rules": {
"comma-dangle": 1,
"quotes": [ 1, "single" ],
"quotes": [1, "single"],
"no-undef": 1,
"global-strict": 0,
"no-extra-semi": 1,
@ -32,6 +30,8 @@
"react/jsx-uses-react": 1
},
"globals": {
"SyntheticInputEvent": false
"SyntheticInputEvent": false,
"SyntheticKeyboardEvent": false,
"Generator": false
}
}

View File

@ -1,18 +1,30 @@
// @flow
import { setLanguage } from 'translations';
export const CONFIG_LANGUAGE_CHANGE = 'CONFIG_LANGUAGE_CHANGE';
export const CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE';
export const CHANGE_LANGUAGE = (value: any) => {
setLanguage(value.sign);
return {
type: CONFIG_LANGUAGE_CHANGE,
value
};
export type ChangeNodeAction = {
type: 'CONFIG_NODE_CHANGE',
// FIXME $keyof?
value: string
};
export const CHANGE_NODE = (value: any) =>
Object({
type: CONFIG_NODE_CHANGE,
value
});
export type ChangeLanguageAction = {
type: 'CONFIG_LANGUAGE_CHANGE',
value: string
};
export type ConfigAction = ChangeNodeAction | ChangeLanguageAction;
export function changeLanguage(sign: string) {
setLanguage(sign);
return {
type: 'CONFIG_LANGUAGE_CHANGE',
value: sign
};
}
export function changeNode(value: string): ChangeNodeAction {
return {
type: 'CONFIG_NODE_CHANGE',
value
};
}

44
common/actions/wallet.js Normal file
View File

@ -0,0 +1,44 @@
// @flow
import type { PrivateKeyUnlockParams } from 'libs/wallet/privkey';
import BaseWallet from 'libs/wallet/base';
export type UnlockPrivateKeyAction = {
type: 'WALLET_UNLOCK_PRIVATE_KEY',
payload: PrivateKeyUnlockParams
};
export type SaveWalletAction = {
type: 'WALLET_SAVE',
payload: BaseWallet
};
export type InitWalletAction = {
type: 'WALLET_INIT'
};
export type WalletAction =
| UnlockPrivateKeyAction
| SaveWalletAction
| InitWalletAction;
export function unlockPrivateKey(
value: PrivateKeyUnlockParams
): UnlockPrivateKeyAction {
return {
type: 'WALLET_UNLOCK_PRIVATE_KEY',
payload: value
};
}
export function saveWallet(value: BaseWallet): SaveWalletAction {
return {
type: 'WALLET_SAVE',
payload: value
};
}
export function initWallet(): InitWalletAction {
return {
type: 'WALLET_INIT'
};
}

View File

@ -1,31 +1,24 @@
// @flow
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TabsOptions from './components/TabsOptions';
import { Link } from 'react-router';
import { Dropdown } from 'components/ui';
import { languages, nodeList } from '../../config/data';
import { languages, NODES } from '../../config/data';
export default class Header extends Component {
static propTypes = {
location: PropTypes.object,
props: {
languageSelection: string,
nodeSelection: string,
// Language DropDown
changeLanguage: PropTypes.func,
languageSelection: PropTypes.object,
// Node Dropdown
changeNode: PropTypes.func,
nodeSelection: PropTypes.object
changeLanguage: (sign: string) => any,
changeNode: (key: string) => any
};
render() {
const {
languageSelection,
changeLanguage,
changeNode,
nodeSelection
} = this.props;
const { languageSelection, changeNode, nodeSelection } = this.props;
const selectedLanguage =
languages.find(l => l.sign === languageSelection) || languages[0];
const selectedNode = NODES[nodeSelection];
return (
<div>
@ -48,10 +41,10 @@ export default class Header extends Component {
</span>
&nbsp;&nbsp;&nbsp;
<Dropdown
ariaLabel={`change language. current language ${languageSelection.name}`}
ariaLabel={`change language. current language ${selectedLanguage.name}`}
options={languages}
formatTitle={o => o.name}
value={languageSelection}
value={selectedLanguage}
extra={[
<li key={'separator'} role="separator" className="divider" />,
<li key={'disclaimer'}>
@ -60,23 +53,23 @@ export default class Header extends Component {
</a>
</li>
]}
onChange={changeLanguage}
onChange={this.changeLanguage}
/>
&nbsp;&nbsp;&nbsp;
<Dropdown
ariaLabel={`change node. current node ${nodeSelection.name} node by ${nodeSelection.service}`}
options={nodeList}
ariaLabel={`change node. current node ${selectedNode.network} node by ${selectedNode.service}`}
options={Object.keys(NODES)}
formatTitle={o => [
o.name,
NODES[o].network,
' ',
<small key="service">({o.service})</small>
<small key="service">
({NODES[o].service})
</small>
]}
value={nodeSelection}
extra={
<li>
<a onClick={() => {}}>
Add Custom Node
</a>
<a onClick={() => {}}>Add Custom Node</a>
</li>
}
onChange={changeNode}
@ -86,8 +79,11 @@ export default class Header extends Component {
</section>
<TabsOptions {...this.props} />
</div>
);
}
changeLanguage = (value: { sign: string }) => {
this.props.changeLanguage(value.sign);
};
}

View File

@ -0,0 +1,63 @@
import React, { Component } from 'react';
import translate from 'translations';
import wallet from 'ethereumjs-wallet';
import ethUtil from 'ethereumjs-util';
export default class KeystoreDecrypt extends Component {
constructor(props) {
super(props);
}
handleFileSelection = event => {
const fileReader = new FileReader();
const inputFile = event.target.files[0];
fileReader.onload = () => {
try {
const keyStoreString = fileReader.result;
const decryptedWallet = wallet.fromV3(
keyStoreString,
'asdfasdfasdf',
true
);
const privateHex = ethUtil.bufferToHex(decryptedWallet._privKey);
const publicHex = ethUtil.bufferToHex(
ethUtil.privateToAddress(decryptedWallet._privKey)
);
console.log(privateHex, publicHex); // TODO: Remove console log, it's only here to let Travis pass
} catch (e) {
console.error('Could not parse Keystore file.', e);
}
};
fileReader.readAsText(inputFile, 'utf-8');
};
render() {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>{translate('ADD_Radio_2_alt')}</h4>
<div className="form-group">
<input
type="file"
id="fselector"
onChange={this.handleFileSelection}
/>
<label htmlFor="fselector">
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex="0"
role="button"
>
{translate('ADD_Radio_2_short')}
</a>
</label>
</div>
</div>
</section>
);
}
}

View File

@ -0,0 +1,27 @@
import React, { Component } from 'react';
import translate from 'translations';
export default class LedgerNanoSDecrypt extends Component {
render() {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>{translate('ADD_Radio_2_alt')}</h4>
<div className="form-group">
<input type="file" id="fselector" />
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex="0"
role="button"
>
{translate('ADD_Radio_2_short')}
</a>
</div>
</div>
</section>
);
}
}

View File

@ -0,0 +1,27 @@
import React, { Component } from 'react';
import translate from 'translations';
export default class MnemonicDecrypt extends Component {
render() {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>{translate('ADD_Radio_2_alt')}</h4>
<div className="form-group">
<input type="file" id="fselector" />
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex="0"
role="button"
>
{translate('ADD_Radio_2_short')}
</a>
</div>
</div>
</section>
);
}
}

View File

@ -0,0 +1,101 @@
// @flow
import React, { Component } from 'react';
import translate from 'translations';
import { isValidPrivKey } from 'libs/validators';
import type { PrivateKeyUnlockParams } from 'libs/wallet/privkey';
export type PrivateKeyValue = PrivateKeyUnlockParams & {
valid: boolean
};
function fixPkey(key) {
if (key.indexOf('0x') === 0) {
return key.slice(2);
}
return key;
}
export default class PrivateKeyDecrypt extends Component {
props: {
value: PrivateKeyUnlockParams,
onChange: (value: PrivateKeyUnlockParams) => void,
onUnlock: () => void
};
render() {
const { key, password } = this.props.value;
const fixedPkey = fixPkey(key);
const isValid = isValidPrivKey(fixedPkey.length);
const isPassRequired = fixedPkey.length > 64;
return (
<section className="col-md-4 col-sm-6">
<div id="selectedTypeKey">
<h4>
{translate('ADD_Radio_3')}
</h4>
<div className="form-group">
<textarea
id="aria-private-key"
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
value={key}
onChange={this.onPkeyChange}
onKeyDown={this.onKeyDown}
placeholder={translate('x_PrivKey2')}
rows="4"
/>
</div>
{isValid &&
isPassRequired &&
<div className="form-group">
<p>
{translate('ADD_Label_3')}
</p>
<input
className={`form-control ${password.length > 0
? 'is-valid'
: 'is-invalid'}`}
value={password}
onChange={this.onPasswordChange}
onKeyDown={this.onKeyDown}
placeholder={translate('x_Password')}
type="password"
/>
</div>}
</div>
</section>
);
}
onPkeyChange = (e: SyntheticInputEvent) => {
const fixedPkey = fixPkey(e.target.value);
const isValid = isValidPrivKey(fixedPkey.length);
const isPassRequired = fixedPkey.length > 64;
const valid =
isValid && (isPassRequired ? this.props.value.password.length > 0 : true);
this.props.onChange({ ...this.props.value, key: e.target.value, valid });
};
onPasswordChange = (e: SyntheticInputEvent) => {
const fixedPkey = fixPkey(this.props.value.key);
const isValid = isValidPrivKey(fixedPkey.length);
const isPassRequired = fixedPkey.length > 64;
const valid =
isValid && (isPassRequired ? e.target.value.length > 0 : true);
this.props.onChange({
...this.props.value,
password: e.target.value,
valid
});
};
onKeyDown = (e: SyntheticKeyboardEvent) => {
if (e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
this.props.onUnlock();
}
};
}

View File

@ -0,0 +1,27 @@
import React, { Component } from 'react';
import translate from 'translations';
export default class TrezorDecrypt extends Component {
render() {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>{translate('ADD_Radio_2_alt')}</h4>
<div className="form-group">
<input type="file" id="fselector" />
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex="0"
role="button"
>
{translate('ADD_Radio_2_short')}
</a>
</div>
</div>
</section>
);
}
}

View File

@ -0,0 +1,27 @@
import React, { Component } from 'react';
import translate from 'translations';
export default class ViewOnlyDecrypt extends Component {
render() {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>{translate('ADD_Radio_2_alt')}</h4>
<div className="form-group">
<input type="file" id="fselector" />
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex="0"
role="button"
>
{translate('ADD_Radio_2_short')}
</a>
</div>
</div>
</section>
);
}
}

View File

@ -0,0 +1,163 @@
// @flow
import React, { Component } from 'react';
import translate from 'translations';
import KeystoreDecrypt from './Keystore';
import PrivateKeyDecrypt from './PrivateKey';
import type { PrivateKeyValue } from './PrivateKey';
import MnemonicDecrypt from './Mnemonic';
import LedgerNanoSDecrypt from './LedgerNano';
import TrezorDecrypt from './Trezor';
import ViewOnlyDecrypt from './ViewOnly';
import map from 'lodash/map';
import { unlockPrivateKey } from 'actions/wallet';
import { connect } from 'react-redux';
const WALLETS = {
'keystore-file': {
lid: 'x_Keystore2',
component: KeystoreDecrypt,
initialParams: {}
},
'private-key': {
lid: 'x_PrivKey2',
component: PrivateKeyDecrypt,
initialParams: {
key: '',
password: ''
},
unlock: unlockPrivateKey
},
'mnemonic-phrase': {
lid: 'x_Mnemonic',
component: MnemonicDecrypt
},
'ledger-nano-s': {
lid: 'x_Ledger',
component: LedgerNanoSDecrypt
},
trezor: {
lid: 'x_Trezor',
component: TrezorDecrypt
},
'view-only': {
lid: 'View with Address Only',
component: ViewOnlyDecrypt
}
};
type UnlockParams = {} | PrivateKeyValue;
type State = {
selectedWalletKey: string,
value: UnlockParams
};
export class WalletDecrypt extends Component {
props: {
// FIXME
dispatch: (action: any) => void
};
state: State = {
selectedWalletKey: 'keystore-file',
value: WALLETS['keystore-file'].initialParams
};
getDecryptionComponent() {
const { selectedWalletKey, value } = this.state;
const selectedWallet = WALLETS[selectedWalletKey];
if (!selectedWallet) {
return null;
}
return (
<selectedWallet.component
value={value}
onChange={this.onChange}
onUnlock={this.onUnlock}
/>
);
}
buildWalletOptions() {
return map(WALLETS, (wallet, key) => {
const isSelected = this.state.selectedWalletKey === key;
return (
<label className="radio" key={key}>
<input
aria-flowto={`aria-${key}`}
aria-labelledby={`${key}-label`}
type="radio"
name="decryption-choice-radio-group"
value={key}
checked={isSelected}
onChange={this.handleDecryptionChoiceChange}
/>
<span id={`${key}-label`}>
{translate(wallet.lid)}
</span>
</label>
);
});
}
handleDecryptionChoiceChange = (event: SyntheticInputEvent) => {
const wallet = WALLETS[event.target.value];
if (!wallet) {
return;
}
this.setState({
selectedWalletKey: event.target.value,
value: wallet.initialParams
});
};
render() {
const decryptionComponent = this.getDecryptionComponent();
return (
<article className="well decrypt-drtv row">
<section className="col-md-4 col-sm-6">
<h4>
{translate('decrypt_Access')}
</h4>
{this.buildWalletOptions()}
</section>
{decryptionComponent}
{!!this.state.value.valid &&
<section className="col-md-4 col-sm-6">
<h4 id="uploadbtntxt-wallet">
{translate('ADD_Label_6')}
</h4>
<div className="form-group">
<a
tabIndex="0"
role="button"
className="btn btn-primary btn-block"
onClick={this.onUnlock}
>
{translate('ADD_Label_6_short')}
</a>
</div>
</section>}
</article>
);
}
onChange = (value: UnlockParams) => {
this.setState({ value });
};
onUnlock = () => {
this.props.dispatch(
WALLETS[this.state.selectedWalletKey].unlock(this.state.value)
);
};
}
export default connect()(WalletDecrypt);

View File

@ -1,3 +1,5 @@
export Header from './Header';
export Footer from './Footer';
export Root from './Root';
// @flow
export { default as Header } from './Header';
export { default as Footer } from './Footer';
export { default as Root } from './Root';

View File

@ -1,26 +1,25 @@
// @flow
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class DropdownComponent extends Component {
static propTypes = {
value: PropTypes.object.isRequired,
options: PropTypes.arrayOf(PropTypes.object).isRequired,
ariaLabel: PropTypes.string.isRequired,
formatTitle: PropTypes.func.isRequired,
extra: PropTypes.node,
onChange: PropTypes.func.isRequired
};
type Props<T> = {
value: T,
options: T[],
ariaLabel: string,
formatTitle: (option: T) => any,
extra?: any,
onChange: (value: T) => void
};
// FIXME
props: {
value: any,
options: any[],
ariaLabel: string,
formatTitle: (option: any) => any,
extra?: any,
onChange: (value: any) => void
};
type State = {
expanded: boolean
};
export default class DropdownComponent<T: *> extends Component<
void,
Props<T>,
State
> {
props: Props<T>;
state = {
expanded: false

View File

@ -2,11 +2,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import translate from 'translations';
import WalletDecrypt from 'components/WalletDecrypt';
import BaseWallet from 'libs/wallet/base';
import { connect } from 'react-redux';
import type { State } from 'reducers';
export default class UnlockHeader extends React.Component {
props: {
title: string
};
type Props = {
title: string,
wallet: BaseWallet
};
export class UnlockHeader extends React.Component {
props: Props;
static propTypes = {
title: PropTypes.string.isRequired
};
@ -17,17 +24,33 @@ export default class UnlockHeader extends React.Component {
expanded: true
};
componentDidUpdate(prevProps: Props) {
if (this.props.wallet && this.props.wallet !== prevProps.wallet) {
this.setState({ expanded: false });
}
// not sure if could happen
if (!this.props.wallet && this.props.wallet !== prevProps.wallet) {
this.setState({ expanded: true });
}
}
render() {
return (
<article className="collapse-container">
<div onClick={this.toggleExpanded}>
<a className="collapse-button">
<span>{this.state.expanded ? '-' : '+'}</span>
<span>
{this.state.expanded ? '-' : '+'}
</span>
</a>
<h1>{translate(this.props.title)}</h1>
<h1>
{translate(this.props.title)}
</h1>
</div>
{this.state.expanded &&
<div>
<WalletDecrypt />
{/* @@if (site === 'cx' ) { <cx-wallet-decrypt-drtv></cx-wallet-decrypt-drtv> }
@@if (site === 'mew' ) { <wallet-decrypt-drtv></wallet-decrypt-drtv> } */}
</div>}
@ -43,3 +66,11 @@ export default class UnlockHeader extends React.Component {
});
};
}
function mapStateToProps(state: State) {
return {
wallet: state.wallet
};
}
export default connect(mapStateToProps)(UnlockHeader);

View File

@ -1,9 +1,14 @@
export const donationAddressMap = {
// @flow
import { RPCNode } from 'libs/nodes';
export const DONATION_ADDRESSES_MAP = {
BTC: '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6',
ETH: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
REP: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8'
};
export const donationAddressMap = DONATION_ADDRESSES_MAP;
export const languages = [
{
sign: 'en',
@ -99,70 +104,23 @@ export const languages = [
}
];
export const nodeList = [
{
export const NETWORKS = {
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: 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', '')
chainId: 1
}
];
};
export const NODES = {
eth_mew: {
network: 'ETH',
lib: new RPCNode('https://api.myetherapi.com/eth'),
service: 'MyEtherWallet',
estimateGas: true,
eip155: true
// 'tokenList': require('./tokens/ethTokens.json'),
// 'abiList': require('./abiDefinitions/ethAbi.json'),
}
};

View File

@ -2,86 +2,65 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Footer, Header } from 'components';
import PropTypes from 'prop-types';
import Notifications from './Notifications';
import { CHANGE_LANGUAGE, CHANGE_NODE } from 'actions/config';
import * as actions from 'actions/config';
class App extends Component {
constructor(props) {
super(props);
}
props: {
// FIXME
children: any,
location: any,
router: any,
isMobile: boolean,
static propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.object,
handleWindowResize: PropTypes.func,
languageSelection: string,
nodeSelection: string,
router: PropTypes.object,
isMobile: PropTypes.bool,
changeLanguage: typeof actions.changeLanguage,
changeNode: typeof actions.changeNode,
handleWindowResize: () => void
};
// BEGIN ACTUAL
languageSelection: PropTypes.object,
changeLanguage: PropTypes.func,
render() {
let {
children,
// APP
languageSelection,
changeLanguage,
changeNode,
nodeSelection
} = this.props;
changeNode: PropTypes.func,
nodeSelection: PropTypes.object,
showNotification: PropTypes.func
let headerProps = {
location,
changeLanguage,
languageSelection,
changeNode,
nodeSelection
};
render() {
let {
children,
// APP
languageSelection,
changeLanguage,
changeNode,
nodeSelection
} = this.props;
let headerProps = {
location,
changeLanguage,
languageSelection,
changeNode,
nodeSelection
};
return (
<div className="page-layout">
<main>
<Header {...headerProps} />
<div className="main-content">
{React.cloneElement(children, { languageSelection })}
</div>
<Footer />
</main>
<Notifications />
</div>
);
}
return (
<div className="page-layout">
<main>
<Header {...headerProps} />
<div className="main-content">
{React.cloneElement(children, { languageSelection })}
</div>
<Footer />
</main>
<Notifications />
</div>
);
}
}
function mapStateToProps(state) {
return {
nodeSelection: state.config.nodeSelection,
nodeToggle: state.config.nodeToggle,
languageSelection: state.config.languageSelection,
languageToggle: state.config.languageToggle
};
return {
nodeSelection: state.config.nodeSelection,
nodeToggle: state.config.nodeToggle,
languageSelection: state.config.languageSelection,
languageToggle: state.config.languageToggle
};
}
function mapDispatchToProps(dispatch) {
return {
// FIXME replace with actual types
changeNode: (i: any) => {
dispatch(CHANGE_NODE(i));
},
changeLanguage: (i: any) => {
dispatch(CHANGE_LANGUAGE(i));
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
export default connect(mapStateToProps, actions)(App);

View File

@ -13,6 +13,9 @@ import {
AddressField
} from './components';
import pickBy from 'lodash/pickBy';
import type { State as AppState } from 'reducers';
import { connect } from 'react-redux';
import BaseWallet from 'libs/wallet/base';
// import type { Transaction } from './types';
import customMessages from './messages';
import { donationAddressMap } from 'config/data';
@ -50,7 +53,8 @@ export class SendTransaction extends React.Component {
query: {
[string]: string
}
}
},
wallet: BaseWallet
};
state: State = {
hasQueryString: false,
@ -69,12 +73,10 @@ export class SendTransaction extends React.Component {
if (Object.keys(queryPresets).length) {
this.setState({ ...queryPresets, hasQueryString: true });
}
this.setState(pickBy(queryPresets));
}
render() {
const unlocked = true; //wallet != null
const unlocked = !!this.props.wallet;
const unitReadable = 'UNITREADABLE';
const nodeUnit = 'NODEUNIT';
const hasEnoughBalance = false;
@ -95,8 +97,7 @@ export class SendTransaction extends React.Component {
return (
<section className="container" style={{ minHeight: '50%' }}>
<div className="tab-content">
<main className="tab-pane active">
<main className="tab-pane active" ng-controller="sendTxCtrl">
{hasQueryString &&
<div className="alert alert-info">
<p>
@ -125,8 +126,7 @@ export class SendTransaction extends React.Component {
<strong>
Warning! You do not have enough funds to
complete this swap.
</strong>
{' '}
</strong>{' '}
<br />
Please add more funds or access a different wallet.
</div>
@ -196,7 +196,6 @@ export class SendTransaction extends React.Component {
{' '}Send Transaction{' '}
</a>
</div>
</section>
{'' /* <!-- / Content --> */}
{
@ -275,5 +274,11 @@ export class SendTransaction extends React.Component {
});
};
}
// export connected version
export default SendTransaction;
function mapStateToProps(state: AppState) {
return {
wallet: state.wallet.inst
};
}
export default connect(mapStateToProps)(SendTransaction);

View File

@ -1,10 +1,9 @@
// @flow
import { donationAddressMap } from 'config/data';
export default [
{
// donation address example
to: donationAddressMap.ETH,
to: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
gasLimit: 21000,
data: '',
msg: 'Thank you for donating to MyEtherWallet. TO THE MOON!'

View File

@ -1,126 +0,0 @@
import React, {Component} from 'react';
export default class ViewWallet extends Component {
constructor(props) {
super(props)
}
render() {
return (
<section className="container" style={{minHeight: '50%'}}>
<div className="tab-content">
<article className="tab-pane active ng-scope"
// ng-if="globalService.currentTab==globalService.tabs.viewWalletInfo.id"
// ng-controller="viewWalletCtrl"
>
<article className="collapse-container">
<div
// ng-click="wd = !wd"
>
{/*<a className="collapse-button"><span ng-show="wd" className="ng-hide">+</span><span*/}
{/*ng-show="!wd">-</span></a>*/}
<h1 className="ng-scope">View Wallet Info</h1>
</div>
<div
// ng-show="!wd"
>
<p className="ng-scope">This allows you to download different versions
of
private keys and re-print your paper wallet. You may want to do this in order to
<a
target="_blank"
href="http://ethereum.stackexchange.com/questions/465/how-to-import-a-plain-private-key-into-geth/">
import
your account into Geth/Mist</a>. If you want to check your balance, we
recommend
using a blockchain explorer like <a target="_blank" href="http://etherscan.io/">etherscan.io</a>.
</p>
{/*<wallet-decrypt-drtv/><article className="well decrypt-drtv row ng-scope" ng-controller="decryptWalletCtrl as $crtl">*/}
<article className="well decrypt-drtv row ng-scope"
// ng-controller="decryptWalletCtrl as $crtl"
>
<section className="col-md-4 col-sm-6">
<h4 className="ng-scope">How would you like to access your
wallet?</h4>
<label className="radio">
<input aria-flowto="aria1"
aria-label="Keystore JSON file"
type="radio"
// ng-model="walletType"
value="fileupload"
className="ng-pristine ng-untouched ng-valid ng-empty"
name="133"/>
<span
// translate=""
className="ng-scope">Keystore File (UTC / JSON)</span></label>
<label className="radio"><input aria-flowto="aria2" aria-label="private key"
type="radio"
// ng-model="walletType"
value="pasteprivkey"
className="ng-pristine ng-untouched ng-valid ng-empty"
name="135"/><span
// translate=""
className="ng-scope">Private Key</span></label>
<label className="radio">
<input aria-flowto="aria3" aria-label="mnemonic phrase"
type="radio"
// ng-model="walletType"
value="pastemnemonic"
className="ng-pristine ng-untouched ng-valid ng-empty"
name="137"/><span
// translate=""
className="ng-scope">Mnemonic Phrase</span></label>
<label className="radio">
<input aria-flowto="aria4" aria-label="parity phrase"
type="radio"
// ng-model="walletType"
value="parityBWallet"
className="ng-pristine ng-untouched ng-valid ng-empty"
name="139"/><span
// translate=""
className="ng-scope">Parity Phrase</span></label>
<label className="radio"
// ng-hide="globalService.currentTab==globalService.tabs.signMsg.id"
>
<input
aria-flowto="aria5" type="radio"
aria-label="Ledger Nano S hardware wallet"
// ng-model="walletType"
value="ledger"
className="ng-pristine ng-untouched ng-valid ng-empty" name="141"/>Ledger
Nano S</label>
<label className="radio"
// ng-hide="globalService.currentTab==globalService.tabs.signMsg.id"
>
<input
aria-flowto="aria6" type="radio" aria-label="Trezor hardware wallet"
// ng-model="walletType"
value="trezor"
className="ng-pristine ng-untouched ng-valid ng-empty"
name="142"/>TREZOR</label>
<label className="radio"
// ng-hide="globalService.currentTab!==globalService.tabs.viewWalletInfo.id"
>
<input
aria-label="address" type="radio"
// ng-model="walletType"
value="addressOnly"
className="ng-pristine ng-untouched ng-valid ng-empty"
name="143"/><span>View with Address Only</span></label>
</section>
</article>
</div>
</article>
</article>
</div>
</section>
)
}
}

View File

@ -0,0 +1,26 @@
// @flow
import React, { Component } from 'react';
import translate from 'translations';
export default class ViewWallet extends Component {
render() {
return (
<section className="container">
<div className="tab-content">
<article className="tab-pane active">
<article className="collapse-container">
<div>
<h1>View Wallet Info</h1>
</div>
<div>
<p>
{translate('VIEWWALLET_Subtitle')}
</p>
</div>
</article>
</article>
</div>
</section>
);
}
}

View File

@ -1,2 +1,2 @@
export ViewWallet from './ViewWallet'
export GenerateWallet from './GenerateWallet'
export WalletDecrypt from './WalletDecrypt';
export GenerateWallet from './GenerateWallet';

View File

@ -11,31 +11,33 @@ import { createLogger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import notificationsSaga from './sagas/notifications';
import ensSaga from './sagas/ens';
import walletSaga from './sagas/wallet';
// application styles
import 'assets/styles/etherwallet-master.less';
const sagaMiddleware = createSagaMiddleware();
let store;
const configureStore = () => {
let sagaApplied = applyMiddleware(sagaMiddleware);
const logger = createLogger({
collapsed: true
});
const sagaMiddleware = createSagaMiddleware();
let middleware;
if (process.env.NODE_ENV !== 'production') {
window.Perf = Perf;
sagaApplied = composeWithDevTools(sagaApplied);
const logger = createLogger({
collapsed: true
});
middleware = applyMiddleware(routerMiddleware(history), logger);
middleware = composeWithDevTools(
applyMiddleware(sagaMiddleware, logger, routerMiddleware(history))
);
} else {
middleware = applyMiddleware(routerMiddleware(history));
middleware = applyMiddleware(sagaMiddleware, routerMiddleware(history));
}
store = createStore(RootReducer, sagaApplied, middleware);
store = createStore(RootReducer, void 0, middleware);
sagaMiddleware.run(notificationsSaga);
sagaMiddleware.run(ensSaga);
sagaMiddleware.run(walletSaga);
return store;
};

View File

@ -0,0 +1,8 @@
// @flow
export default class BaseNode {
// FIXME bignumber?
queryBalance(address: string): Promise<number> {
throw 'Implement me';
}
}

View File

@ -0,0 +1,3 @@
// @flow
export { default as BaseNode } from './base';
export { default as RPCNode } from './rpc';

10
common/libs/nodes/rpc.js Normal file
View File

@ -0,0 +1,10 @@
// @flow
import BaseNode from './base';
export default class RPCNode extends BaseNode {
endpoint: string;
constructor(endpoint: string) {
super();
this.endpoint = endpoint;
}
}

View File

@ -72,3 +72,7 @@ function validateEtherAddress(address: string): boolean {
return true;
else return isChecksumAddress(address);
}
export function isValidPrivKey(length: number): boolean {
return length === 64 || length === 128 || length === 132;
}

View File

@ -0,0 +1,7 @@
// @flow
export default class BaseWallet {
getAddress(): string {
throw 'Implement me';
}
}

View File

@ -0,0 +1,28 @@
// @flow
import BaseWallet from './base';
import {
privateToPublic,
publicToAddress,
toChecksumAddress
} from 'ethereumjs-util';
export type PrivateKeyUnlockParams = {
key: string,
password: string
};
export default class PrivKeyWallet extends BaseWallet {
privKey: Buffer;
pubKey: Buffer;
address: Buffer;
constructor(params: PrivateKeyUnlockParams) {
super();
this.privKey = Buffer.from(params.key, 'hex');
this.pubKey = privateToPublic(this.privKey);
this.address = publicToAddress(this.pubKey);
}
getAddress() {
return toChecksumAddress(`0x${this.address.toString('hex')}`);
}
}

View File

@ -1,6 +1,10 @@
// @flow
import { CONFIG_LANGUAGE_CHANGE, CONFIG_NODE_CHANGE } from 'actions/config';
import { languages, nodeList } from '../config/data';
import type {
ConfigAction,
ChangeNodeAction,
ChangeLanguageAction
} from 'actions/config';
import { languages, NODES } from '../config/data';
export type State = {
// FIXME
@ -8,25 +12,34 @@ export type State = {
nodeSelection: string
};
const initialState = {
languageSelection: languages[0],
nodeSelection: nodeList[0]
const initialState: State = {
languageSelection: languages[0].sign,
nodeSelection: Object.keys(NODES)[0]
};
export function config(state: State = initialState, action): State {
function changeLanguage(state: State, action: ChangeLanguageAction): State {
return {
...state,
languageSelection: action.value
};
}
function changeNode(state: State, action: ChangeNodeAction): State {
return {
...state,
nodeSelection: action.value
};
}
export function config(
state: State = initialState,
action: ConfigAction
): State {
switch (action.type) {
case CONFIG_LANGUAGE_CHANGE: {
return {
...state,
languageSelection: action.value
};
}
case CONFIG_NODE_CHANGE: {
return {
...state,
nodeSelection: action.value
};
}
case 'CONFIG_LANGUAGE_CHANGE':
return changeLanguage(state, action);
case 'CONFIG_NODE_CHANGE':
return changeNode(state, action);
default:
return state;
}

View File

@ -13,15 +13,19 @@ import type { State as NotificationsState } from './notifications';
import * as ens from './ens';
import type { State as EnsState } from './ens';
import * as wallet from './wallet';
import type { State as WalletState } from './wallet';
import { reducer as formReducer } from 'redux-form';
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
export type State = {
generateWallet: GenerateWalletState,
conig: ConfigState,
config: ConfigState,
notifications: NotificationsState,
ens: EnsState
ens: EnsState,
wallet: WalletState
};
export default combineReducers({
@ -30,6 +34,7 @@ export default combineReducers({
...swap,
...notifications,
...ens,
...wallet,
form: formReducer,
routing: routerReducer
});

43
common/reducers/wallet.js Normal file
View File

@ -0,0 +1,43 @@
// @flow
import type {
WalletAction,
SaveWalletAction,
InitWalletAction
} from 'actions/wallet';
import BaseWallet from 'libs/wallet/base';
export type State = {
inst: ?BaseWallet,
balance: number,
tokens: {
[string]: number
}
};
const initialState: State = {
inst: null,
balance: 0,
tokens: {}
};
function saveWallet(state: State, action: SaveWalletAction): State {
return { ...state, inst: action.payload };
}
function initWallet(state: State): State {
return { ...state, balance: 0, tokens: {} };
}
export function wallet(
state: State = initialState,
action: WalletAction
): State {
switch (action.type) {
case 'WALLET_SAVE':
return saveWallet(state, action);
case 'WALLET_INIT':
return initWallet(state);
default:
return state;
}
}

View File

@ -1,12 +1,14 @@
// @flow
import { takeEvery, call, put, select } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import type { Effect } from 'redux-saga/effects';
import { cacheEnsAddress } from 'actions/ens';
import type { ResolveEnsNameAction } from 'actions/ens';
import { getEnsAddress } from 'selectors/ens';
import { donationAddressMap } from 'config/data';
function* resolveEns(action: ResolveEnsNameAction) {
function* resolveEns(action?: ResolveEnsNameAction) {
if (!action) return;
const ensName = action.payload;
// FIXME Add resolve logic
//// _ens.getAddress(scope.addressDrtv.ensAddressField, function(data) {
@ -29,6 +31,6 @@ function* resolveEns(action: ResolveEnsNameAction) {
yield put(cacheEnsAddress(ensName, donationAddressMap.ETH));
}
export default function* notificationsSaga() {
export default function* notificationsSaga(): Generator<Effect, void, any> {
yield takeEvery('ENS_RESOLVE', resolveEns);
}

View File

@ -1,21 +1,23 @@
// @flow
import { takeEvery, put, call } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import type { Effect } from 'redux-saga/effects';
import { closeNotification } from 'actions/notifications';
import type { ShowNotificationAction } from 'actions/notifications';
function* handleNotification(action: ShowNotificationAction) {
const { duration } = action.payload;
// show forever
if (duration === 0) {
return;
}
function* handleNotification(action?: ShowNotificationAction) {
if (!action) return;
const { duration } = action.payload;
// show forever
if (duration === 0) {
return;
}
// FIXME
yield call(delay, duration || 5000);
yield put(closeNotification(action.payload));
// FIXME
yield call(delay, duration || 5000);
yield put(closeNotification(action.payload));
}
export default function* notificationsSaga() {
yield takeEvery('SHOW_NOTIFICATION', handleNotification);
export default function* notificationsSaga(): Generator<Effect, void, any> {
yield takeEvery('SHOW_NOTIFICATION', handleNotification);
}

26
common/sagas/wallet.js Normal file
View File

@ -0,0 +1,26 @@
// @flow
import { takeEvery, call, put, select } from 'redux-saga/effects';
import type { Effect } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import { saveWallet, initWallet } from 'actions/wallet';
import type { UnlockPrivateKeyAction } from 'actions/wallet';
import PrivKeyWallet from 'libs/wallet/privkey';
function* init() {
yield put(initWallet());
// const node = select(getNode);
// yield call();
// fetch balance,
// fetch tokens
yield delay(100);
}
function* unlockPrivateKey(action?: UnlockPrivateKeyAction) {
if (!action) return;
yield put(saveWallet(new PrivKeyWallet(action.payload)));
yield call(init);
}
export default function* notificationsSaga(): Generator<Effect, void, any> {
yield takeEvery('WALLET_UNLOCK_PRIVATE_KEY', unlockPrivateKey);
}

View File

@ -0,0 +1,8 @@
// @flow
import type { State } from 'reducers';
import { BaseNode } from 'libs/nodes';
import { NODES } from 'config/data';
export function getNodeLib(state: State): BaseNode {
return NODES[state.config.nodeSelection].lib;
}

View File

@ -5,54 +5,56 @@ let fallbackLanguage = 'en';
let repository = {};
const languages = [
require('./de'),
require('./el'),
require('./en'),
require('./es'),
require('./fi'),
require('./fr'),
require('./hu'),
require('./id'),
require('./it'),
require('./ja'),
require('./nl'),
require('./no'),
require('./pl'),
require('./pt'),
require('./ru') /*sk, sl, sv */,
require('./ko'),
require('./tr'),
require('./vi'),
require('./zhcn'),
require('./zhtw')
require('./de'),
require('./el'),
require('./en'),
require('./es'),
require('./fi'),
require('./fr'),
require('./hu'),
require('./id'),
require('./it'),
require('./ja'),
require('./nl'),
require('./no'),
require('./pl'),
require('./pt'),
require('./ru') /*sk, sl, sv */,
require('./ko'),
require('./tr'),
require('./vi'),
require('./zhcn'),
require('./zhtw')
];
languages.forEach(l => {
repository[l.code] = l.data;
repository[l.code] = l.data;
});
export function setLanguage(code: string) {
activeLanguage = code;
activeLanguage = code;
}
export default function translate(key: string) {
return markupToReact(
repository[activeLanguage][key] || repository[fallbackLanguage][key] || key
);
return markupToReact(
(repository[activeLanguage] && repository[activeLanguage][key]) ||
repository[fallbackLanguage][key] ||
key
);
}
export function getTranslators() {
return [
'TranslatorName_1',
'TranslatorName_2',
'TranslatorName_3',
'TranslatorName_4',
'TranslatorName_5'
].filter(x => {
const translated = translate(x);
if (typeof translated === 'string') {
return !!translated.trim();
}
return !!translated;
});
return [
'TranslatorName_1',
'TranslatorName_2',
'TranslatorName_3',
'TranslatorName_4',
'TranslatorName_5'
].filter(x => {
const translated = translate(x);
if (typeof translated === 'string') {
return !!translated.trim();
}
return !!translated;
});
}

566
flow-typed/redux-saga_v0.14.x.js vendored Normal file
View File

@ -0,0 +1,566 @@
declare type ReduxSaga$Predicate<T> = (arg: T) => boolean;
declare interface ReduxSaga$Task {
isRunning(): boolean,
isCancelled(): boolean,
result(): any,
result<T>(): T,
error(): any,
done: Promise<any>,
cancel(): void
}
declare interface ReduxSaga$Buffer<T> {
isEmpty(): boolean,
put(message: T): void,
take(): T
}
declare interface ReduxSaga$Channel<T> {
take(cb: (message: T) => void, matcher?: ReduxSaga$Predicate<T>): void,
put(message: T): void,
close(): void
}
declare module 'redux-saga/effects' {
declare type Predicate<T> = ReduxSaga$Predicate<T>;
declare type Task = ReduxSaga$Task;
declare type Buffer<T> = ReduxSaga$Buffer<T>;
declare type Channel<T> = ReduxSaga$Channel<T>;
declare type Action = { type: $Subtype<string> };
declare type Pattern<T> = string | Predicate<T> | (string | Predicate<T>)[];
declare type Effect =
| TakeEffect<any>
| PutEffect<any>
| RaceEffect
| CallEffect
| CpsEffect
| ForkEffect
| JoinEffect
| CancelEffect
| SelectEffect
| ActionChannelEffect<any>
| CancelledEffect
| FlushEffect<any>;
// take
declare interface TakeEffectDescriptor<T> {
pattern: Pattern<T>,
channel: Channel<T>,
maybe?: boolean
}
declare interface TakeEffect<T> {
TAKE: TakeEffectDescriptor<T>
}
declare var take: {
<T>(pattern: Pattern<T>): TakeEffect<T>,
<T>(channel: Channel<T>): TakeEffect<T>,
maybe: {
<T>(pattern: Pattern<T>): TakeEffect<T>,
<T>(channel: Channel<T>): TakeEffect<T>
}
};
declare var takem: void;
// put
declare interface PutEffectDescriptor<T> {
action: T,
channel: Channel<T>
}
declare interface PutEffect<T> {
PUT: PutEffectDescriptor<T>
}
declare var put: {
<T: Action>(action: T): PutEffect<T>,
<T: Action>(channel: Channel<T>, action: T): PutEffect<T>,
resolve: {
<T: Action>(action: T): PutEffect<T>,
<T: Action>(channel: Channel<T>, action: T): PutEffect<T>
},
sync: void
};
// race
declare type RaceEffectDescriptor = { [key: string]: Effect };
declare interface RaceEffect {
RACE: RaceEffectDescriptor
}
declare function race(effects: { [key: string]: Effect }): RaceEffect;
// call & apply
declare interface CallEffectDescriptor {
context: any,
fn: Function,
args: any[]
}
declare type Collable0 = () => any;
declare type Collable1<A> = (a: A) => any;
declare type Collable2<A, B> = (a: A, b: B) => any;
declare type Collable3<A, B, C> = (a: A, b: B, c: C) => any;
declare type Collable4<A, B, C, D> = (a: A, b: B, c: C, d: D) => any;
declare type Collable5<A, B, C, D, E> = (a: A, b: B, c: C, d: D, e: E) => any;
declare type CollableR = (...args: mixed[]) => any;
declare type CallEffectArg<F> = F | [any, F] | { context: any, fn: F };
declare interface CallEffect {
CALL: CallEffectDescriptor
}
declare type CallEffectFactory<R> = {
(fn: CallEffectArg<Collable0>): R,
<A>(fn: CallEffectArg<Collable1<A>>, a: A): R,
<A, B>(fn: CallEffectArg<Collable2<A, B>>, a: A, b: B): R,
<A, B, C>(fn: CallEffectArg<Collable3<A, B, C>>, a: A, b: B, c: C): R,
<A, B, C, D>(
fn: CallEffectArg<Collable4<A, B, C, D>>,
a: A,
b: B,
c: C,
d: D
): R,
<A, B, C, D, E>(
fn: CallEffectArg<Collable5<A, B, C, D, E>>,
a: A,
b: B,
c: C,
d: D,
e: E
): R,
(fn: CallEffectArg<CollableR>, ...args: any[]): R
};
declare var call: CallEffectFactory<CallEffect>;
declare var apply: {
(context: any, fn: Collable0): CallEffect,
<A>(context: any, fn: Collable1<A>, args: [A]): CallEffect,
<A, B>(context: any, fn: Collable2<A, B>, args: [A, B]): CallEffect,
<A, B, C>(
context: any,
fn: Collable3<A, B, C>,
args: [A, B, C]
): CallEffect,
<A, B, C, D>(
context: any,
fn: Collable4<A, B, C, D>,
args: [A, B, C, D]
): CallEffect,
<A, B, C, D, E>(
context: any,
fn: Collable5<A, B, C, D, E>,
args: [A, B, C, D, E]
): CallEffect,
(context: any, fn: CollableR, args: any[]): CallEffect
};
// cps
declare interface CpsEffect {
CPS: CallEffectDescriptor
}
declare type CpsCallback = (error: any, result: any) => void;
declare var cps: {
(fn: CallEffectArg<Collable1<CpsCallback>>): CpsEffect,
<A>(fn: CallEffectArg<Collable2<A, CpsCallback>>, a: A): CpsEffect,
<A, B>(
fn: CallEffectArg<Collable3<A, B, CpsCallback>>,
a: A,
b: B
): CpsEffect,
<A, B, C>(
fn: CallEffectArg<Collable4<A, B, C, CpsCallback>>,
a: A,
b: B,
c: C
): CpsEffect,
<A, B, C, D>(
fn: CallEffectArg<Collable5<A, B, C, D, CpsCallback>>,
a: A,
b: B,
c: C,
d: D
): CpsEffect
};
// fork & spawn
declare interface ForkEffectDescriptor extends CallEffectDescriptor {
detached?: boolean
}
declare interface ForkEffect {
FORK: ForkEffectDescriptor
}
declare var fork: CallEffectFactory<ForkEffect>;
declare var spawn: CallEffectFactory<ForkEffect>;
// join
declare interface JoinEffect {
JOIN: Task
}
declare function join(task: Task): JoinEffect;
// cancel
declare interface CancelEffect {
CANCEL: Task
}
declare function cancel(task: Task): CancelEffect;
// select
declare interface SelectEffectDescriptor {
selector(state: any, ...args: any[]): any,
args: any[]
}
declare interface SelectEffect {
SELECT: SelectEffectDescriptor
}
declare var select: {
(): SelectEffect,
<S>(selector: Collable1<S>): SelectEffect,
<S, A>(selector: Collable2<S, A>, a: A): SelectEffect,
<S, A, B>(selector: Collable3<S, A, B>, a: A, b: B): SelectEffect,
<S, A, B, C>(
selector: Collable4<S, A, B, C>,
a: A,
b: B,
c: C
): SelectEffect,
<S, A, B, C, D>(
selector: Collable5<S, A, B, C, D>,
a: A,
b: B,
c: C,
d: D
): SelectEffect,
(selector: CollableR, ...rest: any[]): SelectEffect
};
// actionChannel
declare interface ActionChannelEffectDescriptor<T> {
pattern: Pattern<T>,
buffer: Buffer<T>
}
declare interface ActionChannelEffect<T> {
ACTION_CHANNEL: ActionChannelEffectDescriptor<T>
}
declare function actionChannel<T>(
pattern: Pattern<T>,
buffer?: Buffer<T>
): ActionChannelEffect<T>;
// actionChannel
declare interface CancelledEffect {
CANCELLED: {}
}
declare function cancelled(): CancelledEffect;
// flush
declare interface FlushEffect<T> {
FLUSH: Channel<T>
}
declare function flush<T>(channel: Channel<T>): FlushEffect<T>;
// takeEvery & takeLatest
declare type Workable0<A> = (action?: A) => any;
declare type Workable1<A, B> = (b: B, action?: A) => any;
declare type Workable2<A, B, C> = (b: B, c: C, action?: A) => any;
declare type Workable3<A, B, C, D> = (b: B, c: C, d: D, action?: A) => any;
declare type Workable4<A, B, C, D, E> = (
b: B,
c: C,
d: D,
e: E,
action?: A
) => any;
declare type WorkableR<A, B, C, D, E, F> = (
b: B,
c: C,
d: D,
e: E,
f: F,
...args: mixed[]
) => any;
declare interface TakeHelper {
<A>(pattern: Pattern<A>, worker: Workable0<A>): ForkEffect,
<A, B>(pattern: Pattern<A>, worker: Workable1<A, B>, b: B): ForkEffect,
<A, B, C>(
pattern: Pattern<A>,
worker: Workable2<A, B, C>,
b: B,
c: C
): ForkEffect,
<A, B, C, D>(
pattern: Pattern<A>,
worker: Workable3<A, B, C, D>,
b: B,
c: C,
d: D
): ForkEffect,
<A, B, C, D, E>(
pattern: Pattern<A>,
worker: Workable4<A, B, C, D, E>,
b: B,
c: C,
d: D,
e: E
): ForkEffect,
<A, B, C, D, E, F>(
pattern: Pattern<A>,
worker: WorkableR<A, B, C, D, E, F>,
b: B,
c: C,
d: D,
e: E,
f: F,
...rest: any[]
): ForkEffect
}
declare var takeEvery: TakeHelper;
declare var takeLatest: TakeHelper;
// throttle
declare var throttle: {
<A>(ms: number, pattern: Pattern<A>, worker: Workable0<A>): ForkEffect,
<A, B>(
ms: number,
pattern: Pattern<A>,
worker: Workable1<A, B>,
b: B
): ForkEffect,
<A, B, C>(
ms: number,
pattern: Pattern<A>,
worker: Workable2<A, B, C>,
b: B,
c: C
): ForkEffect,
<A, B, C, D>(
ms: number,
pattern: Pattern<A>,
worker: Workable3<A, B, C, D>,
b: B,
c: C,
d: D
): ForkEffect,
<A, B, C, D, E>(
ms: number,
pattern: Pattern<A>,
worker: Workable4<A, B, C, D, E>,
b: B,
c: C,
d: D,
e: E
): ForkEffect,
<A, B, C, D, E, F>(
ms: number,
pattern: Pattern<A>,
worker: WorkableR<A, B, C, D, E, F>,
b: B,
c: C,
d: D,
e: E,
f: F,
...rest: any[]
): ForkEffect
};
}
declare module 'redux-saga/utils' {
import type {
Effect,
TakeEffectDescriptor,
PutEffectDescriptor,
RaceEffectDescriptor,
CallEffectDescriptor,
ForkEffectDescriptor,
SelectEffectDescriptor,
ActionChannelEffectDescriptor
} from 'redux-saga/effects';
declare type Task = ReduxSaga$Task;
declare type Channel<T> = ReduxSaga$Channel<T>;
declare type Is = ReduxSaga$Predicate<*>;
declare interface Deferred<R> {
resolve(result: R): void,
reject(error: any): void,
promise: Promise<R>
}
declare interface MockTask extends Task {
setRunning(running: boolean): void,
setResult(result: any): void,
setError(error: any): void
}
declare var TASK: '@@redux-saga/TASK';
declare var SAGA_ACTION: '@@redux-saga/SAGA_ACTION';
declare function noop(): void;
declare var is: {
undef: Is,
notUndef: Is,
func: Is,
number: Is,
array: Is,
promise: Is,
iterator: Is,
task: Is,
observable: Is,
buffer: Is,
pattern: Is,
channel: Is,
helper: Is,
stringableFunc: Is
};
declare function deferred<T, R>(props?: T): T & Deferred<R>;
declare function arrayOfDeffered<T>(length: number): Deferred<T>[];
declare function createMockTask(): MockTask;
declare var asEffect: {
take<T>(effect: Effect): ?TakeEffectDescriptor<T>,
put<T>(effect: Effect): ?PutEffectDescriptor<T>,
race(effect: Effect): ?RaceEffectDescriptor,
call(effect: Effect): ?CallEffectDescriptor,
cps(effect: Effect): ?CallEffectDescriptor,
fork(effect: Effect): ?ForkEffectDescriptor,
join(effect: Effect): ?Task,
cancel(effect: Effect): ?Task,
select(effect: Effect): ?SelectEffectDescriptor,
actionChannel<T>(effect: Effect): ?ActionChannelEffectDescriptor<T>,
cancelled(effect: Effect): ?{},
flush<T>(effect: Effect): ?Channel<T>
};
declare var CHANNEL_END: {
toString(): '@@redux-saga/CHANNEL_END'
};
}
declare module 'redux-saga' {
import type { Middleware } from 'redux';
import type { Effect } from 'redux-saga/effects';
import typeof * as Effects from 'redux-saga/effects';
import typeof * as Utils from 'redux-saga/utils';
declare export type Predicate<T> = ReduxSaga$Predicate<T>;
declare export type Task = ReduxSaga$Task;
declare export type Buffer<T> = ReduxSaga$Buffer<T>;
declare export type Channel<T> = ReduxSaga$Channel<T>;
declare export interface SagaMonitor {
effectTriggered(options: {
effectId: number,
parentEffectId: number,
label: string,
root?: boolean,
effect: Effect
}): void,
effectResolved(effectId: number, result: any): void,
effectRejected(effectId: number, err: any): void,
effectCancelled(effectId: number): void,
actionDispatched<A>(action: A): void
}
declare type Saga0 = () => Generator<*, *, *>;
declare type Saga1<A> = (a: A) => Generator<*, *, *>;
declare type Saga2<A, B> = (a: A, b: B) => Generator<*, *, *>;
declare type Saga3<A, B, C> = (a: A, b: B, c: C) => Generator<*, *, *>;
declare type Saga4<A, B, C, D> = (
a: A,
b: B,
c: C,
d: D
) => Generator<*, *, *>;
declare type SagaR = (...args: mixed[]) => Generator<*, *, *>;
declare export type SagaMiddleware<S, A> = Middleware<S, A> & {
run(saga: Saga0): Task,
run<A>(saga: Saga1<A>, a: A): Task,
run<A, B>(saga: Saga2<A, B>, a: A, B: B): Task,
run<A, B, C>(saga: Saga3<A, B, C>, a: A, B: B, c: C): Task,
run<A, B, C, T4>(saga: Saga4<A, B, C, T4>, a: A, B: B, c: C, d: T4): Task,
run(saga: SagaR, ...args: any[]): Task
};
declare export type Emit<T> = (input: T) => void;
declare export default function createSagaMiddleware<T>(options?: {
sagaMonitor?: SagaMonitor,
emitter: (emit: Emit<T>) => Emit<T>
}): SagaMiddleware<*, *>;
declare export type Unsubscribe = () => void;
declare export type Subscribe<T> = (cb: (input: T) => void) => Unsubscribe;
declare export type Logger = (
level: 'info' | 'warning' | 'error',
...args: Array<any>
) => void;
declare export function runSaga<S, SA, DA>(
saga: Generator<*, *, *>,
io: {
subscribe?: Subscribe<SA>,
dispatch?: (input: DA) => any,
getState?: () => S,
sagaMonitor?: SagaMonitor,
logger?: Logger,
onError?: void
}
): Task;
declare export var END: { type: '@@redux-saga/CHANNEL_END' };
declare export function eventChannel<T>(
subscribe: Subscribe<T>,
buffer?: Buffer<T>,
matcher?: Predicate<T>
): Channel<T>;
declare export function channel<T>(buffer?: Buffer<T>): Channel<T>;
declare export var buffers: {
none<T>(): Buffer<T>,
fixed<T>(limit?: number): Buffer<T>,
dropping<T>(limit?: number): Buffer<T>,
sliding<T>(limit?: number): Buffer<T>,
expanding<T>(limit?: number): Buffer<T>
};
// deprecate
declare export var takeEvery: void;
declare export var takeLatest: void;
declare export var throttle: void;
declare export function delay(ms: number, rest: void): Promise<boolean>;
declare export function delay<T>(ms: number, val: T): Promise<T>;
declare export var CANCEL: '@@redux-saga/cancelPromise';
declare export var effects: Effects;
declare export var utils: Utils;
}

View File

@ -10,6 +10,7 @@
"^routing": "<rootDir>/common/routing",
"^components$": "<rootDir>/common/components",
"^containers$": "<rootDir>/common/containers",
"^translations(.*)": "<rootDir>/common/translations$1"
"^translations(.*)": "<rootDir>/common/translations$1",
"^libs(.*)": "<rootDir>/common/libs$1"
}
}

View File

@ -9,6 +9,7 @@
"ethereumjs-util": "^5.1.2",
"idna-uts46": "^1.1.0",
"lodash": "^4.17.4",
"ethereumjs-wallet": "^0.6.0",
"prop-types": "^15.5.8",
"react": "^15.4.2",
"react-dom": "^15.4.2",

View File

@ -1,12 +1,12 @@
import { CHANGE_LANGUAGE, CONFIG_LANGUAGE_CHANGE } from '../../common/actions/config';
import { changeLanguage } from '../../common/actions/config';
describe('actions', () => {
it('should create an action to change language to index', () => {
const value = { name: 'English', sign: 'en' };
const expectedAction = {
type: CONFIG_LANGUAGE_CHANGE,
value
};
expect(CHANGE_LANGUAGE(value)).toEqual(expectedAction);
});
it('should create an action to change language to index', () => {
const value = 'en';
const expectedAction = {
type: 'CONFIG_LANGUAGE_CHANGE',
value
};
expect(changeLanguage(value)).toEqual(expectedAction);
});
});