From 41b78390edc32a6b4dbdbc71375c8d6079c2f94f Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 10 Oct 2018 21:27:07 +0200 Subject: [PATCH 01/11] add MODAL_CONTEXT - update MODAL action constants - ModalReducer refactoring (field "opened" replaced by "context") - implementing "context" in all components --- src/actions/ModalActions.js | 29 +++++---- src/actions/RouterActions.js | 3 +- src/actions/TrezorConnectActions.js | 3 +- src/actions/constants/modal.js | 18 ++---- src/components/modals/confirm/SignTx/index.js | 3 +- .../modals/confirm/UnverifiedAddress/index.js | 10 +-- .../modals/device/Duplicate/index.js | 9 +-- src/components/modals/device/Forget/index.js | 8 +-- .../modals/device/Remember/index.js | 13 ++-- .../modals/device/WalletType/index.js | 5 +- src/components/modals/index.js | 28 ++++---- .../modals/passphrase/Passphrase/index.js | 12 ++-- .../modals/passphrase/Type/index.js | 3 +- src/components/modals/pin/Invalid/index.js | 3 +- src/components/modals/pin/Pin/index.js | 5 +- src/reducers/ModalReducer.js | 64 +++++++------------ .../Wallet/views/Account/Receive/index.js | 9 ++- 17 files changed, 99 insertions(+), 126 deletions(-) diff --git a/src/actions/ModalActions.js b/src/actions/ModalActions.js index 0ca8f9fa..a9def9fe 100644 --- a/src/actions/ModalActions.js +++ b/src/actions/ModalActions.js @@ -13,8 +13,9 @@ import type { State } from 'reducers/ModalReducer'; export type ModalAction = { type: typeof MODAL.CLOSE } | { - type: typeof MODAL.REMEMBER, - device: TrezorDevice + type: typeof MODAL.OPEN_EXTERNAL_WALLET, + id: string, + url: string, }; export const onPinSubmit = (value: string): Action => { @@ -38,13 +39,6 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (di }); }; -// export const askForRemember = (device: TrezorDevice): Action => { -// return { -// type: MODAL.REMEMBER, -// device -// } -// } - export const onRememberDevice = (device: TrezorDevice): Action => ({ type: CONNECT.REMEMBER, device, @@ -77,9 +71,9 @@ export const onRememberRequest = (prevState: State): ThunkAction => (dispatch: D const state: State = getState().modal; // handle case where forget modal is already opened // TODO: 2 modals at once (two devices disconnected in the same time) - if (prevState.opened && prevState.windowType === CONNECT.REMEMBER_REQUEST) { + if (prevState.context === MODAL.CONTEXT_DEVICE && prevState.windowType === CONNECT.REMEMBER_REQUEST) { // forget current (new) - if (state.opened) { + if (state.context === MODAL.CONTEXT_DEVICE) { dispatch({ type: CONNECT.FORGET, device: state.device, @@ -98,7 +92,7 @@ export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispa // interrupt process of remembering device (force forget) // TODO: the same for disconnect more than 1 device at once const { modal } = getState(); - if (modal.opened && modal.windowType === CONNECT.REMEMBER_REQUEST) { + if (modal.context === MODAL.CONTEXT_DEVICE && modal.windowType === CONNECT.REMEMBER_REQUEST) { if (device.features && modal.device && modal.device.features && modal.device.features.device_id === device.features.device_id) { dispatch({ type: MODAL.CLOSE, @@ -124,6 +118,16 @@ export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean, state }); }; +export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dispatch: Dispatch): void => { + console.warn('OPEN', id, url); + dispatch({ + type: MODAL.OPEN_EXTERNAL_WALLET, + id, + url, + }); +}; + + export default { onPinSubmit, onPassphraseSubmit, @@ -134,4 +138,5 @@ export default { onCancel, onDuplicateDevice, onWalletTypeRequest, + gotoExternalWallet, }; \ No newline at end of file diff --git a/src/actions/RouterActions.js b/src/actions/RouterActions.js index e47a72d8..b764dcb9 100644 --- a/src/actions/RouterActions.js +++ b/src/actions/RouterActions.js @@ -1,6 +1,7 @@ /* @flow */ import { push, LOCATION_CHANGE } from 'react-router-redux'; +import { CONTEXT_NONE } from 'actions/constants/modal'; import { routes } from 'support/routes'; import * as deviceUtils from 'utils/device'; @@ -138,7 +139,7 @@ export const getValidUrl = (action: RouterAction): PayloadAction => (dis // Modal is opened // redirect to previous url - if (getState().modal.opened) { + if (getState().modal.context !== CONTEXT_NONE) { // Corner case: modal is opened and currentParams are still valid // example 1 (valid blocking): url changed while passphrase modal opened but device is still connected (we want user to finish this action) // example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index d25770c1..e67d6927 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -2,6 +2,7 @@ import TrezorConnect, { DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT, } from 'trezor-connect'; +import { CONTEXT_NONE } from 'actions/constants/modal'; import * as CONNECT from 'actions/constants/TrezorConnect'; import * as NOTIFICATION from 'actions/constants/notification'; import { getDuplicateInstanceNumber } from 'reducers/utils'; @@ -237,7 +238,7 @@ export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch const instances = getState().devices.filter(d => d.features && device.features && d.state && !d.remember && d.features.device_id === device.features.device_id); if (instances.length > 0) { const isSelected = deviceUtils.isSelectedDevice(getState().wallet.selectedDevice, instances[0]); - if (!isSelected && getState().modal.opened) { + if (!isSelected && getState().modal.context !== CONTEXT_NONE) { dispatch({ type: CONNECT.FORGET_SILENT, device: instances[0], diff --git a/src/actions/constants/modal.js b/src/actions/constants/modal.js index 917b2029..6e53d156 100644 --- a/src/actions/constants/modal.js +++ b/src/actions/constants/modal.js @@ -1,17 +1,7 @@ /* @flow */ - -export const ON_PASSPHRASE_CHANGE: 'action__on_passphrase_change' = 'action__on_passphrase_change'; -export const ON_PASSPHRASE_SHOW: 'action__on_passphrase_show' = 'action__on_passphrase_show'; -export const ON_PASSPHRASE_HIDE: 'action__on_passphrase_hide' = 'action__on_passphrase_hide'; -export const ON_PASSPHRASE_SAVE: 'action__on_passphrase_save' = 'action__on_passphrase_save'; -export const ON_PASSPHRASE_FORGET: 'action__on_passphrase_forget' = 'action__on_passphrase_forget'; -export const ON_PASSPHRASE_FOCUS: 'action__on_passphrase_focus' = 'action__on_passphrase_focus'; -export const ON_PASSPHRASE_BLUR: 'action__on_passphrase_blur' = 'action__on_passphrase_blur'; -export const ON_PASSPHRASE_SUBMIT: 'action__on_passphrase_submit' = 'action__on_passphrase_submit'; - -export const FORGET: 'modal__forget' = 'modal__forget'; -export const REMEMBER: 'modal__remember' = 'modal__remember'; -export const ON_FORGET: 'modal__on_forget' = 'modal__on_forget'; -export const ON_REMEMBER: 'modal__on_remember' = 'modal__on_remember'; export const CLOSE: 'modal__close' = 'modal__close'; +export const OPEN_EXTERNAL_WALLET: 'modal__external_wallet' = 'modal__external_wallet'; +export const CONTEXT_NONE: 'modal_ctx_none' = 'modal_ctx_none'; +export const CONTEXT_DEVICE: 'modal_ctx_device' = 'modal_ctx_device'; +export const CONTEXT_EXTERNAL_WALLET: 'modal_ctx_external-wallet' = 'modal_ctx_external-wallet'; diff --git a/src/components/modals/confirm/SignTx/index.js b/src/components/modals/confirm/SignTx/index.js index 3b71afa3..2e51d2d2 100644 --- a/src/components/modals/confirm/SignTx/index.js +++ b/src/components/modals/confirm/SignTx/index.js @@ -8,6 +8,7 @@ import Icon from 'components/Icon'; import icons from 'config/icons'; import { H3 } from 'components/Heading'; import { LINE_HEIGHT } from 'config/variables'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from '../../index'; @@ -39,7 +40,7 @@ const Label = styled.div` `; const ConfirmSignTx = (props: Props) => { - if (!props.modal.opened) return null; + if (props.modal.context !== CONTEXT_DEVICE) return null; const { device } = props.modal; const { diff --git a/src/components/modals/confirm/UnverifiedAddress/index.js b/src/components/modals/confirm/UnverifiedAddress/index.js index 1fae5a98..1c933742 100644 --- a/src/components/modals/confirm/UnverifiedAddress/index.js +++ b/src/components/modals/confirm/UnverifiedAddress/index.js @@ -8,6 +8,7 @@ import colors from 'config/colors'; import icons from 'config/icons'; import Button from 'components/Button'; import Link from 'components/Link'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from '../../index'; @@ -56,7 +57,6 @@ class ConfirmUnverifiedAddress extends PureComponent { } verifyAddress() { - if (!this.props.modal.opened) return; const { account } = this.props.selectedAccount; if (!account) return; this.props.modalActions.onCancel(); @@ -64,17 +64,13 @@ class ConfirmUnverifiedAddress extends PureComponent { } showUnverifiedAddress() { - if (!this.props.modal.opened) return; - this.props.modalActions.onCancel(); this.props.receiveActions.showUnverifiedAddress(); } render() { - if (!this.props.modal.opened) return null; - const { - device, - } = this.props.modal; + if (this.props.modal.context !== CONTEXT_DEVICE) return null; + const { device } = this.props.modal; const { onCancel, diff --git a/src/components/modals/device/Duplicate/index.js b/src/components/modals/device/Duplicate/index.js index 6633a9be..ae9598b7 100644 --- a/src/components/modals/device/Duplicate/index.js +++ b/src/components/modals/device/Duplicate/index.js @@ -11,6 +11,7 @@ import Icon from 'components/Icon'; import icons from 'config/icons'; import colors from 'config/colors'; import Link from 'components/Link'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from 'components/modals/index'; @@ -66,8 +67,8 @@ export default class DuplicateDevice extends PureComponent { constructor(props: Props) { super(props); - const device = props.modal.opened ? props.modal.device : null; - if (!device) return; + if (props.modal.context !== CONTEXT_DEVICE) return; + const { device } = props.modal; const instance = getDuplicateInstanceNumber(props.devices, device); @@ -114,13 +115,13 @@ export default class DuplicateDevice extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; submit() { - if (!this.props.modal.opened) return; + if (this.props.modal.context !== CONTEXT_DEVICE) return; const extended: Object = { instanceName: this.state.instanceName, instance: this.state.instance }; this.props.modalActions.onDuplicateDevice({ ...this.props.modal.device, ...extended }); } render() { - if (!this.props.modal.opened) return null; + if (this.props.modal.context !== CONTEXT_DEVICE) return null; const { device } = this.props.modal; const { onCancel } = this.props.modalActions; diff --git a/src/components/modals/device/Forget/index.js b/src/components/modals/device/Forget/index.js index 9da00a51..13ac34bf 100644 --- a/src/components/modals/device/Forget/index.js +++ b/src/components/modals/device/Forget/index.js @@ -5,6 +5,7 @@ import styled from 'styled-components'; import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; import Button from 'components/Button'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from '../../index'; @@ -47,13 +48,12 @@ class ForgetDevice extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; forget() { - if (this.props.modal.opened) { - this.props.modalActions.onForgetSingleDevice(this.props.modal.device); - } + if (this.props.modal.context !== CONTEXT_DEVICE) return; + this.props.modalActions.onForgetSingleDevice(this.props.modal.device); } render() { - if (!this.props.modal.opened) return null; + if (this.props.modal.context !== CONTEXT_DEVICE) return null; const { device } = this.props.modal; const { onCancel } = this.props.modalActions; return ( diff --git a/src/components/modals/device/Remember/index.js b/src/components/modals/device/Remember/index.js index c4dc2c23..e0aab298 100644 --- a/src/components/modals/device/Remember/index.js +++ b/src/components/modals/device/Remember/index.js @@ -5,6 +5,7 @@ import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; import Loader from 'components/Loader'; import Button from 'components/Button'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from '../../index'; @@ -62,9 +63,8 @@ export default class RememberDevice extends PureComponent { // TODO: possible race condition, // device could be already connected but it didn't emit Device.CONNECT event yet window.clearInterval(this.state.ticker); - if (this.props.modal.opened) { - this.props.modalActions.onForgetDevice(this.props.modal.device); - } + if (this.props.modal.context !== CONTEXT_DEVICE) return; + this.props.modalActions.onForgetDevice(this.props.modal.device); } else { this.setState(previousState => ({ countdown: previousState.countdown - 1, @@ -98,13 +98,12 @@ export default class RememberDevice extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; forget() { - if (this.props.modal.opened) { - this.props.modalActions.onForgetDevice(this.props.modal.device); - } + if (this.props.modal.context !== CONTEXT_DEVICE) return; + this.props.modalActions.onForgetDevice(this.props.modal.device); } render() { - if (!this.props.modal.opened) return null; + if (this.props.modal.context !== CONTEXT_DEVICE) return null; const { device, instances } = this.props.modal; const { onRememberDevice } = this.props.modalActions; diff --git a/src/components/modals/device/WalletType/index.js b/src/components/modals/device/WalletType/index.js index fb22b318..5f4e44b7 100644 --- a/src/components/modals/device/WalletType/index.js +++ b/src/components/modals/device/WalletType/index.js @@ -11,6 +11,7 @@ import Link from 'components/Link'; import colors from 'config/colors'; import icons from 'config/icons'; import WalletTypeIcon from 'components/images/WalletType'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from 'components/modals/index'; @@ -89,12 +90,12 @@ class WalletType extends PureComponent { changeType(hidden: boolean, state: ?string) { const { modal } = this.props; - if (!modal.opened) return; + if (modal.context !== CONTEXT_DEVICE) return; this.props.modalActions.onWalletTypeRequest(modal.device, hidden, state); } render() { - if (!this.props.modal.opened) return null; + if (this.props.modal.context !== CONTEXT_DEVICE) return null; const { device } = this.props.modal; const { onCancel } = this.props.modalActions; diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 9a4645cc..761fd2a9 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -12,6 +12,7 @@ import { UI } from 'trezor-connect'; import ModalActions from 'actions/ModalActions'; import ReceiveActions from 'actions/ReceiveActions'; +import * as MODAL from 'actions/constants/modal'; import * as RECEIVE from 'actions/constants/receive'; import * as CONNECT from 'actions/constants/TrezorConnect'; import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; @@ -87,9 +88,9 @@ const ModalWindow = styled.div` class Modal extends React.PureComponent { render() { - if (!this.props.modal.opened) return null; + if (this.props.modal.context === MODAL.CONTEXT_NONE) return null; - const { opened, windowType } = this.props.modal; + const { windowType } = this.props.modal; let component = null; switch (windowType) { case UI.REQUEST_PIN: @@ -131,20 +132,15 @@ class Modal extends React.PureComponent { component = null; } - let ch = null; - if (opened) { - ch = ( - - - - { component } - - - - ); - } - - return ch; + return ( + + + + { component } + + + + ); } } diff --git a/src/components/modals/passphrase/Passphrase/index.js b/src/components/modals/passphrase/Passphrase/index.js index 9850cbd9..1a397175 100644 --- a/src/components/modals/passphrase/Passphrase/index.js +++ b/src/components/modals/passphrase/Passphrase/index.js @@ -8,6 +8,7 @@ import P from 'components/Paragraph'; import Checkbox from 'components/Checkbox'; import Button from 'components/Button'; import Input from 'components/inputs/Input'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from '../../index'; @@ -80,10 +81,8 @@ class Passphrase extends PureComponent { constructor(props: Props) { super(props); - const device = props.modal.opened ? props.modal.device : null; - if (!device) { - return; - } + if (props.modal.context !== CONTEXT_DEVICE) return; + const { device } = props.modal; // Check if this device is already known // if device is already known then only one input is presented @@ -197,7 +196,6 @@ class Passphrase extends PureComponent { handleKeyPress(event: KeyboardEvent) { if (event.key === 'Enter') { event.preventDefault(); - console.warn('ENTER', this.state); if (this.state.doPassphraseInputsMatch) { this.submitPassphrase(); } @@ -205,9 +203,7 @@ class Passphrase extends PureComponent { } render() { - if (!this.props.modal.opened) { - return null; - } + if (this.props.modal.context !== CONTEXT_DEVICE) return null; return ( diff --git a/src/components/modals/passphrase/Type/index.js b/src/components/modals/passphrase/Type/index.js index c42cf3af..bccdcb51 100644 --- a/src/components/modals/passphrase/Type/index.js +++ b/src/components/modals/passphrase/Type/index.js @@ -7,6 +7,7 @@ import icons from 'config/icons'; import styled from 'styled-components'; import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from 'components/modals/index'; @@ -18,7 +19,7 @@ const Wrapper = styled.div` const Header = styled.div``; const Confirmation = (props: Props) => { - if (!props.modal.opened) return null; + if (props.modal.context !== CONTEXT_DEVICE) return null; const { device } = props.modal; return ( diff --git a/src/components/modals/pin/Invalid/index.js b/src/components/modals/pin/Invalid/index.js index 239b21ce..71b81d5e 100644 --- a/src/components/modals/pin/Invalid/index.js +++ b/src/components/modals/pin/Invalid/index.js @@ -4,6 +4,7 @@ import React from 'react'; import styled from 'styled-components'; import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import type { Props } from '../../index'; @@ -12,7 +13,7 @@ const Wrapper = styled.div` `; const InvalidPin = (props: Props) => { - if (!props.modal.opened) return null; + if (props.modal.context !== CONTEXT_DEVICE) return null; const { device } = props.modal; return ( diff --git a/src/components/modals/pin/Pin/index.js b/src/components/modals/pin/Pin/index.js index fa1fc805..1784184e 100644 --- a/src/components/modals/pin/Pin/index.js +++ b/src/components/modals/pin/Pin/index.js @@ -4,10 +4,11 @@ import { H2 } from 'components/Heading'; import React, { PureComponent } from 'react'; import Link from 'components/Link'; import styled from 'styled-components'; - import Button from 'components/Button'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; import PinButton from './components/Button'; import PinInput from './components/Input'; + import type { Props } from '../../index'; type State = { @@ -132,7 +133,7 @@ class Pin extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; render() { - if (!this.props.modal.opened) return null; + if (this.props.modal.context !== CONTEXT_DEVICE) return null; const { onPinSubmit } = this.props.modalActions; const { device } = this.props.modal; const { pin } = this.state; diff --git a/src/reducers/ModalReducer.js b/src/reducers/ModalReducer.js index 8cfde96d..6f033699 100644 --- a/src/reducers/ModalReducer.js +++ b/src/reducers/ModalReducer.js @@ -9,92 +9,70 @@ import * as CONNECT from 'actions/constants/TrezorConnect'; import type { Action, TrezorDevice } from 'flowtype'; export type State = { - opened: false; + context: typeof MODAL.CONTEXT_NONE; } | { - opened: true; + context: typeof MODAL.CONTEXT_DEVICE, device: TrezorDevice; instances?: Array; windowType?: string; +} | { + context: typeof MODAL.CONTEXT_EXTERNAL_WALLET, + windowType?: string; } const initialState: State = { - opened: false, - // instances: null, - // windowType: null + context: MODAL.CONTEXT_NONE, }; export default function modal(state: State = initialState, action: Action): State { switch (action.type) { case RECEIVE.REQUEST_UNVERIFIED: - return { - opened: true, - device: action.device, - windowType: action.type, - }; - + case CONNECT.FORGET_REQUEST: + case CONNECT.TRY_TO_DUPLICATE: case CONNECT.REQUEST_WALLET_TYPE: return { - opened: true, + context: MODAL.CONTEXT_DEVICE, device: action.device, windowType: action.type, }; case CONNECT.REMEMBER_REQUEST: return { - opened: true, + context: MODAL.CONTEXT_DEVICE, device: action.device, instances: action.instances, windowType: action.type, }; - case CONNECT.FORGET_REQUEST: - return { - opened: true, - device: action.device, - windowType: action.type, - }; - - case CONNECT.TRY_TO_DUPLICATE: - return { - opened: true, - device: action.device, - windowType: action.type, - }; + // device acquired + // close modal case DEVICE.CHANGED: - if (state.opened && action.device.path === state.device.path && action.device.status === 'occupied') { + if (state.context === MODAL.CONTEXT_DEVICE && action.device.path === state.device.path && action.device.status === 'occupied') { return initialState; } - return state; + // device with context assigned to modal was disconnected + // close modal case DEVICE.DISCONNECT: - if (state.opened && action.device.path === state.device.path) { + if (state.context === MODAL.CONTEXT_DEVICE && action.device.path === state.device.path) { return initialState; } return state; - // case DEVICE.CONNECT : - // case DEVICE.CONNECT_UNACQUIRED : - // if (state.opened && state.windowType === CONNECT.TRY_TO_FORGET) { - // return { - // ...initialState, - // passphraseCached: state.passphraseCached - // } - // } - // return state; case UI.REQUEST_PIN: case UI.INVALID_PIN: case UI.REQUEST_PASSPHRASE: return { - opened: true, + context: MODAL.CONTEXT_DEVICE, device: action.payload.device, windowType: action.type, }; case UI.REQUEST_BUTTON: return { - opened: true, + context: MODAL.CONTEXT_DEVICE, device: action.payload.device, windowType: action.payload.code, }; @@ -106,6 +84,12 @@ export default function modal(state: State = initialState, action: Action): Stat case CONNECT.REMEMBER: return initialState; + case MODAL.OPEN_EXTERNAL_WALLET: + return { + context: MODAL.CONTEXT_EXTERNAL_WALLET, + windowType: action.id, + }; + default: return state; } diff --git a/src/views/Wallet/views/Account/Receive/index.js b/src/views/Wallet/views/Account/Receive/index.js index 80237ef3..99830694 100644 --- a/src/views/Wallet/views/Account/Receive/index.js +++ b/src/views/Wallet/views/Account/Receive/index.js @@ -148,14 +148,13 @@ const AccountReceive = (props: Props) => { addressUnverified, } = props.receive; + const isAddressVerifying = props.modal.context === 'device' && props.modal.windowType === 'ButtonRequest_Address'; + const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; + let address = `${account.address.substring(0, 20)}...`; - if (addressVerified - || addressUnverified - || (props.modal.opened && props.modal.windowType === 'ButtonRequest_Address')) { + if (addressVerified || addressUnverified || isAddressVerifying) { ({ address } = account); } - const isAddressVerifying = props.modal.opened && props.modal.windowType === 'ButtonRequest_Address'; - const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; return ( From 49ca5992fde3abc89ba308dcd3b188af83dd8d08 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 10 Oct 2018 21:50:20 +0200 Subject: [PATCH 02/11] added NemWallet component --- .../modals/external/NemWallet/index.js | 49 +++++++++++++++++++ src/components/modals/index.js | 5 ++ 2 files changed, 54 insertions(+) create mode 100644 src/components/modals/external/NemWallet/index.js diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/NemWallet/index.js new file mode 100644 index 00000000..a461e5c5 --- /dev/null +++ b/src/components/modals/external/NemWallet/index.js @@ -0,0 +1,49 @@ +/* @flow */ + +import React from 'react'; +import styled from 'styled-components'; + +import colors from 'config/colors'; +import icons from 'config/icons'; + +import Icon from 'components/Icon'; +import Link from 'components/Link'; +import { H3 } from 'components/Heading'; +import P from 'components/Paragraph'; + +import type { Props } from 'components/modals/index'; + +const Wrapper = styled.div` + width: 360px; + padding: 24px 48px; +`; + +const Header = styled.div``; + +const StyledLink = styled(Link)` + position: absolute; + right: 15px; + top: 10px; +`; + +const Confirmation = (props: Props) => { + const { onCancel } = props.modalActions; + return ( + + + + +
+ +

NEM Wallet

+

If you enter a wrong passphrase, you will not unlock the desired hidden wallet.

+
+
+ ); +}; + +export default Confirmation; \ No newline at end of file diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 761fd2a9..890a271e 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -32,6 +32,8 @@ import RememberDevice from 'components/modals/device/Remember'; import DuplicateDevice from 'components/modals/device/Duplicate'; import WalletType from 'components/modals/device/WalletType'; +import NemWallet from 'components/modals/external/NemWallet'; + type OwnProps = { } type StateProps = { @@ -128,6 +130,9 @@ class Modal extends React.PureComponent { component = (); break; + case 'xem': + component = (); + break; default: component = null; } From e59c9ec7140586abcfb0267947a1412e98f5bf2a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 11 Oct 2018 13:06:16 +0200 Subject: [PATCH 03/11] refacotred modals components --- src/components/modals/Container.js | 55 ++++ .../modals/confirm/Address/index.js | 13 +- src/components/modals/confirm/SignTx/index.js | 26 +- .../modals/confirm/UnverifiedAddress/index.js | 49 ++-- .../modals/device/Duplicate/index.js | 51 ++-- src/components/modals/device/Forget/index.js | 28 +- .../modals/device/Remember/index.js | 36 ++- .../modals/device/WalletType/index.js | 33 ++- .../modals/external/NemWallet/index.js | 41 +-- src/components/modals/index.js | 243 +++++++++--------- .../modals/passphrase/Passphrase/index.js | 30 ++- .../modals/passphrase/Type/index.js | 43 ++-- src/components/modals/pin/Invalid/index.js | 26 +- src/components/modals/pin/Pin/index.js | 28 +- src/views/Wallet/index.js | 2 +- 15 files changed, 440 insertions(+), 264 deletions(-) create mode 100644 src/components/modals/Container.js diff --git a/src/components/modals/Container.js b/src/components/modals/Container.js new file mode 100644 index 00000000..62944a43 --- /dev/null +++ b/src/components/modals/Container.js @@ -0,0 +1,55 @@ +/* @flow */ +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +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 StateProps = { + modal: $ElementType, + accounts: $ElementType, + devices: $ElementType, + connect: $ElementType, + selectedAccount: $ElementType, + sendForm: $ElementType, + receive: $ElementType, + localStorage: $ElementType, + wallet: $ElementType, +} + +type DispatchProps = { + modalActions: typeof ModalActions, + receiveActions: typeof ReceiveActions, +} + +export type Props = StateProps & DispatchProps; + +const mapStateToProps: MapStateToProps = (state: State): StateProps => ({ + modal: state.modal, + accounts: state.accounts, + devices: state.devices, + connect: state.connect, + selectedAccount: state.selectedAccount, + sendForm: state.sendForm, + receive: state.receive, + localStorage: state.localStorage, + wallet: state.wallet, +}); + +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + modalActions: bindActionCreators(ModalActions, dispatch), + receiveActions: bindActionCreators(ReceiveActions, dispatch), +}); + +// export default connect(mapStateToProps, mapDispatchToProps)(Modal); +export default withRouter( + connect(mapStateToProps, mapDispatchToProps)(Modal), +); \ No newline at end of file diff --git a/src/components/modals/confirm/Address/index.js b/src/components/modals/confirm/Address/index.js index b3b4958f..9581561d 100644 --- a/src/components/modals/confirm/Address/index.js +++ b/src/components/modals/confirm/Address/index.js @@ -1,13 +1,16 @@ /* @flow */ import React from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; -import H3 from 'components/Heading'; + import colors from 'config/colors'; -import P from 'components/Paragraph'; import { FONT_SIZE } from 'config/variables'; -import type { Props } from '../../index'; +import H3 from 'components/Heading'; +import P from 'components/Paragraph'; + +import type { Props } from '../../Container'; const Wrapper = styled.div` width: 390px; @@ -49,4 +52,8 @@ const ConfirmAddress = (props: Props) => { ); }; +ConfirmAddress.propTypes = { + selectedAccount: PropTypes.object.isRequired, +}; + export default ConfirmAddress; \ No newline at end of file diff --git a/src/components/modals/confirm/SignTx/index.js b/src/components/modals/confirm/SignTx/index.js index 2e51d2d2..4668979f 100644 --- a/src/components/modals/confirm/SignTx/index.js +++ b/src/components/modals/confirm/SignTx/index.js @@ -1,16 +1,24 @@ /* @flow */ import React from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + +import icons from 'config/icons'; import colors from 'config/colors'; +import { LINE_HEIGHT } from 'config/variables'; + import P from 'components/Paragraph'; import Icon from 'components/Icon'; -import icons from 'config/icons'; import { H3 } from 'components/Heading'; -import { LINE_HEIGHT } from 'config/variables'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from '../../index'; +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + sendForm: $ElementType; +} const Wrapper = styled.div` width: 390px; @@ -40,9 +48,6 @@ const Label = styled.div` `; const ConfirmSignTx = (props: Props) => { - if (props.modal.context !== CONTEXT_DEVICE) return null; - const { device } = props.modal; - const { amount, address, @@ -54,7 +59,7 @@ const ConfirmSignTx = (props: Props) => {
-

Confirm transaction on { device.label } device

+

Confirm transaction on { props.device.label } device

Details are shown on display

@@ -69,4 +74,9 @@ const ConfirmSignTx = (props: Props) => { ); }; +ConfirmSignTx.propTypes = { + device: PropTypes.object.isRequired, + sendForm: PropTypes.object.isRequired, +}; + export default ConfirmSignTx; \ No newline at end of file diff --git a/src/components/modals/confirm/UnverifiedAddress/index.js b/src/components/modals/confirm/UnverifiedAddress/index.js index 1c933742..992d3325 100644 --- a/src/components/modals/confirm/UnverifiedAddress/index.js +++ b/src/components/modals/confirm/UnverifiedAddress/index.js @@ -1,16 +1,27 @@ /* @flow */ import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; + +import icons from 'config/icons'; +import colors from 'config/colors'; + import { H2 } from 'components/Heading'; import P from 'components/Paragraph'; -import styled from 'styled-components'; import Icon from 'components/Icon'; -import colors from 'config/colors'; -import icons from 'config/icons'; import Button from 'components/Button'; import Link from 'components/Link'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from '../../index'; +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + account: $ElementType<$ElementType, 'account'>; + showAddress: $ElementType<$ElementType, 'showAddress'>; + showUnverifiedAddress: $ElementType<$ElementType, 'showUnverifiedAddress'>; + onCancel: $ElementType<$ElementType, 'onCancel'>; +} const StyledLink = styled(Link)` position: absolute; @@ -57,24 +68,20 @@ class ConfirmUnverifiedAddress extends PureComponent { } verifyAddress() { - const { account } = this.props.selectedAccount; + const { account, onCancel, showAddress } = this.props; if (!account) return; - this.props.modalActions.onCancel(); - this.props.receiveActions.showAddress(account.addressPath); + onCancel(); + showAddress(account.addressPath); } showUnverifiedAddress() { - this.props.modalActions.onCancel(); - this.props.receiveActions.showUnverifiedAddress(); + const { onCancel, showUnverifiedAddress } = this.props; + onCancel(); + showUnverifiedAddress(); } render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - const { device } = this.props.modal; - - const { - onCancel, - } = this.props.modalActions; + const { device, account, onCancel } = this.props; let deviceStatus: string; let claim: string; @@ -97,7 +104,7 @@ class ConfirmUnverifiedAddress extends PureComponent {

{ deviceStatus }

To prevent phishing attacks, you should verify the address on your TREZOR first. { claim } to continue with the verification process. - (!this.props.selectedAccount.account ? this.verifyAddress() : 'false')}>Try again + (!account ? this.verifyAddress() : 'false')}>Try again this.showUnverifiedAddress()}>Show unverified address
@@ -105,4 +112,12 @@ class ConfirmUnverifiedAddress extends PureComponent { } } +ConfirmUnverifiedAddress.propTypes = { + device: PropTypes.object.isRequired, + account: PropTypes.object.isRequired, + showAddress: PropTypes.func.isRequired, + showUnverifiedAddress: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; + export default ConfirmUnverifiedAddress; \ No newline at end of file diff --git a/src/components/modals/device/Duplicate/index.js b/src/components/modals/device/Duplicate/index.js index ae9598b7..03e5e007 100644 --- a/src/components/modals/device/Duplicate/index.js +++ b/src/components/modals/device/Duplicate/index.js @@ -1,19 +1,30 @@ /* @flow */ import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + +import icons from 'config/icons'; +import colors from 'config/colors'; +import { FONT_SIZE } from 'config/variables'; + import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; import Button from 'components/Button'; import Input from 'components/inputs/Input'; -import { getDuplicateInstanceNumber } from 'reducers/utils'; -import { FONT_SIZE } from 'config/variables'; import Icon from 'components/Icon'; -import icons from 'config/icons'; -import colors from 'config/colors'; import Link from 'components/Link'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from 'components/modals/index'; +import { getDuplicateInstanceNumber } from 'reducers/utils'; + +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + devices: $ElementType; + onDuplicateDevice: $ElementType<$ElementType, 'onDuplicateDevice'>; + onCancel: $ElementType<$ElementType, 'onCancel'>; +} type State = { defaultName: string; @@ -63,17 +74,14 @@ const ErrorMessage = styled.div` width: 100%; `; -export default class DuplicateDevice extends PureComponent { +class DuplicateDevice extends PureComponent { constructor(props: Props) { super(props); - if (props.modal.context !== CONTEXT_DEVICE) return; - const { device } = props.modal; - - const instance = getDuplicateInstanceNumber(props.devices, device); + const instance = getDuplicateInstanceNumber(props.devices, props.device); this.state = { - defaultName: `${device.label} (${instance.toString()})`, + defaultName: `${props.device.label} (${instance.toString()})`, instance, instanceName: null, isUsed: false, @@ -115,16 +123,12 @@ export default class DuplicateDevice extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; submit() { - if (this.props.modal.context !== CONTEXT_DEVICE) return; const extended: Object = { instanceName: this.state.instanceName, instance: this.state.instance }; - this.props.modalActions.onDuplicateDevice({ ...this.props.modal.device, ...extended }); + this.props.onDuplicateDevice({ ...this.props.device, ...extended }); } render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - - const { device } = this.props.modal; - const { onCancel } = this.props.modalActions; + const { device, onCancel } = this.props; const { defaultName, instanceName, @@ -168,4 +172,13 @@ export default class DuplicateDevice extends PureComponent {
); } -} \ No newline at end of file +} + +DuplicateDevice.propTypes = { + device: PropTypes.object.isRequired, + devices: PropTypes.array.isRequired, + onDuplicateDevice: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; + +export default DuplicateDevice; \ No newline at end of file diff --git a/src/components/modals/device/Forget/index.js b/src/components/modals/device/Forget/index.js index 13ac34bf..d20702ca 100644 --- a/src/components/modals/device/Forget/index.js +++ b/src/components/modals/device/Forget/index.js @@ -1,13 +1,21 @@ /* @flow */ import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; import Button from 'components/Button'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from '../../index'; +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + onForgetSingleDevice: $ElementType<$ElementType, 'onForgetSingleDevice'>; + onCancel: $ElementType<$ElementType, 'onCancel'>; +} const Wrapper = styled.div` width: 360px; @@ -48,25 +56,27 @@ class ForgetDevice extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; forget() { - if (this.props.modal.context !== CONTEXT_DEVICE) return; - this.props.modalActions.onForgetSingleDevice(this.props.modal.device); + this.props.onForgetSingleDevice(this.props.device); } render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - const { device } = this.props.modal; - const { onCancel } = this.props.modalActions; return ( -

Forget { device.instanceLabel }?

+

Forget { this.props.device.instanceLabel }?

Forgetting only removes the device from the list on the left, your coins are still safe and you can access them by reconnecting your TREZOR again. this.forget()}>Forget - Don't forget + Don't forget
); } } +ForgetDevice.propTypes = { + device: PropTypes.object.isRequired, + onForgetSingleDevice: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; + export default ForgetDevice; \ No newline at end of file diff --git a/src/components/modals/device/Remember/index.js b/src/components/modals/device/Remember/index.js index e0aab298..937e04bc 100644 --- a/src/components/modals/device/Remember/index.js +++ b/src/components/modals/device/Remember/index.js @@ -1,13 +1,22 @@ /* @flow */ import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; import Loader from 'components/Loader'; import Button from 'components/Button'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from '../../index'; +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + instances: ?Array; + onRememberDevice: $ElementType<$ElementType, 'onRememberDevice'>; + onForgetDevice: $ElementType<$ElementType, 'onForgetDevice'>; +} type State = { countdown: number; @@ -48,7 +57,7 @@ const StyledLoader = styled(Loader)` left: 200px; `; -export default class RememberDevice extends PureComponent { +class RememberDevice extends PureComponent { constructor(props: Props) { super(props); @@ -63,8 +72,7 @@ export default class RememberDevice extends PureComponent { // TODO: possible race condition, // device could be already connected but it didn't emit Device.CONNECT event yet window.clearInterval(this.state.ticker); - if (this.props.modal.context !== CONTEXT_DEVICE) return; - this.props.modalActions.onForgetDevice(this.props.modal.device); + this.props.onForgetDevice(this.props.device); } else { this.setState(previousState => ({ countdown: previousState.countdown - 1, @@ -98,14 +106,11 @@ export default class RememberDevice extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; forget() { - if (this.props.modal.context !== CONTEXT_DEVICE) return; - this.props.modalActions.onForgetDevice(this.props.modal.device); + this.props.onForgetDevice(this.props.device); } render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - const { device, instances } = this.props.modal; - const { onRememberDevice } = this.props.modalActions; + const { device, instances, onRememberDevice } = this.props; let { label } = device; const devicePlural: string = instances && instances.length > 1 ? 'devices or to remember them' : 'device or to remember it'; @@ -143,4 +148,13 @@ export default class RememberDevice extends PureComponent { ); } -} \ No newline at end of file +} + +RememberDevice.propTypes = { + device: PropTypes.object.isRequired, + instances: PropTypes.array.isRequired, + onRememberDevice: PropTypes.func.isRequired, + onForgetDevice: PropTypes.func.isRequired, +}; + +export default RememberDevice; \ No newline at end of file diff --git a/src/components/modals/device/WalletType/index.js b/src/components/modals/device/WalletType/index.js index 5f4e44b7..eb11b8a9 100644 --- a/src/components/modals/device/WalletType/index.js +++ b/src/components/modals/device/WalletType/index.js @@ -1,19 +1,28 @@ /* @flow */ import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import styled, { css } from 'styled-components'; + +import icons from 'config/icons'; +import colors from 'config/colors'; + import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; import Button from 'components/Button'; import Tooltip from 'components/Tooltip'; import Icon from 'components/Icon'; import Link from 'components/Link'; -import colors from 'config/colors'; -import icons from 'config/icons'; import WalletTypeIcon from 'components/images/WalletType'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from 'components/modals/index'; +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + onWalletTypeRequest: $ElementType<$ElementType, 'onWalletTypeRequest'>; + onCancel: $ElementType<$ElementType, 'onCancel'>; +} const Wrapper = styled.div` width: 360px; @@ -89,15 +98,11 @@ class WalletType extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; changeType(hidden: boolean, state: ?string) { - const { modal } = this.props; - if (modal.context !== CONTEXT_DEVICE) return; - this.props.modalActions.onWalletTypeRequest(modal.device, hidden, state); + this.props.onWalletTypeRequest(this.props.device, hidden, state); } render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - const { device } = this.props.modal; - const { onCancel } = this.props.modalActions; + const { device, onCancel } = this.props; return ( @@ -110,7 +115,7 @@ class WalletType extends PureComponent { /> )} - Change wallet type for { device.instanceLabel } + { device.state ? 'Change' : 'Select' } wallet type for { device.instanceLabel }
@@ -148,4 +153,10 @@ class WalletType extends PureComponent { } } +WalletType.propTypes = { + device: PropTypes.object.isRequired, + onWalletTypeRequest: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; + export default WalletType; \ No newline at end of file diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/NemWallet/index.js index a461e5c5..8d5fbdfe 100644 --- a/src/components/modals/external/NemWallet/index.js +++ b/src/components/modals/external/NemWallet/index.js @@ -11,7 +11,11 @@ import Link from 'components/Link'; import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; -import type { Props } from 'components/modals/index'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + onCancel: $ElementType<$ElementType, 'onCancel'>; +} const Wrapper = styled.div` width: 360px; @@ -26,24 +30,21 @@ const StyledLink = styled(Link)` top: 10px; `; -const Confirmation = (props: Props) => { - const { onCancel } = props.modalActions; - return ( - - - - -
- -

NEM Wallet

-

If you enter a wrong passphrase, you will not unlock the desired hidden wallet.

-
-
- ); -}; +const Confirmation = (props: Props) => ( + + + + +
+ +

NEM Wallet

+

If you enter a wrong passphrase, you will not unlock the desired hidden wallet.

+
+
+); export default Confirmation; \ No newline at end of file diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 890a271e..54e37ae8 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -1,67 +1,39 @@ /* @flow */ + import * as React from 'react'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; +import { CSSTransition } from 'react-transition-group'; + import styled from 'styled-components'; import colors from 'config/colors'; -import { CSSTransition } from 'react-transition-group'; import { UI } from 'trezor-connect'; - -import ModalActions from 'actions/ModalActions'; -import ReceiveActions from 'actions/ReceiveActions'; - import * as MODAL from 'actions/constants/modal'; import * as RECEIVE from 'actions/constants/receive'; import * as CONNECT from 'actions/constants/TrezorConnect'; -import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; -import type { State, Dispatch } from 'flowtype'; +// device context import Pin from 'components/modals/pin/Pin'; import InvalidPin from 'components/modals/pin/Invalid'; - import Passphrase from 'components/modals/passphrase/Passphrase'; import PassphraseType from 'components/modals/passphrase/Type'; - import ConfirmSignTx from 'components/modals/confirm/SignTx'; import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress'; - import ForgetDevice from 'components/modals/device/Forget'; import RememberDevice from 'components/modals/device/Remember'; import DuplicateDevice from 'components/modals/device/Duplicate'; import WalletType from 'components/modals/device/WalletType'; +// external context import NemWallet from 'components/modals/external/NemWallet'; -type OwnProps = { } - -type StateProps = { - modal: $ElementType, - accounts: $ElementType, - devices: $ElementType, - connect: $ElementType, - selectedAccount: $ElementType, - sendForm: $ElementType, - receive: $ElementType, - localStorage: $ElementType, - wallet: $ElementType, -} - -type DispatchProps = { - modalActions: typeof ModalActions, - receiveActions: typeof ReceiveActions, -} - -export type Props = StateProps & DispatchProps; +import type { Props } from './Container'; const Fade = (props: { children: React.Node}) => ( - { props.children } + >{ props.children } ); @@ -88,85 +60,124 @@ const ModalWindow = styled.div` text-align: center; `; -class Modal extends React.PureComponent { - render() { - if (this.props.modal.context === MODAL.CONTEXT_NONE) return null; - - const { windowType } = this.props.modal; - let component = null; - switch (windowType) { - case UI.REQUEST_PIN: - component = (); - break; - case UI.INVALID_PIN: - component = (); - break; - case UI.REQUEST_PASSPHRASE: - component = (); - break; - case 'ButtonRequest_SignTx': - component = (); - break; - case 'ButtonRequest_PassphraseType': - component = (); - break; - case RECEIVE.REQUEST_UNVERIFIED: - component = (); - break; - - case CONNECT.REMEMBER_REQUEST: - component = (); - break; - - case CONNECT.FORGET_REQUEST: - component = (); - break; - - case CONNECT.TRY_TO_DUPLICATE: - component = (); - break; - - case CONNECT.REQUEST_WALLET_TYPE: - component = (); - break; - - case 'xem': - component = (); - break; - default: - component = null; - } - - return ( - - - - { component } - - - - ); +// get modal component with device context +const getDeviceContextModal = (props: Props) => { + const { modal, modalActions } = props; + if (modal.context !== MODAL.CONTEXT_DEVICE) return null; + + switch (modal.windowType) { + case UI.REQUEST_PIN: + return ( + ); + + case UI.INVALID_PIN: + return ; + + case UI.REQUEST_PASSPHRASE: + return ( + ); + + case 'ButtonRequest_PassphraseType': + return ; + + case 'ButtonRequest_SignTx': + return ; + + case RECEIVE.REQUEST_UNVERIFIED: + return ( + ); + + case CONNECT.REMEMBER_REQUEST: + return ( + ); + + case CONNECT.FORGET_REQUEST: + return ( + ); + + case CONNECT.TRY_TO_DUPLICATE: + return ( + ); + + case CONNECT.REQUEST_WALLET_TYPE: + return ( + ); + + default: + return null; } -} - -const mapStateToProps: MapStateToProps = (state: State): StateProps => ({ - modal: state.modal, - accounts: state.accounts, - devices: state.devices, - connect: state.connect, - selectedAccount: state.selectedAccount, - sendForm: state.sendForm, - receive: state.receive, - localStorage: state.localStorage, - wallet: state.wallet, -}); - -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ - modalActions: bindActionCreators(ModalActions, dispatch), - receiveActions: bindActionCreators(ReceiveActions, dispatch), -}); - -// export default connect(mapStateToProps, mapDispatchToProps)(Modal); -export default withRouter( - connect(mapStateToProps, mapDispatchToProps)(Modal), -); +}; + +// get modal component with external context +const getExternalContextModal = (props: Props) => { + const { modal, modalActions } = props; + if (modal.context !== MODAL.CONTEXT_EXTERNAL_WALLET) return null; + + switch (modal.windowType) { + case 'xem': + return (); + default: + return null; + } +}; + +// modal container component +const Modal = (props: Props) => { + const { modal } = props; + if (modal.context === MODAL.CONTEXT_NONE) return null; + + let component = null; + switch (modal.context) { + case MODAL.CONTEXT_DEVICE: + component = getDeviceContextModal(props); + break; + case MODAL.CONTEXT_EXTERNAL_WALLET: + component = getExternalContextModal(props); + break; + default: + break; + } + + return ( + + + + { component } + + + + ); +}; + +export default Modal; diff --git a/src/components/modals/passphrase/Passphrase/index.js b/src/components/modals/passphrase/Passphrase/index.js index 1a397175..79137762 100644 --- a/src/components/modals/passphrase/Passphrase/index.js +++ b/src/components/modals/passphrase/Passphrase/index.js @@ -1,16 +1,25 @@ /* @flow */ import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + import colors from 'config/colors'; import { FONT_SIZE, TRANSITION } from 'config/variables'; + import { H2 } from 'components/Heading'; import P from 'components/Paragraph'; import Checkbox from 'components/Checkbox'; import Button from 'components/Button'; import Input from 'components/inputs/Input'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from '../../index'; +import type { TrezorDevice } from 'flowtype'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + selectedDevice: ?TrezorDevice; + onPassphraseSubmit: $ElementType<$ElementType, 'onPassphraseSubmit'>; +} type State = { deviceLabel: string, @@ -80,13 +89,10 @@ const LinkButton = styled(Button)` class Passphrase extends PureComponent { constructor(props: Props) { super(props); - - if (props.modal.context !== CONTEXT_DEVICE) return; - const { device } = props.modal; + const { device, selectedDevice } = props; // Check if this device is already known // if device is already known then only one input is presented - const { selectedDevice } = props.wallet; let deviceLabel = device.label; let shouldShowSingleInput = false; if (selectedDevice && selectedDevice.path === device.path) { @@ -104,8 +110,6 @@ class Passphrase extends PureComponent { }; } - state: State; - componentDidMount() { this.passphraseInput.focus(); @@ -179,7 +183,7 @@ class Passphrase extends PureComponent { } submitPassphrase(shouldLeavePassphraseBlank: boolean = false) { - const { onPassphraseSubmit } = this.props.modalActions; + const { onPassphraseSubmit } = this.props; const passphrase = this.state.passphraseInputValue; // Reset state so same passphrase isn't filled when the modal will be visible again @@ -203,8 +207,6 @@ class Passphrase extends PureComponent { } render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - return (

Enter {this.state.deviceLabel} passphrase

@@ -273,4 +275,10 @@ class Passphrase extends PureComponent { } } +Passphrase.propTypes = { + device: PropTypes.object.isRequired, + selectedDevice: PropTypes.object.isRequired, + onPassphraseSubmit: PropTypes.func.isRequired, +}; + export default Passphrase; \ No newline at end of file diff --git a/src/components/modals/passphrase/Type/index.js b/src/components/modals/passphrase/Type/index.js index bccdcb51..ce659800 100644 --- a/src/components/modals/passphrase/Type/index.js +++ b/src/components/modals/passphrase/Type/index.js @@ -1,15 +1,21 @@ /* @flow */ import React from 'react'; -import Icon from 'components/Icon'; -import colors from 'config/colors'; -import icons from 'config/icons'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + +import icons from 'config/icons'; +import colors from 'config/colors'; + +import Icon from 'components/Icon'; import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from 'components/modals/index'; +import type { TrezorDevice } from 'flowtype'; + +type Props = { + device: TrezorDevice; +} const Wrapper = styled.div` width: 360px; @@ -18,19 +24,18 @@ const Wrapper = styled.div` const Header = styled.div``; -const Confirmation = (props: Props) => { - if (props.modal.context !== CONTEXT_DEVICE) return null; - const { device } = props.modal; - - return ( - -
- -

Complete the action on { device.label } device

-

If you enter a wrong passphrase, you will not unlock the desired hidden wallet.

-
-
- ); +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.

+
+
+); + +PassphraseType.propTypes = { + device: PropTypes.object.isRequired, }; -export default Confirmation; \ No newline at end of file +export default PassphraseType; \ No newline at end of file diff --git a/src/components/modals/pin/Invalid/index.js b/src/components/modals/pin/Invalid/index.js index 71b81d5e..e69d7554 100644 --- a/src/components/modals/pin/Invalid/index.js +++ b/src/components/modals/pin/Invalid/index.js @@ -1,27 +1,31 @@ /* @flow */ import React from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; + import { H3 } from 'components/Heading'; import P from 'components/Paragraph'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; -import type { Props } from '../../index'; +import type { TrezorDevice } from 'flowtype'; + +type Props = { + device: TrezorDevice; +} const Wrapper = styled.div` padding: 24px 48px; `; -const InvalidPin = (props: Props) => { - if (props.modal.context !== CONTEXT_DEVICE) return null; +const InvalidPin = (props: Props) => ( + +

Entered PIN for { props.device.label } is not correct

+

Retrying...

+
+); - const { device } = props.modal; - return ( - -

Entered PIN for { device.label } is not correct

-

Retrying...

-
- ); +InvalidPin.propTypes = { + device: PropTypes.object.isRequired, }; export default InvalidPin; \ No newline at end of file diff --git a/src/components/modals/pin/Pin/index.js b/src/components/modals/pin/Pin/index.js index 1784184e..7a8eed98 100644 --- a/src/components/modals/pin/Pin/index.js +++ b/src/components/modals/pin/Pin/index.js @@ -1,15 +1,24 @@ /* @flow */ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; + import P from 'components/Paragraph'; import { H2 } from 'components/Heading'; -import React, { PureComponent } from 'react'; import Link from 'components/Link'; -import styled from 'styled-components'; import Button from 'components/Button'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; + +import type { TrezorDevice } from 'flowtype'; + import PinButton from './components/Button'; import PinInput from './components/Input'; -import type { Props } from '../../index'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + device: TrezorDevice; + onPinSubmit: $ElementType<$ElementType, 'onPinSubmit'>; +} type State = { pin: string; @@ -75,7 +84,7 @@ class Pin extends PureComponent { } keyboardHandler(event: KeyboardEvent): void { - const { onPinSubmit } = this.props.modalActions; + const { onPinSubmit } = this.props; const { pin } = this.state; event.preventDefault(); @@ -133,9 +142,7 @@ class Pin extends PureComponent { keyboardHandler: (event: KeyboardEvent) => void; render() { - if (this.props.modal.context !== CONTEXT_DEVICE) return null; - const { onPinSubmit } = this.props.modalActions; - const { device } = this.props.modal; + const { device, onPinSubmit } = this.props; const { pin } = this.state; return ( @@ -174,4 +181,9 @@ class Pin extends PureComponent { } } +Pin.propTypes = { + device: PropTypes.object.isRequired, + onPinSubmit: PropTypes.func.isRequired, +}; + export default Pin; \ No newline at end of file diff --git a/src/views/Wallet/index.js b/src/views/Wallet/index.js index 48eb27aa..78679df7 100644 --- a/src/views/Wallet/index.js +++ b/src/views/Wallet/index.js @@ -11,7 +11,7 @@ import type { State } from 'flowtype'; import Header from 'components/Header'; import Footer from 'components/Footer'; -import ModalContainer from 'components/modals'; +import ModalContainer from 'components/modals/Container'; import AppNotifications from 'components/notifications/App'; import ContextNotifications from 'components/notifications/Context'; From eab3bde184393cde2aaf2fbfbcd17fb0d43dcc98 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 11 Oct 2018 13:06:51 +0200 Subject: [PATCH 04/11] Link component: target parameter --- src/components/Link/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Link/index.js b/src/components/Link/index.js index 8c782374..3a81db1b 100644 --- a/src/components/Link/index.js +++ b/src/components/Link/index.js @@ -62,7 +62,7 @@ class Link extends PureComponent { Date: Thu, 11 Oct 2018 13:07:29 +0200 Subject: [PATCH 05/11] fixed deviceUtils.isSelectedDevice --- src/actions/TrezorConnectActions.js | 2 +- src/utils/device.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index e67d6927..85d03cf3 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -237,7 +237,7 @@ export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch if (device.features) { const instances = getState().devices.filter(d => d.features && device.features && d.state && !d.remember && d.features.device_id === device.features.device_id); if (instances.length > 0) { - const isSelected = deviceUtils.isSelectedDevice(getState().wallet.selectedDevice, instances[0]); + const isSelected = deviceUtils.isSelectedDevice(getState().wallet.selectedDevice, device); if (!isSelected && getState().modal.context !== CONTEXT_NONE) { dispatch({ type: CONNECT.FORGET_SILENT, diff --git a/src/utils/device.js b/src/utils/device.js index 94924325..8bf3a368 100644 --- a/src/utils/device.js +++ b/src/utils/device.js @@ -2,6 +2,7 @@ import colors from 'config/colors'; +import type { Device } from 'trezor-connect'; import type { TrezorDevice, State, @@ -91,7 +92,7 @@ export const isDeviceAccessible = (device: ?TrezorDevice): boolean => { return device.mode === 'normal' && device.firmware !== 'required'; }; -export const isSelectedDevice = (current: ?TrezorDevice, device: ?TrezorDevice): boolean => !!((current && device && (current.path === device.path && current.instance === device.instance))); +export const isSelectedDevice = (selected: ?TrezorDevice, device: ?(TrezorDevice | Device)): boolean => !!((selected && device && (selected.path === device.path && (device.ts && selected.instance === device.instance)))); export const getVersion = (device: TrezorDevice): string => { let version; From 5eb5e3f774071edf6eb9ff15a9f2a1c06b2bacba Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 11 Oct 2018 13:07:58 +0200 Subject: [PATCH 06/11] close modal on DEVICE.CONNECT action --- src/reducers/ModalReducer.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/reducers/ModalReducer.js b/src/reducers/ModalReducer.js index 6f033699..a425676a 100644 --- a/src/reducers/ModalReducer.js +++ b/src/reducers/ModalReducer.js @@ -52,6 +52,15 @@ export default function modal(state: State = initialState, action: Action): Stat } return state; + // device connected + // close modal if modal context is not 'device' + case DEVICE.CONNECT: + case DEVICE.CONNECT_UNACQUIRED: + if (state.context !== MODAL.CONTEXT_DEVICE) { + return initialState; + } + return state; + // device with context assigned to modal was disconnected // close modal case DEVICE.DISCONNECT: From a9246c0c06f88c2ada88c7b0a1795aab351add45 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 11 Oct 2018 13:27:15 +0200 Subject: [PATCH 07/11] NemWallet component --- .../NemImage/images/nem-download.png | Bin 0 -> 85010 bytes .../NemWallet/components/NemImage/index.js | 15 +++++++++ .../modals/external/NemWallet/index.js | 31 ++++++++++++------ 3 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 src/components/modals/external/NemWallet/components/NemImage/images/nem-download.png create mode 100644 src/components/modals/external/NemWallet/components/NemImage/index.js diff --git a/src/components/modals/external/NemWallet/components/NemImage/images/nem-download.png b/src/components/modals/external/NemWallet/components/NemImage/images/nem-download.png new file mode 100644 index 0000000000000000000000000000000000000000..c7551c31fe36314e74903eed63d380fe126022cd GIT binary patch literal 85010 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

hDl36|~XXpl? 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 Vs)3K$=*{a(N=#m~Tv*Tl{{XvQlwtq? literal 0 HcmV?d00001 diff --git a/src/components/modals/external/NemWallet/components/NemImage/index.js b/src/components/modals/external/NemWallet/components/NemImage/index.js new file mode 100644 index 00000000..2306c435 --- /dev/null +++ b/src/components/modals/external/NemWallet/components/NemImage/index.js @@ -0,0 +1,15 @@ +/* @flow */ + +import React from 'react'; +import styled from 'styled-components'; +import DownloadImg from './images/nem-download.png'; + +const Img = styled.img` + display: block; + width: 100%; + height: auto; +`; + +const NemImage = () => ; + +export default NemImage; diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/NemWallet/index.js index 8d5fbdfe..8e2fcc53 100644 --- a/src/components/modals/external/NemWallet/index.js +++ b/src/components/modals/external/NemWallet/index.js @@ -1,6 +1,7 @@ /* @flow */ import React from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; import colors from 'config/colors'; @@ -8,9 +9,11 @@ import icons from 'config/icons'; import Icon from 'components/Icon'; import Link from 'components/Link'; -import { H3 } from 'components/Heading'; +import Button from 'components/Button'; +import { H3, H4 } from 'components/Heading'; import P from 'components/Paragraph'; +import NemImage from './components/NemImage'; import type { Props as BaseProps } from '../../Container'; type Props = { @@ -18,11 +21,15 @@ type Props = { } const Wrapper = styled.div` - width: 360px; + width: 100%; + max-width: 620px; padding: 24px 48px; `; -const Header = styled.div``; +const StyledButton = styled(Button)` + margin: 0 0 10px 0; + width: 100%; +`; const StyledLink = styled(Link)` position: absolute; @@ -30,7 +37,7 @@ const StyledLink = styled(Link)` top: 10px; `; -const Confirmation = (props: Props) => ( +const NemWallet = (props: Props) => ( ( icon={icons.CLOSE} /> -
- -

NEM Wallet

-

If you enter a wrong passphrase, you will not unlock the desired hidden wallet.

-
+

NEM Wallet

+

We have partnered up with the NEM Foundation to provide you with a full-fledged NEM Wallet.

+

Make sure you download the Universal Client for TREZOR support.

+ + Go to nem.io
); -export default Confirmation; \ No newline at end of file +NemWallet.propTypes = { + onCancel: PropTypes.func.isRequired, +}; + +export default NemWallet; \ No newline at end of file From 42841b0b8483585298e2adc251e88bf868a86d87 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 11 Oct 2018 13:27:32 +0200 Subject: [PATCH 08/11] add externalWallet to CoinMenu --- src/constants/coins.js | 1 + .../components/LeftNavigation/Container.js | 2 + .../components/CoinMenu/index.js | 55 ++++++++++++------- .../LeftNavigation/components/common.js | 2 + 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/constants/coins.js b/src/constants/coins.js index d6a46972..71d4f2e2 100644 --- a/src/constants/coins.js +++ b/src/constants/coins.js @@ -48,5 +48,6 @@ export default [ id: 'xem', coinName: 'NEM', url: 'https://nem.io/downloads/', + external: true, }, ]; \ No newline at end of file diff --git a/src/views/Wallet/components/LeftNavigation/Container.js b/src/views/Wallet/components/LeftNavigation/Container.js index d3c1c18a..f88e87ae 100644 --- a/src/views/Wallet/components/LeftNavigation/Container.js +++ b/src/views/Wallet/components/LeftNavigation/Container.js @@ -7,6 +7,7 @@ import { withRouter } from 'react-router-dom'; 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'; @@ -38,6 +39,7 @@ const mapDispatchToProps: MapDispatchToProps duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch), gotoDeviceSettings: bindActionCreators(RouterActions.gotoDeviceSettings, dispatch), onSelectDevice: bindActionCreators(RouterActions.selectDevice, dispatch), + gotoExternalWallet: bindActionCreators(ModalActions.gotoExternalWallet, dispatch), }); export default withRouter( diff --git a/src/views/Wallet/components/LeftNavigation/components/CoinMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/CoinMenu/index.js index d22b3cd5..8391bc17 100644 --- a/src/views/Wallet/components/LeftNavigation/components/CoinMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/CoinMenu/index.js @@ -1,3 +1,5 @@ +/* @flow */ + import styled from 'styled-components'; import coins from 'constants/coins'; import colors from 'config/colors'; @@ -5,12 +7,19 @@ import ICONS from 'config/icons'; import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; import { NavLink } from 'react-router-dom'; +import Link from 'components/Link'; import Divider from '../Divider'; import RowCoin from '../RowCoin'; +import type { Props } from '../common'; + const Wrapper = styled.div``; -class CoinMenu extends PureComponent { +const ExternalWallet = styled.div` + cursor: pointer; +`; + +class CoinMenu extends PureComponent { getBaseUrl() { const { selectedDevice } = this.props.wallet; let baseUrl = ''; @@ -24,6 +33,27 @@ class CoinMenu extends PureComponent { return baseUrl; } + getOtherCoins() { + return coins.map((coin) => { + const row = ( + + ); + + if (coin.external) return this.props.gotoExternalWallet(coin.id, coin.url)}>{row}; + return {row}; + }); + } + render() { const { config } = this.props.localStorage; return ( @@ -46,31 +76,16 @@ class CoinMenu extends PureComponent { textRight="(You will be redirected)" hasBorder /> - {coins.map(coin => ( -
- - - ))} + {this.getOtherCoins()} ); } } CoinMenu.propTypes = { - config: PropTypes.object, - wallet: PropTypes.object, - selectedDevice: PropTypes.object, - localStorage: PropTypes.object, + localStorage: PropTypes.object.isRequired, + wallet: PropTypes.object.isRequired, + gotoExternalWallet: PropTypes.func.isRequired, }; export default CoinMenu; diff --git a/src/views/Wallet/components/LeftNavigation/components/common.js b/src/views/Wallet/components/LeftNavigation/components/common.js index c4c45ff4..8a6b9c0e 100644 --- a/src/views/Wallet/components/LeftNavigation/components/common.js +++ b/src/views/Wallet/components/LeftNavigation/components/common.js @@ -2,6 +2,7 @@ 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 { toggleDeviceDropdown } from 'actions/WalletActions'; import type { State } from 'flowtype'; @@ -25,6 +26,7 @@ export type DispatchProps = { duplicateDevice: typeof TrezorConnectActions.duplicateDevice, gotoDeviceSettings: typeof RouterActions.gotoDeviceSettings, onSelectDevice: typeof RouterActions.selectDevice, + gotoExternalWallet: typeof ModalActions.gotoExternalWallet, } export type Props = StateProps & DispatchProps; \ No newline at end of file From 0de30758a863349371d973a3809ef3fbd49d3bb6 Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Thu, 11 Oct 2018 15:05:14 +0200 Subject: [PATCH 09/11] Removed unnecessary component --- .../NemWallet/components/NemImage/index.js | 15 --------------- .../NemImage => }/images/nem-download.png | Bin .../modals/external/NemWallet/index.js | 10 ++++++++-- 3 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 src/components/modals/external/NemWallet/components/NemImage/index.js rename src/components/modals/external/NemWallet/{components/NemImage => }/images/nem-download.png (100%) diff --git a/src/components/modals/external/NemWallet/components/NemImage/index.js b/src/components/modals/external/NemWallet/components/NemImage/index.js deleted file mode 100644 index 2306c435..00000000 --- a/src/components/modals/external/NemWallet/components/NemImage/index.js +++ /dev/null @@ -1,15 +0,0 @@ -/* @flow */ - -import React from 'react'; -import styled from 'styled-components'; -import DownloadImg from './images/nem-download.png'; - -const Img = styled.img` - display: block; - width: 100%; - height: auto; -`; - -const NemImage = () => ; - -export default NemImage; diff --git a/src/components/modals/external/NemWallet/components/NemImage/images/nem-download.png b/src/components/modals/external/NemWallet/images/nem-download.png similarity index 100% rename from src/components/modals/external/NemWallet/components/NemImage/images/nem-download.png rename to src/components/modals/external/NemWallet/images/nem-download.png diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/NemWallet/index.js index 8e2fcc53..4835d79c 100644 --- a/src/components/modals/external/NemWallet/index.js +++ b/src/components/modals/external/NemWallet/index.js @@ -13,7 +13,7 @@ import Button from 'components/Button'; import { H3, H4 } from 'components/Heading'; import P from 'components/Paragraph'; -import NemImage from './components/NemImage'; +import NemImage from './images/nem-download.png'; import type { Props as BaseProps } from '../../Container'; type Props = { @@ -37,6 +37,12 @@ const StyledLink = styled(Link)` top: 10px; `; +const Img = styled.img` + display: block; + width: 100%; + height: auto; +`; + const NemWallet = (props: Props) => ( @@ -49,7 +55,7 @@ const NemWallet = (props: Props) => (

NEM Wallet

We have partnered up with the NEM Foundation to provide you with a full-fledged NEM Wallet.

Make sure you download the Universal Client for TREZOR support.

- + Go to nem.io
); From 9c19827a9c0ac214dc6a3b366b4fa0468a1dd5d0 Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Thu, 11 Oct 2018 15:18:01 +0200 Subject: [PATCH 10/11] Proper link for NEM coin --- src/components/modals/external/NemWallet/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/NemWallet/index.js index 4835d79c..468fe3a0 100644 --- a/src/components/modals/external/NemWallet/index.js +++ b/src/components/modals/external/NemWallet/index.js @@ -3,15 +3,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; - import colors from 'config/colors'; import icons from 'config/icons'; - import Icon from 'components/Icon'; import Link from 'components/Link'; import Button from 'components/Button'; import { H3, H4 } from 'components/Heading'; import P from 'components/Paragraph'; +import coins from 'constants/coins'; import NemImage from './images/nem-download.png'; import type { Props as BaseProps } from '../../Container'; @@ -56,7 +55,9 @@ const NemWallet = (props: Props) => (

We have partnered up with the NEM Foundation to provide you with a full-fledged NEM Wallet.

Make sure you download the Universal Client for TREZOR support.

- Go to nem.io + i.id === 'xem').url}> + Go to nem.io + ); From bc6ff28fe936171bdc68eacb53ca447302a67cac Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Thu, 11 Oct 2018 15:18:01 +0200 Subject: [PATCH 11/11] Added link for NEM coin --- src/components/modals/external/NemWallet/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/NemWallet/index.js index 4835d79c..468fe3a0 100644 --- a/src/components/modals/external/NemWallet/index.js +++ b/src/components/modals/external/NemWallet/index.js @@ -3,15 +3,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; - import colors from 'config/colors'; import icons from 'config/icons'; - import Icon from 'components/Icon'; import Link from 'components/Link'; import Button from 'components/Button'; import { H3, H4 } from 'components/Heading'; import P from 'components/Paragraph'; +import coins from 'constants/coins'; import NemImage from './images/nem-download.png'; import type { Props as BaseProps } from '../../Container'; @@ -56,7 +55,9 @@ const NemWallet = (props: Props) => (

We have partnered up with the NEM Foundation to provide you with a full-fledged NEM Wallet.

Make sure you download the Universal Client for TREZOR support.

- Go to nem.io + i.id === 'xem').url}> + Go to nem.io + );