From a623c1eea5c91bc35df442dc20739e42685fe583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Mon, 9 Apr 2018 17:25:30 +0200 Subject: [PATCH] Add CurrentAddress component --- package.json | 6 +- src/components/Breadcrumb/Step.js | 3 +- src/components/CurrentAddress/index.js | 102 ++++++++++++ src/components/CurrentAddress/stories.js | 19 +++ src/components/DeviceCheckAddress.js | 69 ++++++++ src/components/DeviceConfirm/index.js | 37 ++--- src/components/RequestAmount/index.js | 74 ++++++--- src/components/RequestAmount/stories.js | 11 +- src/components/SelectAccount/index.js | 5 +- src/components/SelectAccount/stories.js | 5 +- src/components/base/InputCurrency/index.js | 5 +- src/components/base/Modal/index.js | 6 +- src/components/base/Select/index.js | 109 +++++++------ src/components/base/Select/stories.js | 2 + src/components/modals/AddAccount/index.js | 2 +- .../modals/Receive/03-step-confirm-address.js | 48 +++--- .../modals/Receive/04-step-receive-funds.js | 44 +++++ src/components/modals/Receive/index.js | 152 +++++++++++++++--- src/components/modals/Send/index.js | 2 +- src/icons/AngleLeft.js | 12 ++ static/i18n/en/common.yml | 1 + static/i18n/en/currentAddress.yml | 1 + static/i18n/en/receive.yml | 2 +- yarn.lock | 36 ++--- 24 files changed, 568 insertions(+), 185 deletions(-) create mode 100644 src/components/CurrentAddress/index.js create mode 100644 src/components/CurrentAddress/stories.js create mode 100644 src/components/DeviceCheckAddress.js create mode 100644 src/components/modals/Receive/04-step-receive-funds.js create mode 100644 src/icons/AngleLeft.js create mode 100644 static/i18n/en/currentAddress.yml diff --git a/package.json b/package.json index 12b72da0..36e51a4b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@ledgerhq/hw-app-eth": "^4.7.3", "@ledgerhq/hw-transport": "^4.7.3", "@ledgerhq/hw-transport-node-hid": "^4.7.6", - "@ledgerhq/wallet-common": "^0.13.0", + "@ledgerhq/wallet-common": "^0.13.2", "axios": "^0.18.0", "bcryptjs": "^2.4.3", "bitcoinjs-lib": "^3.3.2", @@ -71,8 +71,8 @@ "object-path": "^0.11.4", "qrcode": "^1.2.0", "query-string": "^6.0.0", - "raven": "^2.4.2", - "raven-js": "^3.24.0", + "raven": "^2.5.0", + "raven-js": "^3.24.1", "react": "^16.3.1", "react-dom": "^16.3.1", "react-i18next": "^7.5.1", diff --git a/src/components/Breadcrumb/Step.js b/src/components/Breadcrumb/Step.js index 5e444fcb..c0adc53b 100644 --- a/src/components/Breadcrumb/Step.js +++ b/src/components/Breadcrumb/Step.js @@ -29,7 +29,6 @@ const Number = styled(Box).attrs({ font-size: 10px; height: ${RADIUS}px; line-height: 10px; - padding-left: 1px; transition: all ease-in-out 0.1s ${p => (p.isActive ? 0.4 : 0)}s; width: ${RADIUS}px; ` @@ -60,7 +59,7 @@ const Bar = styled.div` const Label = styled(Box).attrs({ fontSize: 3, - ff: 'Museo Sans|Regular', + ff: 'Museo Sans|Bold', })` position: absolute; margin-top: 27px; diff --git a/src/components/CurrentAddress/index.js b/src/components/CurrentAddress/index.js new file mode 100644 index 00000000..86ce82d8 --- /dev/null +++ b/src/components/CurrentAddress/index.js @@ -0,0 +1,102 @@ +// @flow + +import React, { PureComponent } from 'react' +import { translate } from 'react-i18next' +import styled from 'styled-components' + +import type { Account } from '@ledgerhq/wallet-common/lib/types' +import type { T } from 'types/common' + +import { rgba } from 'styles/helpers' + +import Box from 'components/base/Box' +import QRCode from 'components/base/QRCode' + +import IconInfoCircle from 'icons/InfoCircle' + +const Container = styled(Box).attrs({ + borderRadius: 1, + alignItems: 'center', + bg: p => (p.withQRCode ? 'lightGrey' : 'transparent'), + p: 5, +})`` + +const WrapperAddress = styled(Box).attrs({ + alignItems: 'center', + borderRadius: 1, + py: p => (p.notValid ? 4 : 0), + px: 4, +})` + background: ${p => (p.notValid ? rgba(p.theme.colors.alertRed, 0.05) : 'transparent')}; + border: ${p => (p.notValid ? `1px dashed ${rgba(p.theme.colors.alertRed, 0.26)}` : 'none')}; +` + +const Address = styled(Box).attrs({ + bg: p => (p.notValid ? 'transparent' : p.withQRCode ? 'white' : 'lightGrey'), + borderRadius: 1, + color: 'dark', + ff: 'Open Sans|SemiBold', + fontSize: 4, + mt: 2, + px: p => (p.notValid ? 0 : 4), + py: p => (p.notValid ? 0 : 3), +})` + border: ${p => (p.notValid ? 'none' : `1px dashed ${p.theme.colors.fog}`)}; + cursor: text; + user-select: text; +` + +const Label = styled(Box).attrs({ + alignItems: 'center', + color: 'graphite', + ff: 'Open Sans|SemiBold', + fontSize: 4, + flow: 1, + horizontal: true, +})`` + +type Props = { + account: Account, + addressVerified: null | boolean, + amount: null | string, + t: T, + withQRCode: boolean, +} + +class CurrentAddress extends PureComponent { + static defaultProps = { + addressVerified: null, + amount: null, + withQRCode: false, + } + + render() { + const { amount, account, t, addressVerified, withQRCode } = this.props + + const notValid = addressVerified === false + + return ( + + + {withQRCode && ( + + + + )} + +
+ {account.address} +
+
+
+ ) + } +} + +export default translate()(CurrentAddress) diff --git a/src/components/CurrentAddress/stories.js b/src/components/CurrentAddress/stories.js new file mode 100644 index 00000000..69837cae --- /dev/null +++ b/src/components/CurrentAddress/stories.js @@ -0,0 +1,19 @@ +// @flow + +import React from 'react' +import { storiesOf } from '@storybook/react' +import { boolean } from '@storybook/addon-knobs' + +import CurrentAddress from 'components/CurrentAddress' + +import { accounts } from 'components/SelectAccount/stories' + +const stories = storiesOf('Components', module) + +stories.add('CurrentAddress', () => ( + +)) diff --git a/src/components/DeviceCheckAddress.js b/src/components/DeviceCheckAddress.js new file mode 100644 index 00000000..1d9beff5 --- /dev/null +++ b/src/components/DeviceCheckAddress.js @@ -0,0 +1,69 @@ +// @flow + +import { PureComponent } from 'react' +import { ipcRenderer } from 'electron' + +import type { Account } from '@ledgerhq/wallet-common/lib/types' +import type { Device } from 'types/common' + +import { sendEvent } from 'renderer/events' + +type Props = { + onCheck: Function, + render: Function, + account: Account, + device: Device, +} + +type State = { + isVerified: null | boolean, +} + +class CheckAddress extends PureComponent { + state = { + isVerified: null, + } + + componentDidMount() { + const { device, account } = this.props + ipcRenderer.on('msg', this.handleMsgEvent) + this.verifyAddress({ device, account }) + } + + componentWillUnmount() { + ipcRenderer.removeListener('msg', this.handleMsgEvent) + } + + handleMsgEvent = (e: any, { type }: { type: string }) => { + const { onCheck } = this.props + + if (type === 'wallet.verifyAddress.success') { + this.setState({ + isVerified: true, + }) + onCheck(true) + } + + if (type === 'wallet.verifyAddress.fail') { + this.setState({ + isVerified: false, + }) + onCheck(false) + } + } + + verifyAddress = ({ device, account }: { device: Device, account: Account }) => + sendEvent('usb', 'wallet.verifyAddress', { + pathDevice: device.path, + path: `${account.rootPath}${account.path}`, + }) + + render() { + const { render } = this.props + const { isVerified } = this.state + + return render({ isVerified }) + } +} + +export default CheckAddress diff --git a/src/components/DeviceConfirm/index.js b/src/components/DeviceConfirm/index.js index ae65f243..4d2f8f41 100644 --- a/src/components/DeviceConfirm/index.js +++ b/src/components/DeviceConfirm/index.js @@ -15,7 +15,7 @@ const pulseAnimation = p => keyframes` box-shadow: 0 0 0 1px ${rgba(p.theme.colors.wallet, 0.4)}; } 70% { - box-shadow: 0 0 0 8px ${rgba(p.theme.colors.wallet, 0)}; + box-shadow: 0 0 0 6px ${rgba(p.theme.colors.wallet, 0)}; } 100% { box-shadow: 0 0 0 0 ${rgba(p.theme.colors.wallet, 0)}; @@ -26,7 +26,7 @@ const Wrapper = styled(Box).attrs({ color: p => (p.notValid ? 'alertRed' : 'wallet'), relative: true, })` - padding-top: 40px; + padding-top: ${p => (p.notValid ? 0 : 30)}px; transition: all ease-in-out 0.1s; ` @@ -55,40 +55,29 @@ const PushButton = styled(Box)` position: absolute; width: 1px; - &:before, - &:after { + &:before { + animation: ${p => pulseAnimation(p)} 1s linear infinite; + background-color: ${p => p.theme.colors.wallet}; border-radius: 50%; bottom: 0; + box-sizing: border-box; content: ' '; display: block; - position: absolute; - left: 50%; - } - - &:before { - animation: ${p => pulseAnimation(p)} 1s linear infinite; - background-color: ${p => p.theme.colors.wallet}; height: 9px; + left: 50%; margin-bottom: -4px; - margin-left: -5px; + margin-left: -4px; + position: absolute; width: 9px; z-index: 1; } - - &:after { - background-color: ${p => rgba(p.theme.colors.wallet, 0.4)}; - height: 15px; - margin-bottom: -7px; - margin-left: -8px; - width: 15px; - } ` type Props = { notValid: boolean, } -export default (props: Props) => ( +const DeviceConfirm = (props: Props) => ( {!props.notValid && } @@ -183,3 +172,9 @@ export default (props: Props) => ( ) + +DeviceConfirm.defaultProps = { + notValid: false, +} + +export default DeviceConfirm diff --git a/src/components/RequestAmount/index.js b/src/components/RequestAmount/index.js index d8dcd796..b037f75f 100644 --- a/src/components/RequestAmount/index.js +++ b/src/components/RequestAmount/index.js @@ -64,9 +64,17 @@ type Props = { // used to calculate the opposite field value (right & left) getCounterValue: CalculateCounterValue, getReverseCounterValue: CalculateCounterValue, + + // display max button + withMax: boolean, } export class RequestAmount extends PureComponent { + static defaultProps = { + max: Infinity, + withMax: true, + } + handleClickMax = () => { this.props.onChange(this.props.max) } @@ -81,35 +89,49 @@ export class RequestAmount extends PureComponent { } } - render() { - const { t, value, account, rightUnit, getCounterValue } = this.props + renderInputs(containerProps: Object) { + const { value, account, rightUnit, getCounterValue } = this.props const right = getCounterValue(account.currency, rightUnit)(value) return ( - - - {account.unit.code}} - /> - = - {rightUnit.code}} - showAllDigits - /> - - - - + + {account.unit.code}} + /> + = + {rightUnit.code}} + showAllDigits + /> + + ) + } + + render() { + const { withMax, t } = this.props + + return ( + + {withMax ? ( + {this.renderInputs({ style: { width: 156 } })} + ) : ( + this.renderInputs({ grow: true }) + )} + {withMax && ( + + + + )} ) } diff --git a/src/components/RequestAmount/stories.js b/src/components/RequestAmount/stories.js index d4aa88a4..049968ab 100644 --- a/src/components/RequestAmount/stories.js +++ b/src/components/RequestAmount/stories.js @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react' import { storiesOf } from '@storybook/react' +import { text, boolean } from '@storybook/addon-knobs' import { action } from '@storybook/addon-actions' import { accounts } from 'components/SelectAccount/stories' @@ -23,17 +24,21 @@ class Wrapper extends PureComponent { this.setState({ value }) } render() { + const { max, withMax } = this.props const { value } = this.state return ( ) } } -stories.add('RequestAmount', () => ) +stories.add('RequestAmount', () => ( + +)) diff --git a/src/components/SelectAccount/index.js b/src/components/SelectAccount/index.js index cc1f322f..2d098ecd 100644 --- a/src/components/SelectAccount/index.js +++ b/src/components/SelectAccount/index.js @@ -24,7 +24,7 @@ const renderItem = a => { const Icon = getIconByCoinType(a.coinType) const { color } = a.currency return ( - + {Icon && ( @@ -49,8 +49,9 @@ type Props = { t: T, } -const RawSelectAccount = ({ accounts, onChange, value, t }: Props) => ( +const RawSelectAccount = ({ accounts, onChange, value, t, ...props }: Props) => ( - - - + + } + /> ) : ( - {selectedItem && renderSelected ? ( - renderSelected(selectedItem) - ) : ( - {placeholder} - )} + {renderSelectedItem({ selectedItem, renderSelected, placeholder })} - - - + )} {isOpen && diff --git a/src/components/base/Select/stories.js b/src/components/base/Select/stories.js index ba750dd5..75b0c414 100644 --- a/src/components/base/Select/stories.js +++ b/src/components/base/Select/stories.js @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react' import { storiesOf } from '@storybook/react' +import { boolean } from '@storybook/addon-knobs' import Box from 'components/base/Box' import Select from 'components/base/Select' @@ -57,6 +58,7 @@ stories.add('basic', () => ( {onChange => (