From 3fe49a37489094daf107b6910b98019363268a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Mon, 16 Apr 2018 14:19:43 +0200 Subject: [PATCH 1/2] Better flow for modals Send --- package.json | 14 +- src/components/AccountPage/index.js | 29 +-- src/components/BalanceSummary/BalanceInfos.js | 8 +- .../__snapshots__/CounterValue.test.js.snap | 36 ++-- src/components/DashboardPage/AccountCard.js | 1 + .../OperationsList/ConfirmationCheck.js | 6 +- src/components/SelectAccount/index.js | 4 +- src/components/base/DropDown/index.js | 3 +- .../__snapshots__/FormattedVal.test.js.snap | 36 ++-- src/components/base/FormattedVal/index.js | 6 +- src/components/base/Modal/ModalBody.js | 4 +- src/components/modals/AddAccount/index.js | 2 +- src/components/modals/PrevButton.js | 37 ++++ src/components/modals/Receive/index.js | 25 +-- src/components/modals/Send/01-step-amount.js | 26 +-- .../modals/Send/02-step-connect-device.js | 37 ---- src/components/modals/Send/Footer.js | 4 +- src/components/modals/Send/index.js | 155 +++++++++++---- static/i18n/en/common.yml | 1 + static/i18n/en/send.yml | 1 + yarn.lock | 183 +++++++++++------- 21 files changed, 357 insertions(+), 261 deletions(-) create mode 100644 src/components/modals/PrevButton.js delete mode 100644 src/components/modals/Send/02-step-connect-device.js diff --git a/package.json b/package.json index cd10c37c..6cdaabba 100644 --- a/package.json +++ b/package.json @@ -59,16 +59,16 @@ "cross-env": "^5.1.4", "d3": "^5.0.0", "debug": "^3.1.0", - "downshift": "^1.31.6", + "downshift": "^1.31.7", "electron-store": "^1.3.0", "electron-updater": "^2.21.4", "fuse.js": "^3.2.0", "history": "^4.7.2", - "i18next": "^11.1.1", + "i18next": "^11.2.2", "i18next-node-fs-backend": "^1.0.0", "ledger-test-library": "KhalilBellakrid/ledger-test-library-nodejs#7d37482", "lodash": "^4.17.5", - "moment": "^2.22.0", + "moment": "^2.22.1", "object-path": "^0.11.4", "qrcode": "^1.2.0", "qs": "^6.5.1", @@ -77,7 +77,7 @@ "react": "^16.3.1", "react-dom": "^16.3.1", "react-flip-ticker": "^0.3.0", - "react-i18next": "^7.5.1", + "react-i18next": "^7.6.0", "react-mortal": "^3.2.0", "react-motion": "^0.5.2", "react-qr-reader": "^2.1.0", @@ -94,7 +94,7 @@ "source-map-support": "^0.5.4", "styled-components": "^3.2.5", "styled-system": "^2.2.1", - "tippy.js": "^2.5.0", + "tippy.js": "^2.5.2", "ws": "^5.1.1" }, "devDependencies": { @@ -111,7 +111,7 @@ "@storybook/addons": "^3.4.1", "@storybook/react": "^3.4.1", "babel-core": "7.0.0-bridge.0", - "babel-eslint": "^8.2.1", + "babel-eslint": "^8.2.3", "babel-jest": "^22.4.3", "babel-loader": "^8.0.0-beta.2", "babel-plugin-module-resolver": "^3.1.1", @@ -129,7 +129,7 @@ "eslint-config-airbnb": "^16.1.0", "eslint-config-prettier": "^2.9.0", "eslint-import-resolver-babel-module": "^5.0.0-beta.0", - "eslint-plugin-flowtype": "^2.46.0", + "eslint-plugin-flowtype": "^2.46.2", "eslint-plugin-import": "^2.11.0", "eslint-plugin-jsx-a11y": "^6.0.3", "eslint-plugin-react": "^7.7.0", diff --git a/src/components/AccountPage/index.js b/src/components/AccountPage/index.js index 6bc7a406..2ed8ab2a 100644 --- a/src/components/AccountPage/index.js +++ b/src/components/AccountPage/index.js @@ -85,7 +85,9 @@ class AccountPage extends PureComponent { return } - if (process.platform === 'darwin') { + if (process.platform === 'darwin' && this._cacheBalance !== data.totalBalance) { + this._cacheBalance = data.totalBalance + ipcRenderer.send('touch-bar-update', { text: account.name, color: account.currency.color, @@ -107,6 +109,8 @@ class AccountPage extends PureComponent { daysCount: item.value, }) + _cacheBalance = null + render() { const { account, openModal, t, counterValue } = this.props const { selectedTime, daysCount } = this.state @@ -117,7 +121,8 @@ class AccountPage extends PureComponent { } return ( - + // Force re-render account page, for avoid animation + @@ -153,17 +158,15 @@ class AccountPage extends PureComponent { - - - + + + {t('dashboard:totalBalance')} diff --git a/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap b/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap index bfecc4b5..668aee53 100644 --- a/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap +++ b/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap @@ -1,55 +1,55 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`components CounterValue basic 1`] = ` - + USD 10.00 - + `; exports[`components CounterValue specifying ticker different from default 1`] = ` - + USD 5.00 - + `; exports[`components CounterValue using countervalue different from default 1`] = ` - + EUR 0.42 - + `; exports[`components CounterValue with time travel whith date in countervalues 1`] = ` - + USD 20.00 - + `; exports[`components CounterValue with time travel whith date not in countervalues 1`] = ` - + USD 0.00 - + `; exports[`components CounterValue without countervalues populated 1`] = ` - + USD 0.00 - + `; diff --git a/src/components/DashboardPage/AccountCard.js b/src/components/DashboardPage/AccountCard.js index 4f95e7c8..efbd3d72 100644 --- a/src/components/DashboardPage/AccountCard.js +++ b/src/components/DashboardPage/AccountCard.js @@ -72,6 +72,7 @@ const AccountCard = ({ ( - {type === 'from' ? : } + {type === 'from' ? : } {!isConfirmed && ( diff --git a/src/components/SelectAccount/index.js b/src/components/SelectAccount/index.js index 2d098ecd..c08ac6bc 100644 --- a/src/components/SelectAccount/index.js +++ b/src/components/SelectAccount/index.js @@ -5,8 +5,8 @@ import { connect } from 'react-redux' import { translate } from 'react-i18next' import noop from 'lodash/noop' import { getIconByCoinType } from '@ledgerhq/currencies/react' -import type { Account } from '@ledgerhq/wallet-common/lib/types' +import type { Account } from '@ledgerhq/wallet-common/lib/types' import type { T } from 'types/common' import { getVisibleAccounts } from 'reducers/accounts' @@ -56,7 +56,7 @@ const RawSelectAccount = ({ accounts, onChange, value, t, ...props }: Props) => renderSelected={renderItem} renderItem={renderItem} keyProp="id" - items={accounts} + items={accounts.sort((a, b) => (a.name < b.name ? -1 : 1))} placeholder={t('common:selectAccount')} fontSize={4} onChange={onChange} diff --git a/src/components/base/DropDown/index.js b/src/components/base/DropDown/index.js index 3d8a076b..58b655bc 100644 --- a/src/components/base/DropDown/index.js +++ b/src/components/base/DropDown/index.js @@ -19,8 +19,9 @@ const Drop = styled(Box).attrs({ p: 2, })` position: absolute; - top: 100%; right: 0; + top: 100%; + z-index: 1; ` export const DropDownItem = styled(Box).attrs({ diff --git a/src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap b/src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap index b4a3a0c6..d785fdf0 100644 --- a/src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap +++ b/src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap @@ -1,55 +1,55 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`components FormattedVal renders a fiat 1`] = ` - 20.00 - + `; exports[`components FormattedVal renders a formatted val 1`] = ` - 4 - + `; exports[`components FormattedVal renders a percent 1`] = ` - 30 % - + `; exports[`components FormattedVal shows code 1`] = ` - BTC 4 - + `; exports[`components FormattedVal shows sign 1`] = ` - + 4 - + `; exports[`components FormattedVal shows sign 2`] = ` - - 4 - + `; diff --git a/src/components/base/FormattedVal/index.js b/src/components/base/FormattedVal/index.js index be2f2ca7..8ad251b6 100644 --- a/src/components/base/FormattedVal/index.js +++ b/src/components/base/FormattedVal/index.js @@ -11,13 +11,13 @@ import type { Unit } from '@ledgerhq/currencies' import { formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies' import Box from 'components/base/Box' -import Text from 'components/base/Text' import IconBottom from 'icons/Bottom' import IconTop from 'icons/Top' -const T = styled(Text).attrs({ +const T = styled(Box).attrs({ ff: 'Rubik', + horizontal: true, color: p => p.withIcon ? p.theme.colors.dark @@ -25,7 +25,7 @@ const T = styled(Text).attrs({ ? p.theme.colors.alertRed : p.theme.colors.positiveGreen, })` - line-height: 0.9; + line-height: 1.2; white-space: pre; ` diff --git a/src/components/base/Modal/ModalBody.js b/src/components/base/Modal/ModalBody.js index 7ae128b1..17d9fa8b 100644 --- a/src/components/base/Modal/ModalBody.js +++ b/src/components/base/Modal/ModalBody.js @@ -73,7 +73,9 @@ const Body = styled(Box).attrs({ bg: p => p.theme.colors.white, relative: true, borderRadius: 1, -})`` +})` + box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.2); +` const appear = keyframes` from { opacity: 0; } diff --git a/src/components/modals/AddAccount/index.js b/src/components/modals/AddAccount/index.js index 062eb561..5287335f 100644 --- a/src/components/modals/AddAccount/index.js +++ b/src/components/modals/AddAccount/index.js @@ -362,7 +362,7 @@ class AddAccountModal extends PureComponent { {t('addAccount:title')} - + {this.renderStep()} {stepIndex !== 2 && ( diff --git a/src/components/modals/PrevButton.js b/src/components/modals/PrevButton.js new file mode 100644 index 00000000..1b0d8313 --- /dev/null +++ b/src/components/modals/PrevButton.js @@ -0,0 +1,37 @@ +// @flow + +import React from 'react' +import { translate } from 'react-i18next' +import styled from 'styled-components' + +import type { T } from 'types/common' + +import Button from 'components/base/Button' +import Box from 'components/base/Box' + +import IconAngleLeft from 'icons/AngleLeft' + +const Wrapper = styled(Button).attrs({ + fontSize: 4, + ml: 4, +})` + left: 0; + margin-top: -18px; + position: absolute; + top: 50%; +` + +type Props = { + t: T, +} + +const PrevButton = ({ t, ...props }: Props) => ( + + + + {t('common:back')} + + +) + +export default translate()(PrevButton) diff --git a/src/components/modals/Receive/index.js b/src/components/modals/Receive/index.js index 885aa343..da74514f 100644 --- a/src/components/modals/Receive/index.js +++ b/src/components/modals/Receive/index.js @@ -1,7 +1,6 @@ // @flow import React, { Fragment, PureComponent } from 'react' -import styled from 'styled-components' import { translate } from 'react-i18next' import type { Account } from '@ledgerhq/wallet-common/lib/types' @@ -10,25 +9,16 @@ import type { T, Device } from 'types/common' import { MODAL_RECEIVE } from 'config/constants' import Box from 'components/base/Box' -import Button from 'components/base/Button' import Breadcrumb from 'components/Breadcrumb' +import Button from 'components/base/Button' import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' +import PrevButton from 'components/modals/PrevButton' import StepConnectDevice from 'components/modals/StepConnectDevice' -import IconAngleLeft from 'icons/AngleLeft' - import StepAccount from './01-step-account' import StepConfirmAddress from './03-step-confirm-address' import StepReceiveFunds from './04-step-receive-funds' -const PrevButton = styled(Button).attrs({ - fontSize: 4, - ml: 4, -})` - position: absolute; - left: 0; -` - type Props = { t: T, } @@ -286,19 +276,12 @@ class ReceiveModal extends PureComponent { render={({ onClose }) => ( - {canPrev && ( - - - - {t('common:back')} - - - )} + {canPrev && } {t('receive:title')} - {/* RECIPIENT ADDRESS */} - - - - - {account && ( + {/* RECIPIENT ADDRESS */} + + + + + {/* AMOUNT */} diff --git a/src/components/modals/Send/02-step-connect-device.js b/src/components/modals/Send/02-step-connect-device.js deleted file mode 100644 index 930b0ccd..00000000 --- a/src/components/modals/Send/02-step-connect-device.js +++ /dev/null @@ -1,37 +0,0 @@ -// @flow - -import React from 'react' -import type { Account } from '@ledgerhq/wallet-common/lib/types' - -import DeviceMonit from 'components/DeviceMonit' - -type Props = { - account: Account | null, - isDeviceReady: boolean, - onChange: Function, -} - -function StepConnectDevice(props: Props) { - const { account, isDeviceReady, onChange } = props - const setReady = onChange('isDeviceReady') - if (!account) { - return null - } - return ( -
- { - if (status === 'appOpened' && !isDeviceReady) { - setReady(true) - } - if (status !== 'appOpened' && isDeviceReady) { - setReady(false) - } - }} - /> -
- ) -} - -export default StepConnectDevice diff --git a/src/components/modals/Send/Footer.js b/src/components/modals/Send/Footer.js index 141aae1b..978da05a 100644 --- a/src/components/modals/Send/Footer.js +++ b/src/components/modals/Send/Footer.js @@ -7,10 +7,10 @@ import type { T } from 'types/common' import { ModalFooter } from 'components/base/Modal' import Box from 'components/base/Box' -import Label from 'components/base/Label' +import Button from 'components/base/Button' import CounterValue from 'components/CounterValue' import FormattedVal from 'components/base/FormattedVal' -import Button from 'components/base/Button' +import Label from 'components/base/Label' import Text from 'components/base/Text' type Props = { diff --git a/src/components/modals/Send/index.js b/src/components/modals/Send/index.js index d74be19c..3719eac8 100644 --- a/src/components/modals/Send/index.js +++ b/src/components/modals/Send/index.js @@ -2,35 +2,42 @@ import React, { PureComponent } from 'react' import { translate } from 'react-i18next' -import get from 'lodash/get' +import { connect } from 'react-redux' +import { compose } from 'redux' + import type { Account } from '@ledgerhq/wallet-common/lib/types' +import type { T, Device } from 'types/common' -import type { T } from 'types/common' +import { getVisibleAccounts } from 'reducers/accounts' import { MODAL_SEND } from 'config/constants' import Breadcrumb from 'components/Breadcrumb' -import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal' +import Button from 'components/base/Button' +import Modal, { ModalFooter, ModalBody, ModalTitle, ModalContent } from 'components/base/Modal' +import PrevButton from 'components/modals/PrevButton' +import StepConnectDevice from 'components/modals/StepConnectDevice' import Footer from './Footer' import StepAmount from './01-step-amount' -import StepConnectDevice from './02-step-connect-device' import StepVerification from './03-step-verification' import StepConfirmation from './04-step-confirmation' type Props = { + accounts: Account[], t: T, } type State = { - stepIndex: number, - isDeviceReady: boolean, + account: Account | null, amount: number, + appStatus: null | string, + deviceSelected: Device | null, fees: number, - account: Account | null, - recipientAddress: string, isRBF: boolean, + recipientAddress: string, + stepIndex: number, } const GET_STEPS = t => [ @@ -40,35 +47,52 @@ const GET_STEPS = t => [ { label: t('send:steps.confirmation.title'), Comp: StepConfirmation }, ] +const mapStateToProps = state => ({ + accounts: getVisibleAccounts(state).sort((a, b) => (a.name < b.name ? -1 : 1)), +}) + const INITIAL_STATE = { - stepIndex: 0, - isDeviceReady: false, account: null, - recipientAddress: '', amount: 0, + appStatus: null, + deviceSelected: null, fees: 0, isRBF: false, + recipientAddress: '', + stepIndex: 0, } class SendModal extends PureComponent { state = INITIAL_STATE - _steps = GET_STEPS(this.props.t) _account: Account | null = null + _steps = GET_STEPS(this.props.t) - canNext = account => { + canNext = () => { const { stepIndex } = this.state - // informations if (stepIndex === 0) { const { amount, recipientAddress } = this.state - return !!amount && !!recipientAddress && !!account + return !!amount && !!recipientAddress && !!this._account + } + + if (stepIndex === 1) { + const { deviceSelected, appStatus } = this.state + return deviceSelected !== null && appStatus === 'success' } - // connect device + if (stepIndex === 2) { + return true + } + + return false + } + + canPrev = () => { + const { stepIndex } = this.state + if (stepIndex === 1) { - const { isDeviceReady } = this.state - return !!isDeviceReady + return true } return false @@ -84,6 +108,34 @@ class SendModal extends PureComponent { this.setState({ stepIndex: stepIndex + 1 }) } + handleChangeDevice = d => this.setState({ deviceSelected: d }) + + handleChangeStatus = (deviceStatus, appStatus) => this.setState({ appStatus }) + + handlePrevStep = () => { + const { stepIndex } = this.state + + let newStepIndex + + switch (stepIndex) { + default: + case 1: + newStepIndex = 0 + break + + case 2: + case 3: + newStepIndex = 1 + break + } + + this.setState({ + appStatus: null, + deviceSelected: null, + stepIndex: newStepIndex, + }) + } + createChangeHandler = key => value => { const patch = { [key]: value } // ensure max is always restecped when changing fees @@ -104,54 +156,75 @@ class SendModal extends PureComponent { this.setState(patch) } - renderStep = acc => { - const { stepIndex, account, amount, ...othersState } = this.state + renderStep = () => { + const { t } = this.props + const { stepIndex, amount, deviceSelected, ...otherState } = this.state const step = this._steps[stepIndex] if (!step) { return null } const { Comp } = step + + const props = (predicate, props) => (predicate ? props : {}) + const stepProps = { - ...othersState, + ...otherState, + t, amount, - account: account || acc, + account: this._account, + ...props(stepIndex === 1, { + accountName: this._account ? this._account.name : undefined, + deviceSelected, + onChangeDevice: this.handleChangeDevice, + onStatusChange: this.handleChangeStatus, + }), } return } render() { - const { t } = this.props + const { accounts, t } = this.props const { stepIndex, amount, account, fees } = this.state + const canNext = this.canNext() + const canPrev = this.canPrev() + return ( { - const acc = account || get(data, 'account', null) - const canNext = this.canNext(acc) - // hack: access the selected account, living in modal data, outside // of the modal render function - this._account = acc + this._account = account || (data && data.account) || accounts[0] return ( - - {t('send:title')} + + + {canPrev && } + {t('send:title')} + - - {this.renderStep(acc)} + + {this.renderStep()} - {acc && ( -