diff --git a/src/actions/SelectedAccountActions.js b/src/actions/SelectedAccountActions.js index b2dd5fe9..b6a93220 100644 --- a/src/actions/SelectedAccountActions.js +++ b/src/actions/SelectedAccountActions.js @@ -8,6 +8,7 @@ import * as TOKEN from 'actions/constants/token'; import * as PENDING from 'actions/constants/pendingTx'; import * as reducerUtils from 'reducers/utils'; +import { initialState } from 'reducers/SelectedAccountReducer'; import type { PayloadAction, @@ -17,7 +18,12 @@ import type { State, } from 'flowtype'; -type SelectedAccountState = $ElementType; +import type { + State as SelectedAccountState, + Loader, + Notification, + ExceptionPage, +} from 'reducers/SelectedAccountReducer'; export type SelectedAccountAction = { type: typeof ACCOUNT.DISPOSE, @@ -26,18 +32,38 @@ export type SelectedAccountAction = { payload: SelectedAccountState, }; -type AccountStatus = { - type: ?string; // notification type - title: ?string; // notification title - message?: ?string; // notification message - shouldRender: boolean; // should render account page -} - export const dispose = (): Action => ({ type: ACCOUNT.DISPOSE, }); -const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => { +// display exception page instead of component body +const getExceptionPage = (state: State, selectedAccount: SelectedAccountState): ?ExceptionPage => { + const device = state.wallet.selectedDevice; + const { discovery, network } = selectedAccount; + if (!device || !device.features || !network || !discovery) return null; + + if (discovery.fwOutdated) { + return { + type: 'info', + title: `Device ${device.instanceLabel} firmware is outdated`, + message: 'TODO: update firmware explanation', + shortcut: network.shortcut, + }; + } + if (discovery.fwNotSupported) { + return { + type: 'fwNotSupported', + title: `${network.name} is not supported with Trezor ${device.features.model}`, + message: 'Find more information on Trezor Wiki.', + shortcut: network.shortcut, + }; + } + + return null; +}; + +// display loader instead of component body +const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?Loader => { const device = state.wallet.selectedDevice; const { account, @@ -53,11 +79,11 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): }; } - // corner case: accountState didn't finish loading state after LOCATION_CHANGE action + // corner case: SelectedAccountState didn't change after LOCATION_CHANGE action if (!network) { return { type: 'progress', - title: 'Loading account state...', + title: 'Loading account', shouldRender: false, }; } @@ -66,24 +92,6 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): if (account) return null; // account not found (yet). checking why... - if (discovery && discovery.fwOutdated) { - return { - type: 'info', - title: `Device ${device.instanceLabel} firmware is outdated`, - message: 'TODO: update firmware explanation', - shouldRender: false, - }; - } - - if (discovery && discovery.fwNotSupported) { - return { - type: 'fwNotSupported', - title: `${network.name} is not supported with Trezor ${(device.features || {}).model}`, - message: 'Find more information on Trezor Wiki.', - shouldRender: false, - }; - } - if (!discovery || (discovery.waitingForDevice || discovery.interrupted)) { if (device.connected) { // case 1: device is connected but discovery not started yet (probably waiting for auth) @@ -130,7 +138,8 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): }; }; -const getAccountNotification = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => { +// display notification above the component, with or without component body +const getAccountNotification = (state: State, selectedAccount: SelectedAccountState): ?(Notification & { shouldRender: boolean }) => { const device = state.wallet.selectedDevice; const { account, network, discovery } = selectedAccount; if (!device || !network) return null; @@ -198,16 +207,6 @@ export const observe = (prevState: State, action: Action): PayloadAction { const { network, notification } = props.selectedAccount; - if (network && notification.type && notification.title) { - if (notification.type === 'backend') { - // special case: backend is down - // TODO: this is a different component with "auto resolve" button - return ( - { - await props.blockchainReconnect(network.shortcut); - }, - }] - } - /> - ); - } + if (!network || !notification) return null; + + if (notification.type === 'backend') { + // special case: backend is down + // TODO: this is a different component with "auto resolve" button return ( { + await props.blockchainReconnect(network.shortcut); + }, + }] + } /> ); } - - return null; + return ( + + ); }; \ No newline at end of file diff --git a/src/reducers/SelectedAccountReducer.js b/src/reducers/SelectedAccountReducer.js index a7ce0b65..fba64661 100644 --- a/src/reducers/SelectedAccountReducer.js +++ b/src/reducers/SelectedAccountReducer.js @@ -10,23 +10,35 @@ import type { Discovery, } from 'flowtype'; +export type Loader = { + type: string, + title: string, + message?: string, +} + +export type Notification = { + type: string, + title: string, + message?: string, +} + +export type ExceptionPage = { + type: ?string, + title: ?string, + message: ?string, + shortcut: string, +} + export type State = { - location: string; - account: ?Account; - network: ?Network; + location: string, + account: ?Account, + network: ?Network, tokens: Array, pending: Array, discovery: ?Discovery, - notification: { - type: ?string, - title: ?string, - message: ?string, - }, - loader: { - type: ?string, - title: ?string, - message: ?string, - }, + loader: ?Loader, + notification: ?Notification, + exceptionPage: ?ExceptionPage, shouldRender: boolean, }; @@ -37,16 +49,9 @@ export const initialState: State = { tokens: [], pending: [], discovery: null, - notification: { - type: null, - title: null, - message: null, - }, - loader: { - type: null, - title: null, - message: null, - }, + loader: null, + notification: null, + exceptionPage: null, shouldRender: false, }; diff --git a/src/views/Wallet/components/Content/components/FirmwareOutdated/index.js b/src/views/Wallet/components/Content/components/FirmwareOutdated/index.js new file mode 100644 index 00000000..01f8994d --- /dev/null +++ b/src/views/Wallet/components/Content/components/FirmwareOutdated/index.js @@ -0,0 +1,76 @@ +/* @flow */ +import React from 'react'; +import styled from 'styled-components'; +import colors from 'config/colors'; + +import { FONT_SIZE } from 'config/variables'; +import { H2 } from 'components/Heading'; +import Button from 'components/Button'; +import Link from 'components/Link'; +import CoinLogo from 'components/images/CoinLogo'; + +const getInfoUrl = (networkShortcut: ?string) => { + const urls = { + default: 'https://wiki.trezor.io', + xrp: 'https://wiki.trezor.io/Ripple_(XRP)', + }; + + return networkShortcut ? urls[networkShortcut] : urls.default; +}; + +type Props = { + networkShortcut: ?string, + title: ?string, + message: ?string, +} + +const Wrapper = styled.div` + display: flex; + background: ${colors.WHITE}; + flex-direction: column; + flex: 1; +`; + +const CoinLogoWrapper = styled.div` + margin: 10px 0; +`; + +const StyledCoinLogo = styled(CoinLogo)` + width: 32px; +`; + +const StyledLink = styled(Link)` + padding-top: 24px; +`; + +const Row = styled.div` + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const Message = styled.div` + font-size: ${FONT_SIZE.SMALL}; + color: ${colors.TEXT_SECONDARY}; + text-align: center; +`; + +// eslint-disable-next-line arrow-body-style +const FirmwareOutdated = (props: Props) => { + return ( + + + {props.networkShortcut && } +

{props.title}

+ {props.message} + + + +
+
+ ); +}; + +export default FirmwareOutdated; diff --git a/src/views/Wallet/components/Content/index.js b/src/views/Wallet/components/Content/index.js index f5a38ebc..d1283fd7 100644 --- a/src/views/Wallet/components/Content/index.js +++ b/src/views/Wallet/components/Content/index.js @@ -1,11 +1,24 @@ -import React from 'react'; +/* @flow */ + +import * as React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import Loader from 'components/Loader'; import { FONT_SIZE } from 'config/variables'; import colors from 'config/colors'; + +import type { State } from 'flowtype'; + +import FirmwareOutdated from './components/FirmwareOutdated'; import FirmwareUnsupported from './components/FirmwareUnsupported'; +type Props = { + children?: React.Node, + isLoading?: boolean, + loader?: $ElementType<$ElementType, 'loader'>, + exceptionPage?: $ElementType<$ElementType, 'exceptionPage'>, +} + const Wrapper = styled.div` display: flex; flex: 1; @@ -37,38 +50,43 @@ const Row = styled.div` flex-direction: row; `; +const getExceptionPage = (exceptionPage) => { + const { title, message, shortcut } = exceptionPage; + switch (exceptionPage.type) { + case 'fwOutdated': + return ; + case 'fwNotSupported': + return ; + default: return null; + } +}; + const Content = ({ children, - title, - message, - type, - networkShortcut, isLoading = false, -}) => ( + loader, + exceptionPage, +}: Props) => ( {(!isLoading) && children} - {isLoading && (type === 'progress' || type === 'info') && ( + {isLoading && exceptionPage && getExceptionPage(exceptionPage)} + {isLoading && loader && ( - {type === 'progress' && } - {title || 'Initializing accounts'} + {loader.type === 'progress' && } + {loader.title || 'Initializing accounts'} - {message && {message}} + {loader.message && {loader.message}} )} - {isLoading && (type === 'fwNotSupported') && ( - - )} ); Content.propTypes = { children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]), isLoading: PropTypes.bool, - title: PropTypes.string, - message: PropTypes.string, - type: PropTypes.string, - networkShortcut: PropTypes.string, + loader: PropTypes.object, + exceptionPage: PropTypes.object, }; export default Content; diff --git a/src/views/Wallet/views/Account/Receive/ethereum/index.js b/src/views/Wallet/views/Account/Receive/ethereum/index.js index 89340dcc..5646b7ed 100644 --- a/src/views/Wallet/views/Account/Receive/ethereum/index.js +++ b/src/views/Wallet/views/Account/Receive/ethereum/index.js @@ -96,11 +96,12 @@ const AccountReceive = (props: Props) => { account, discovery, shouldRender, - loader, - network, } = props.selectedAccount; - const { type, title, message } = loader; - if (!device || !account || !discovery || !shouldRender) return ; + + if (!device || !account || !discovery || !shouldRender) { + const { loader, exceptionPage } = props.selectedAccount; + return ; + } const { addressVerified, diff --git a/src/views/Wallet/views/Account/Receive/ripple/index.js b/src/views/Wallet/views/Account/Receive/ripple/index.js index 94383077..c308eafd 100644 --- a/src/views/Wallet/views/Account/Receive/ripple/index.js +++ b/src/views/Wallet/views/Account/Receive/ripple/index.js @@ -96,11 +96,12 @@ const AccountReceive = (props: Props) => { account, discovery, shouldRender, - loader, - network, } = props.selectedAccount; - const { type, title, message } = loader; - if (!device || !account || !discovery || !shouldRender) return ; + + if (!device || !account || !discovery || !shouldRender) { + const { loader, exceptionPage } = props.selectedAccount; + return ; + } const { addressVerified, diff --git a/src/views/Wallet/views/Account/Send/ethereum/index.js b/src/views/Wallet/views/Account/Send/ethereum/index.js index f92674eb..63a86a14 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/index.js +++ b/src/views/Wallet/views/Account/Send/ethereum/index.js @@ -184,7 +184,6 @@ const AccountSend = (props: Props) => { discovery, tokens, shouldRender, - loader, } = props.selectedAccount; const { address, @@ -213,8 +212,11 @@ const AccountSend = (props: Props) => { updateFeeLevels, onSend, } = props.sendFormActions; - const { type, title, message } = loader; - if (!device || !account || !discovery || !network || !shouldRender) return ; + + if (!device || !account || !discovery || !network || !shouldRender) { + const { loader, exceptionPage } = props.selectedAccount; + return ; + } const isCurrentCurrencyToken = networkSymbol !== currency; diff --git a/src/views/Wallet/views/Account/Send/ripple/index.js b/src/views/Wallet/views/Account/Send/ripple/index.js index 7d7caf9e..136031bc 100644 --- a/src/views/Wallet/views/Account/Send/ripple/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/index.js @@ -122,7 +122,6 @@ const AccountSend = (props: Props) => { network, discovery, shouldRender, - loader, } = props.selectedAccount; const { address, @@ -142,8 +141,11 @@ const AccountSend = (props: Props) => { onSetMax, onSend, } = props.sendFormActions; - const { type, title, message } = loader; - if (!device || !account || !discovery || !network || !shouldRender) return ; + + if (!device || !account || !discovery || !network || !shouldRender) { + const { loader, exceptionPage } = props.selectedAccount; + return ; + } let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending; let sendButtonText: string = ` ${total} ${network.symbol}`; diff --git a/src/views/Wallet/views/Account/SignVerify/index.js b/src/views/Wallet/views/Account/SignVerify/index.js index fc8d39a0..5424afa2 100644 --- a/src/views/Wallet/views/Account/SignVerify/index.js +++ b/src/views/Wallet/views/Account/SignVerify/index.js @@ -57,10 +57,14 @@ class SignVerify extends Component { render() { const device = this.props.wallet.selectedDevice; const { - account, discovery, shouldRender, notification, network, + account, discovery, shouldRender, } = this.props.selectedAccount; - const { type, title, message } = notification; - if (!device || !account || !discovery || !shouldRender) return ; + + if (!device || !account || !discovery || !shouldRender) { + const { loader, exceptionPage } = this.props.selectedAccount; + return ; + } + const { signVerifyActions, signVerify: { diff --git a/src/views/Wallet/views/Account/Summary/ethereum/index.js b/src/views/Wallet/views/Account/Summary/ethereum/index.js index a2ce8d95..ba895fe7 100644 --- a/src/views/Wallet/views/Account/Summary/ethereum/index.js +++ b/src/views/Wallet/views/Account/Summary/ethereum/index.js @@ -76,13 +76,13 @@ const AccountSummary = (props: Props) => { network, tokens, pending, - loader, shouldRender, } = props.selectedAccount; - const { type, title, message } = loader; - - if (!device || !account || !network || !shouldRender) return ; + if (!device || !account || !network || !shouldRender) { + const { loader, exceptionPage } = props.selectedAccount; + return ; + } const explorerLink: string = `${network.explorer.address}${account.address}`; const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol); diff --git a/src/views/Wallet/views/Account/Summary/ripple/index.js b/src/views/Wallet/views/Account/Summary/ripple/index.js index ea0501e1..f66bde06 100644 --- a/src/views/Wallet/views/Account/Summary/ripple/index.js +++ b/src/views/Wallet/views/Account/Summary/ripple/index.js @@ -66,13 +66,13 @@ const AccountSummary = (props: Props) => { account, network, pending, - loader, shouldRender, } = props.selectedAccount; - const { type, title, message } = loader; - - if (!device || !account || !network || !shouldRender) return ; + if (!device || !account || !network || !shouldRender) { + const { loader, exceptionPage } = props.selectedAccount; + return ; + } const explorerLink: string = `${network.explorer.address}${account.address}`; const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);