diff --git a/.flowconfig b/.flowconfig index 3ff66441..9b970efd 100644 --- a/.flowconfig +++ b/.flowconfig @@ -17,6 +17,7 @@ ./src/flowtype/npm/react-redux_v5.x.x.js ./src/flowtype/npm/react-router_v4.x.x.js ./src/flowtype/npm/react-router-dom_v4.x.x.js +; ./src/flowtype/npm/react-intl_v2.x.x.js // TODO: uncomment to get proper flow support for intl (needs some flow fixing) ./src/flowtype/npm/connected-react-router.js ./src/flowtype/npm/bignumber.js ./src/flowtype/npm/ethereum-types.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c71347..63958eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## 1.2.0-beta +__added__ +- Localization +- Ability to hide balances +- Fiat currency switcher +- Application settings +- Button to copy log to clipboard +- Import tool (for support) +- Prettier + +__updated__ +- flow-bin 0.9.0 + +__changed__ +- Ripple explorer to xrpscan +- Coins sorted by market cap +- Link to "Bitcoin wallet" opens in the same tab +- Most components are now from trezor-ui-components + +__removed__ +- Text "you will be redirected" from coins menu + +__fixed__ +- Arrow animation in Send tab +- Sign and Verify columns size +- Sign and Verify validation for disabling submit buttons +- Token select shows all tokens options +- "Check for devices" button in device menu +- Close xlm, xem modals when opening external wallet + ## 1.1.1-beta __added__ - Ripple destination tag option diff --git a/package.json b/package.json index 987e9ca1..12e799e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trezor-wallet", - "version": "1.1.1-beta", + "version": "1.2.0-beta", "author": "TREZOR ", "description": "", "bin": { @@ -80,7 +80,7 @@ "styled-components": "^4.1.3", "styled-normalize": "^8.0.6", "trezor-bridge-communicator": "1.0.2", - "trezor-connect": "7.0.2-electron.4", + "trezor-connect": "7.0.2", "trezor-ui-components": "^1.0.0-beta.12", "wallet-address-validator": "^0.2.4", "web3": "1.0.0-beta.35", @@ -123,7 +123,7 @@ "eslint-plugin-prettier": "^3.0.1", "eslint-plugin-react": "^7.12.4", "file-loader": "3.0.1", - "flow-bin": "0.75.0", + "flow-bin": "0.90", "jest": "^24.1.0", "prettier": "^1.16.4", "prettier-eslint": "^8.8.2", diff --git a/public/data/appConfig.json b/public/data/appConfig.json index ee2fe5b8..2520b4cb 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -105,8 +105,8 @@ ] }, "explorer": { - "tx": "https://xrpcharts.ripple.com/#/transactions/", - "address": "https://xrpcharts.ripple.com/#/graph/" + "tx": "https://xrpscan.com/tx/", + "address": "https://xrpscan.com/account/" }, "hasSignVerify": false }, diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index 22ca82a0..57e97251 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -16,6 +16,7 @@ import type { Account, } from 'flowtype'; import type { Discovery, State } from 'reducers/DiscoveryReducer'; +import * as LocalStorageActions from 'actions/LocalStorageActions'; import * as BlockchainActions from './BlockchainActions'; import * as EthereumDiscoveryActions from './ethereum/DiscoveryActions'; import * as RippleDiscoveryActions from './ripple/DiscoveryActions'; @@ -120,6 +121,7 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean) } if (!discoveryProcess) { + dispatch(addImportedAccounts()); dispatch(begin(device, network)); } else if (discoveryProcess.completed && !ignoreCompleted) { dispatch({ @@ -380,3 +382,17 @@ export const addAccount = (): ThunkAction => (dispatch: Dispatch, getState: GetS if (!selected) return; dispatch(start(selected, getState().router.location.state.network, true)); }; + +export const addImportedAccounts = (): ThunkAction => (dispatch: Dispatch): void => { + // get imported accounts from local storage + const importedAccounts = LocalStorageActions.getImportedAccounts(); + if (importedAccounts) { + // create each account + importedAccounts.forEach(account => { + dispatch({ + type: ACCOUNT.CREATE, + payload: account, + }); + }); + } +}; diff --git a/src/actions/ImportAccountActions.js b/src/actions/ImportAccountActions.js new file mode 100644 index 00000000..ff6c24fe --- /dev/null +++ b/src/actions/ImportAccountActions.js @@ -0,0 +1,153 @@ +/* @flow */ + +import * as ACCOUNT from 'actions/constants/account'; +import * as IMPORT from 'actions/constants/importAccount'; +import * as NOTIFICATION from 'actions/constants/notification'; +import type { AsyncAction, TrezorDevice, Network, Dispatch, GetState } from 'flowtype'; +import * as BlockchainActions from 'actions/ethereum/BlockchainActions'; +import * as LocalStorageActions from 'actions/LocalStorageActions'; +import TrezorConnect from 'trezor-connect'; +import { toDecimalAmount } from 'utils/formatUtils'; + +export type ImportAccountAction = + | { + type: typeof IMPORT.START, + } + | { + type: typeof IMPORT.SUCCESS, + } + | { + type: typeof IMPORT.FAIL, + error: ?string, + }; + +export const importAddress = ( + address: string, + network: Network, + device: ?TrezorDevice +): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + if (!device) return; + + dispatch({ + type: IMPORT.START, + }); + + let payload; + const index = getState().accounts.filter( + a => + a.imported === true && + a.network === network.shortcut && + device && + a.deviceState === device.state + ).length; + + try { + if (network.type === 'ethereum') { + const account = await dispatch( + BlockchainActions.discoverAccount(device, address, network.shortcut) + ); + + const empty = account.nonce <= 0 && account.balance === '0'; + payload = { + imported: true, + index, + network: network.shortcut, + deviceID: device.features ? device.features.device_id : '0', + deviceState: device.state || '0', + accountPath: account.path || [], + descriptor: account.descriptor, + + balance: account.balance, + availableBalance: account.balance, + block: account.block, + transactions: account.transactions, + empty, + + networkType: 'ethereum', + nonce: account.nonce, + }; + dispatch({ + type: ACCOUNT.CREATE, + payload, + }); + dispatch({ + type: IMPORT.SUCCESS, + }); + dispatch(LocalStorageActions.setImportedAccount(payload)); + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'success', + title: 'The account has been successfully imported', + cancelable: true, + }, + }); + } else if (network.type === 'ripple') { + const response = await TrezorConnect.rippleGetAccountInfo({ + account: { + descriptor: address, + }, + coin: network.shortcut, + }); + + // handle TREZOR response error + if (!response.success) { + throw new Error(response.payload.error); + } + + const account = response.payload; + const empty = account.sequence <= 0 && account.balance === '0'; + + payload = { + imported: true, + index, + network: network.shortcut, + deviceID: device.features ? device.features.device_id : '0', + deviceState: device.state || '0', + accountPath: account.path || [], + descriptor: account.descriptor, + + balance: toDecimalAmount(account.balance, network.decimals), + availableBalance: toDecimalAmount(account.availableBalance, network.decimals), + block: account.block, + transactions: account.transactions, + empty, + + networkType: 'ripple', + sequence: account.sequence, + reserve: toDecimalAmount(account.reserve, network.decimals), + }; + dispatch({ + type: ACCOUNT.CREATE, + payload, + }); + dispatch({ + type: IMPORT.SUCCESS, + }); + dispatch(LocalStorageActions.setImportedAccount(payload)); + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'success', + title: 'The account has been successfully imported', + cancelable: true, + }, + }); + } + } catch (error) { + dispatch({ + type: IMPORT.FAIL, + error: error.message, + }); + + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Import account error', + message: error.message, + cancelable: true, + }, + }); + } +}; diff --git a/src/actions/LocalStorageActions.js b/src/actions/LocalStorageActions.js index baf0a171..2a2a2736 100644 --- a/src/actions/LocalStorageActions.js +++ b/src/actions/LocalStorageActions.js @@ -52,6 +52,7 @@ const { STORAGE_PATH } = storageUtils; const KEY_VERSION: string = `${STORAGE_PATH}version`; const KEY_DEVICES: string = `${STORAGE_PATH}devices`; const KEY_ACCOUNTS: string = `${STORAGE_PATH}accounts`; +const KEY_IMPORTED_ACCOUNTS: string = `${STORAGE_PATH}importedAccounts`; const KEY_DISCOVERY: string = `${STORAGE_PATH}discovery`; const KEY_TOKENS: string = `${STORAGE_PATH}tokens`; const KEY_PENDING: string = `${STORAGE_PATH}pending`; @@ -321,3 +322,36 @@ export const setLocalCurrency = (): ThunkAction => ( const { localCurrency } = getState().wallet; storageUtils.set(TYPE, KEY_LOCAL_CURRENCY, JSON.stringify(localCurrency)); }; + +export const setImportedAccount = (account: Account): ThunkAction => (): void => { + const prevImportedAccounts: ?Array = getImportedAccounts(); + let importedAccounts = [account]; + if (prevImportedAccounts) { + importedAccounts = importedAccounts.concat(prevImportedAccounts); + } + storageUtils.set(TYPE, KEY_IMPORTED_ACCOUNTS, JSON.stringify(importedAccounts)); +}; + +export const getImportedAccounts = (): ?Array => { + const importedAccounts: ?string = storageUtils.get(TYPE, KEY_IMPORTED_ACCOUNTS); + if (importedAccounts) { + return JSON.parse(importedAccounts); + } + return null; +}; + +export const removeImportedAccounts = (device: TrezorDevice): ThunkAction => ( + dispatch: Dispatch +): void => { + const importedAccounts: ?Array = getImportedAccounts(); + if (!importedAccounts) return; + + const deviceId = device.features ? device.features.device_id : null; + const filteredImportedAccounts = importedAccounts.filter( + account => account.deviceID !== deviceId + ); + storageUtils.remove(TYPE, KEY_IMPORTED_ACCOUNTS); + filteredImportedAccounts.forEach(account => { + dispatch(setImportedAccount(account)); + }); +}; diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index e7d7315e..0b5ce5f2 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -155,7 +155,7 @@ export const init = (): AsyncAction => async ( if (buildUtils.isDev()) { // eslint-disable-next-line window.__TREZOR_CONNECT_SRC = - typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect-electron/'; // eslint-disable-line no-underscore-dangle + typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle // window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://localhost:8088/'; // eslint-disable-line no-underscore-dangle window.TrezorConnect = TrezorConnect; } diff --git a/src/actions/constants/importAccount.js b/src/actions/constants/importAccount.js new file mode 100644 index 00000000..ac5fd498 --- /dev/null +++ b/src/actions/constants/importAccount.js @@ -0,0 +1,5 @@ +/* @flow */ + +export const START: 'import__account__start' = 'import__account__start'; +export const SUCCESS: 'import__account__success' = 'import__account__success'; +export const FAIL: 'import__account__fail' = 'import__account__fail'; diff --git a/src/components/Footer/index.js b/src/components/Footer/index.js index 5ad08447..855de592 100644 --- a/src/components/Footer/index.js +++ b/src/components/Footer/index.js @@ -7,16 +7,24 @@ import { Link, colors } from 'trezor-ui-components'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; +import type { State, Dispatch } from 'flowtype'; import { FONT_SIZE, SCREEN_SIZE, FOOTER_HEIGHT } from 'config/variables'; import * as LogActions from 'actions/LogActions'; import l10nMessages from './index.messages'; -type Props = { - opened: boolean, +type OwnProps = {| isLanding: boolean, - toggle: () => any, -}; +|}; +type StateProps = {| + opened: boolean, +|}; + +type DispatchProps = {| + toggle: typeof LogActions.toggle, +|}; + +type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; const Wrapper = styled.div` width: 100%; @@ -134,15 +142,15 @@ Footer.propTypes = { toggle: PropTypes.func.isRequired, }; -const mapStateToProps = state => ({ +const mapStateToProps = (state: State): StateProps => ({ opened: state.log.opened, }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ toggle: bindActionCreators(LogActions.toggle, dispatch), }); -export default connect( +export default connect( mapStateToProps, mapDispatchToProps )(Footer); diff --git a/src/components/Header/components/LanguagePicker/Container.js b/src/components/Header/components/LanguagePicker/Container.js index d811287c..8c51ccd6 100644 --- a/src/components/Header/components/LanguagePicker/Container.js +++ b/src/components/Header/components/LanguagePicker/Container.js @@ -1,41 +1,33 @@ /* @flow */ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; import * as WalletActions from 'actions/WalletActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import LanguagePicker from './index'; -type StateProps = { +type StateProps = {| language: string, -}; +|}; -type DispatchProps = { +type DispatchProps = {| fetchLocale: typeof WalletActions.fetchLocale, -}; +|}; -type OwnProps = {}; +type OwnProps = {||}; -export type Props = StateProps & DispatchProps; +export type Props = {| ...StateProps, ...DispatchProps, ...OwnProps |}; -const mapStateToProps: MapStateToProps = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ language: state.wallet.language, }); -const mapDispatchToProps: MapDispatchToProps = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ fetchLocale: bindActionCreators(WalletActions.fetchLocale, dispatch), }); -export default withRouter( - connect( - mapStateToProps, - mapDispatchToProps - )(LanguagePicker) -); +export default connect( + mapStateToProps, + mapDispatchToProps +)(LanguagePicker); diff --git a/src/components/Log/index.js b/src/components/Log/index.js index 0bc434a3..acd51053 100644 --- a/src/components/Log/index.js +++ b/src/components/Log/index.js @@ -12,12 +12,18 @@ import * as LogActions from 'actions/LogActions'; import type { State, Dispatch } from 'flowtype'; import l10nMessages from './index.messages'; -type Props = { +type OwnProps = {||}; +type StateProps = {| log: $ElementType, +|}; + +type DispatchProps = {| toggle: typeof LogActions.toggle, copyToClipboard: typeof LogActions.copyToClipboard, resetCopyState: typeof LogActions.resetCopyState, -}; +|}; + +type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; const Wrapper = styled.div` position: relative; @@ -64,7 +70,12 @@ const ButtonCopy = styled(Button)` margin-top: 10px; `; -const Log = (props: Props): ?React$Element => { +const TooltipContainer = styled.div` + display: flex; + flex-direction: row; +`; + +const Log = (props: Props) => { if (!props.log.opened) return null; const copyBtn = ( @@ -87,15 +98,16 @@ const Log = (props: Props): ?React$Element => { {props.log.copied ? ( - } - afterVisibleChange={props.resetCopyState} - > - {copyBtn} - + + } + onHidden={props.resetCopyState} + > + {copyBtn} + + ) : ( {copyBtn} )} @@ -103,11 +115,11 @@ const Log = (props: Props): ?React$Element => { ); }; -export default connect( - (state: State) => ({ +export default connect( + (state: State): StateProps => ({ log: state.log, }), - (dispatch: Dispatch) => ({ + (dispatch: Dispatch): DispatchProps => ({ toggle: bindActionCreators(LogActions.toggle, dispatch), copyToClipboard: bindActionCreators(LogActions.copyToClipboard, dispatch), resetCopyState: bindActionCreators(LogActions.resetCopyState, dispatch), diff --git a/src/components/Transaction/index.js b/src/components/Transaction/index.js index b36f2b52..2e035b2a 100644 --- a/src/components/Transaction/index.js +++ b/src/components/Transaction/index.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; - +import { SCREEN_SIZE } from 'config/variables'; import { Link, colors } from 'trezor-ui-components'; import type { Transaction, Network } from 'flowtype'; @@ -17,14 +17,19 @@ const Wrapper = styled.div` padding: 14px 0; display: flex; flex-direction: row; + word-break: break-all; &:last-child { border-bottom: 0px; } + + @media screen and (max-width: ${SCREEN_SIZE.SM}) { + flex-direction: column; + } `; const Addresses = styled.div` - flex: 1; + flex: 1 1 auto; `; const Address = styled.div` @@ -43,9 +48,16 @@ const Date = styled(Link)` line-height: 18px; padding-right: 8px; border-bottom: 0px; + flex: 0 1 auto; + word-break: normal; +`; + +const TransactionHash = styled(Date)` + word-break: break-all; `; const Value = styled.div` + flex: 1 1 auto; padding-left: 8px; white-space: nowrap; text-align: right; @@ -100,9 +112,9 @@ const TransactionItem = ({ tx, network }: Props) => {
{addr}
))} {!tx.blockHeight && ( - + Transaction hash: {tx.hash} - + )} diff --git a/src/components/modals/Container.js b/src/components/modals/Container.js index a18433c8..e2a372c6 100644 --- a/src/components/modals/Container.js +++ b/src/components/modals/Container.js @@ -6,14 +6,13 @@ import { withRouter } from 'react-router-dom'; import ModalActions from 'actions/ModalActions'; import ReceiveActions from 'actions/ReceiveActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import Modal from './index'; -type OwnProps = {}; +type OwnProps = {||}; -type StateProps = { +type StateProps = {| modal: $ElementType, accounts: $ElementType, devices: $ElementType, @@ -24,18 +23,16 @@ type StateProps = { receive: $ElementType, localStorage: $ElementType, wallet: $ElementType, -}; +|}; -type DispatchProps = { +type DispatchProps = {| modalActions: typeof ModalActions, receiveActions: typeof ReceiveActions, -}; +|}; -export type Props = StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ modal: state.modal, accounts: state.accounts, devices: state.devices, @@ -48,15 +45,13 @@ const mapStateToProps: MapStateToProps = ( wallet: state.wallet, }); -const mapDispatchToProps: MapDispatchToProps = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ modalActions: bindActionCreators(ModalActions, dispatch), receiveActions: bindActionCreators(ReceiveActions, dispatch), }); // export default connect(mapStateToProps, mapDispatchToProps)(Modal); -export default withRouter( +export default withRouter( connect( mapStateToProps, mapDispatchToProps diff --git a/src/components/modals/QrModal/index.js b/src/components/modals/QrModal/index.js index 6ac10641..d206f82a 100644 --- a/src/components/modals/QrModal/index.js +++ b/src/components/modals/QrModal/index.js @@ -6,6 +6,7 @@ import QrReader from 'react-qr-reader'; import styled from 'styled-components'; import { FormattedMessage, injectIntl } from 'react-intl'; import { Link, Icon, P, H5, icons, colors } from 'trezor-ui-components'; +import type { IntlShape } from 'react-intl'; import { parseUri } from 'utils/cryptoUriParser'; import type { parsedURI } from 'utils/cryptoUriParser'; @@ -55,7 +56,7 @@ type Props = { onScan: (data: parsedURI) => any, onError?: (error: any) => any, onCancel?: $ElementType<$ElementType, 'onCancel'>, - intl: any, + intl: IntlShape, }; type State = { diff --git a/src/components/modals/device/WalletType/index.js b/src/components/modals/device/WalletType/index.js index ca37ba72..3b35c07c 100644 --- a/src/components/modals/device/WalletType/index.js +++ b/src/components/modals/device/WalletType/index.js @@ -3,6 +3,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import styled, { css } from 'styled-components'; +import type { IntlShape } from 'react-intl'; import { H5, P, Button, Tooltip, Link, Icon, icons, colors } from 'trezor-ui-components'; @@ -15,7 +16,7 @@ import l10nMessages from './index.messages'; import type { Props as BaseProps } from '../../Container'; type Props = { - intl: any, + intl: IntlShape, device: TrezorDevice, onWalletTypeRequest: $ElementType< $ElementType, diff --git a/src/components/modals/external/Stellar/images/xlm.png b/src/components/modals/external/Stellar/images/xlm.png index 7c0deeb3..6ddc3371 100644 Binary files a/src/components/modals/external/Stellar/images/xlm.png and b/src/components/modals/external/Stellar/images/xlm.png differ diff --git a/src/components/modals/passphrase/Type/index.js b/src/components/modals/passphrase/Type/index.js index d95dee28..b1559028 100644 --- a/src/components/modals/passphrase/Type/index.js +++ b/src/components/modals/passphrase/Type/index.js @@ -18,12 +18,16 @@ const Wrapper = styled.div` padding: 30px 48px; `; +const StyledDeviceIcon = styled(DeviceIcon)` + margin-bottom: 10px; +`; + const Header = styled.div``; const PassphraseType = (props: Props) => (
- +
Complete the action on {props.device.label} device

If you enter a wrong passphrase, you will not unlock the desired hidden wallet. diff --git a/src/components/notifications/App/components/UpdateFirmware/index.js b/src/components/notifications/App/components/UpdateFirmware/index.js index 8e2d6930..005c1d94 100644 --- a/src/components/notifications/App/components/UpdateFirmware/index.js +++ b/src/components/notifications/App/components/UpdateFirmware/index.js @@ -5,11 +5,16 @@ import l10nCommonMessages from 'views/common.messages'; import { withRouter } from 'react-router-dom'; import { matchPath } from 'react-router'; import { getPattern } from 'support/routes'; -import type { Props } from '../../index'; +import type { ContextRouter } from 'react-router'; +import type { Props as BaseProps } from '../../index'; import l10nMessages from './index.messages'; -export default withRouter((props: Props & { location: any }) => { +type OwnProps = {||}; + +export type Props = {| ...OwnProps, ...BaseProps |}; + +const UpdateFirmware = (props: {| ...Props, ...ContextRouter |}) => { const { selectedDevice } = props.wallet; const outdated = selectedDevice && selectedDevice.features && selectedDevice.firmware === 'outdated'; @@ -36,4 +41,6 @@ export default withRouter((props: Props & { location: any }) => { ]} /> ); -}); +}; + +export default withRouter<{| ...Props |}>(UpdateFirmware); diff --git a/src/components/notifications/App/index.js b/src/components/notifications/App/index.js index 1538caa7..a3ff6a5c 100644 --- a/src/components/notifications/App/index.js +++ b/src/components/notifications/App/index.js @@ -4,32 +4,33 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import * as NotificationActions from 'actions/NotificationActions'; import * as RouterActions from 'actions/RouterActions'; +import type { intlShape } from 'react-intl'; import OnlineStatus from './components/OnlineStatus'; import UpdateBridge from './components/UpdateBridge'; import UpdateFirmware from './components/UpdateFirmware'; import NoBackup from './components/NoBackup'; -export type StateProps = { +type OwnProps = {| + intl: intlShape, +|}; + +export type StateProps = {| connect: $ElementType, wallet: $ElementType, children?: React.Node, -}; +|}; -export type DispatchProps = { +export type DispatchProps = {| close: typeof NotificationActions.close, routerActions: typeof RouterActions, -}; -type OwnProps = { - intl: any, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; const Notifications = (props: Props) => ( @@ -40,22 +41,18 @@ const Notifications = (props: Props) => ( ); -const mapStateToProps: MapStateToProps = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ connect: state.connect, wallet: state.wallet, }); -const mapDispatchToProps: MapDispatchToProps = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ close: bindActionCreators(NotificationActions.close, dispatch), routerActions: bindActionCreators(RouterActions, dispatch), }); export default injectIntl( - connect( + connect( mapStateToProps, mapDispatchToProps )(Notifications) diff --git a/src/components/notifications/Context/components/Static/index.js b/src/components/notifications/Context/components/Static/index.js index 55c71577..d14c5aa0 100644 --- a/src/components/notifications/Context/components/Static/index.js +++ b/src/components/notifications/Context/components/Static/index.js @@ -4,20 +4,25 @@ import * as React from 'react'; import { Notification, Link } from 'trezor-ui-components'; import Bignumber from 'bignumber.js'; import { FormattedMessage } from 'react-intl'; +import { withRouter } from 'react-router-dom'; +import type { ContextRouter } from 'react-router'; + import l10nCommonMessages from 'views/common.messages'; +import { matchPath } from 'react-router'; +import { getPattern } from 'support/routes'; import l10nMessages from './index.messages'; import type { Props } from '../../index'; -export default (props: Props) => { +export default withRouter((props: {| ...Props, ...ContextRouter |}) => { const { selectedAccount } = props; const { account } = selectedAccount; const { location } = props.router; - const notifications: Array = []; + const notifications = []; - if (!location || !selectedAccount || !account) return null; + if (!location) return null; // Ripple minimum reserve notification - if (account.networkType === 'ripple') { + if (selectedAccount && account && account.networkType === 'ripple') { const { reserve, balance } = account; const bigBalance = new Bignumber(balance); const bigReserve = new Bignumber(reserve); @@ -51,5 +56,28 @@ export default (props: Props) => { } } + // Import tool notification + if (matchPath(location.pathname, { path: getPattern('wallet-import') })) { + notifications.push( + + ); + } + + if (account && account.imported) { + notifications.push( + + ); + } + return {notifications}; -}; +}); diff --git a/src/components/notifications/Context/index.js b/src/components/notifications/Context/index.js index 81aea768..ff2de3a3 100644 --- a/src/components/notifications/Context/index.js +++ b/src/components/notifications/Context/index.js @@ -3,8 +3,8 @@ import * as React from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import { reconnect } from 'actions/DiscoveryActions'; @@ -14,24 +14,25 @@ import StaticNotifications from './components/Static'; import AccountNotifications from './components/Account'; import ActionNotifications from './components/Action'; -export type StateProps = { +type OwnProps = {| + intl: IntlShape, +|}; + +export type StateProps = {| router: $ElementType, notifications: $ElementType, selectedAccount: $ElementType, wallet: $ElementType, blockchain: $ElementType, children?: React.Node, -}; +|}; -export type DispatchProps = { +export type DispatchProps = {| close: typeof NotificationActions.close, blockchainReconnect: typeof reconnect, -}; -type OwnProps = { - intl: any, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; const Notifications = (props: Props) => ( @@ -41,9 +42,7 @@ const Notifications = (props: Props) => ( ); -const mapStateToProps: MapStateToProps = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ router: state.router, notifications: state.notifications, selectedAccount: state.selectedAccount, @@ -51,15 +50,13 @@ const mapStateToProps: MapStateToProps = ( blockchain: state.blockchain, }); -const mapDispatchToProps: MapDispatchToProps = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ close: bindActionCreators(NotificationActions.close, dispatch), blockchainReconnect: bindActionCreators(reconnect, dispatch), }); -export default injectIntl( - connect( +export default injectIntl( + connect( mapStateToProps, mapDispatchToProps )(Notifications) diff --git a/src/config/variables.js b/src/config/variables.js index 2bea148b..80eb0c5e 100644 --- a/src/config/variables.js +++ b/src/config/variables.js @@ -39,6 +39,7 @@ export const FONT_SIZE = { H3: '1rem', H4: '0.8571rem', COUNTER: '0.7857rem', + BADGE: '0.7857rem', }; export const FONT_WEIGHT = { diff --git a/src/flowtype/index.js b/src/flowtype/index.js index be8638cf..e91fa06d 100644 --- a/src/flowtype/index.js +++ b/src/flowtype/index.js @@ -32,6 +32,7 @@ import type { TokenAction } from 'actions/TokenActions'; import type { TrezorConnectAction } from 'actions/TrezorConnectActions'; import type { WalletAction } from 'actions/WalletActions'; import type { Web3Action } from 'actions/Web3Actions'; +import type { ImportAccountAction } from 'actions/ImportAccountActions'; import type { FiatRateAction } from 'services/CoingeckoService'; // this service has no action file, all is written inside one file import type { @@ -119,8 +120,15 @@ type UiEventAction = { // }, }; +// TODO: join this message with uiMessage +type IFrameHandshake = { + type: 'iframe_handshake', + payload: any, +}; + export type Action = | RouterAction + | IFrameHandshake | TransportEventAction | DeviceEventAction | UiEventAction @@ -142,7 +150,8 @@ export type Action = | TrezorConnectAction | WalletAction | Web3Action - | FiatRateAction; + | FiatRateAction + | ImportAccountAction; export type State = ReducersState; diff --git a/src/flowtype/npm/react-intl_v2.x.x.js b/src/flowtype/npm/react-intl_v2.x.x.js new file mode 100644 index 00000000..2942eef3 --- /dev/null +++ b/src/flowtype/npm/react-intl_v2.x.x.js @@ -0,0 +1,259 @@ +/** + * Original implementation of this file by @marudor at https://github.com/marudor/flowInterfaces + * Copied here based on intention to merge with flow-typed expressed here: + * https://github.com/marudor/flowInterfaces/issues/6 + */ +// Mostly from https://github.com/yahoo/react-intl/wiki/API#react-intl-api +declare module "react-intl" { + import type { Element, ChildrenArray } from "react"; + + declare type $npm$ReactIntl$LocaleData = { + locale: string, + [key: string]: any + }; + + declare type $npm$ReactIntl$MessageDescriptor = { + id: string, + description?: string, + defaultMessage?: string + }; + + declare type $npm$ReactIntl$IntlConfig = { + locale: string, + formats: Object, + messages: { [id: string]: string }, + + defaultLocale?: string, + defaultFormats?: Object + }; + + declare type $npm$ReactIntl$IntlProviderConfig = { + locale?: string, + formats?: Object, + messages?: { [id: string]: string }, + + defaultLocale?: string, + defaultFormats?: Object + }; + + declare type $npm$ReactIntl$IntlFormat = { + formatDate: (value: any, options?: Object) => string, + formatTime: (value: any, options?: Object) => string, + formatRelative: (value: any, options?: Object) => string, + formatNumber: (value: any, options?: Object) => string, + formatPlural: (value: any, options?: Object) => string, + formatMessage: ( + messageDescriptor: $npm$ReactIntl$MessageDescriptor, + values?: Object + ) => string, + formatHTMLMessage: ( + messageDescriptor: $npm$ReactIntl$MessageDescriptor, + values?: Object + ) => string + }; + + declare type $npm$ReactIntl$IntlShape = $npm$ReactIntl$IntlConfig & + $npm$ReactIntl$IntlFormat & { now: () => number }; + + declare type $npm$ReactIntl$DateTimeFormatOptions = { + localeMatcher?: "best fit" | "lookup", + formatMatcher?: "basic" | "best fit", + + timeZone?: string, + hour12?: boolean, + + weekday?: "narrow" | "short" | "long", + era?: "narrow" | "short" | "long", + year?: "numeric" | "2-digit", + month?: "numeric" | "2-digit" | "narrow" | "short" | "long", + day?: "numeric" | "2-digit", + hour?: "numeric" | "2-digit", + minute?: "numeric" | "2-digit", + second?: "numeric" | "2-digit", + timeZoneName?: "short" | "long" + }; + + declare type $npm$ReactIntl$RelativeFormatOptions = { + style?: "best fit" | "numeric", + units?: "second" | "minute" | "hour" | "day" | "month" | "year" + }; + + declare type $npm$ReactIntl$NumberFormatOptions = { + localeMatcher?: "best fit" | "lookup", + + style?: "decimal" | "currency" | "percent", + + currency?: string, + currencyDisplay?: "symbol" | "code" | "name", + + useGrouping?: boolean, + + minimumIntegerDigits?: number, + minimumFractionDigits?: number, + maximumFractionDigits?: number, + minimumSignificantDigits?: number, + maximumSignificantDigits?: number + }; + + declare type $npm$ReactIntl$PluralFormatOptions = { + style?: "cardinal" | "ordinal" + }; + + declare type $npm$ReactIntl$PluralCategoryString = + | "zero" + | "one" + | "two" + | "few" + | "many" + | "other"; + + declare type $npm$ReactIntl$DateParseable = number | string | Date; + // PropType checker + declare function intlShape( + props: Object, + propName: string, + componentName: string + ): void; + declare function addLocaleData( + data: $npm$ReactIntl$LocaleData | Array<$npm$ReactIntl$LocaleData> + ): void; + declare function defineMessages< + T: { [key: string]: $Exact<$npm$ReactIntl$MessageDescriptor> } + >( + messageDescriptors: T + ): T; + + declare type InjectIntlProvidedProps = { + intl: $npm$ReactIntl$IntlShape + } + + declare type InjectIntlVoidProps = { + intl: $npm$ReactIntl$IntlShape | void + } + + declare type ComponentWithDefaultProps = + | React$ComponentType + | React$StatelessFunctionalComponent + | ChildrenArray>; + + declare type InjectIntlOptions = { + intlPropName?: string, + withRef?: boolean + } + + declare class IntlInjectedComponent extends React$Component { + static WrappedComponent: Class>, + static defaultProps: TDefaultProps, + props: TOwnProps + } + + declare type IntlInjectedComponentClass = Class< + IntlInjectedComponent + >; + + declare function injectIntl>( + WrappedComponent: Component, + options?: InjectIntlOptions, + ): React$ComponentType< + $Diff, InjectIntlVoidProps> + >; + + declare function formatMessage( + messageDescriptor: $npm$ReactIntl$MessageDescriptor, + values?: Object + ): string; + declare function formatHTMLMessage( + messageDescriptor: $npm$ReactIntl$MessageDescriptor, + values?: Object + ): string; + declare function formatDate( + value: any, + options?: $npm$ReactIntl$DateTimeFormatOptions & { format: string } + ): string; + declare function formatTime( + value: any, + options?: $npm$ReactIntl$DateTimeFormatOptions & { format: string } + ): string; + declare function formatRelative( + value: any, + options?: $npm$ReactIntl$RelativeFormatOptions & { + format: string, + now: any + } + ): string; + declare function formatNumber( + value: any, + options?: $npm$ReactIntl$NumberFormatOptions & { format: string } + ): string; + declare function formatPlural( + value: any, + options?: $npm$ReactIntl$PluralFormatOptions + ): $npm$ReactIntl$PluralCategoryString; + + declare class FormattedMessage extends React$Component< + $npm$ReactIntl$MessageDescriptor & { + values?: Object, + tagName?: string, + children?: + | ((...formattedMessage: Array) => React$Node) + | (string => React$Node) + } + > {} + declare class FormattedHTMLMessage extends React$Component< + $npm$ReactIntl$DateTimeFormatOptions & { + values?: Object, + tagName?: string, + children?: (...formattedMessage: Array) => React$Node + } + > {} + declare class FormattedDate extends React$Component< + $npm$ReactIntl$DateTimeFormatOptions & { + value: $npm$ReactIntl$DateParseable, + format?: string, + children?: (formattedDate: string) => React$Node + } + > {} + declare class FormattedTime extends React$Component< + $npm$ReactIntl$DateTimeFormatOptions & { + value: $npm$ReactIntl$DateParseable, + format?: string, + children?: (formattedDate: string) => React$Node + } + > {} + declare class FormattedRelative extends React$Component< + $npm$ReactIntl$RelativeFormatOptions & { + value: $npm$ReactIntl$DateParseable, + format?: string, + updateInterval?: number, + initialNow?: $npm$ReactIntl$DateParseable, + children?: (formattedDate: string) => React$Node + } + > {} + declare class FormattedNumber extends React$Component< + $npm$ReactIntl$NumberFormatOptions & { + value: number | string, + format?: string, + children?: (formattedNumber: string) => React$Node + } + > {} + declare class FormattedPlural extends React$Component< + $npm$ReactIntl$PluralFormatOptions & { + value: number | string, + other: React$Node, + zero?: React$Node, + one?: React$Node, + two?: React$Node, + few?: React$Node, + many?: React$Node, + children?: (formattedPlural: React$Node) => React$Node + } + > {} + declare class IntlProvider extends React$Component< + $npm$ReactIntl$IntlProviderConfig & { + children?: React$Node, + initialNow?: $npm$ReactIntl$DateParseable + } + > {} + declare type IntlShape = $npm$ReactIntl$IntlShape; + declare type MessageDescriptor = $npm$ReactIntl$MessageDescriptor; + } \ No newline at end of file diff --git a/src/flowtype/npm/react-redux_v5.x.x.js b/src/flowtype/npm/react-redux_v5.x.x.js index 58471492..0df28a83 100644 --- a/src/flowtype/npm/react-redux_v5.x.x.js +++ b/src/flowtype/npm/react-redux_v5.x.x.js @@ -1,127 +1,275 @@ -// flow-typed signature: 59b0c4be0e1408f21e2446be96c79804 -// flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.54.x +/** +The order of type arguments for connect() is as follows: -import type { Dispatch, Store } from 'redux'; +connect(…) -declare module 'react-redux' { - /* +In Flow v0.89 only the first two are mandatory to specify. Other 4 can be repaced with the new awesome type placeholder: - S = State - A = Action - D = Dispatch - OP = OwnProps - SP = StateProps - DP = DispatchProps +connect(…) - */ +But beware, in case of weird type errors somewhere in random places +just type everything and get to a green field and only then try to +remove the definitions you see bogus. - declare type MapStateToProps = ( - state: S, - ownProps: OP - ) => ((state: S, ownProps: OP) => SP) | SP; +Decrypting the abbreviations: + WC = Component being wrapped + S = State + D = Dispatch + OP = OwnProps + SP = StateProps + DP = DispatchProps + MP = Merge props + RSP = Returned state props + RDP = Returned dispatch props + RMP = Returned merge props + CP = Props for returned component + Com = React Component + ST = Static properties of Com + EFO = Extra factory options (used only in connectAdvanced) +*/ - declare type MapDispatchToProps = +declare module "react-redux" { + // ------------------------------------------------------------ + // Typings for connect() + // ------------------------------------------------------------ + + declare export type Options = {| + pure?: boolean, + withRef?: boolean, + areStatesEqual?: (next: S, prev: S) => boolean, + areOwnPropsEqual?: (next: OP, prev: OP) => boolean, + areStatePropsEqual?: (next: SP, prev: SP) => boolean, + areMergedPropsEqual?: (next: MP, prev: MP) => boolean, + storeKey?: string, + |}; + + declare type MapStateToProps<-S, -OP, +SP> = + | ((state: S, ownProps: OP) => SP) + // If you want to use the factory function but get a strange error + // like "function is not an object" then just type the factory function + // like this: + // const factory: (State, OwnProps) => (State, OwnProps) => StateProps + // and provide the StateProps type to the SP type parameter. + | ((state: S, ownProps: OP) => (state: S, ownProps: OP) => SP); + + declare type Bind = ((...A) => R) => (...A) => $Call; + + declare type MapDispatchToPropsFn = | ((dispatch: D, ownProps: OP) => DP) - | DP; + // If you want to use the factory function but get a strange error + // like "function is not an object" then just type the factory function + // like this: + // const factory: (Dispatch, OwnProps) => (Dispatch, OwnProps) => DispatchProps + // and provide the DispatchProps type to the DP type parameter. + | ((dispatch: D, ownProps: OP) => (dispatch: D, ownProps: OP) => DP); - declare type MergeProps = ( + declare class ConnectedComponent extends React$Component { + static +WrappedComponent: WC; + getWrappedInstance(): React$ElementRef; + } + declare type Connector = >( + WC, + ) => Class> & WC; + + // No `mergeProps` argument + + declare export function connect<-P, -OP, -SP, -DP, -S, -D>( + mapStateToProps?: null | void, + mapDispatchToProps?: null | void, + mergeProps?: null | void, + options?: ?Options, + // Got error like inexact OwnProps is incompatible with exact object type? + // Just make your OP parameter an exact object. + ): Connector; + + declare export function connect<-P, -OP, -SP, -DP, -S, -D>( + // If you get error here try adding return type to your mapStateToProps function + mapStateToProps: MapStateToProps, + mapDispatchToProps?: null | void, + mergeProps?: null | void, + options?: ?Options, + // Got error like inexact OwnProps is incompatible with exact object type? + // Just make your OP parameter an exact object. + ): Connector; + + // In this case DP is an object of functions which has been bound to dispatch + // by the given mapDispatchToProps function. + declare export function connect<-P, -OP, -SP, -DP, S, D>( + mapStateToProps: null | void, + mapDispatchToProps: MapDispatchToPropsFn, + mergeProps?: null | void, + options?: ?Options, + // Got error like inexact OwnProps is incompatible with exact object type? + // Just make your OP parameter an exact object. + ): Connector; + + // In this case DP is an object of action creators not yet bound to dispatch, + // this difference is not important in the vanila redux, + // but in case of usage with redux-thunk, the return type may differ. + declare export function connect<-P, -OP, -SP, -DP, S, D>( + mapStateToProps: null | void, + mapDispatchToProps: DP, + mergeProps?: null | void, + options?: ?Options, + // Got error like inexact OwnProps is incompatible with exact object type? + // Just make your OP parameter an exact object. + ): Connector> |}>; + + declare export function connect<-P, -OP, -SP, -DP, S, D>( + // If you get error here try adding return type to your mapStateToProps function + mapStateToProps: MapStateToProps, + mapDispatchToProps: MapDispatchToPropsFn, + mergeProps?: null | void, + options?: ?Options, + // Got error like inexact OwnProps is incompatible with exact object type? + // Just make your OP parameter an exact object. + ): Connector; + + declare export function connect<-P, -OP, -SP, -DP, S, D>( + // If you get error here try adding return type to your mapStateToProps function + mapStateToProps: MapStateToProps, + mapDispatchToProps: DP, + mergeProps?: null | void, + options?: ?Options, + // Got error like inexact OwnProps is incompatible with exact object type? + // Just make your OP parameter an exact object. + ): Connector> |}>; + + // With `mergeProps` argument + + declare type MergeProps<+P, -OP, -SP, -DP> = ( stateProps: SP, dispatchProps: DP, - ownProps: OP + ownProps: OP, ) => P; - declare type Context = { store: Store<*, *> }; + declare export function connect<-P, -OP, -SP: {||}, -DP: {||}, S, D>( + mapStateToProps: null | void, + mapDispatchToProps: null | void, + // If you get error here try adding return type to you mapStateToProps function + mergeProps: MergeProps, + options?: ?Options, + ): Connector; - declare type ComponentWithDefaultProps = Class> & { defaultProps: DP }; + declare export function connect<-P, -OP, -SP, -DP: {||}, S, D>( + mapStateToProps: MapStateToProps, + mapDispatchToProps: null | void, + // If you get error here try adding return type to you mapStateToProps function + mergeProps: MergeProps, + options?: ?Options, + ): Connector; - declare class ConnectedComponentWithDefaultProps< - OP, - DP, - CP - > extends React$Component { - static defaultProps: DP, // <= workaround for https://github.com/facebook/flow/issues/4644 - static WrappedComponent: Class>, - getWrappedInstance(): React$Component, - props: OP, - state: void - } + // In this case DP is an object of functions which has been bound to dispatch + // by the given mapDispatchToProps function. + declare export function connect<-P, -OP, -SP: {||}, -DP, S, D>( + mapStateToProps: null | void, + mapDispatchToProps: MapDispatchToPropsFn, + mergeProps: MergeProps, + options?: ?Options, + ): Connector; - declare class ConnectedComponent extends React$Component { - static WrappedComponent: Class>, - getWrappedInstance(): React$Component

, - props: OP, - state: void - } + // In this case DP is an object of action creators not yet bound to dispatch, + // this difference is not important in the vanila redux, + // but in case of usage with redux-thunk, the return type may differ. + declare export function connect<-P, -OP, -SP: {||}, -DP, S, D>( + mapStateToProps: null | void, + mapDispatchToProps: DP, + mergeProps: MergeProps>>, + options?: ?Options, + ): Connector; - declare type ConnectedComponentWithDefaultPropsClass = Class>; + // In this case DP is an object of functions which has been bound to dispatch + // by the given mapDispatchToProps function. + declare export function connect<-P, -OP, -SP, -DP, S, D>( + mapStateToProps: MapStateToProps, + mapDispatchToProps: MapDispatchToPropsFn, + mergeProps: MergeProps, + options?: ?Options, + ): Connector; - declare type ConnectedComponentClass = Class>; + // In this case DP is an object of action creators not yet bound to dispatch, + // this difference is not important in the vanila redux, + // but in case of usage with redux-thunk, the return type may differ. + declare export function connect<-P, -OP, -SP, -DP, S, D>( + mapStateToProps: MapStateToProps, + mapDispatchToProps: DP, + mergeProps: MergeProps>>, + options?: ?Options, + ): Connector; - declare type Connector = (( - component: ComponentWithDefaultProps - ) => ConnectedComponentWithDefaultPropsClass) & - ((component: React$ComponentType

) => ConnectedComponentClass); + // ------------------------------------------------------------ + // Typings for Provider + // ------------------------------------------------------------ - declare class Provider extends React$Component<{ - store: Store, - children?: any + declare export class Provider extends React$Component<{ + store: Store, + children?: React$Node, }> {} - declare function createProvider( + declare export function createProvider( storeKey?: string, - subKey?: string - ): Provider<*, *>; + subKey?: string, + ): Class>; - declare type ConnectOptions = { - pure?: boolean, - withRef?: boolean + // ------------------------------------------------------------ + // Typings for connectAdvanced() + // ------------------------------------------------------------ + + declare type ConnectAdvancedOptions = { + getDisplayName?: (name: string) => string, + methodName?: string, + renderCountProp?: string, + shouldHandleStateChanges?: boolean, + storeKey?: string, + withRef?: boolean, }; - declare type Null = null | void; + declare type SelectorFactoryOptions = { + getDisplayName: (name: string) => string, + methodName: string, + renderCountProp: ?string, + shouldHandleStateChanges: boolean, + storeKey: string, + withRef: boolean, + displayName: string, + wrappedComponentName: string, + WrappedComponent: Com, + }; - declare function connect( - ...rest: Array // <= workaround for https://github.com/facebook/flow/issues/2360 - ): Connector } & OP>>; + declare type MapStateToPropsEx = ( + state: S, + props: SP, + ) => RSP; - declare function connect( - mapStateToProps: Null, - mapDispatchToProps: Null, - mergeProps: Null, - options: ConnectOptions - ): Connector } & OP>>; + declare type SelectorFactory< + Com: React$ComponentType<*>, + Dispatch, + S: Object, + OP: Object, + EFO: Object, + CP: Object, + > = ( + dispatch: Dispatch, + factoryOptions: SelectorFactoryOptions & EFO, + ) => MapStateToPropsEx; - declare function connect( - mapStateToProps: MapStateToProps, - mapDispatchToProps: Null, - mergeProps: Null, - options?: ConnectOptions - ): Connector } & OP>>; + declare export function connectAdvanced< + Com: React$ComponentType<*>, + D, + S: Object, + OP: Object, + CP: Object, + EFO: Object, + ST: { [_: $Keys]: any }, + >( + selectorFactory: SelectorFactory, + connectAdvancedOptions: ?(ConnectAdvancedOptions & EFO), + ): (component: Com) => React$ComponentType & $Shape; - declare function connect( - mapStateToProps: Null, - mapDispatchToProps: MapDispatchToProps, - mergeProps: Null, - options?: ConnectOptions - ): Connector>; - - declare function connect( - mapStateToProps: MapStateToProps, - mapDispatchToProps: MapDispatchToProps, - mergeProps: Null, - options?: ConnectOptions - ): Connector>; - - declare function connect( - mapStateToProps: MapStateToProps, - mapDispatchToProps: Null, - mergeProps: MergeProps, - options?: ConnectOptions - ): Connector; - - declare function connect( - mapStateToProps: MapStateToProps, - mapDispatchToProps: MapDispatchToProps, - mergeProps: MergeProps, - options?: ConnectOptions - ): Connector; + declare export default { + Provider: typeof Provider, + createProvider: typeof createProvider, + connect: typeof connect, + connectAdvanced: typeof connectAdvanced, + }; } \ No newline at end of file diff --git a/src/reducers/AccountsReducer.js b/src/reducers/AccountsReducer.js index e84452c3..3fe36f19 100644 --- a/src/reducers/AccountsReducer.js +++ b/src/reducers/AccountsReducer.js @@ -75,6 +75,11 @@ const createAccount = (state: State, account: Account): State => { } const newState: State = [...state]; newState.push(account); + + // sort the accounts array so the imported accounts always come before discovered accounts + if (account.imported) { + newState.sort((a, b) => Number(b.imported) - Number(a.imported) || a.index - b.index); + } return newState; }; diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index 7dca1bc5..30b42061 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -95,7 +95,11 @@ const complete = (state: State, action: DiscoveryCompleteAction): State => { const accountCreate = (state: State, account: Account): State => { const index: number = findIndex(state, account.network, account.deviceState); const newState: State = [...state]; - newState[index].accountIndex++; + // do not increment index when adding imported account + // imported accounts should not interfere with the index used in discovery proccess. + if (!account.imported) { + newState[index].accountIndex++; + } return newState; }; diff --git a/src/reducers/ImportAccountReducer.js b/src/reducers/ImportAccountReducer.js new file mode 100644 index 00000000..7ecd5e59 --- /dev/null +++ b/src/reducers/ImportAccountReducer.js @@ -0,0 +1,43 @@ +/* @flow */ + +import * as IMPORT from 'actions/constants/importAccount'; + +import type { Action } from 'flowtype'; + +export type ImportState = { + loading: boolean, + error: ?string, +}; + +export const initialState: ImportState = { + loading: false, + error: null, +}; + +export default (state: ImportState = initialState, action: Action): ImportState => { + switch (action.type) { + case IMPORT.START: + return { + ...state, + loading: true, + error: null, + }; + + case IMPORT.SUCCESS: + return { + ...state, + loading: false, + error: null, + }; + + case IMPORT.FAIL: + return { + ...state, + loading: false, + error: action.error, + }; + + default: + return state; + } +}; diff --git a/src/reducers/TrezorConnectReducer.js b/src/reducers/TrezorConnectReducer.js index 135a1dd4..7d087f18 100644 --- a/src/reducers/TrezorConnectReducer.js +++ b/src/reducers/TrezorConnectReducer.js @@ -1,5 +1,5 @@ /* @flow */ -import { TRANSPORT, IFRAME } from 'trezor-connect'; +import { TRANSPORT, UI } from 'trezor-connect'; import * as CONNECT from 'actions/constants/TrezorConnect'; import type { Action } from 'flowtype'; @@ -66,7 +66,7 @@ export default function connect(state: State = initialState, action: Action): St error: action.error, }; // trezor-connect iframe loaded - case IFRAME.LOADED: + case UI.IFRAME_HANDSHAKE: return { ...state, initialized: true, diff --git a/src/reducers/index.js b/src/reducers/index.js index e51fb473..6af93617 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -2,6 +2,7 @@ import { combineReducers } from 'redux'; import { connectRouter } from 'connected-react-router'; import type { State } from 'connected-react-router'; +import type { Action } from 'flowtype'; import log from 'reducers/LogReducer'; import localStorage from 'reducers/LocalStorageReducer'; @@ -10,6 +11,7 @@ import notifications from 'reducers/NotificationReducer'; import modal from 'reducers/ModalReducer'; import web3 from 'reducers/Web3Reducer'; import accounts from 'reducers/AccountsReducer'; +import importAccount from 'reducers/ImportAccountReducer'; import selectedAccount from 'reducers/SelectedAccountReducer'; import sendFormEthereum from 'reducers/SendFormEthereumReducer'; import sendFormRipple from 'reducers/SendFormRippleReducer'; @@ -31,6 +33,7 @@ const reducers = { notifications, modal, web3, + importAccount, accounts, selectedAccount, sendFormEthereum, @@ -60,8 +63,8 @@ export type Reducers = typeof reducers; type $ExtractFunctionReturn = (v: (...args: any) => V) => V; export type ReducersState = $ObjMap; -export default (history: any) => - combineReducers({ +export default (history: Object) => + combineReducers({ ...reducers, router: connectRouter(history), }); diff --git a/src/reducers/utils/index.js b/src/reducers/utils/index.js index 473852d0..41422f4c 100644 --- a/src/reducers/utils/index.js +++ b/src/reducers/utils/index.js @@ -83,10 +83,15 @@ export const getSelectedAccount = (state: State): ?Account => { const locationState = state.router.location.state; if (!device || !locationState.network || !locationState.account) return null; - const index: number = parseInt(locationState.account, 10); + // imported account index has 'i' prefix + const isImported = /^i\d+$/i.test(locationState.account); + const index: number = isImported + ? parseInt(locationState.account.substr(1), 10) + : parseInt(locationState.account, 10); return state.accounts.find( a => + a.imported === isImported && a.deviceState === device.state && a.index === index && a.network === locationState.network diff --git a/src/services/LocalStorageService.js b/src/services/LocalStorageService.js index c5d9daf6..018941ba 100644 --- a/src/services/LocalStorageService.js +++ b/src/services/LocalStorageService.js @@ -58,6 +58,10 @@ const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: Middlewar case CONNECT.FORGET: case CONNECT.FORGET_SINGLE: case CONNECT.FORGET_SILENT: + api.dispatch(LocalStorageActions.save()); + api.dispatch(LocalStorageActions.removeImportedAccounts(action.device)); + break; + case CONNECT.RECEIVE_WALLET_TYPE: case DEVICE.CHANGED: case DEVICE.DISCONNECT: diff --git a/src/services/LogService.js b/src/services/LogService.js index ad797e3c..ee9d7d31 100644 --- a/src/services/LogService.js +++ b/src/services/LogService.js @@ -2,6 +2,7 @@ import * as LogActions from 'actions/LogActions'; import { TRANSPORT, DEVICE } from 'trezor-connect'; import * as DISCOVERY from 'actions/constants/discovery'; +import * as ACCOUNT from 'actions/constants/account'; import type { Middleware, MiddlewareAPI, MiddlewareDispatch, Action } from 'flowtype'; @@ -10,6 +11,7 @@ const actions: Array = [ DEVICE.CONNECT, DEVICE.DISCONNECT, DISCOVERY.START, + ACCOUNT.CREATE, ]; /** @@ -40,6 +42,9 @@ const LogService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch case DISCOVERY.START: api.dispatch(LogActions.add('Discovery started', action)); break; + case ACCOUNT.CREATE: + api.dispatch(LogActions.add('Account created', action)); + break; default: break; } diff --git a/src/store.js b/src/store.js index d260a0fc..e6895fa7 100644 --- a/src/store.js +++ b/src/store.js @@ -12,11 +12,10 @@ import Raven from 'raven-js'; import RavenMiddleware from 'redux-raven-middleware'; import * as buildUtils from 'utils/build'; -import type { Action, GetState } from 'flowtype'; +import type { State, Action, Dispatch, GetState } from 'flowtype'; export const history: History = createHistory({ queryKey: false }); -const initialState: any = {}; const enhancers = []; const middlewares = [thunk, routerMiddleware(history)]; @@ -61,4 +60,4 @@ if (buildUtils.isDev()) { ); } -export default createStore(createRootReducer(history), initialState, composedEnhancers); +export default createStore(createRootReducer(history), composedEnhancers); diff --git a/src/support/ConnectedIntlProvider.js b/src/support/ConnectedIntlProvider.js index 70c525cb..547ebaa0 100644 --- a/src/support/ConnectedIntlProvider.js +++ b/src/support/ConnectedIntlProvider.js @@ -2,7 +2,6 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import type { MapStateToProps } from 'react-redux'; import type { State } from 'flowtype'; import { IntlProvider, addLocaleData } from 'react-intl'; @@ -43,20 +42,21 @@ addLocaleData([ ...zh, ]); -type OwnProps = { +type OwnProps = {| children: React.Node, -}; +|}; -type StateProps = { +type StateProps = {| locale: string, messages: { [string]: string }, -}; +|}; -type Props = StateProps & OwnProps; +type Props = {| + ...OwnProps, + ...StateProps, +|}; -const mapStateToProps: MapStateToProps = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ locale: state.wallet.language, messages: state.wallet.messages, }); @@ -71,7 +71,6 @@ const ReactIntlProvider = ({ children, locale, messages }: Props) => ( ); -export default connect( - mapStateToProps, - null -)(ReactIntlProvider); +export default connect(mapStateToProps)( + ReactIntlProvider +); diff --git a/src/support/routes.js b/src/support/routes.js index abc46b11..77f84172 100644 --- a/src/support/routes.js +++ b/src/support/routes.js @@ -23,9 +23,9 @@ export const routes: Array = [ fields: ['bridge'], }, { - name: 'landing-import', - pattern: '/import', - fields: ['import'], + name: 'wallet-import', + pattern: '/device/:device/import', + fields: ['device', 'import'], }, { name: 'wallet-settings', diff --git a/src/views/Landing/components/BetaDisclaimer/index.js b/src/views/Landing/components/BetaDisclaimer/index.js index 332c4d53..8ddb0207 100644 --- a/src/views/Landing/components/BetaDisclaimer/index.js +++ b/src/views/Landing/components/BetaDisclaimer/index.js @@ -9,8 +9,19 @@ import { Button, Icon, P, H5, colors, icons } from 'trezor-ui-components'; import { FONT_SIZE } from 'config/variables'; import * as WalletActions from 'actions/WalletActions'; +import type { State, Dispatch } from 'flowtype'; import l10nMessages from './index.messages'; +type OwnProps = {||}; + +type StateProps = {||}; + +type DispatchProps = {| + close: typeof WalletActions.hideBetaDisclaimer, +|}; + +type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; + const Wrapper = styled.div` width: 100%; min-height: 100vh; @@ -50,7 +61,7 @@ const StyledIcon = styled(Icon)` top: -1px; `; -const BetaDisclaimer = (props: { close: () => void }) => ( +const BetaDisclaimer = (props: Props) => (

@@ -115,9 +126,9 @@ const BetaDisclaimer = (props: { close: () => void }) => ( ); -export default connect( +export default connect( null, - (dispatch: Dispatch) => ({ + (dispatch: Dispatch): DispatchProps => ({ close: bindActionCreators(WalletActions.hideBetaDisclaimer, dispatch), }) )(BetaDisclaimer); diff --git a/src/views/Landing/components/ConnectDevice/index.js b/src/views/Landing/components/ConnectDevice/index.js index 53cd696b..7046ec1a 100644 --- a/src/views/Landing/components/ConnectDevice/index.js +++ b/src/views/Landing/components/ConnectDevice/index.js @@ -5,7 +5,7 @@ import styled, { keyframes } from 'styled-components'; import TrezorConnect from 'trezor-connect'; import l10nCommonMessages from 'views/common.messages'; -import { Button, P, H5, Link, icons, colors } from 'trezor-ui-components'; +import { Button, P, H1, Link, icons, colors } from 'trezor-ui-components'; import { PULSATE } from 'config/animations'; import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables'; @@ -140,9 +140,9 @@ class ConnectDevice extends PureComponent { return ( - <H5 claim> + <H1 claim> <FormattedMessage {...l10nMessages.TR_THE_PRIVATE_BANK_IN_YOUR_HANDS} /> - </H5> + </H1> <P> <FormattedMessage {...l10nMessages.TR_TREZOR_WALLET_IS_AN_EASY_DASH} /> </P> diff --git a/src/views/Landing/views/Import/Container.js b/src/views/Landing/views/Import/Container.js deleted file mode 100644 index 5991a397..00000000 --- a/src/views/Landing/views/Import/Container.js +++ /dev/null @@ -1,42 +0,0 @@ -/* @flow */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import * as RouterActions from 'actions/RouterActions'; - -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; -import type { State, Dispatch } from 'flowtype'; -import ImportView from './index'; - -export type StateProps = { - transport: $ElementType<$ElementType<State, 'connect'>, 'transport'>, - children?: React.Node, -}; - -type DispatchProps = { - selectFirstAvailableDevice: typeof RouterActions.selectFirstAvailableDevice, -}; - -type OwnProps = {}; - -export type Props = StateProps & DispatchProps; - -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ - transport: state.connect.transport, -}); - -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ - selectFirstAvailableDevice: bindActionCreators( - RouterActions.selectFirstAvailableDevice, - dispatch - ), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ImportView); diff --git a/src/views/Landing/views/Import/index.js b/src/views/Landing/views/Import/index.js deleted file mode 100644 index 3b689ed0..00000000 --- a/src/views/Landing/views/Import/index.js +++ /dev/null @@ -1,28 +0,0 @@ -/* @flow */ - -import React from 'react'; -import styled from 'styled-components'; -import { Button, Link, Icon, H5, icons, colors } from 'trezor-ui-components'; -import LandingWrapper from 'views/Landing/components/LandingWrapper'; - -const Wrapper = styled.div` - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - -const Import = () => ( - <LandingWrapper> - <Wrapper> - <Icon size={60} color={colors.WARNING_PRIMARY} icon={icons.WARNING} /> - <H5>Import tool is under construction</H5> - <Link to="/"> - <Button>Take me back</Button> - </Link> - </Wrapper> - </LandingWrapper> -); - -export default Import; diff --git a/src/views/Landing/views/InstallBridge/Container.js b/src/views/Landing/views/InstallBridge/Container.js index 8bfaf5de..d61a7dc8 100644 --- a/src/views/Landing/views/InstallBridge/Container.js +++ b/src/views/Landing/views/InstallBridge/Container.js @@ -4,39 +4,34 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as RouterActions from 'actions/RouterActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import InstallBridge from './index'; -export type StateProps = { - transport: $ElementType<$ElementType<State, 'connect'>, 'transport'>, +type OwnProps = {| children?: React.Node, -}; +|}; +export type StateProps = {| + transport: $ElementType<$ElementType<State, 'connect'>, 'transport'>, +|}; -type DispatchProps = { +type DispatchProps = {| selectFirstAvailableDevice: typeof RouterActions.selectFirstAvailableDevice, -}; +|}; -type OwnProps = {}; +export type Props = {| ...StateProps, ...DispatchProps, ...OwnProps |}; -export type Props = StateProps & DispatchProps; - -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ transport: state.connect.transport, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ selectFirstAvailableDevice: bindActionCreators( RouterActions.selectFirstAvailableDevice, dispatch ), }); -export default connect( +export default connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( mapStateToProps, mapDispatchToProps )(InstallBridge); diff --git a/src/views/Landing/views/Root/Container.js b/src/views/Landing/views/Root/Container.js index 9c893749..0f6d2208 100644 --- a/src/views/Landing/views/Root/Container.js +++ b/src/views/Landing/views/Root/Container.js @@ -2,11 +2,13 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import type { MapStateToProps } from 'react-redux'; import type { State } from 'flowtype'; import RootView from './index'; -export type StateProps = { +type OwnProps = {| + children?: React.Node, +|}; +export type StateProps = {| localStorage: $ElementType<State, 'localStorage'>, modal: $ElementType<State, 'modal'>, wallet: $ElementType<State, 'wallet'>, @@ -15,16 +17,13 @@ export type StateProps = { wallet: $ElementType<State, 'wallet'>, devices: $ElementType<State, 'devices'>, children?: React.Node, -}; +|}; -type DispatchProps = {}; -type OwnProps = {}; +type DispatchProps = {||}; -export type Props = StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ localStorage: state.localStorage, modal: state.modal, wallet: state.wallet, @@ -33,7 +32,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( devices: state.devices, }); -export default connect( - mapStateToProps, - null -)(RootView); +export default connect<Props, OwnProps, StateProps, DispatchProps, State, _>(mapStateToProps)( + RootView +); diff --git a/src/views/Wallet/components/Content/index.js b/src/views/Wallet/components/Content/index.js index 4682ffe9..c214cff2 100644 --- a/src/views/Wallet/components/Content/index.js +++ b/src/views/Wallet/components/Content/index.js @@ -40,11 +40,14 @@ const Loading = styled.div` flex-direction: column; `; +const LoaderWrapper = styled.div` + margin-right: 10px; +`; + const Title = styled(H4)` font-size: ${FONT_SIZE.BIGGER}; font-weight: ${FONT_WEIGHT.NORMAL}; color: ${props => (props.type === 'progress' ? colors.TEXT_SECONDARY : '')}; - margin-left: 10px; text-align: center; padding: 0; `; @@ -80,7 +83,11 @@ const Content = ({ className, children, isLoading = false, loader, exceptionPage {isLoading && loader && ( <Loading> <Row> - {loader.type === 'progress' && <Loader size={30} />} + {loader.type === 'progress' && ( + <LoaderWrapper> + <Loader size={30} /> + </LoaderWrapper> + )} <Title type={loader.type}> {loader.title || ( <FormattedMessage {...l10nMessages.TR_INITIALIZING_ACCOUNTS} /> diff --git a/src/views/Wallet/components/LeftNavigation/Container.js b/src/views/Wallet/components/LeftNavigation/Container.js index d5d21d47..17a84e37 100644 --- a/src/views/Wallet/components/LeftNavigation/Container.js +++ b/src/views/Wallet/components/LeftNavigation/Container.js @@ -8,18 +8,15 @@ import * as TrezorConnectActions from 'actions/TrezorConnectActions'; import * as DiscoveryActions from 'actions/DiscoveryActions'; import * as RouterActions from 'actions/RouterActions'; import * as ModalActions from 'actions/ModalActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import type { StateProps, DispatchProps } from './components/common'; import LeftNavigation from './index'; -type OwnProps = {}; +type OwnProps = {||}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ connect: state.connect, accounts: state.accounts, router: state.router, @@ -31,9 +28,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( pending: state.pending, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch), addAccount: bindActionCreators(DiscoveryActions.addAccount, dispatch), acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch), @@ -46,7 +41,7 @@ const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> setHideBalance: bindActionCreators(setHideBalance, dispatch), }); -export default withRouter( +export default withRouter<OwnProps>( connect( mapStateToProps, mapDispatchToProps diff --git a/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js index b680de05..0c441fb3 100644 --- a/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js @@ -28,8 +28,6 @@ const Text = styled.span` const RowAccountWrapper = styled.div` width: 100%; display: flex; - flex-direction: column; - align-items: flex-start; padding: ${LEFT_NAVIGATION_ROW.PADDING}; font-size: ${FONT_SIZE.BASE}; color: ${colors.TEXT_PRIMARY}; @@ -86,6 +84,25 @@ const DiscoveryLoadingText = styled.span` margin-left: 14px; `; +const Col = styled.div` + display: flex; + flex: 1; + flex-direction: column; +`; + +const RightCol = styled(Col)` + justify-content: center; +`; + +const Badge = styled.div` + padding: 4px 8px; + background: lightslategray; + color: white; + font-size: ${FONT_SIZE.BADGE}; + border-radius: 3px; + align-self: flex-end; +`; + // TODO: Refactorize deviceStatus & selectedAccounts const AccountMenu = (props: Props) => { const selected = props.wallet.selectedDevice; @@ -102,10 +119,18 @@ const AccountMenu = (props: Props) => { if (!selected || !network) return null; const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network); + const discoveryAccounts: Accounts = deviceAccounts.filter( + account => account.imported === false + ); const selectedAccounts = deviceAccounts.map((account, i) => { // const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`; - const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`); + let url: string; + if (account.imported) { + url = location.pathname.replace(/account+\/(i?[0-9]*)/, `account/i${account.index}`); + } else { + url = location.pathname.replace(/account+\/(i?[0-9]*)/, `account/${account.index}`); + } let balance: ?string = null; const fiatRates = props.fiat.find(f => f.network === network.shortcut); @@ -125,36 +150,41 @@ const AccountMenu = (props: Props) => { } } - const urlAccountIndex = parseInt(props.router.location.state.account, 10); return ( - <NavLink to={url} key={account.index}> + <NavLink to={url} key={url}> <Row column> - <RowAccountWrapper - isSelected={urlAccountIndex === account.index} - borderTop={account.index === 0} - > - <FormattedMessage - {...l10nCommonMessages.TR_ACCOUNT_HASH} - values={{ number: account.index + 1 }} - /> - {balance && !props.wallet.hideBalance && ( - <Text> - {balance} - {fiatRates && ( - <FormattedNumber - currency={localCurrency} - value={fiat} - minimumFractionDigits={2} - // eslint-disable-next-line react/style-prop-object - style="currency" - /> - )} - </Text> - )} - {!balance && ( - <Text> - <FormattedMessage {...l10nMessages.TR_LOADING_DOT_DOT_DOT} /> - </Text> + <RowAccountWrapper isSelected={location.pathname === url} borderTop={i === 0}> + <Col> + <FormattedMessage + {...(account.imported + ? l10nCommonMessages.TR_IMPORTED_ACCOUNT_HASH + : l10nCommonMessages.TR_ACCOUNT_HASH)} + values={{ number: account.index + 1 }} + /> + {balance && !props.wallet.hideBalance && ( + <Text> + {balance} + {fiatRates && ( + <FormattedNumber + currency={localCurrency} + value={fiat} + minimumFractionDigits={2} + // eslint-disable-next-line react/style-prop-object + style="currency" + /> + )} + </Text> + )} + {!balance && ( + <Text> + <FormattedMessage {...l10nMessages.TR_LOADING_DOT_DOT_DOT} /> + </Text> + )} + </Col> + {account.imported && ( + <RightCol> + <Badge>watch-only</Badge> + </RightCol> )} </RowAccountWrapper> </Row> @@ -168,7 +198,7 @@ const AccountMenu = (props: Props) => { ); if (discovery && discovery.completed) { - const lastAccount = deviceAccounts[deviceAccounts.length - 1]; + const lastAccount = discoveryAccounts[discoveryAccounts.length - 1]; if (!selected.connected) { discoveryStatus = ( <Tooltip diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js index 887b3bf9..127fbc58 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js @@ -96,7 +96,7 @@ class MenuItems extends PureComponent { </Label> </Item> )} - {!this.showRenewSession() && ( + {this.showRenewSession() && ( <Item onClick={() => this.props.acquireDevice()}> <IconWrapper> <DeviceIcon diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js index 0d235654..3c8683ff 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js @@ -109,7 +109,7 @@ class DeviceMenu extends PureComponent<Props> { mouseDownHandler: (event: MouseEvent) => void; - blurHandler: (event: FocusEvent) => void; + blurHandler: () => void; showDivider() { return this.props.devices.length > 1; diff --git a/src/views/Wallet/components/LeftNavigation/components/common.js b/src/views/Wallet/components/LeftNavigation/components/common.js index f492cc62..5c1a71b0 100644 --- a/src/views/Wallet/components/LeftNavigation/components/common.js +++ b/src/views/Wallet/components/LeftNavigation/components/common.js @@ -6,7 +6,7 @@ import * as ModalActions from 'actions/ModalActions'; import * as WalletActions from 'actions/WalletActions'; import type { State } from 'flowtype'; -export type StateProps = { +export type StateProps = {| connect: $ElementType<State, 'connect'>, accounts: $ElementType<State, 'accounts'>, router: $ElementType<State, 'router'>, @@ -16,9 +16,9 @@ export type StateProps = { wallet: $ElementType<State, 'wallet'>, devices: $ElementType<State, 'devices'>, pending: $ElementType<State, 'pending'>, -}; +|}; -export type DispatchProps = { +export type DispatchProps = {| toggleDeviceDropdown: typeof WalletActions.toggleDeviceDropdown, toggleSidebar: typeof WalletActions.toggleSidebar, setHideBalance: typeof WalletActions.setHideBalance, @@ -29,6 +29,6 @@ export type DispatchProps = { gotoDeviceSettings: typeof RouterActions.gotoDeviceSettings, onSelectDevice: typeof RouterActions.selectDevice, gotoExternalWallet: typeof ModalActions.gotoExternalWallet, -}; +|}; -export type Props = StateProps & DispatchProps; +export type Props = {| ...StateProps, ...DispatchProps |}; diff --git a/src/views/Wallet/components/Title/index.js b/src/views/Wallet/components/Title/index.js index 3eefdea7..1a15b325 100644 --- a/src/views/Wallet/components/Title/index.js +++ b/src/views/Wallet/components/Title/index.js @@ -1,9 +1,14 @@ -import React from 'react'; +/* @flow */ +import * as React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import { colors } from 'trezor-ui-components'; import { FONT_SIZE, FONT_WEIGHT } from 'config/variables'; +type OwnProps = {| + children?: React.Node, +|}; + const Wrapper = styled.div` font-size: ${FONT_SIZE.WALLET_TITLE}; font-weight: ${FONT_WEIGHT.MEDIUM}; @@ -11,7 +16,7 @@ const Wrapper = styled.div` padding-bottom: 35px; `; -const Title = ({ children }) => <Wrapper>{children}</Wrapper>; +const Title = ({ children }: OwnProps) => <Wrapper>{children}</Wrapper>; Title.propTypes = { children: PropTypes.node, diff --git a/src/views/Wallet/components/TopNavigationAccount/index.js b/src/views/Wallet/components/TopNavigationAccount/index.js index 02a514ce..9a237f47 100644 --- a/src/views/Wallet/components/TopNavigationAccount/index.js +++ b/src/views/Wallet/components/TopNavigationAccount/index.js @@ -12,11 +12,16 @@ import { FormattedMessage } from 'react-intl'; import l10nMessages from './index.messages'; import Indicator from './components/Indicator'; -type Props = { +type OwnProps = {||}; + +type StateProps = {| router: $ElementType<State, 'router'>, selectedAccount: $ElementType<State, 'selectedAccount'>, localStorage: $ElementType<State, 'localStorage'>, -}; +|}; + +type Props = {| ...OwnProps, ...StateProps |}; + type LocalState = { wrapper: ?HTMLElement, }; @@ -73,7 +78,7 @@ const StyledNavLink = styled(NavLink)` `; class TopNavigationAccount extends React.PureComponent<Props, LocalState> { - constructor(props) { + constructor(props: Props) { super(props); this.state = { wrapper: null, @@ -92,11 +97,13 @@ class TopNavigationAccount extends React.PureComponent<Props, LocalState> { const { config } = this.props.localStorage; const { state, pathname } = this.props.router.location; if (!state) return null; - const { network } = this.props.selectedAccount; + const { network, account } = this.props.selectedAccount; if (!network) return null; const networkConfig = config.networks.find(c => c.shortcut === network.shortcut); if (!networkConfig) return null; + const isAccountImported = account && account.imported; + const basePath = `/device/${state.device}/network/${state.network}/account/${ state.account }`; @@ -123,8 +130,8 @@ class TopNavigationAccount extends React.PureComponent<Props, LocalState> { } } -export default connect( - (state: State): Props => ({ +export default connect<Props, OwnProps, StateProps, _, State, _>( + (state: State): StateProps => ({ router: state.router, selectedAccount: state.selectedAccount, localStorage: state.localStorage, diff --git a/src/views/Wallet/index.js b/src/views/Wallet/index.js index 380b3d88..a93a9a90 100644 --- a/src/views/Wallet/index.js +++ b/src/views/Wallet/index.js @@ -7,11 +7,9 @@ import { connect } from 'react-redux'; import { Route, withRouter } from 'react-router-dom'; import { getPattern } from 'support/routes'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; -import type { State } from 'flowtype'; +import type { State, Dispatch } from 'flowtype'; -import type { WalletAction } from 'actions/WalletActions'; -import { toggleSidebar } from 'actions/WalletActions'; +import * as WalletActions from 'actions/WalletActions'; import { bindActionCreators } from 'redux'; import Header from 'components/Header'; @@ -29,18 +27,19 @@ import TopNavigationAccount from './components/TopNavigationAccount'; import TopNavigationDeviceSettings from './components/TopNavigationDeviceSettings'; import TopNavigationWalletSettings from './components/TopNavigationWalletSettings'; -type StateProps = { +type StateProps = {| wallet: $ElementType<State, 'wallet'>, +|}; + +type DispatchProps = {| + toggleSidebar: typeof WalletActions.toggleSidebar, +|}; + +type OwnProps = {| children?: React.Node, -}; +|}; -type DispatchProps = { - toggleSidebar: WalletAction, -}; - -type OwnProps = {}; - -export type Props = StateProps & DispatchProps; +export type Props = {| ...StateProps, ...DispatchProps, ...OwnProps |}; const AppWrapper = styled.div` position: relative; @@ -137,26 +136,22 @@ const Wallet = (props: Props) => ( <ContextNotifications /> <Log /> <Body>{props.children}</Body> - <Footer /> + <Footer isLanding={false} /> </MainContent> </WalletWrapper> <ModalContainer /> </AppWrapper> ); -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ wallet: state.wallet, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ - toggleSidebar: bindActionCreators(toggleSidebar, dispatch), +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + toggleSidebar: bindActionCreators(WalletActions.toggleSidebar, dispatch), }); -export default withRouter( +export default withRouter<Props>( connect( mapStateToProps, mapDispatchToProps diff --git a/src/views/Wallet/views/Account/Receive/ethereum/Container.js b/src/views/Wallet/views/Account/Receive/ethereum/Container.js index d4a281c8..03492f8d 100644 --- a/src/views/Wallet/views/Account/Receive/ethereum/Container.js +++ b/src/views/Wallet/views/Account/Receive/ethereum/Container.js @@ -2,47 +2,41 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; -import { showAddress } from 'actions/ReceiveActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; +import * as ReceiveActions from 'actions/ReceiveActions'; import type { State, Dispatch } from 'flowtype'; import Receive from './index'; -type OwnProps = { - intl: any, -}; +type OwnProps = {| + intl: IntlShape, +|}; -type StateProps = { +type StateProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, receive: $ElementType<State, 'receive'>, modal: $ElementType<State, 'modal'>, wallet: $ElementType<State, 'wallet'>, -}; +|}; -type DispatchProps = { - showAddress: typeof showAddress, -}; +type DispatchProps = {| + showAddress: typeof ReceiveActions.showAddress, +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ selectedAccount: state.selectedAccount, receive: state.receive, modal: state.modal, wallet: state.wallet, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ - showAddress: bindActionCreators(showAddress, dispatch), +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch), }); -export default injectIntl( - connect( - mapStateToProps, - mapDispatchToProps - )(Receive) -); +export default connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( + mapStateToProps, + mapDispatchToProps +)(injectIntl<Props>(Receive)); diff --git a/src/views/Wallet/views/Account/Receive/ethereum/index.js b/src/views/Wallet/views/Account/Receive/ethereum/index.js index 406820db..f48436e1 100644 --- a/src/views/Wallet/views/Account/Receive/ethereum/index.js +++ b/src/views/Wallet/views/Account/Receive/ethereum/index.js @@ -95,10 +95,11 @@ const AccountReceive = (props: Props) => { const isAddressVerifying = props.modal.context === CONTEXT_DEVICE && props.modal.windowType === 'ButtonRequest_Address'; - const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; + const isAddressHidden = + !isAddressVerifying && !addressVerified && !addressUnverified && !account.imported; let address = `${account.descriptor.substring(0, 20)}...`; - if (addressVerified || addressUnverified || isAddressVerifying) { + if (addressVerified || addressUnverified || isAddressVerifying || account.imported) { address = account.descriptor; } @@ -166,7 +167,7 @@ const AccountReceive = (props: Props) => { ) } /> - {!(addressVerified || addressUnverified) && ( + {!(addressVerified || addressUnverified) && !account.imported && ( <ShowAddressButton icon={ICONS.EYE} onClick={() => props.showAddress(account.accountPath)} @@ -176,20 +177,21 @@ const AccountReceive = (props: Props) => { </ShowAddressButton> )} </Row> - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - <QrWrapper> - <Label> - <FormattedMessage {...l10nReceiveMessages.TR_QR_CODE} /> - </Label> - <StyledQRCode - bgColor="#FFFFFF" - fgColor="#000000" - level="Q" - style={{ width: 150 }} - value={account.descriptor} - /> - </QrWrapper> - )} + {((addressVerified || addressUnverified) && !isAddressVerifying) || + (account.imported && ( + <QrWrapper> + <Label> + <FormattedMessage {...l10nReceiveMessages.TR_QR_CODE} /> + </Label> + <StyledQRCode + bgColor="#FFFFFF" + fgColor="#000000" + level="Q" + style={{ width: 150 }} + value={account.descriptor} + /> + </QrWrapper> + ))} </AddressWrapper> </React.Fragment> </Content> diff --git a/src/views/Wallet/views/Account/Receive/index.js b/src/views/Wallet/views/Account/Receive/index.js index 94354052..b4d32ee0 100644 --- a/src/views/Wallet/views/Account/Receive/index.js +++ b/src/views/Wallet/views/Account/Receive/index.js @@ -8,17 +8,17 @@ import EthereumTypeReceiveForm from './ethereum/Container'; import RippleTypeReceiveForm from './ripple/Container'; import BitcoinTypeReceiveForm from './bitcoin/Container'; -export type BaseProps = { +export type BaseProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, -}; +|}; // return container for requested network type -export default connect( +export default connect<BaseProps, any, _, _, _, _>( (state: State): BaseProps => ({ selectedAccount: state.selectedAccount, }), null -)(props => { +)((props: BaseProps) => { const { network } = props.selectedAccount; if (!network) return null; diff --git a/src/views/Wallet/views/Account/Receive/ripple/Container.js b/src/views/Wallet/views/Account/Receive/ripple/Container.js index d4a281c8..37bd5d68 100644 --- a/src/views/Wallet/views/Account/Receive/ripple/Container.js +++ b/src/views/Wallet/views/Account/Receive/ripple/Container.js @@ -2,41 +2,37 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import { showAddress } from 'actions/ReceiveActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import Receive from './index'; -type OwnProps = { - intl: any, -}; +type OwnProps = {| + intl: IntlShape, +|}; -type StateProps = { +type StateProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, receive: $ElementType<State, 'receive'>, modal: $ElementType<State, 'modal'>, wallet: $ElementType<State, 'wallet'>, -}; +|}; -type DispatchProps = { +type DispatchProps = {| showAddress: typeof showAddress, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ selectedAccount: state.selectedAccount, receive: state.receive, modal: state.modal, wallet: state.wallet, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ showAddress: bindActionCreators(showAddress, dispatch), }); diff --git a/src/views/Wallet/views/Account/Receive/ripple/index.js b/src/views/Wallet/views/Account/Receive/ripple/index.js index 93480906..f1b0e8c5 100644 --- a/src/views/Wallet/views/Account/Receive/ripple/index.js +++ b/src/views/Wallet/views/Account/Receive/ripple/index.js @@ -101,10 +101,11 @@ const AccountReceive = (props: Props) => { const isAddressVerifying = props.modal.context === CONTEXT_DEVICE && props.modal.windowType === 'ButtonRequest_Address'; - const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; + const isAddressHidden = + !isAddressVerifying && !addressVerified && !addressUnverified && !account.imported; let address = `${account.descriptor.substring(0, 20)}...`; - if (addressVerified || addressUnverified || isAddressVerifying) { + if (addressVerified || addressUnverified || isAddressVerifying || account.imported) { address = account.descriptor; } @@ -172,7 +173,7 @@ const AccountReceive = (props: Props) => { ) } /> - {!(addressVerified || addressUnverified) && ( + {!(addressVerified || addressUnverified) && !account.imported && ( <ShowAddressButton onClick={() => props.showAddress(account.accountPath)} isDisabled={device.connected && !discovery.completed} @@ -182,20 +183,21 @@ const AccountReceive = (props: Props) => { </ShowAddressButton> )} </Row> - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - <QrWrapper> - <Label> - <FormattedMessage {...l10nReceiveMessages.TR_QR_CODE} /> - </Label> - <StyledQRCode - bgColor="#FFFFFF" - fgColor="#000000" - level="Q" - style={{ width: 150 }} - value={account.descriptor} - /> - </QrWrapper> - )} + {((addressVerified || addressUnverified) && !isAddressVerifying) || + (account.imported && ( + <QrWrapper> + <Label> + <FormattedMessage {...l10nReceiveMessages.TR_QR_CODE} /> + </Label> + <StyledQRCode + bgColor="#FFFFFF" + fgColor="#000000" + level="Q" + style={{ width: 150 }} + value={account.descriptor} + /> + </QrWrapper> + ))} </AddressWrapper> </React.Fragment> </Content> diff --git a/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js b/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js index 080a61c8..6d0ab40b 100644 --- a/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js +++ b/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js @@ -1,7 +1,7 @@ /* @flow */ import React from 'react'; import styled from 'styled-components'; -import { colors, H5 } from 'trezor-ui-components'; +import { colors, H5, P } from 'trezor-ui-components'; import Transaction from 'components/Transaction'; import type { Network } from 'reducers/LocalStorageReducer'; @@ -18,6 +18,8 @@ const Wrapper = styled.div` border-top: 1px solid ${colors.DIVIDER}; `; +const NoTransactions = styled(P)``; + const PendingTransactions = (props: Props) => { // const pending = props.pending.filter(tx => !tx.rejected).concat(testData); const pending = props.pending.filter(tx => !tx.rejected); @@ -25,6 +27,9 @@ const PendingTransactions = (props: Props) => { return ( <Wrapper> <H5>Pending transactions</H5> + {pending.length === 0 && ( + <NoTransactions>There are no pending transactions</NoTransactions> + )} {pending.map(tx => ( <Transaction key={tx.hash} network={props.network} tx={tx} /> ))} diff --git a/src/views/Wallet/views/Account/Send/ethereum/Container.js b/src/views/Wallet/views/Account/Send/ethereum/Container.js index 81a959b9..2a9d86fe 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/Container.js +++ b/src/views/Wallet/views/Account/Send/ethereum/Container.js @@ -2,36 +2,34 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; +import type { IntlShape } from 'react-intl'; import { injectIntl } from 'react-intl'; import SendFormActions from 'actions/ethereum/SendFormActions'; import { openQrModal } from 'actions/ModalActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import AccountSend from './index'; -type OwnProps = { - intl: any, -}; +type OwnProps = {| + intl: IntlShape, +|}; -export type StateProps = { +export type StateProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, sendForm: $ElementType<State, 'sendFormEthereum'>, wallet: $ElementType<State, 'wallet'>, fiat: $ElementType<State, 'fiat'>, localStorage: $ElementType<State, 'localStorage'>, -}; +|}; -export type DispatchProps = { +export type DispatchProps = {| sendFormActions: typeof SendFormActions, openQrModal: typeof openQrModal, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ selectedAccount: state.selectedAccount, sendForm: state.sendFormEthereum, wallet: state.wallet, @@ -39,16 +37,12 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( localStorage: state.localStorage, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ sendFormActions: bindActionCreators(SendFormActions, dispatch), openQrModal: bindActionCreators(openQrModal, dispatch), }); -export default injectIntl( - connect( - mapStateToProps, - mapDispatchToProps - )(AccountSend) -); +export default connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( + mapStateToProps, + mapDispatchToProps +)(injectIntl<OwnProps>(AccountSend)); diff --git a/src/views/Wallet/views/Account/Send/ethereum/components/AdvancedForm/index.js b/src/views/Wallet/views/Account/Send/ethereum/components/AdvancedForm/index.js index 579fee6a..75fbb723 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/components/AdvancedForm/index.js +++ b/src/views/Wallet/views/Account/Send/ethereum/components/AdvancedForm/index.js @@ -12,6 +12,8 @@ import { colors, icons as ICONS, } from 'trezor-ui-components'; +import type { IntlShape } from 'react-intl'; + import { FONT_SIZE } from 'config/variables'; import l10nCommonMessages from 'views/common.messages'; @@ -19,10 +21,7 @@ import l10nMessages from './index.messages'; import type { Props as BaseProps } from '../../Container'; -type Props = BaseProps & { - intl: any, - children: React.Node, -}; +type Props = {| ...BaseProps, intl: IntlShape, children: React.Node |}; // TODO: Decide on a small screen width for the whole app // and put it inside config/variables.js diff --git a/src/views/Wallet/views/Account/Send/ethereum/index.js b/src/views/Wallet/views/Account/Send/ethereum/index.js index 73fd4000..fe6ebed5 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/index.js +++ b/src/views/Wallet/views/Account/Send/ethereum/index.js @@ -312,7 +312,9 @@ const AccountSend = (props: Props) => { total === '0' || amount.length === 0 || address.length === 0 || - sending; + sending || + account.imported; + let amountText = ''; if (networkSymbol !== currency && amount.length > 0 && !errors.amount) { amountText = `${amount} ${currency.toUpperCase()}`; @@ -516,13 +518,14 @@ const AccountSend = (props: Props) => { </AdvancedForm> )} - {props.selectedAccount.pending.length > 0 && ( - <PendingTransactions - pending={props.selectedAccount.pending} - tokens={props.selectedAccount.tokens} - network={network} - /> - )} + {props.selectedAccount.pending.length > 0 || + (account.imported && ( + <PendingTransactions + pending={props.selectedAccount.pending} + tokens={props.selectedAccount.tokens} + network={network} + /> + ))} </Content> ); }; diff --git a/src/views/Wallet/views/Account/Send/index.js b/src/views/Wallet/views/Account/Send/index.js index fd5e6b1b..4607d60c 100644 --- a/src/views/Wallet/views/Account/Send/index.js +++ b/src/views/Wallet/views/Account/Send/index.js @@ -7,17 +7,17 @@ import EthereumTypeSendForm from './ethereum/Container'; import RippleTypeSendForm from './ripple/Container'; import BitcoinTypeSendForm from './bitcoin/Container'; -export type BaseProps = { +export type BaseProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, -}; +|}; // return container for requested network type -export default connect( +export default connect<BaseProps, any, _, _, _, _>( (state: State): BaseProps => ({ selectedAccount: state.selectedAccount, }), null -)(props => { +)((props: BaseProps) => { const { network } = props.selectedAccount; if (!network) return null; diff --git a/src/views/Wallet/views/Account/Send/ripple/Container.js b/src/views/Wallet/views/Account/Send/ripple/Container.js index ad14bdb4..9207387e 100644 --- a/src/views/Wallet/views/Account/Send/ripple/Container.js +++ b/src/views/Wallet/views/Account/Send/ripple/Container.js @@ -3,35 +3,33 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import SendFormActions from 'actions/ripple/SendFormActions'; import { openQrModal } from 'actions/ModalActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import AccountSend from './index'; -type OwnProps = { - intl: any, -}; +type OwnProps = {| + intl: IntlShape, +|}; -export type StateProps = { +export type StateProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, sendForm: $ElementType<State, 'sendFormRipple'>, wallet: $ElementType<State, 'wallet'>, fiat: $ElementType<State, 'fiat'>, localStorage: $ElementType<State, 'localStorage'>, -}; +|}; -export type DispatchProps = { +export type DispatchProps = {| sendFormActions: typeof SendFormActions, openQrModal: typeof openQrModal, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ selectedAccount: state.selectedAccount, sendForm: state.sendFormRipple, wallet: state.wallet, @@ -39,15 +37,13 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( localStorage: state.localStorage, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ sendFormActions: bindActionCreators(SendFormActions, dispatch), openQrModal: bindActionCreators(openQrModal, dispatch), }); export default injectIntl( - connect( + connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( mapStateToProps, mapDispatchToProps )(AccountSend) diff --git a/src/views/Wallet/views/Account/Send/ripple/components/AdvancedForm/index.js b/src/views/Wallet/views/Account/Send/ripple/components/AdvancedForm/index.js index 1811c77e..fcf2c39f 100644 --- a/src/views/Wallet/views/Account/Send/ripple/components/AdvancedForm/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/components/AdvancedForm/index.js @@ -11,9 +11,7 @@ import l10nMessages from './index.messages'; import type { Props as BaseProps } from '../../Container'; -type Props = BaseProps & { - children: React.Node, -}; +type Props = {| ...BaseProps, children: React.Node |}; // TODO: Decide on a small screen width for the whole app // and put it inside config/variables.js diff --git a/src/views/Wallet/views/Account/Send/ripple/index.js b/src/views/Wallet/views/Account/Send/ripple/index.js index d3e144d0..c2c2b949 100644 --- a/src/views/Wallet/views/Account/Send/ripple/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/index.js @@ -279,7 +279,9 @@ const AccountSend = (props: Props) => { total === '0' || amount.length === 0 || address.length === 0 || - sending; + sending || + account.imported; + let sendButtonText = <FormattedMessage {...l10nSendMessages.TR_SEND} values={{ amount: '' }} />; if (total !== '0') { sendButtonText = ( @@ -481,13 +483,14 @@ const AccountSend = (props: Props) => { </AdvancedForm> )} - {props.selectedAccount.pending.length > 0 && ( - <PendingTransactions - pending={props.selectedAccount.pending} - tokens={props.selectedAccount.tokens} - network={network} - /> - )} + {props.selectedAccount.pending.length > 0 || + (account.imported && ( + <PendingTransactions + pending={props.selectedAccount.pending} + tokens={props.selectedAccount.tokens} + network={network} + /> + ))} </Content> ); }; diff --git a/src/views/Wallet/views/Account/SignVerify/Container.js b/src/views/Wallet/views/Account/SignVerify/Container.js index 747812be..1f1cda05 100644 --- a/src/views/Wallet/views/Account/SignVerify/Container.js +++ b/src/views/Wallet/views/Account/SignVerify/Container.js @@ -3,49 +3,45 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import SignVerifyActions from 'actions/SignVerifyActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import Component from './index'; -type OwnProps = { - intl: any, -}; +type OwnProps = {| + intl: IntlShape, +|}; export type Error = { inputName: string, message: ?string, }; -export type StateProps = { +export type StateProps = {| wallet: $ElementType<State, 'wallet'>, selectedAccount: $ElementType<State, 'selectedAccount'>, signVerify: $ElementType<State, 'signVerify'>, -}; +|}; -export type DispatchProps = { +export type DispatchProps = {| signVerifyActions: typeof SignVerifyActions, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ wallet: state.wallet, selectedAccount: state.selectedAccount, signVerify: state.signVerify, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ signVerifyActions: bindActionCreators(SignVerifyActions, dispatch), }); export default injectIntl( - connect( + connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( mapStateToProps, mapDispatchToProps )(Component) diff --git a/src/views/Wallet/views/Account/Summary/components/Balance/index.js b/src/views/Wallet/views/Account/Summary/components/Balance/index.js index d8e4eb5e..f81e7445 100644 --- a/src/views/Wallet/views/Account/Summary/components/Balance/index.js +++ b/src/views/Wallet/views/Account/Summary/components/Balance/index.js @@ -92,11 +92,14 @@ const Label = styled.div` const StyledIcon = styled(Icon)` cursor: pointer; - margin-left: 6px; align-items: center; margin-top: -5px; `; +const TooltipContainer = styled.div` + margin-left: 6px; +`; + const TooltipWrapper = styled.div` display: flex; align-items: center; @@ -138,13 +141,15 @@ class AccountBalance extends PureComponent<Props, State> { } const NoRatesTooltip = ( - <Tooltip - maxWidth={285} - placement="top" - content={<FormattedMessage {...l10nMessages.TR_FIAT_RATES_ARE_NOT_CURRENTLY} />} - > - <StyledIcon icon={ICONS.HELP} color={colors.TEXT_SECONDARY} size={12} /> - </Tooltip> + <TooltipContainer> + <Tooltip + maxWidth={285} + placement="top" + content={<FormattedMessage {...l10nMessages.TR_FIAT_RATES_ARE_NOT_CURRENTLY} />} + > + <StyledIcon icon={ICONS.HELP} color={colors.TEXT_SECONDARY} size={12} /> + </Tooltip> + </TooltipContainer> ); return ( diff --git a/src/views/Wallet/views/Account/Summary/ethereum/Container.js b/src/views/Wallet/views/Account/Summary/ethereum/Container.js index d2502c3e..e2e74eea 100644 --- a/src/views/Wallet/views/Account/Summary/ethereum/Container.js +++ b/src/views/Wallet/views/Account/Summary/ethereum/Container.js @@ -2,37 +2,35 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import * as TokenActions from 'actions/TokenActions'; import type { State, Dispatch } from 'flowtype'; import Summary from './index'; -type OwnProps = { - intl: any, -}; +type OwnProps = {| + intl: IntlShape, +|}; -type StateProps = { +type StateProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, summary: $ElementType<State, 'summary'>, wallet: $ElementType<State, 'wallet'>, tokens: $ElementType<State, 'tokens'>, fiat: $ElementType<State, 'fiat'>, localStorage: $ElementType<State, 'localStorage'>, -}; +|}; -type DispatchProps = { +type DispatchProps = {| addToken: typeof TokenActions.add, loadTokens: typeof TokenActions.load, removeToken: typeof TokenActions.remove, -}; +|}; -export type Props = OwnProps & StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ selectedAccount: state.selectedAccount, summary: state.summary, wallet: state.wallet, @@ -41,16 +39,14 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( localStorage: state.localStorage, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ addToken: bindActionCreators(TokenActions.add, dispatch), loadTokens: bindActionCreators(TokenActions.load, dispatch), removeToken: bindActionCreators(TokenActions.remove, dispatch), }); export default injectIntl( - connect( + connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( mapStateToProps, mapDispatchToProps )(Summary) diff --git a/src/views/Wallet/views/Account/Summary/ethereum/index.js b/src/views/Wallet/views/Account/Summary/ethereum/index.js index 2788da89..6d1b0d4a 100644 --- a/src/views/Wallet/views/Account/Summary/ethereum/index.js +++ b/src/views/Wallet/views/Account/Summary/ethereum/index.js @@ -94,7 +94,9 @@ const AccountSummary = (props: Props) => { <StyledCoinLogo height={23} network={account.network} /> <AccountTitle> <FormattedMessage - {...l10nCommonMessages.TR_ACCOUNT_HASH} + {...(account.imported + ? l10nCommonMessages.TR_IMPORTED_ACCOUNT_HASH + : l10nCommonMessages.TR_ACCOUNT_HASH)} values={{ number: parseInt(account.index, 10) + 1 }} /> </AccountTitle> diff --git a/src/views/Wallet/views/Account/Summary/index.js b/src/views/Wallet/views/Account/Summary/index.js index 4766bcf9..4ce7357a 100644 --- a/src/views/Wallet/views/Account/Summary/index.js +++ b/src/views/Wallet/views/Account/Summary/index.js @@ -7,17 +7,16 @@ import EthereumTypeSummary from './ethereum/Container'; import RippleTypeSummary from './ripple/Container'; import BitcoinTypeSummary from './bitcoin/Container'; -type WrapperProps = { +type WrapperProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, -}; +|}; // return container for requested network type -export default connect( +export default connect<WrapperProps, any, _, _, _, _>( (state: State): WrapperProps => ({ selectedAccount: state.selectedAccount, - }), - null -)(props => { + }) +)((props: WrapperProps) => { const { network } = props.selectedAccount; if (!network) return null; diff --git a/src/views/Wallet/views/Account/Summary/ripple/Container.js b/src/views/Wallet/views/Account/Summary/ripple/Container.js index b9f954dc..edabd85a 100644 --- a/src/views/Wallet/views/Account/Summary/ripple/Container.js +++ b/src/views/Wallet/views/Account/Summary/ripple/Container.js @@ -2,34 +2,31 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import * as TokenActions from 'actions/TokenActions'; import type { State, Dispatch } from 'flowtype'; import Summary from './index'; -type OwnProps = {}; +type OwnProps = {||}; -type StateProps = { +type StateProps = {| selectedAccount: $ElementType<State, 'selectedAccount'>, summary: $ElementType<State, 'summary'>, wallet: $ElementType<State, 'wallet'>, tokens: $ElementType<State, 'tokens'>, fiat: $ElementType<State, 'fiat'>, localStorage: $ElementType<State, 'localStorage'>, -}; +|}; -type DispatchProps = { +type DispatchProps = {| addToken: typeof TokenActions.add, loadTokens: typeof TokenActions.load, removeToken: typeof TokenActions.remove, -}; +|}; -export type Props = StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ selectedAccount: state.selectedAccount, summary: state.summary, wallet: state.wallet, @@ -38,15 +35,13 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( localStorage: state.localStorage, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ addToken: bindActionCreators(TokenActions.add, dispatch), loadTokens: bindActionCreators(TokenActions.load, dispatch), removeToken: bindActionCreators(TokenActions.remove, dispatch), }); -export default connect( +export default connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( mapStateToProps, mapDispatchToProps )(Summary); diff --git a/src/views/Wallet/views/Account/Summary/ripple/components/Balance/index.js b/src/views/Wallet/views/Account/Summary/ripple/components/Balance/index.js index ace9475b..723b4d37 100644 --- a/src/views/Wallet/views/Account/Summary/ripple/components/Balance/index.js +++ b/src/views/Wallet/views/Account/Summary/ripple/components/Balance/index.js @@ -111,7 +111,6 @@ class AccountBalance extends PureComponent<Props, State> { } componentDidUpdate(prevProps: Props) { - console.log(this.props.isHidden); if (prevProps.isHidden !== this.props.isHidden) { // eslint-disable-next-line react/no-did-update-set-state this.setState({ diff --git a/src/views/Wallet/views/Account/Summary/ripple/index.js b/src/views/Wallet/views/Account/Summary/ripple/index.js index 3d42bea0..0c9dde3b 100644 --- a/src/views/Wallet/views/Account/Summary/ripple/index.js +++ b/src/views/Wallet/views/Account/Summary/ripple/index.js @@ -44,6 +44,10 @@ const StyledCoinLogo = styled(CoinLogo)` margin-right: 10px; `; +const StyledLink = styled(Link)` + font-size: ${FONT_SIZE.SMALL}; +`; + const AccountSummary = (props: Props) => { const device = props.wallet.selectedDevice; const { account, network, pending, shouldRender } = props.selectedAccount; @@ -69,17 +73,19 @@ const AccountSummary = (props: Props) => { <StyledCoinLogo height={23} network={account.network} /> <AccountTitle> <FormattedMessage - {...l10nCommonMessages.TR_ACCOUNT_HASH} + {...(account.imported + ? l10nCommonMessages.TR_IMPORTED_ACCOUNT_HASH + : l10nCommonMessages.TR_ACCOUNT_HASH)} values={{ number: parseInt(account.index, 10) + 1 }} /> </AccountTitle> </AccountName> {!account.empty && ( - <Link href={explorerLink} isGray> + <StyledLink href={explorerLink} isGray> <FormattedMessage {...l10nSummaryMessages.TR_SEE_FULL_TRANSACTION_HISTORY} /> - </Link> + </StyledLink> )} </AccountHeading> <AccountBalance diff --git a/src/views/Wallet/views/Acquire/index.js b/src/views/Wallet/views/Acquire/index.js index aff611fe..8e84263f 100644 --- a/src/views/Wallet/views/Acquire/index.js +++ b/src/views/Wallet/views/Acquire/index.js @@ -4,17 +4,26 @@ import styled from 'styled-components'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import { colors, Notification } from 'trezor-ui-components'; import * as TrezorConnectActions from 'actions/TrezorConnectActions'; import type { State, Dispatch } from 'flowtype'; import l10nMessages from './index.messages'; -type Props = { - intl: any, +type OwnProps = {| + intl: IntlShape, +|}; + +type StateProps = {| acquiring: boolean, +|}; + +type DispatchProps = {| acquireDevice: typeof TrezorConnectActions.acquire, -}; +|}; + +type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; const Wrapper = styled.div` display: flex; @@ -43,12 +52,12 @@ const Acquire = (props: Props) => ( </Wrapper> ); -export default injectIntl( - connect( - (state: State) => ({ +export default injectIntl<OwnProps>( + connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( + (state: State): StateProps => ({ acquiring: state.connect.acquiringDevice, }), - (dispatch: Dispatch) => ({ + (dispatch: Dispatch): DispatchProps => ({ acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch), }) )(Acquire) diff --git a/src/views/Wallet/views/Bootloader/index.js b/src/views/Wallet/views/Bootloader/index.js index 45107181..23b78fc4 100644 --- a/src/views/Wallet/views/Bootloader/index.js +++ b/src/views/Wallet/views/Bootloader/index.js @@ -1,7 +1,7 @@ +/* @flow */ import React from 'react'; import styled from 'styled-components'; import { H4, P } from 'trezor-ui-components'; -import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import l10nMessages from './index.messages'; @@ -42,7 +42,4 @@ const Bootloader = () => ( </Wrapper> ); -export default connect( - null, - null -)(Bootloader); +export default Bootloader; diff --git a/src/views/Wallet/views/Dashboard/Container.js b/src/views/Wallet/views/Dashboard/Container.js index 768e6cec..6665c82e 100644 --- a/src/views/Wallet/views/Dashboard/Container.js +++ b/src/views/Wallet/views/Dashboard/Container.js @@ -1,26 +1,20 @@ /* @flow */ import { connect } from 'react-redux'; -import type { MapStateToProps } from 'react-redux'; -import type { State } from 'flowtype'; +import type { State, Dispatch } from 'flowtype'; import Dashboard from './index'; -type OwnProps = {}; +type OwnProps = {||}; -type StateProps = { +type StateProps = {| localStorage: $ElementType<State, 'localStorage'>, wallet: $ElementType<State, 'wallet'>, -}; +|}; -export type Props = StateProps; +export type Props = {| ...StateProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ localStorage: state.localStorage, wallet: state.wallet, }); -export default connect( - mapStateToProps, - null -)(Dashboard); +export default connect<Props, OwnProps, StateProps, _, State, Dispatch>(mapStateToProps)(Dashboard); diff --git a/src/views/Wallet/views/DeviceSettings/index.js b/src/views/Wallet/views/DeviceSettings/index.js index 894d37ca..fa5875e0 100644 --- a/src/views/Wallet/views/DeviceSettings/index.js +++ b/src/views/Wallet/views/DeviceSettings/index.js @@ -4,7 +4,6 @@ import styled from 'styled-components'; import { H4, Icon, Button, P, Link, colors, icons as ICONS } from 'trezor-ui-components'; import { getOldWalletUrl } from 'utils/url'; import Content from 'views/Wallet/components/Content'; -import { connect } from 'react-redux'; import type { TrezorDevice } from 'flowtype'; @@ -51,7 +50,4 @@ const DeviceSettings = (props: Props) => ( </Content> ); -export default connect( - null, - null -)(DeviceSettings); +export default DeviceSettings; diff --git a/src/views/Wallet/views/FirmwareUpdate/index.js b/src/views/Wallet/views/FirmwareUpdate/index.js index 661ccfad..aa728ac4 100644 --- a/src/views/Wallet/views/FirmwareUpdate/index.js +++ b/src/views/Wallet/views/FirmwareUpdate/index.js @@ -17,9 +17,17 @@ import l10nCommonMessages from 'views/common.messages'; import type { TrezorDevice, State, Dispatch } from 'flowtype'; import l10nMessages from './index.messages'; -type Props = { +type OwnProps = {||}; + +type StateProps = {| device: ?TrezorDevice, -}; +|}; + +type DispatchProps = {| + cancel: typeof RouterActions.selectFirstAvailableDevice, +|}; + +type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; const Wrapper = styled.section` display: flex; @@ -180,11 +188,11 @@ const FirmwareUpdate = (props: Props) => ( </Wrapper> ); -export default connect( - (state: State) => ({ +export default connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( + (state: State): StateProps => ({ device: state.wallet.selectedDevice, }), - (dispatch: Dispatch) => ({ + (dispatch: Dispatch): DispatchProps => ({ cancel: bindActionCreators(RouterActions.selectFirstAvailableDevice, dispatch), }) )(FirmwareUpdate); diff --git a/src/views/Wallet/views/Import/Container.js b/src/views/Wallet/views/Import/Container.js new file mode 100644 index 00000000..e0ea276f --- /dev/null +++ b/src/views/Wallet/views/Import/Container.js @@ -0,0 +1,37 @@ +/* @flow */ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import * as ImportAccountActions from 'actions/ImportAccountActions'; +import type { State, Dispatch, TrezorDevice, Config } from 'flowtype'; +import ImportView from './index'; + +type OwnProps = {| + children?: React.Node, +|}; +export type StateProps = {| + device: ?TrezorDevice, + config: Config, + importAccount: $ElementType<State, 'importAccount'>, +|}; + +type DispatchProps = {| + importAddress: typeof ImportAccountActions.importAddress, +|}; + +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; + +const mapStateToProps = (state: State): StateProps => ({ + config: state.localStorage.config, + device: state.wallet.selectedDevice, + importAccount: state.importAccount, +}); + +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + importAddress: bindActionCreators(ImportAccountActions.importAddress, dispatch), +}); + +export default connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( + mapStateToProps, + mapDispatchToProps +)(ImportView); diff --git a/src/views/Wallet/views/Import/index.js b/src/views/Wallet/views/Import/index.js new file mode 100644 index 00000000..f58a8518 --- /dev/null +++ b/src/views/Wallet/views/Import/index.js @@ -0,0 +1,105 @@ +/* @flow */ + +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { FormattedMessage } from 'react-intl'; +import { Select, Button, Input, Link, colors } from 'trezor-ui-components'; +import l10nCommonMessages from 'views/common.messages'; +import type { Props } from './Container'; + +const Wrapper = styled.div` + text-align: left; + flex-direction: column; + display: flex; + padding: 24px; + min-width: 300px; +`; + +const StyledSelect = styled(Select)` + min-width: 100px; +`; + +const InputRow = styled.div` + margin-bottom: 16px; +`; + +const Label = styled.div` + color: ${colors.TEXT_SECONDARY}; + padding-bottom: 10px; +`; + +const ButtonActions = styled.div` + display: flex; + flex-direction: row; + + justify-content: flex-end; +`; + +const ButtonWrapper = styled.div` + & + & { + margin-left: 10px; + } +`; + +const Import = (props: Props) => { + const [selectedNetwork, setSelectedNetwork] = useState(null); + const [address, setAddress] = useState(''); + + const { networks } = props.config; + return ( + // <LandingWrapper> + <Wrapper> + <InputRow> + <Label>Select network</Label> + <StyledSelect + value={selectedNetwork} + options={networks + .sort((a, b) => a.shortcut.localeCompare(b.shortcut)) + .map(net => ({ + label: net.shortcut, + value: net, + }))} + onChange={option => setSelectedNetwork(option)} + /> + </InputRow> + + <InputRow> + <Input + topLabel="Address" + name="cryptoAddress" + value={address} + onChange={e => setAddress(e.target.value)} + type="text" + /> + </InputRow> + <ButtonActions> + <ButtonWrapper> + <Link to="/"> + <Button isWhite> + <FormattedMessage {...l10nCommonMessages.TR_CLOSE} /> + </Button> + </Link> + </ButtonWrapper> + + <ButtonWrapper> + <Button + isDisabled={ + !selectedNetwork || address === '' || props.importAccount.loading + } + onClick={() => + props.importAddress( + address, + (selectedNetwork || {}).value, + props.device + ) + } + > + Import + </Button> + </ButtonWrapper> + </ButtonActions> + </Wrapper> + // </LandingWrapper> + ); +}; +export default Import; diff --git a/src/views/Wallet/views/Initialize/index.js b/src/views/Wallet/views/Initialize/index.js index 3e5bb338..92252466 100644 --- a/src/views/Wallet/views/Initialize/index.js +++ b/src/views/Wallet/views/Initialize/index.js @@ -3,7 +3,6 @@ import styled from 'styled-components'; import { H4, Button, P } from 'trezor-ui-components'; import { getOldWalletUrl } from 'utils/url'; import React from 'react'; -import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import l10nCommonMessages from 'views/common.messages'; @@ -54,7 +53,4 @@ const Initialize = (props: Props) => ( </Wrapper> ); -export default connect( - null, - null -)(Initialize); +export default Initialize; diff --git a/src/views/Wallet/views/Seedless/index.js b/src/views/Wallet/views/Seedless/index.js index 6e70bfb0..817844b4 100644 --- a/src/views/Wallet/views/Seedless/index.js +++ b/src/views/Wallet/views/Seedless/index.js @@ -1,7 +1,6 @@ import styled from 'styled-components'; import { H4, P } from 'trezor-ui-components'; import React from 'react'; -import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import l10nMessages from './index.messages'; @@ -37,7 +36,4 @@ const Seedless = () => ( </Wrapper> ); -export default connect( - null, - null -)(Seedless); +export default Seedless; diff --git a/src/views/Wallet/views/UnreadableDevice/index.js b/src/views/Wallet/views/UnreadableDevice/index.js index 6f9bfdbf..847f59a5 100644 --- a/src/views/Wallet/views/UnreadableDevice/index.js +++ b/src/views/Wallet/views/UnreadableDevice/index.js @@ -4,11 +4,12 @@ import React from 'react'; import styled from 'styled-components'; import { Notification } from 'trezor-ui-components'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import l10nMessages from './index.messages'; const Wrapper = styled.div``; -const UnreadableDevice = ({ intl }: { intl: any }) => ( +const UnreadableDevice = ({ intl }: { intl: IntlShape }) => ( <Wrapper> <Notification title={intl.formatMessage(l10nMessages.TR_UNREADABLE_DEVICE)} diff --git a/src/views/Wallet/views/WalletSettings/Container.js b/src/views/Wallet/views/WalletSettings/Container.js index 6b76b4b2..19b4390b 100644 --- a/src/views/Wallet/views/WalletSettings/Container.js +++ b/src/views/Wallet/views/WalletSettings/Container.js @@ -2,44 +2,42 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { injectIntl } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import * as WalletActions from 'actions/WalletActions'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { State, Dispatch } from 'flowtype'; import WalletSettings from './index'; -type OwnProps = {}; +type OwnProps = {| + intl: IntlShape, +|}; -type StateProps = { +type StateProps = {| wallet: $ElementType<State, 'wallet'>, fiat: $ElementType<State, 'fiat'>, localStorage: $ElementType<State, 'localStorage'>, -}; +|}; -type DispatchProps = { +type DispatchProps = {| setLocalCurrency: typeof WalletActions.setLocalCurrency, setHideBalance: typeof WalletActions.setHideBalance, -}; +|}; -export type Props = StateProps & DispatchProps; +export type Props = {| ...OwnProps, ...StateProps, ...DispatchProps |}; -const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = ( - state: State -): StateProps => ({ +const mapStateToProps = (state: State): StateProps => ({ wallet: state.wallet, fiat: state.fiat, localStorage: state.localStorage, }); -const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = ( - dispatch: Dispatch -): DispatchProps => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ setLocalCurrency: bindActionCreators(WalletActions.setLocalCurrency, dispatch), setHideBalance: bindActionCreators(WalletActions.setHideBalance, dispatch), }); -export default injectIntl( - connect( +export default injectIntl<OwnProps>( + connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>( mapStateToProps, mapDispatchToProps )(WalletSettings) diff --git a/src/views/common.messages.js b/src/views/common.messages.js index 0d0c9f5b..43047d19 100644 --- a/src/views/common.messages.js +++ b/src/views/common.messages.js @@ -16,6 +16,11 @@ const definedMessages: Messages = defineMessages({ defaultMessage: 'Account #{number}', description: 'Used in auto-generated account label', }, + TR_IMPORTED_ACCOUNT_HASH: { + id: 'TR_IMPORTED_ACCOUNT_HASH', + defaultMessage: 'Imported account #{number}', + description: 'Used in auto-generated label for imported accounts', + }, TR_CLEAR: { id: 'TR_CLEAR', defaultMessage: 'Clear', diff --git a/src/views/index.js b/src/views/index.js index be84722a..ecb5b90b 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -14,7 +14,6 @@ import { getPattern } from 'support/routes'; // landing views import RootView from 'views/Landing/views/Root/Container'; import InstallBridge from 'views/Landing/views/InstallBridge/Container'; -import ImportView from 'views/Landing/views/Import/Container'; // wallet views import WalletContainer from 'views/Wallet'; @@ -23,6 +22,7 @@ import AccountSend from 'views/Wallet/views/Account/Send'; import AccountReceive from 'views/Wallet/views/Account/Receive'; import AccountSignVerify from 'views/Wallet/views/Account/SignVerify/Container'; +import WalletImport from 'views/Wallet/views/Import/Container'; import WalletDashboard from 'views/Wallet/views/Dashboard/Container'; import WalletDeviceSettings from 'views/Wallet/views/DeviceSettings'; import WalletSettings from 'views/Wallet/views/WalletSettings/Container'; @@ -44,11 +44,16 @@ const App = () => ( <Route exact path={getPattern('landing-home')} component={RootView} /> <Route exact path={getPattern('landing-version')} component={Version} /> <Route exact path={getPattern('landing-bridge')} component={InstallBridge} /> - <Route exact path={getPattern('landing-import')} component={ImportView} /> <Route> <ErrorBoundary> <ImagesPreloader /> <WalletContainer> + <Route + exact + path={getPattern('wallet-import')} + component={WalletImport} + /> + <Route exact path={getPattern('wallet-settings')} diff --git a/yarn.lock b/yarn.lock index 2afe40f3..7242290d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4984,9 +4984,10 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== -flow-bin@0.75.0: - version "0.75.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.75.0.tgz#b96d1ee99d3b446a3226be66b4013224ce9df260" +flow-bin@0.90: + version "0.90.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.90.0.tgz#733b6d29a8c8a22b9a5d273611d9a8402faf3445" + integrity sha512-/syDchjhLLL7nELK1ggyWJifGXuMCTz74kvkjR1t9DcmasMrilLl9qAAotsACcNb98etEEJpsCrvP7WL64kadw== flush-write-stream@^1.0.0: version "1.0.2" @@ -11139,10 +11140,10 @@ trezor-bridge-communicator@1.0.2: request "^2.88.0" whatwg-fetch "^3.0.0" -trezor-connect@7.0.2-electron.4: - version "7.0.2-electron.4" - resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-7.0.2-electron.4.tgz#2fad96f11f0136dada5ae6a4dbc4a24b8408a131" - integrity sha512-c+DyAnaSI5GeSw7ETSK3CzekDsHR2vI5qltNavpmg8B16aBLvyeL89VCkXZQrxdZGESvO5/vcOMbr1Rdp/sGPQ== +trezor-connect@7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-7.0.2.tgz#8b1b0d1b3b6dc6564bc3d22fe8f54ba826f2a242" + integrity sha512-KAFOqxEHHaFvrG8NGLFlM/QxHcwIa3gwfXcgTjCYM0g0zRpwIQBwe35AKsjAQO5yiTJQGa0Cu5MZufGJRGYjjw== dependencies: "@babel/runtime" "^7.3.1" events "^3.0.0"