diff --git a/.eslintrc b/.eslintrc index 0c77d1ce..6bc0340c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,6 +10,7 @@ }, "rules": { "react/require-default-props": 0, + "react/forbid-prop-types": 0, "react/destructuring-assignment": 0, "react/jsx-one-expression-per-line": 0, "react/jsx-indent": [2, 4], diff --git a/src/js/components/common/Button.js b/src/js/components/common/Button.js index 6c96c199..6a2db3d1 100644 --- a/src/js/components/common/Button.js +++ b/src/js/components/common/Button.js @@ -13,15 +13,12 @@ const Wrapper = styled.button` background: ${colors.GREEN_PRIMARY}; color: ${colors.WHITE}; border: 0; - &:hover { background: ${colors.GREEN_SECONDARY}; } - &:active { background: ${colors.GREEN_TERTIARY}; } - ${props => props.disabled && css` pointer-events: none; color: ${colors.TEXT_SECONDARY}; @@ -56,4 +53,4 @@ Button.defaultProps = { white: false, }; -export default Button; +export default Button; \ No newline at end of file diff --git a/src/js/components/modal/index.js b/src/js/components/modal/index.js index f565e441..47984166 100644 --- a/src/js/components/modal/index.js +++ b/src/js/components/modal/index.js @@ -103,6 +103,8 @@ class Modal extends Component { case CONNECT.TRY_TO_DUPLICATE: component = (); break; + + default: null; } let ch = null; diff --git a/src/js/config/icons.js b/src/js/config/icons.js index 02df2b2c..7f45c659 100644 --- a/src/js/config/icons.js +++ b/src/js/config/icons.js @@ -1,4 +1,4 @@ export default { - REDIRECT: 'M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z', + REDIRECT: 'M16 8c-4.418 0-8 3.583-8 8 0 4.419 3.582 8 8 8s8-3.581 8-8c0-4.417-3.582-8-8-8zM16.533 19.733v-2.133c-3.2-1.067-4.267 0-5.333 2.133 0-5.333 3.2-6.4 5.333-6.4v-2.133l4.267 4.267-4.267 4.266z', WARNING: 'M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z', }; \ No newline at end of file diff --git a/src/js/views/Device/views/Account/components/Receive/Container.js b/src/js/views/Device/views/Account/components/Receive/Container.js new file mode 100644 index 00000000..6285193f --- /dev/null +++ b/src/js/views/Device/views/Account/components/Receive/Container.js @@ -0,0 +1,45 @@ +/* @flow */ + + +import React, { Component, PropTypes } from 'react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import { default as ReceiveActions } from 'actions/ReceiveActions'; +import * as TokenActions from 'actions/TokenActions'; +import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; +import type { State, Dispatch } from 'flowtype'; +import Receive from './Receive'; + +import type { + StateProps as BaseStateProps, + DispatchProps as BaseDispatchProps, +} from '../SelectedAccount'; + +type OwnProps = { } + +type StateProps = BaseStateProps & { + receive: $ElementType, + modal: $ElementType, +} + +type DispatchProps = BaseDispatchProps & { + showAddress: typeof ReceiveActions.showAddress +}; + +export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps; + +const mapStateToProps: MapStateToProps = (state: State, own: OwnProps): StateProps => ({ + className: 'receive', + selectedAccount: state.selectedAccount, + wallet: state.wallet, + + receive: state.receive, + modal: state.modal, +}); + +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Receive); \ No newline at end of file diff --git a/src/js/views/Device/views/Account/components/Receive/index.js b/src/js/views/Device/views/Account/components/Receive/index.js new file mode 100644 index 00000000..4873496c --- /dev/null +++ b/src/js/views/Device/views/Account/components/Receive/index.js @@ -0,0 +1,101 @@ +/* @flow */ +import React, { Component } from 'react'; +import styled from 'styled-components'; +import { H2 } from 'components/common/Heading'; + +import Tooltip from 'rc-tooltip'; +import { QRCode } from 'react-qr-svg'; + +import { Notification } from 'components/common/Notification'; +import SelectedAccount from '../SelectedAccount'; + +import type { Props } from './index'; + +const Wrapper = styled.div``; +const StyledH2 = styled(H2)` + padding: 20px 48px; +`; + +const Receive = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + network, + discovery, + } = props.selectedAccount; + + if (!device || !account || !discovery) return null; + + const { + addressVerified, + addressUnverified, + } = props.receive; + + let qrCode = null; + let address = `${account.address.substring(0, 20)}...`; + let className = 'address hidden'; + let button = ( + + ); + + if (addressVerified || addressUnverified) { + qrCode = ( + + ); + address = account.address; + className = addressUnverified ? 'address unverified' : 'address'; + + const tooltip = addressUnverified + ? (
Unverified address.
{ device.connected && device.available ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }
) + : (
{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify address.' }
); + + button = ( + } + overlay={tooltip} + placement="bottomRight" + > + + + ); + } + + let ver = null; + if (props.modal.opened && props.modal.windowType === 'ButtonRequest_Address') { + className = 'address verifying'; + address = account.address; + ver = (
Confirm address on TREZOR
); + button = (
{ account.network } account #{ (account.index + 1) }
); + } + + return ( + + Receive Ethereum or tokens +
+ { ver } +
+ { address } +
+ { button } +
+ { qrCode } +
+ ); +}; + +export default (props: Props) => ( + + + +); \ No newline at end of file diff --git a/src/js/views/Device/views/Account/components/SelectedAccount/index.js b/src/js/views/Device/views/Account/components/SelectedAccount/index.js new file mode 100644 index 00000000..1fdd6c92 --- /dev/null +++ b/src/js/views/Device/views/Account/components/SelectedAccount/index.js @@ -0,0 +1,114 @@ +/* @flow */ + + +import * as React from 'react'; +import { Notification } from 'components/common/Notification'; + +import type { + State, TrezorDevice, Action, ThunkAction, +} from 'flowtype'; +import type { Account } from 'reducers/AccountsReducer'; +import type { Discovery } from 'reducers/DiscoveryReducer'; + +export type StateProps = { + className: string; + selectedAccount: $ElementType, + wallet: $ElementType, + children?: React.Node +} + +export type DispatchProps = { + +} + +export type Props = StateProps & DispatchProps; + +const SelectedAccount = (props: Props) => { + const device = props.wallet.selectedDevice; + if (!device || !device.state) { + return (
); + } + + const accountState = props.selectedAccount; + + const { + account, + discovery, + } = accountState; + + // account not found (yet). checking why... + if (!account) { + if (!discovery || discovery.waitingForDevice) { + if (device.connected) { + // case 1: device is connected but discovery not started yet (probably waiting for auth) + if (device.available) { + return ( +
+ +
+ ); + } + // case 2: device is unavailable (created with different passphrase settings) account cannot be accessed + return ( +
+ +
+ ); + } + // case 3: device is disconnected + return ( +
+ +
+ ); + } if (discovery.waitingForBackend) { + // case 4: backend is not working + return ( +
+ +
+ ); + } if (discovery.completed) { + // case 5: account not found and discovery is completed + return ( +
+ +
+ ); + } + // case 6: discovery is not completed yet + return ( +
+ +
+ ); + } + + let notification: ?React$Element = null; + if (!device.connected) { + notification = ; + } else if (!device.available) { + notification = ; + } + + if (discovery && !discovery.completed && !notification) { + notification = ; + } + + return ( +
+ { notification } + { props.children } +
+ ); +}; + +export default SelectedAccount; \ No newline at end of file diff --git a/src/js/views/Device/views/Account/components/Send/Container.js b/src/js/views/Device/views/Account/components/Send/Container.js new file mode 100644 index 00000000..c37f2556 --- /dev/null +++ b/src/js/views/Device/views/Account/components/Send/Container.js @@ -0,0 +1,47 @@ +/* @flow */ + + +import * as React from 'react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import { default as SendFormActions } from 'actions/SendFormActions'; +import * as SessionStorageActions from 'actions/SessionStorageActions'; +import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; +import type { State, Dispatch } from 'flowtype'; +import SendForm from './SendForm'; + +import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount'; + +type OwnProps = { } + +export type StateProps = BaseStateProps & { + sendForm: $ElementType, + fiat: $ElementType, + localStorage: $ElementType, + children?: React.Node; +} + +export type DispatchProps = BaseDispatchProps & { + sendFormActions: typeof SendFormActions, + saveSessionStorage: typeof SessionStorageActions.save +} + +export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps; + +const mapStateToProps: MapStateToProps = (state: State, own: OwnProps): StateProps => ({ + className: 'send-from', + selectedAccount: state.selectedAccount, + wallet: state.wallet, + + sendForm: state.sendForm, + fiat: state.fiat, + localStorage: state.localStorage, +}); + +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + sendFormActions: bindActionCreators(SendFormActions, dispatch), + saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(SendForm); \ No newline at end of file diff --git a/src/js/views/Device/views/Account/components/Send/components/AdvancedForm.js b/src/js/views/Device/views/Account/components/Send/components/AdvancedForm.js new file mode 100644 index 00000000..978f9268 --- /dev/null +++ b/src/js/views/Device/views/Account/components/Send/components/AdvancedForm.js @@ -0,0 +1,191 @@ +/* @flow */ + + +import React from 'react'; +import Tooltip from 'rc-tooltip'; +import type { Props as BaseProps } from './index'; + +type Props = { + selectedAccount: $ElementType, + sendForm: $ElementType, + sendFormActions: $ElementType, + children?: $ElementType, +}; + +const AdvancedForm = (props: Props) => { + const { + account, + network, + } = props.selectedAccount; + + const { + networkSymbol, + currency, + gasPrice, + gasLimit, + recommendedGasPrice, + calculatingGasLimit, + nonce, + data, + errors, + warnings, + infos, + advanced, + } = props.sendForm; + + const { + toggleAdvanced, + onGasPriceChange, + onGasLimitChange, + onNonceChange, + onDataChange, + } = props.sendFormActions; + + if (!advanced) { + return ( +
+ Advanced settings + { props.children } +
+ ); + } + + const nonceTooltip = ( +
+ TODO
+
+ ); + + let gasLimitTooltipCurrency: string; + let gasLimitTooltipValue: string; + if (networkSymbol !== currency) { + gasLimitTooltipCurrency = 'tokens'; + gasLimitTooltipValue = network.defaultGasLimitTokens; + } else { + gasLimitTooltipCurrency = networkSymbol; + gasLimitTooltipValue = network.defaultGasLimit; + } + + const gasLimitTooltip = ( +
+ Gas limit is the amount of gas to send with your transaction.
+ TX fee = gas price * gas limit & is paid to miners for including your TX in a block.
+ Increasing this number will not get your TX mined faster.
+ Default value for sending { gasLimitTooltipCurrency } is { gasLimitTooltipValue } +
+ ); + + const gasPriceTooltip = ( +
+ Gas Price is the amount you pay per unit of gas.
+ TX fee = gas price * gas limit & is paid to miners for including your TX in a block.
+ Higher the gas price = faster transaction, but more expensive. Recommended is { recommendedGasPrice } GWEI.
+ Read more +
+ ); + + const dataTooltip = ( +
+ Data is usually used when you send transactions to contracts. +
+ ); + + + return ( +
+ Advanced settings +
+ {/*
+
} + overlay={ nonceTooltip } + placement="top"> + + + + onNonceChange(event.target.value) } /> + { errors.nonce ? ({ errors.nonce }) : null } + { warnings.nonce ? ({ warnings.nonce }) : null } +
*/} +
+ + 0} + onChange={event => onGasLimitChange(event.target.value)} + /> + { errors.gasLimit ? ({ errors.gasLimit }) : null } + { warnings.gasLimit ? ({ warnings.gasLimit }) : null } + { calculatingGasLimit ? (Calculating...) : null } +
+
+ + onGasPriceChange(event.target.value)} + /> + { errors.gasPrice ? ({ errors.gasPrice }) : null } +
+
+ +
+ +