Browse Source

Refactor Send modal, integrate RequestAmount and RecipientAddress

master
meriadec 7 years ago
parent
commit
7283a04023
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 21
      src/components/Breadcrumb/index.js
  2. 57
      src/components/RecipientAddress/index.js
  3. 10
      src/components/RequestAmount/index.js
  4. 5
      src/components/base/Input/index.js
  5. 6
      src/components/base/Label.js
  6. 24
      src/components/base/LabelInfoTooltip/index.js
  7. 4
      src/components/base/Tooltip/index.js
  8. 179
      src/components/modals/Send.js
  9. 73
      src/components/modals/Send/01-step-amount.js
  10. 7
      src/components/modals/Send/02-step-connect-device.js
  11. 7
      src/components/modals/Send/03-step-verification.js
  12. 7
      src/components/modals/Send/04-step-confirmation.js
  13. 52
      src/components/modals/Send/Footer.js
  14. 109
      src/components/modals/Send/index.js
  15. 10
      src/icons/InfoCircle.js
  16. 22
      static/i18n/en/send.yml

21
src/components/Breadcrumb/index.js

@ -25,13 +25,20 @@ class Breadcrumb extends PureComponent<Props> {
render() {
const { items, currentStep, ...props } = this.props
return (
<Wrapper {...props}>
{items.map((item, i) => (
<Step key={i} isActive={i < parseInt(currentStep, 10)} isFirst={i === 0} number={i + 1}>
{item.label}
</Step>
))}
</Wrapper>
<Box {...props}>
<Wrapper>
{items.map((item, i) => (
<Step
key={i}
isActive={i <= parseInt(currentStep, 10)}
isFirst={i === 0}
number={i + 1}
>
{item.label}
</Step>
))}
</Wrapper>
</Box>
)
}
}

57
src/components/RecipientAddress/index.js

@ -1,31 +1,32 @@
// @flow
import React, { PureComponent, Fragment } from 'react'
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import QrReader from 'react-qr-reader'
import noop from 'lodash/noop'
import { radii } from 'styles/theme'
import Box from 'components/base/Box'
import Input from 'components/base/Input'
import IconQrCode from 'icons/QrCode'
const WrapperIcon = ({ onClick }: { onClick: Function }) => (
<Box color="graphite" style={{ position: 'absolute', right: 15 }}>
<IconQrCode height={30} width={30} style={{ cursor: 'pointer' }} onClick={onClick} />
</Box>
)
const InputAddress = styled(Input).attrs({
type: 'text',
const Right = styled(Box).attrs({
bg: 'lightGrey',
px: 3,
align: 'center',
justify: 'center',
})`
padding-right: ${p => p.withQrCode && '55px'};
border-top-right-radius: ${radii[1]}px;
border-bottom-right-radius: ${radii[1]}px;
border-left: 1px solid ${p => p.theme.colors.fog};
`
const WrapperQrCode = styled(Box)`
margin-top: 10px;
position: absolute;
right: 15px;
right: 0;
top: 100%;
`
@ -65,21 +66,25 @@ class RecipientAddress extends PureComponent<Props, State> {
return (
<Box relative justifyContent="center">
<InputAddress value={value} withQrCode={withQrCode} onChange={onChange} />
{withQrCode && (
<Fragment>
<WrapperIcon onClick={this.handleClickQrCode} />
{qrReaderOpened && (
<WrapperQrCode>
<QrReader
onScan={this.handleScanQrCode}
onError={noop}
style={{ height: qrCodeSize, width: qrCodeSize }}
/>
</WrapperQrCode>
)}
</Fragment>
)}
<Input
value={value}
withQrCode={withQrCode}
onChange={onChange}
renderRight={
<Right onClick={this.handleClickQrCode}>
<IconQrCode width={16} height={16} />
{qrReaderOpened && (
<WrapperQrCode>
<QrReader
onScan={this.handleScanQrCode}
onError={noop}
style={{ height: qrCodeSize, width: qrCodeSize }}
/>
</WrapperQrCode>
)}
</Right>
}
/>
</Box>
)
}

10
src/components/RequestAmount/index.js

@ -112,11 +112,13 @@ type Props = {
value: Object,
}
export type DoubleVal = {
left: number,
right: number,
}
type State = {
max: {
left: number,
right: number,
},
max: DoubleVal,
value: {
left: string | number,
right: string | number,

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

@ -45,6 +45,7 @@ type Props = {
onFocus: Function,
renderLeft?: any,
renderRight?: any,
containerProps?: Object,
}
type State = {
@ -93,10 +94,10 @@ class Input extends PureComponent<Props, State> {
render() {
const { isFocus } = this.state
const { renderLeft, renderRight } = this.props
const { renderLeft, renderRight, containerProps } = this.props
return (
<Container onClick={this.handleClick} isFocus={isFocus} shrink>
<Container onClick={this.handleClick} isFocus={isFocus} shrink {...containerProps}>
{renderLeft}
<Box px={3} grow shrink>
<Base

6
src/components/base/Label.js

@ -1,5 +1,5 @@
import styled from 'styled-components'
import { fontSize, color } from 'styled-system'
import { fontSize, color, alignItems } from 'styled-system'
import fontFamily from 'styles/styled/fontFamily'
@ -7,9 +7,11 @@ export default styled.label.attrs({
fontSize: 3,
ff: 'Museo Sans|Regular',
color: 'grey',
align: 'center',
})`
${alignItems};
${color};
${fontSize};
${fontFamily};
display: block;
display: flex;
`

24
src/components/base/LabelInfoTooltip/index.js

@ -0,0 +1,24 @@
// @flow
import React from 'react'
import Box from 'components/base/Box'
import Tooltip from 'components/base/Tooltip'
import IconInfoCircle from 'icons/InfoCircle'
type Props = {
text: string,
}
function LabelInfoTooltip(props: Props) {
const { text, ...p } = props
return (
<Box {...p}>
<Tooltip render={() => text} style={{ height: 12 }}>
<IconInfoCircle width={12} height={12} />
</Tooltip>
</Box>
)
}
export default LabelInfoTooltip

4
src/components/base/Tooltip/index.js

@ -86,10 +86,10 @@ class Tooltip extends PureComponent<Props> {
_template = undefined
render() {
const { children, render } = this.props
const { children, render, ...props } = this.props
return (
<Container innerRef={n => (this._node = n)}>
<Container innerRef={n => (this._node = n)} {...props}>
<Template>
<TooltipContainer innerRef={n => (this._template = n)}>{render()}</TooltipContainer>
</Template>

179
src/components/modals/Send.js

@ -1,179 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import get from 'lodash/get'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import type { T } from 'types/common'
import { MODAL_SEND } from 'constants'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Input from 'components/base/Input'
import Label from 'components/base/Label'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal'
import Breadcrumb from 'components/Breadcrumb'
import RecipientAddress from 'components/RecipientAddress'
import SelectAccount from 'components/SelectAccount'
import FormattedVal from 'components/base/FormattedVal'
const Steps = {
'1': ({ t, ...props }: Object) => (
<form
onSubmit={(e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
if (props.canSubmit) {
props.onChangeStep('2')
}
}}
>
<Box flow={4}>
<Box flow={1}>
<Label>Account to debit</Label>
<SelectAccount onChange={props.onChangeInput('account')} value={props.value.account} />
</Box>
<Box flow={1}>
<Label>Recipient address</Label>
<RecipientAddress onChange={props.onChangeInput('address')} value={props.value.address} />
</Box>
<Box flow={1}>
<Label>Amount</Label>
<Input onChange={props.onChangeInput('amount')} value={props.value.amount} />
</Box>
</Box>
</form>
),
'2': (props: Object) => (
<div>
<div>summary</div>
<div>{props.value.amount}</div>
<div>to {props.value.address}</div>
<div>from {props.value.account.name}</div>
</div>
),
}
type InputValue = {
account: any,
address: string,
amount: string,
}
type Step = '1' | '2'
type State = {
inputValue: InputValue,
step: Step,
}
type Props = {
t: T,
}
const defaultState = {
inputValue: {
account: null,
address: '',
amount: '',
},
step: '1',
}
class Send extends PureComponent<Props, State> {
state = {
...defaultState,
}
getStepProps(data: any) {
const { inputValue, step } = this.state
const { t } = this.props
const props = (predicate, props, defaults = {}) => (predicate ? props : defaults)
const account = inputValue.account || get(data, 'account')
return {
...props(step === '1', {
canSubmit: account && inputValue.address.trim() !== '' && inputValue.amount.trim() !== '',
onChangeInput: this.handleChangeInput,
value: {
...inputValue,
account,
},
}),
...props(step === '2', {
value: {
...inputValue,
account,
},
}),
onChangeStep: this.handleChangeStep,
t,
}
}
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
...prev.inputValue,
[key]: value,
},
}))
handleChangeStep = (step: Step) =>
this.setState({
step,
})
handleHide = () =>
this.setState({
...defaultState,
})
_steps = [
'sendModal:Amount',
'sendModal:Summary',
'sendModal:SecureValidation',
'sendModal:Confirmation',
].map(v => ({ label: this.props.t(v) }))
render() {
const { step } = this.state
const { t } = this.props
const Step = Steps[step]
return (
<Modal
name={MODAL_SEND}
onHide={this.handleHide}
render={({ data, onClose }) => (
<ModalBody onClose={onClose}>
<ModalTitle>{t('send:title')}</ModalTitle>
<ModalContent>
<Box mb={6} mt={2}>
<Breadcrumb currentStep={step} items={this._steps} />
</Box>
<Step {...this.getStepProps(data)} />
</ModalContent>
<ModalFooter horizontal align="center">
<Box grow>
<Label>{'Total spent'}</Label>
<FormattedVal
color="dark"
val={15496420404}
unit={getDefaultUnitByCoinType(0)}
showCode
/>
</Box>
<Button primary>{'Next'}</Button>
</ModalFooter>
</ModalBody>
)}
/>
)
}
}
export default translate()(Send)

73
src/components/modals/Send/01-step-amount.js

@ -0,0 +1,73 @@
// @flow
import React, { Fragment } from 'react'
import type { Account, T } from 'types/common'
import type { DoubleVal } from 'components/RequestAmount'
import Box from 'components/base/Box'
import SelectAccount from 'components/SelectAccount'
import Label from 'components/base/Label'
import LabelInfoTooltip from 'components/base/LabelInfoTooltip'
import RecipientAddress from 'components/RecipientAddress'
import RequestAmount from 'components/RequestAmount'
import Select from 'components/base/Select'
import Input from 'components/base/Input'
type Props = {
account: Account | null,
onChange: Function,
recipientAddress: string,
amount: DoubleVal,
t: T,
}
function StepAmount(props: Props) {
const { onChange, account, recipientAddress, t, amount } = props
return (
<Box flow={4}>
<Box flow={1}>
<Label>{t('send:steps.amount.selectAccountDebit')}</Label>
<SelectAccount onChange={onChange('account')} value={account} />
</Box>
<Box flow={1}>
<Label>
<span>{t('send:steps.amount.recipientAddress')}</span>
<LabelInfoTooltip ml={1} text={t('send:steps.amount.recipientAddress')} />
</Label>
<RecipientAddress
withQrCode
value={recipientAddress}
onChange={onChange('recipientAddress')}
/>
</Box>
{account && (
<Fragment>
<Box flow={1}>
<Label>{t('send:steps.amount.amount')}</Label>
<RequestAmount account={account} onChange={onChange('amount')} value={amount} />
</Box>
<Box flow={1}>
<Label>
<span>{t('send:steps.amount.fees')}</span>
<LabelInfoTooltip ml={1} text={t('send:steps.amount.fees')} />
</Label>
<Box horizontal flow={5}>
<Select
placeholder="Choose a chess player..."
items={[{ key: 'custom', name: 'Custom' }]}
value={{ key: 'custom', name: 'Custom' }}
renderSelected={item => item.name}
onChange={onChange('fees')}
/>
<Input containerProps={{ grow: true }} />
</Box>
</Box>
</Fragment>
)}
</Box>
)
}
export default StepAmount

7
src/components/modals/Send/02-step-connect-device.js

@ -0,0 +1,7 @@
import React from 'react'
function StepConnectDevice() {
return <div>step connect device</div>
}
export default StepConnectDevice

7
src/components/modals/Send/03-step-verification.js

@ -0,0 +1,7 @@
import React from 'react'
function StepVerification() {
return <div>step verification</div>
}
export default StepVerification

7
src/components/modals/Send/04-step-confirmation.js

@ -0,0 +1,7 @@
import React from 'react'
function StepConfirmation() {
return <div>step confirmation</div>
}
export default StepConfirmation

52
src/components/modals/Send/Footer.js

@ -0,0 +1,52 @@
// @flow
import React from 'react'
import type { T, Account } from 'types/common'
import type { DoubleVal } from 'components/RequestAmount'
import { ModalFooter } from 'components/base/Modal'
import Box from 'components/base/Box'
import Label from 'components/base/Label'
import FormattedVal from 'components/base/FormattedVal'
import Button from 'components/base/Button'
import Text from 'components/base/Text'
type Props = {
t: T,
account: Account,
amount: DoubleVal,
onNext: Function,
}
function Footer({ account, amount, t, onNext }: Props) {
return (
<ModalFooter horizontal align="center">
<Box grow>
<Label>{t('send:totalSpent')}</Label>
<Box horizontal flow={2} align="center">
<FormattedVal
color="dark"
val={amount.left * 10 ** account.unit.magnitude}
unit={account.unit}
showCode
/>
<Box horizontal align="center">
<Text ff="Rubik" fontSize={3}>
{'('}
</Text>
<FormattedVal color="grey" fontSize={3} val={amount.right} fiat="USD" showCode />
<Text ff="Rubik" fontSize={3}>
{')'}
</Text>
</Box>
</Box>
</Box>
<Button primary onClick={onNext}>
{'Next'}
</Button>
</ModalFooter>
)
}
export default Footer

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

@ -0,0 +1,109 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import get from 'lodash/get'
import type { T, Account } from 'types/common'
import type { DoubleVal } from 'components/RequestAmount'
import { MODAL_SEND } from 'constants'
import Breadcrumb from 'components/Breadcrumb'
import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal'
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 = {
t: T,
}
type State = {
stepIndex: number,
amount: DoubleVal,
account: Account | null,
recipientAddress: string,
fees: number,
}
const GET_STEPS = t => [
{ label: t('send:steps.amount.title'), Comp: StepAmount },
{ label: t('send:steps.connectDevice.title'), Comp: StepConnectDevice },
{ label: t('send:steps.verification.title'), Comp: StepVerification },
{ label: t('send:steps.confirmation.title'), Comp: StepConfirmation },
]
const INITIAL_STATE = {
stepIndex: 0,
account: null,
recipientAddress: '',
amount: {
left: 0,
right: 0,
},
fees: 0,
}
class SendModal extends PureComponent<Props, State> {
state = INITIAL_STATE
_steps = GET_STEPS(this.props.t)
handleReset = () => this.setState(INITIAL_STATE)
handleNextStep = () => {
const { stepIndex } = this.state
if (stepIndex >= this._steps.length - 1) {
return
}
this.setState({ stepIndex: stepIndex + 1 })
}
createChangeHandler = key => value => this.setState({ [key]: value })
renderStep = acc => {
const { stepIndex, account } = this.state
const step = this._steps[stepIndex]
if (!step) {
return null
}
const { Comp } = step
const stepProps = {
...this.state,
account: account || acc,
}
return <Comp onChange={this.createChangeHandler} {...stepProps} {...this.props} />
}
render() {
const { t } = this.props
const { stepIndex, amount, account } = this.state
return (
<Modal
name={MODAL_SEND}
onHide={this.handleReset}
render={({ data, onClose }) => {
const acc = account || get(data, 'account', null)
return (
<ModalBody onClose={onClose}>
<ModalTitle>{t('send:title')}</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} mt={2} currentStep={stepIndex} items={this._steps} />
{this.renderStep(acc)}
</ModalContent>
{acc && <Footer onNext={this.handleNextStep} account={acc} amount={amount} t={t} />}
</ModalBody>
)
}}
/>
)
}
}
export default translate()(SendModal)

10
src/icons/InfoCircle.js

@ -0,0 +1,10 @@
import React from 'react'
export default props => (
<svg viewBox="0 0 16 16" {...props}>
<path
fill="currentColor"
d="M8 .25a7.751 7.751 0 0 0 0 15.5A7.75 7.75 0 1 0 8 .25zm0 14A6.246 6.246 0 0 1 1.75 8 6.248 6.248 0 0 1 8 1.75 6.248 6.248 0 0 1 14.25 8 6.246 6.246 0 0 1 8 14.25zM8 3.687a1.312 1.312 0 1 1 0 2.625 1.312 1.312 0 0 1 0-2.625zm1.75 7.938a.375.375 0 0 1-.375.375h-2.75a.375.375 0 0 1-.375-.375v-.75c0-.207.168-.375.375-.375H7v-2h-.375a.375.375 0 0 1-.375-.375v-.75c0-.207.168-.375.375-.375h2c.207 0 .375.168.375.375V10.5h.375c.207 0 .375.168.375.375v.75z"
/>
</svg>
)

22
static/i18n/en/send.yml

@ -1,5 +1,17 @@
title: Send
Amount: Amount
Summary: Summary
SecureValidation: Secure validation
Confirmation: Confirmation
title: Send funds
totalSpent: Total spent
steps:
amount:
title: Informations
selectAccountDebit: Select an account to debit
recipientAddress: Recipient address
amount: Amount
max: Max
fees: Fees
advancedOptions: Advanced options
connectDevice:
title: Connect device
verification:
title: Verification
confirmation:
title: Confirmation

Loading…
Cancel
Save