diff --git a/src/actions/ModalActions.js b/src/actions/ModalActions.js index ea2434d9..0ca8f9fa 100644 --- a/src/actions/ModalActions.js +++ b/src/actions/ModalActions.js @@ -112,7 +112,7 @@ export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispa } }; -export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean): ThunkAction => (dispatch: Dispatch): void => { +export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean, state: ?string): ThunkAction => (dispatch: Dispatch): void => { dispatch({ type: MODAL.CLOSE, }); @@ -120,6 +120,7 @@ export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean): Thun type: CONNECT.RECEIVE_WALLET_TYPE, device, hidden, + state, }); }; diff --git a/src/actions/SendFormActions.js b/src/actions/SendFormActions.js index 90ddf02e..bf1fe1a5 100644 --- a/src/actions/SendFormActions.js +++ b/src/actions/SendFormActions.js @@ -11,6 +11,7 @@ import * as ValidationActions from 'actions/SendFormValidationActions'; import { initialState } from 'reducers/SendFormReducer'; import { findToken } from 'reducers/TokensReducer'; import * as reducerUtils from 'reducers/utils'; +import * as ethUtils from 'utils/ethUtils'; import type { Dispatch, @@ -397,8 +398,7 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: } const requestedData = state.data; - const re = /^[0-9A-Fa-f]+$/g; // TODO: allow "0x" prefix - if (!re.test(requestedData)) { + if (!ethUtils.isHex(requestedData)) { // stop "calculatingGasLimit" process dispatch(onGasLimitChange(requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString())); return; diff --git a/src/actions/SendFormValidationActions.js b/src/actions/SendFormValidationActions.js index 97c71ed4..327ccff3 100644 --- a/src/actions/SendFormValidationActions.js +++ b/src/actions/SendFormValidationActions.js @@ -6,6 +6,7 @@ import EthereumjsUnits from 'ethereumjs-units'; import { findToken } from 'reducers/TokensReducer'; import { findDevice, getPendingAmount } from 'reducers/utils'; import * as SEND from 'actions/constants/send'; +import * as ethUtils from 'utils/ethUtils'; import type { Dispatch, @@ -19,7 +20,6 @@ const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)? const UPPERCASE_RE = new RegExp('^(.*[A-Z].*)$'); const ABS_RE = new RegExp('^[0-9]+$'); const ETH_18_RE = new RegExp('^(0|0\\.([0-9]{0,18})?|[1-9][0-9]*\\.?([0-9]{0,18})?|\\.[0-9]{0,18})$'); -const HEX_RE = new RegExp('^[0-9A-Fa-f]+$'); const dynamicRegexp = (decimals: number): RegExp => { if (decimals > 0) { return new RegExp(`^(0|0\\.([0-9]{0,${decimals}})?|[1-9][0-9]*\\.?([0-9]{0,${decimals}})?|\\.[0-9]{1,${decimals}})$`); @@ -326,7 +326,7 @@ export const nonceValidation = ($state: State): PayloadAction => (dispatc export const dataValidation = ($state: State): PayloadAction => (): State => { const state = { ...$state }; if (!state.touched.data || state.data.length === 0) return state; - if (!HEX_RE.test(state.data)) { + if (!ethUtils.isHex(state.data)) { state.errors.data = 'Data is not valid hexadecimal'; } return state; diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index c5928afa..9c44f8ce 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -79,6 +79,7 @@ export type TrezorConnectAction = { type: typeof CONNECT.RECEIVE_WALLET_TYPE, device: TrezorDevice, hidden: boolean, + state: ?string, }; export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { diff --git a/src/actions/TxActions.js b/src/actions/TxActions.js index 67c93c9f..e3d35cd5 100644 --- a/src/actions/TxActions.js +++ b/src/actions/TxActions.js @@ -5,6 +5,7 @@ import EthereumjsUnits from 'ethereumjs-units'; import BigNumber from 'bignumber.js'; import { toHex } from 'web3-utils'; // eslint-disable-line import/no-extraneous-dependencies import { initWeb3 } from 'actions/Web3Actions'; +import * as ethUtils from 'utils/ethUtils'; import type { Dispatch, @@ -29,10 +30,9 @@ type EthereumTxRequest = { export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction => async (dispatch: Dispatch): Promise => { const instance = await dispatch(initWeb3(tx.network)); const { token } = tx; - let data: string = `0x${tx.data}`; // TODO: check if already prefixed + let data: string = ethUtils.sanitizeHex(tx.data); let value: string = toHex(EthereumjsUnits.convert(tx.amount, 'ether', 'wei')); let to: string = tx.to; // eslint-disable-line prefer-destructuring - if (token) { // smart contract transaction const contract = instance.erc20.clone(); diff --git a/src/actions/Web3Actions.js b/src/actions/Web3Actions.js index 53c26a3b..2c3d9131 100644 --- a/src/actions/Web3Actions.js +++ b/src/actions/Web3Actions.js @@ -7,6 +7,7 @@ import EthereumjsUnits from 'ethereumjs-units'; import type { EstimateGasOptions } from 'web3'; import * as WEB3 from 'actions/constants/web3'; import * as PENDING from 'actions/constants/pendingTx'; +import * as ethUtils from 'utils/ethUtils'; import type { Dispatch, @@ -272,8 +273,7 @@ export const updateGasPrice = (network: string): PromiseAction => async (d export const estimateGasLimit = (network: string, $options: EstimateGasOptions): PromiseAction => async (dispatch: Dispatch): Promise => { const instance = await dispatch(initWeb3(network)); - // TODO: allow data starting with 0x ... - const data = `0x${$options.data.length % 2 === 0 ? $options.data : `0${$options.data}`}`; + const data = ethUtils.sanitizeHex($options.data); const options = { ...$options, to: '0x0000000000000000000000000000000000000000', diff --git a/src/components/Heading/index.js b/src/components/Heading/index.js index d62aef5d..c2a8165c 100644 --- a/src/components/Heading/index.js +++ b/src/components/Heading/index.js @@ -6,13 +6,14 @@ const baseStyles = css` text-rendering: optimizeLegibility; color: ${colors.TEXT_PRIMARY}; font-weight: bold; - padding: 0; margin: 0; + padding: 0; `; const H1 = styled.h1` ${baseStyles}; font-size: 18px; + padding-bottom: 10px; `; const H2 = styled.h2` @@ -23,16 +24,19 @@ const H2 = styled.h2` font-size: 36px; padding-bottom: 24px `} + padding-bottom: 10px; `; const H3 = styled.h3` ${baseStyles}; font-size: 14px; + margin-bottom: 10px; `; const H4 = styled.h4` ${baseStyles}; font-size: 12px; + padding-bottom: 10px; `; export { diff --git a/src/components/Paragraph/index.js b/src/components/Paragraph/index.js index 34f498de..9c9a8374 100644 --- a/src/components/Paragraph/index.js +++ b/src/components/Paragraph/index.js @@ -9,6 +9,7 @@ const Wrapper = styled.p` line-height: ${LINE_HEIGHT.BASE}; color: ${colors.TEXT_SECONDARY}; padding: 0; + margin: 0; ${props => props.isSmaller && css` font-size: ${FONT_SIZE.SMALLER}; diff --git a/src/components/images/TrezorImage/index.js b/src/components/images/TrezorImage/index.js index ed83f81a..effc6b78 100644 --- a/src/components/images/TrezorImage/index.js +++ b/src/components/images/TrezorImage/index.js @@ -1,22 +1,31 @@ +/* @flow */ + import React from 'react'; import styled from 'styled-components'; import PropTypes from 'prop-types'; +type Props = { + model: string; +} + const Wrapper = styled.div``; const Img = styled.img` width: ${props => (props.model === 'T' ? '17px' : '13px')}; `; -const TrezorImage = ({ model }) => ( - - - -); +const TrezorImage = ({ model }: Props) => { + // $FlowIssue + const src = require(`./images/trezor-${model}.png`); // eslint-disable-line + return ( + + + + ); +}; TrezorImage.propTypes = { model: PropTypes.string, - status: PropTypes.string, }; export default TrezorImage; diff --git a/src/components/modals/device/WalletType/index.js b/src/components/modals/device/WalletType/index.js index 00e7ff88..c22568fa 100644 --- a/src/components/modals/device/WalletType/index.js +++ b/src/components/modals/device/WalletType/index.js @@ -86,10 +86,10 @@ class WalletType extends Component { keyboardHandler: (event: KeyboardEvent) => void; - changeType(hidden: boolean) { + changeType(hidden: boolean, state: ?string) { const { modal } = this.props; if (!modal.opened) return; - this.props.modalActions.onWalletTypeRequest(modal.device, hidden); + this.props.modalActions.onWalletTypeRequest(modal.device, hidden, state); } render() { @@ -115,7 +115,7 @@ class WalletType extends Component { Standard Wallet

Continue to access your standard wallet.

- this.changeType(false)}>Go to your standard wallet + this.changeType(true)}>Go to your standard wallet { Hidden Wallet

You will be asked to enter your passphrase to unlock your hidden wallet.

- this.changeType(true)}>Go to your hidden wallet + this.changeType(true, device.state)}>Go to your hidden wallet
); diff --git a/src/components/modals/pin/Pin/components/Input/index.js b/src/components/modals/pin/Pin/components/Input/index.js index a861ae12..db078871 100644 --- a/src/components/modals/pin/Pin/components/Input/index.js +++ b/src/components/modals/pin/Pin/components/Input/index.js @@ -18,6 +18,7 @@ const StyledInput = styled.input` padding: 5px 31px 10px 20px; color: ${colors.TEXT_PRIMARY}; background: transparent; + border: 1px solid ${colors.DIVIDER}; `; const StyledIcon = styled(Icon)` diff --git a/src/components/modals/pin/Pin/index.js b/src/components/modals/pin/Pin/index.js index 5862097f..d890ec77 100644 --- a/src/components/modals/pin/Pin/index.js +++ b/src/components/modals/pin/Pin/index.js @@ -40,10 +40,6 @@ const Footer = styled.div` `; class Pin extends Component { - keyboardHandler: (event: KeyboardEvent) => void; - - state: State; - constructor(props: Props) { super(props); @@ -52,6 +48,15 @@ class Pin extends Component { }; } + componentWillMount(): void { + this.keyboardHandler = this.keyboardHandler.bind(this); + window.addEventListener('keydown', this.keyboardHandler, false); + } + + componentWillUnmount(): void { + window.removeEventListener('keydown', this.keyboardHandler, false); + } + onPinAdd = (input: number): void => { let { pin } = this.state; if (pin.length < 9) { @@ -120,18 +125,11 @@ class Pin extends Component { case 105: this.onPinAdd(9); break; + default: break; } } - - componentWillMount(): void { - this.keyboardHandler = this.keyboardHandler.bind(this); - window.addEventListener('keydown', this.keyboardHandler, false); - } - - componentWillUnmount(): void { - window.removeEventListener('keydown', this.keyboardHandler, false); - } + keyboardHandler: (event: KeyboardEvent) => void; render() { if (!this.props.modal.opened) return null; @@ -165,7 +163,7 @@ class Pin extends Component { Not sure how PIN works? Learn more diff --git a/src/components/notifications/Context/components/Account/index.js b/src/components/notifications/Context/components/Account/index.js index fe42bbd1..6a74883c 100644 --- a/src/components/notifications/Context/components/Account/index.js +++ b/src/components/notifications/Context/components/Account/index.js @@ -6,12 +6,11 @@ import type { Props } from '../../index'; // There could be only one account notification export default (props: Props) => { - const { notification } = props.selectedAccount; - if (notification) { + const { network, notification } = props.selectedAccount; + if (network && notification) { if (notification.type === 'backend') { // special case: backend is down // TODO: this is a different component with "auto resolve" button - return ( { [{ label: 'Connect', callback: async () => { - await props.blockchainReconnect('trop'); + await props.blockchainReconnect(network.network); }, }] } /> ); - - // return (); } return (); } diff --git a/src/index.js b/src/index.js index 931e8c16..b3c6abc8 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,7 @@ import React from 'react'; import { render } from 'react-dom'; import baseStyles from 'support/styles'; -import { onBeforeUnload } from 'actions/WalletActions'; import App from 'views/index'; -import store from './store'; const root: ?HTMLElement = document.getElementById('root'); if (root) { @@ -13,7 +11,8 @@ if (root) { } window.onbeforeunload = () => { - store.dispatch(onBeforeUnload()); + // $FlowIssue: render empty component + render(null, root); }; // Application life cycle starts in services/WalletService.js \ No newline at end of file diff --git a/src/services/WalletService.js b/src/services/WalletService.js index dac93767..24b35496 100644 --- a/src/services/WalletService.js +++ b/src/services/WalletService.js @@ -110,7 +110,9 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa api.dispatch(DiscoveryActions.restore()); break; case CONNECT.RECEIVE_WALLET_TYPE: - api.dispatch(RouterActions.selectFirstAvailableDevice(true)); + if (action.state) { + api.dispatch(RouterActions.selectFirstAvailableDevice(true)); + } api.dispatch(TrezorConnectActions.authorizeDevice()); break; default: break; diff --git a/src/utils/ethUtils.js b/src/utils/ethUtils.js index 2d4b553e..baa15dbb 100644 --- a/src/utils/ethUtils.js +++ b/src/utils/ethUtils.js @@ -7,15 +7,14 @@ export const decimalToHex = (dec: number): string => new BigNumber(dec).toString export const padLeftEven = (hex: string): string => (hex.length % 2 !== 0 ? `0${hex}` : hex); -export const sanitizeHex = ($hex: number | string): ?string => { - if (typeof $hex !== 'string') return null; - const hex = $hex.substring(0, 2) === '0x' ? $hex.substring(2) : $hex; +export const sanitizeHex = ($hex: string): string => { + const hex = $hex.toLowerCase().substring(0, 2) === '0x' ? $hex.substring(2) : $hex; if (hex === '') return ''; return `0x${padLeftEven(hex)}`; }; export const hexToDecimal = (hex: number): string => { - const sanitized: ?string = sanitizeHex(hex); + const sanitized: ?string = sanitizeHex(hex.toString()); return !sanitized ? 'null' : new BigNumber(sanitized).toString(); }; @@ -40,4 +39,9 @@ export const validateAddress = (address: string): ?string => { return 'Address is not a valid checksum'; } return null; +}; + +export const isHex = (str: string): boolean => { + const regExp = /^(0x|0X)?[0-9A-Fa-f]+$/g; + return regExp.test(str); }; \ No newline at end of file diff --git a/src/views/Landing/components/InstallBridge/index.js b/src/views/Landing/components/InstallBridge/index.js index c0743e3d..fcec71fc 100644 --- a/src/views/Landing/components/InstallBridge/index.js +++ b/src/views/Landing/components/InstallBridge/index.js @@ -3,9 +3,11 @@ import React, { Component } from 'react'; import styled from 'styled-components'; import colors from 'config/colors'; +import PropTypes from 'prop-types'; import { FONT_SIZE, FONT_WEIGHT } from 'config/variables'; import { Select } from 'components/Select'; import Link from 'components/Link'; +import { H1 } from 'components/Heading'; import Button from 'components/Button'; import Loader from 'components/Loader'; import P from 'components/Paragraph'; @@ -30,26 +32,39 @@ type State = { } type Props = { + selectFirstAvailableDevice: typeof RouterActions.selectFirstAvailableDevice, transport: $ElementType; } -const InstallBridgeWrapper = styled.div` +const Wrapper = styled.div` display: flex; flex-direction: column; justify-content: center; - padding-top: 0px; + align-items: center; max-width: 500px; `; -const TitleHeader = styled.h3` - font-size: ${FONT_SIZE.BIGGEST}; +const Top = styled.div` display: flex; + flex-direction: column; + justify-content: center; + max-width: 500px; + flex: 1; + padding-top: 30px; +`; + +const Bottom = styled.div` + padding-bottom: 20px; +`; + +const TitleHeader = styled(H1)` + display: flex; + font-size: ${FONT_SIZE.BIGGEST}; justify-content: center; align-items: center; - margin-bottom: 24px; `; -const BridgeVersion = styled.span` +const Version = styled.span` color: ${colors.GREEN_PRIMARY}; padding: 6px 10px; border: 1px solid ${colors.GREEN_PRIMARY}; @@ -68,7 +83,7 @@ const SelectWrapper = styled(Select)` width: 180px; `; -const DownloadBridgeWrapper = styled.div` +const Download = styled.div` margin: 24px auto; display: flex; align-items: center; @@ -81,7 +96,29 @@ const DownloadBridgeButton = styled(Button)` align-items: center; `; -export default class InstallBridge extends Component { +const GoBack = styled.span` + color: ${colors.GREEN_PRIMARY}; + text-decoration: underline; + + &:hover { + cursor: pointer; + text-decoration: none; + } +`; + +const Ol = styled.ul` + margin: 0 auto; + color: ${colors.TEXT_SECONDARY}; + font-size: ${FONT_SIZE.BASE}; + padding: 10px 0 15px 25px; + text-align: left; +`; + +const Li = styled.li` + text-align: justify; +`; + +class InstallBridge extends Component { constructor(props: Props) { super(props); @@ -113,67 +150,68 @@ export default class InstallBridge extends Component { if (!target) { return ; } - - const changelog = this.props.transport.bridge.changelog.map(entry => ( -
  • {entry}
  • - )); - const url = `${this.state.uri}${target.value}`; return ( - - TREZOR Bridge.{this.state.currentVersion} -

    New communication tool to facilitate the connection between your TREZOR and your internet browser.

    - - this.onChange(v)} - options={this.state.installers} - /> - - - + + + TREZOR Bridge{this.state.currentVersion} +

    New communication tool to facilitate the connection between your TREZOR and your internet browser.

    + + this.onChange(v)} + options={this.state.installers} + /> + + + Download latest Bridge {this.state.latestVersion} - - -
    - {target.signature && ( -

    + + + +

      + {this.props.transport.bridge.changelog.map(entry => ( +
    1. {entry}
    2. + ))} +
    +

    + Learn more about latest versions in Check PGP signature + >Changelog

    - )} -

    - { changelog } - Learn more about latest versions in - Changelog - -

    - {this.props.transport.type && ( - -

    - No, i dont want to upgrade Bridge now, -

    +

    + {target.signature && ( + Check PGP signature + + )} +

    + + + {this.props.transport.type && (

    - Take me back to the wallet + No, i dont want to upgrade Bridge now
    + Take me this.props.selectFirstAvailableDevice()}>back to the wallet

    -
    - )} -
    + )} + + ); } -} \ No newline at end of file +} + +export default InstallBridge; \ No newline at end of file diff --git a/src/views/Landing/index.js b/src/views/Landing/index.js index 00b5facd..9266fa09 100644 --- a/src/views/Landing/index.js +++ b/src/views/Landing/index.js @@ -36,7 +36,6 @@ const LandingWrapper = styled.div` const LandingContent = styled.div` flex: 1; display: flex; - align-items: center; justify-content: center; `; @@ -85,6 +84,7 @@ export default (props: Props) => { const shouldShowUnsupportedBrowser = browserState.supported === false; const isLoading = !shouldShowInitializationError && !shouldShowInstallBridge && !shouldShowConnectDevice && !shouldShowUnsupportedBrowser && !localStorageError; + return ( {isLoading && } @@ -103,7 +103,7 @@ export default (props: Props) => { {shouldShowUnsupportedBrowser && } - {shouldShowInstallBridge && } + {shouldShowInstallBridge && } {(shouldShowConnectDevice || shouldShowDisconnectDevice) && (