Browse Source

Add CurrentAddress component

master
Loëck Vézien 7 years ago
parent
commit
a623c1eea5
No known key found for this signature in database GPG Key ID: CBCDCE384E853AC4
  1. 6
      package.json
  2. 3
      src/components/Breadcrumb/Step.js
  3. 102
      src/components/CurrentAddress/index.js
  4. 19
      src/components/CurrentAddress/stories.js
  5. 69
      src/components/DeviceCheckAddress.js
  6. 37
      src/components/DeviceConfirm/index.js
  7. 74
      src/components/RequestAmount/index.js
  8. 11
      src/components/RequestAmount/stories.js
  9. 5
      src/components/SelectAccount/index.js
  10. 5
      src/components/SelectAccount/stories.js
  11. 5
      src/components/base/InputCurrency/index.js
  12. 6
      src/components/base/Modal/index.js
  13. 109
      src/components/base/Select/index.js
  14. 2
      src/components/base/Select/stories.js
  15. 2
      src/components/modals/AddAccount/index.js
  16. 48
      src/components/modals/Receive/03-step-confirm-address.js
  17. 44
      src/components/modals/Receive/04-step-receive-funds.js
  18. 152
      src/components/modals/Receive/index.js
  19. 2
      src/components/modals/Send/index.js
  20. 12
      src/icons/AngleLeft.js
  21. 1
      static/i18n/en/common.yml
  22. 1
      static/i18n/en/currentAddress.yml
  23. 2
      static/i18n/en/receive.yml
  24. 36
      yarn.lock

6
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",

3
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;

102
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<Props> {
static defaultProps = {
addressVerified: null,
amount: null,
withQRCode: false,
}
render() {
const { amount, account, t, addressVerified, withQRCode } = this.props
const notValid = addressVerified === false
return (
<Container withQRCode={withQRCode} notValid={notValid}>
<WrapperAddress notValid={notValid}>
{withQRCode && (
<Box mb={4}>
<QRCode
size={150}
data={`bitcoin:${account.address}${amount ? `?amount=${amount}` : ''}`}
/>
</Box>
)}
<Label>
<Box>{t('currentAddress:label')}</Box>
<IconInfoCircle size={12} />
</Label>
<Address withQRCode={withQRCode} notValid={notValid}>
{account.address}
</Address>
</WrapperAddress>
</Container>
)
}
}
export default translate()(CurrentAddress)

19
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', () => (
<CurrentAddress
addressVerified={boolean('addressVerified', true)}
account={accounts[0]}
withQRCode={boolean('withQRCode', false)}
/>
))

69
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<Props, State> {
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

37
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) => (
<Wrapper {...props}>
{!props.notValid && <PushButton />}
<Check notValid={props.notValid} />
@ -183,3 +172,9 @@ export default (props: Props) => (
</svg>
</Wrapper>
)
DeviceConfirm.defaultProps = {
notValid: false,
}
export default DeviceConfirm

74
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<Props> {
static defaultProps = {
max: Infinity,
withMax: true,
}
handleClickMax = () => {
this.props.onChange(this.props.max)
}
@ -81,35 +89,49 @@ export class RequestAmount extends PureComponent<Props> {
}
}
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 (
<Box horizontal flow="5">
<Box horizontal align="center">
<InputCurrency
containerProps={{ style: { width: 156 } }}
unit={account.unit}
value={value}
onChange={this.handleChangeAmount('left')}
renderRight={<InputRight>{account.unit.code}</InputRight>}
/>
<InputCenter>=</InputCenter>
<InputCurrency
containerProps={{ style: { width: 156 } }}
unit={rightUnit}
value={right}
onChange={this.handleChangeAmount('right')}
renderRight={<InputRight>{rightUnit.code}</InputRight>}
showAllDigits
/>
</Box>
<Box grow justify="flex-end">
<Button primary onClick={this.handleClickMax}>
{t('common:max')}
</Button>
</Box>
<Box horizontal grow shrink>
<InputCurrency
containerProps={containerProps}
unit={account.unit}
value={value}
onChange={this.handleChangeAmount('left')}
renderRight={<InputRight>{account.unit.code}</InputRight>}
/>
<InputCenter>=</InputCenter>
<InputCurrency
containerProps={containerProps}
unit={rightUnit}
value={right}
onChange={this.handleChangeAmount('right')}
renderRight={<InputRight>{rightUnit.code}</InputRight>}
showAllDigits
/>
</Box>
)
}
render() {
const { withMax, t } = this.props
return (
<Box horizontal flow={5} alignItems="center">
{withMax ? (
<Box horizontal>{this.renderInputs({ style: { width: 156 } })}</Box>
) : (
this.renderInputs({ grow: true })
)}
{withMax && (
<Box grow justify="flex-end">
<Button primary onClick={this.handleClickMax}>
{t('common:max')}
</Button>
</Box>
)}
</Box>
)
}

11
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<any, State> {
this.setState({ value })
}
render() {
const { max, withMax } = this.props
const { value } = this.state
return (
<RequestAmount
counterValue="USD"
account={accounts[0]}
counterValue="USD"
max={max}
onChange={this.handleChange}
value={value}
max={4e8}
withMax={withMax}
/>
)
}
}
stories.add('RequestAmount', () => <Wrapper />)
stories.add('RequestAmount', () => (
<Wrapper withMax={boolean('withMax', true)} max={Number(text('max', '4e8'))} />
))

5
src/components/SelectAccount/index.js

@ -24,7 +24,7 @@ const renderItem = a => {
const Icon = getIconByCoinType(a.coinType)
const { color } = a.currency
return (
<Box horizontal alignItems="center" flow={2}>
<Box grow horizontal alignItems="center" flow={2}>
{Icon && (
<Box style={{ width: 16, height: 16, color }}>
<Icon size={16} />
@ -49,8 +49,9 @@ type Props = {
t: T,
}
const RawSelectAccount = ({ accounts, onChange, value, t }: Props) => (
const RawSelectAccount = ({ accounts, onChange, value, t, ...props }: Props) => (
<Select
{...props}
value={value && accounts.find(a => value && a.id === value.id)}
renderSelected={renderItem}
renderItem={renderItem}

5
src/components/SelectAccount/stories.js

@ -12,7 +12,10 @@ const stories = storiesOf('Components', module)
export const accounts = [...Array(20)].map(() => ({
id: chance.string(),
address: chance.string(),
address: chance.string({
pool: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
length: 30,
}),
addresses: [],
balance: chance.integer({ min: 10000000000, max: 2000000000000 }),
balanceByDay: {},

5
src/components/base/InputCurrency/index.js

@ -68,7 +68,7 @@ class InputCurrency extends PureComponent<Props, State> {
unit: this.props.unit,
}
componentWillMount() {
componentDidMount() {
this.syncInput({ isFocused: false })
}
@ -125,7 +125,8 @@ class InputCurrency extends PureComponent<Props, State> {
const { unit } = this.state
this.setState({
isFocused,
displayValue: value === 0 ? '' : format(unit, value, { isFocused, showAllDigits }),
displayValue:
value === '' || value === 0 ? '' : format(unit, value, { isFocused, showAllDigits }),
})
}

6
src/components/base/Modal/index.js

@ -187,11 +187,13 @@ export class Modal extends Component<Props> {
}
export const ModalTitle = styled(Box).attrs({
alignItems: 'center',
color: 'dark',
ff: 'Museo Sans|Regular',
fontSize: 6,
color: 'dark',
align: 'center',
justifyContent: 'center',
p: 5,
relative: true,
})``
export const ModalFooter = styled(Box).attrs({

109
src/components/base/Select/index.js

@ -14,7 +14,6 @@ import Search from 'components/base/Search'
import Text from 'components/base/Text'
import IconCheck from 'icons/Check'
import IconAngleDown from 'icons/AngleDown'
type Props = {
bg?: string,
@ -34,32 +33,40 @@ type Props = {
renderSelected?: any => Element<*>,
searchable?: boolean,
value?: Object | null,
disabled: boolean,
}
const Container = styled(Box).attrs({ relative: true, color: 'graphite' })``
const TriggerBtn = styled(Box).attrs({
alignItems: 'center',
ff: 'Open Sans|SemiBold',
flow: 2,
fontSize: 4,
pl: 3,
pr: 5,
horizontal: true,
px: 3,
})`
${space};
height: 40px;
background: ${p => p.bg || p.theme.colors.white};
background: ${p => (p.disabled ? p.theme.colors.lightGrey : p.bg || p.theme.colors.white)};
border-bottom-left-radius: ${p => (p.flatLeft ? 0 : p.theme.radii[1])}px;
border-bottom-right-radius: ${p => (p.flatRight ? 0 : p.theme.radii[1])}px;
border-top-left-radius: ${p => (p.flatLeft ? 0 : p.theme.radii[1])}px;
border-top-right-radius: ${p => (p.flatRight ? 0 : p.theme.radii[1])}px;
border: 1px solid ${p => p.theme.colors.fog};
color: ${p => p.theme.colors.graphite};
cursor: pointer;
cursor: ${p => (p.disabled ? 'cursor' : 'pointer')};
display: flex;
width: 100%;
&:focus {
outline: none;
border-color: ${p => p.theme.colors.wallet};
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;
${p =>
p.disabled
? ''
: `
border-color: ${p.theme.colors.wallet};
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;`};
}
${p => {
@ -110,22 +117,6 @@ const Dropdown = styled(Box).attrs({
z-index: 1;
`
const FloatingDown = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
mr: 2,
})`
position: absolute;
top: 0;
right: 0;
bottom: 0;
color: ${p => p.theme.colors.grey};
// to "simulate" border to make arrows appears at the exact same place as
// the no-input version
padding-right: 1px;
`
const IconSelected = styled(Box).attrs({
color: 'wallet',
alignItems: 'center',
@ -136,12 +127,31 @@ const IconSelected = styled(Box).attrs({
opacity: ${p => (p.selected ? 1 : 0)};
`
const AngleDown = props => (
<Box color="grey" alignItems="center" justifyContent="center" {...props}>
<svg viewBox="0 0 16 16" width="16" height="16">
<path
fill="currentColor"
d="M7.70785815 10.86875l-5.08670521-4.5875c-.16153725-.146875-.16153725-.384375 0-.53125l.68051867-.61875c.16153726-.146875.42274645-.146875.58428371 0L8 8.834375l4.1140447-3.703125c.1615372-.146875.4227464-.146875.5842837 0l.6805187.61875c.1615372.146875.1615372.384375 0 .53125l-5.08670525 4.5875c-.16153726.146875-.42274644.146875-.5842837 0z"
/>
</svg>
</Box>
)
const renderSelectedItem = ({ selectedItem, renderSelected, placeholder }: any) =>
selectedItem && renderSelected ? (
renderSelected(selectedItem)
) : (
<Text color="fog">{placeholder}</Text>
)
class Select extends PureComponent<Props> {
static defaultProps = {
bg: undefined,
disabled: false,
fakeFocusRight: false,
flatLeft: false,
flatRight: false,
fakeFocusRight: false,
itemToString: (item: Object) => item && item.name,
keyProp: undefined,
maxHeight: 300,
@ -227,18 +237,19 @@ class Select extends PureComponent<Props> {
render() {
const {
disabled,
fakeFocusRight,
flatLeft,
flatRight,
fakeFocusRight,
items,
searchable,
itemToString,
fuseOptions,
highlight,
items,
itemToString,
onChange,
placeholder,
renderHighlight,
renderSelected,
placeholder,
onChange,
searchable,
value,
...props
} = this.props
@ -262,42 +273,46 @@ class Select extends PureComponent<Props> {
this._scrollToSelectedItem = true
}
if (disabled) {
return (
<Container {...getRootProps({ refKey: 'innerRef' })}>
<TriggerBtn disabled bg={props.bg} tabIndex={0}>
{renderSelectedItem({ selectedItem, renderSelected, placeholder })}
</TriggerBtn>
</Container>
)
}
return (
<Container
{...getRootProps({ refKey: 'innerRef' })}
{...props}
horizontal
onKeyDown={() => (this._useKeyboard = true)}
onKeyUp={() => (this._useKeyboard = false)}
>
{searchable ? (
<Box relative>
<Input keepEvent {...getInputProps({ placeholder })} onClick={openMenu} />
<FloatingDown>
<IconAngleDown size={16} />
</FloatingDown>
<Box grow>
<Input
keepEvent
{...getInputProps({ placeholder })}
onClick={openMenu}
renderRight={<AngleDown mr={2} />}
/>
</Box>
) : (
<TriggerBtn
{...getToggleButtonProps()}
bg={props.bg}
alignItems="center"
fakeFocusRight={fakeFocusRight}
flatLeft={flatLeft}
flatRight={flatRight}
fakeFocusRight={fakeFocusRight}
flow={2}
horizontal
tabIndex={0}
>
<Box grow>
{selectedItem && renderSelected ? (
renderSelected(selectedItem)
) : (
<Text color="fog">{placeholder}</Text>
)}
{renderSelectedItem({ selectedItem, renderSelected, placeholder })}
</Box>
<FloatingDown>
<IconAngleDown size={16} />
</FloatingDown>
<AngleDown mr={-1} />
</TriggerBtn>
)}
{isOpen &&

2
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', () => (
<Wrapper>
{onChange => (
<Select
disabled={boolean('disabled', false)}
placeholder="Choose a chess player..."
items={itemsChessPlayers}
renderSelected={item => item.name}

2
src/components/modals/AddAccount/index.js

@ -362,7 +362,7 @@ class AddAccountModal extends PureComponent<Props, State> {
<ModalBody onClose={onClose}>
<ModalTitle>{t('addAccount:title')}</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={this._steps} />
<Breadcrumb mb={5} currentStep={stepIndex} items={this._steps} />
{this.renderStep()}
</ModalContent>
{stepIndex !== 2 && (

48
src/components/modals/Receive/03-step-confirm-address.js

@ -4,11 +4,12 @@ import React from 'react'
import styled from 'styled-components'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import type { T } from 'types/common'
import type { Device, T } from 'types/common'
import Box from 'components/base/Box'
import IconInfoCircle from 'icons/InfoCircle'
import CurrentAddress from 'components/CurrentAddress'
import DeviceConfirm from 'components/DeviceConfirm'
import DeviceCheckAddress from 'components/DeviceCheckAddress'
const Container = styled(Box).attrs({
alignItems: 'center',
@ -22,36 +23,17 @@ const Title = styled(Box).attrs({
mb: 1,
})``
const Address = styled(Box).attrs({
bg: 'lightGrey',
ff: 'Open Sans|SemiBold',
px: 4,
py: 3,
borderRadius: 1,
mt: 2,
})`
border: 1px dashed ${p => p.theme.colors.fog};
cursor: text;
user-select: text;
`
const Text = styled(Box).attrs({
color: 'smoke',
mb: 5,
})`
text-align: center;
`
const Label = styled(Box).attrs({
alignItems: 'center',
color: 'graphite',
ff: 'Open Sans|SemiBold',
flow: 1,
horizontal: true,
})``
type Props = {
account: Account,
addressVerified: null | boolean,
device: Device | null,
onCheck: Function,
t: T,
}
@ -59,10 +41,16 @@ export default (props: Props) => (
<Container>
<Title>{props.t('receive:steps.confirmAddress.action')}</Title>
<Text>{props.t('receive:steps.confirmAddress.text')}</Text>
<Label>
<Box>{props.t('receive:steps.confirmAddress.label')}</Box>
<IconInfoCircle size={12} />
</Label>
<Address>{props.account.address}</Address>
<CurrentAddress addressVerified={props.addressVerified} account={props.account} />
{props.device && (
<Box mb={2}>
<DeviceCheckAddress
account={props.account}
device={props.device}
onCheck={props.onCheck}
render={({ isVerified }) => <DeviceConfirm notValid={isVerified === false} />}
/>
</Box>
)}
</Container>
)

44
src/components/modals/Receive/04-step-receive-funds.js

@ -0,0 +1,44 @@
// @flow
import React from 'react'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import CurrentAddress from 'components/CurrentAddress'
import Label from 'components/base/Label'
import SelectAccount from 'components/SelectAccount'
import RequestAmount from 'components/RequestAmount'
type Props = {
account: Account,
addressVerified: null | boolean,
amount: string | number,
onChangeAmount: Function,
t: T,
}
export default (props: Props) => (
<Box flow={5}>
<Box flow={1}>
<Label>{props.t('receive:steps.chooseAccount.label')}</Label>
<SelectAccount disabled value={props.account} />
</Box>
<Box flow={1}>
<Label>{props.t('receive:steps.receiveFunds.label')}</Label>
<RequestAmount
account={props.account}
onChange={props.onChangeAmount}
value={props.amount}
withMax={false}
/>
</Box>
<CurrentAddress
addressVerified={props.addressVerified}
amount={props.amount}
account={props.account}
withQRCode
/>
</Box>
)

152
src/components/modals/Receive/index.js

@ -1,6 +1,7 @@
// @flow
import React, { Fragment, PureComponent } from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import get from 'lodash/get'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
@ -15,8 +16,19 @@ import Breadcrumb from 'components/Breadcrumb'
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal'
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,
@ -24,8 +36,10 @@ type Props = {
type State = {
account: Account | null,
deviceSelected: Device | null,
amount: string | number,
addressVerified: null | boolean,
appStatus: null | string,
deviceSelected: Device | null,
stepIndex: number,
}
@ -33,12 +47,15 @@ const GET_STEPS = t => [
{ label: t('receive:steps.chooseAccount.title'), Comp: StepAccount },
{ label: t('receive:steps.connectDevice.title'), Comp: StepConnectDevice },
{ label: t('receive:steps.confirmAddress.title'), Comp: StepConfirmAddress },
{ label: t('receive:steps.receiveFunds.title'), Comp: StepReceiveFunds },
]
const INITIAL_STATE = {
account: null,
deviceSelected: null,
addressVerified: null,
amount: '',
appStatus: null,
deviceSelected: null,
stepIndex: 0,
}
@ -63,15 +80,33 @@ class ReceiveModal extends PureComponent<Props, State> {
}
canClose = () => {
const { stepIndex } = this.state
const { stepIndex, addressVerified } = this.state
if (stepIndex === 2) {
return false
return addressVerified === false
}
return true
}
canPrev = () => {
const { addressVerified, stepIndex } = this.state
if (stepIndex === 1) {
return true
}
if (stepIndex === 2) {
return addressVerified === false
}
if (stepIndex === 3) {
return true
}
return false
}
handleReset = () => this.setState(INITIAL_STATE)
handleNextStep = () => {
@ -82,14 +117,57 @@ class ReceiveModal extends PureComponent<Props, State> {
this.setState({ stepIndex: stepIndex + 1 })
}
handlePrevStep = () => {
const { stepIndex } = this.state
let newStepIndex
switch (stepIndex) {
default:
case 1:
newStepIndex = 0
break
case 2:
case 3:
newStepIndex = 1
break
}
this.setState({
deviceSelected: null,
appStatus: null,
addressVerified: null,
stepIndex: newStepIndex,
})
}
handleChangeDevice = d => this.setState({ deviceSelected: d })
handleChangeAccount = account => this.setState({ account })
handleChangeStatus = (deviceStatus, appStatus) => this.setState({ appStatus })
handleCheckAddress = isVerified => {
this.setState({
addressVerified: isVerified,
})
if (isVerified === true) {
this.handleNextStep()
}
}
handleChangeAmount = amount => this.setState({ amount })
handleSkipStep = () =>
this.setState({
addressVerified: false,
stepIndex: this._steps.length - 1, // last step
})
renderStep = acc => {
const { deviceSelected, stepIndex } = this.state
const { amount, addressVerified, deviceSelected, stepIndex } = this.state
const { t } = this.props
const step = this._steps[stepIndex]
if (!step) {
@ -111,6 +189,16 @@ class ReceiveModal extends PureComponent<Props, State> {
onChangeDevice: this.handleChangeDevice,
onStatusChange: this.handleChangeStatus,
}),
...props(stepIndex === 2, {
addressVerified,
onCheck: this.handleCheckAddress,
device: deviceSelected,
}),
...props(stepIndex === 3, {
addressVerified,
amount,
onChangeAmount: this.handleChangeAmount,
}),
}
return <Comp {...stepProps} />
@ -121,23 +209,31 @@ class ReceiveModal extends PureComponent<Props, State> {
const { stepIndex } = this.state
let onClick
let props
switch (stepIndex) {
case 2:
props = {
primary: true,
children: 'Contact Support',
}
break
default:
onClick = this.handleNextStep
}
const props = {
primary: true,
disabled: !this.canNext(acc),
onClick,
children: t('common:next'),
props = {
primary: true,
disabled: !this.canNext(acc),
onClick,
children: t('common:next'),
}
}
return (
<Fragment>
{stepIndex === 1 && (
<Button fontSize={4}>{t('receive:steps.connectDevice.withoutDevice')}</Button>
<Button onClick={this.handleSkipStep} fontSize={4}>
{t('receive:steps.connectDevice.withoutDevice')}
</Button>
)}
<Button {...props} />
</Fragment>
@ -149,6 +245,7 @@ class ReceiveModal extends PureComponent<Props, State> {
const { stepIndex, account } = this.state
const canClose = this.canClose()
const canPrev = this.canPrev()
return (
<Modal
@ -159,18 +256,29 @@ class ReceiveModal extends PureComponent<Props, State> {
const acc = account || get(data, 'account', null)
return (
<ModalBody onClose={canClose ? onClose : undefined} deferHeight={330}>
<ModalTitle>{t('receive:title')}</ModalTitle>
<ModalTitle>
{canPrev && (
<PrevButton onClick={this.handlePrevStep}>
<Box horizontal alignItems="center">
<IconAngleLeft size={16} />
{t('common:back')}
</Box>
</PrevButton>
)}
{t('receive:title')}
</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={this._steps} />
<Breadcrumb mb={5} currentStep={stepIndex} items={this._steps} />
{this.renderStep(acc)}
</ModalContent>
{canClose && (
<ModalFooter>
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{this.renderButton(acc)}
</Box>
</ModalFooter>
)}
{stepIndex !== 3 &&
canClose && (
<ModalFooter>
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{this.renderButton(acc)}
</Box>
</ModalFooter>
)}
</ModalBody>
)
}}

2
src/components/modals/Send/index.js

@ -140,7 +140,7 @@ class SendModal extends PureComponent<Props, State> {
<ModalBody onClose={onClose} deferHeight={acc ? 630 : 355}>
<ModalTitle>{t('send:title')}</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} mt={2} currentStep={stepIndex} items={this._steps} />
<Breadcrumb mb={5} mt={2} currentStep={stepIndex} items={this._steps} />
{this.renderStep(acc)}
</ModalContent>
{acc && (

12
src/icons/AngleLeft.js

@ -0,0 +1,12 @@
// @flow
import React from 'react'
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" height={size} width={size} {...p}>
<path
fill="currentColor"
d="M5.78300939 7.734375l3.68125-3.625c.146875-.146875.384375-.146875.53125 0l.22187501.221875c.146875.146875.146875.384375 0 .53125L7.02050939 8l3.19375001 3.1375c.146875.146875.146875.384375 0 .53125l-.22187501.221875c-.146875.146875-.384375.146875-.53125 0l-3.68125-3.625c-.14375-.146875-.14375-.384375.003125-.53125z"
/>
</svg>
)

1
static/i18n/en/common.yml

@ -11,3 +11,4 @@ editProfile: Edit profile
lockApplication: Lock application
max: Max
next: Next
back: Back

1
static/i18n/en/currentAddress.yml

@ -0,0 +1 @@
label: Current address

2
static/i18n/en/receive.yml

@ -13,7 +13,7 @@ steps:
title: Confirm Address
action: Confirm address on device
text: To receive funds, confirm the address on your device.
label: Current address
receiveFunds:
title: Receive Funds
label: Amount (Optional)

36
yarn.lock

@ -947,18 +947,16 @@
dependencies:
events "^2.0.0"
"@ledgerhq/wallet-common@^0.13.0":
version "0.13.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-common/-/wallet-common-0.13.0.tgz#e35613692f6da6549047becf09ebbdc264d6ff85"
"@ledgerhq/wallet-common@^0.13.2":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-common/-/wallet-common-0.13.2.tgz#55bff35179acb8e4c12836eeea7885ebaa85e6c4"
dependencies:
"@ledgerhq/currencies" "^4.10.1"
axios "^0.18.0"
babel-jest "^22.4.3"
invariant "^2.2.2"
lodash "^4.17.4"
prando "^3.0.1"
react "^16.0.0"
timemachine "^0.3.0"
"@posthtml/esm@^1.0.0":
version "1.0.0"
@ -4782,7 +4780,7 @@ electron-builder-lib@~20.6.2:
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder@^20.8.1:
electron-builder@^20.0.4, electron-builder@^20.8.1:
version "20.8.1"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.8.1.tgz#3d19607a7f7d3ee7f3e110a6fc66c720ed1d2cc0"
dependencies:
@ -4962,7 +4960,7 @@ electron-webpack@^2.0.1:
webpack-merge "^4.1.2"
yargs "^11.1.0"
electron@1.8.4:
electron@1.8.4, electron@^1.8.2:
version "1.8.4"
resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.4.tgz#cca8d0e6889f238f55b414ad224f03e03b226a38"
dependencies:
@ -10016,17 +10014,17 @@ range-parser@^1.0.3, range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
raven-js@^3.24.0:
version "3.24.0"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.24.0.tgz#59464d8bc4b3812ae87a282e9bb98ecad5b4b047"
raven-js@^3.24.1:
version "3.24.1"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.24.1.tgz#7327e08786248517eedd36205bf50f1047821ffa"
raven@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/raven/-/raven-2.4.2.tgz#0129e2adc30788646fd530b67d08a8ce25d4f6dc"
raven@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/raven/-/raven-2.5.0.tgz#7a2b4ce8b9ff884448924ad19ca33b574bce59a2"
dependencies:
cookie "0.3.1"
md5 "^2.2.1"
stack-trace "0.0.9"
stack-trace "0.0.10"
timed-out "4.0.1"
uuid "3.0.0"
@ -11466,9 +11464,9 @@ stable@~0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.6.tgz#910f5d2aed7b520c6e777499c1f32e139fdecb10"
stack-trace@0.0.9:
version "0.0.9"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695"
stack-trace@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
stack-utils@^1.0.1:
version "1.0.1"
@ -11944,10 +11942,6 @@ timed-out@4.0.1, timed-out@^4.0.0, timed-out@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
timemachine@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/timemachine/-/timemachine-0.3.0.tgz#83e2eb696cbfe4696c1708e6a3700df4e62fc525"
timers-browserify@^2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae"

Loading…
Cancel
Save