+
- {items.map((item, i) => (
-
- {item.label}
-
- ))}
+ {items.map((item, i) => {
+ let status = 'next'
+
+ const stepIndex = parseInt(currentStep, 10)
+
+ if (i === stepIndex) {
+ status = 'active'
+ }
+
+ if (i < stepIndex) {
+ status = 'valid'
+ }
+
+ if (stepsErrors.includes(i)) {
+ status = 'error'
+ }
+
+ if (stepsDisabled.includes(i)) {
+ status = 'disable'
+ }
+
+ return (
+
+ {item.label}
+
+ )
+ })}
+ 0
+ ? [
+ stepsDisabled[0] === 0 ? 0 : indexToPurcent(stepsDisabled[0] + 1, itemsLength),
+ indexToPurcent(stepsDisabled[stepsDisabled.length - 1], itemsLength),
+ ]
+ : null
+ }
+ current={!currentStep ? 100 : indexToPurcent(currentStep, itemsLength)}
+ />
)
}
diff --git a/src/components/Breadcrumb/stories.js b/src/components/Breadcrumb/stories.js
index 618b4768..7807f0b7 100644
--- a/src/components/Breadcrumb/stories.js
+++ b/src/components/Breadcrumb/stories.js
@@ -2,23 +2,31 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
-import { number } from '@storybook/addon-knobs'
+import { array, number } from '@storybook/addon-knobs'
import Breadcrumb from 'components/Breadcrumb'
const stories = storiesOf('Components', module)
stories.add('Breadcrumb', () => (
-
+
+ Number(a))}
+ stepsErrors={array('stepsErrors', []).map(a => Number(a))}
+ items={[
+ { label: 'Amount' },
+ { label: 'Summary' },
+ { label: 'Secure validation' },
+ { label: 'Confirmation' },
+ ]}
+ />
+
))
diff --git a/src/components/CurrentAddress/index.js b/src/components/CurrentAddress/index.js
new file mode 100644
index 00000000..977029d5
--- /dev/null
+++ b/src/components/CurrentAddress/index.js
@@ -0,0 +1,206 @@
+// @flow
+
+import React, { PureComponent } from 'react'
+import { translate } from 'react-i18next'
+import styled from 'styled-components'
+
+import noop from 'lodash/noop'
+
+import type { T } from 'types/common'
+
+import { rgba } from 'styles/helpers'
+
+import Box from 'components/base/Box'
+import CopyToClipboard from 'components/base/CopyToClipboard'
+import Print from 'components/base/Print'
+import QRCode from 'components/base/QRCode'
+
+import IconCheck from 'icons/Check'
+import IconCopy from 'icons/Copy'
+import IconInfoCircle from 'icons/InfoCircle'
+import IconPrint from 'icons/Print'
+import IconShare from 'icons/Share'
+import IconShield from 'icons/Shield'
+
+const Container = styled(Box).attrs({
+ borderRadius: 1,
+ alignItems: 'center',
+ bg: p => (p.withQRCode ? 'lightGrey' : 'transparent'),
+ py: 5,
+ px: 7,
+})``
+
+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')};
+ width: 100%;
+`
+
+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,
+})``
+
+const Footer = styled(Box).attrs({
+ horizontal: true,
+ mt: 5,
+})`
+ text-transform: uppercase;
+ width: 100%;
+`
+
+const FooterButtonWrapper = styled(Box).attrs({
+ color: 'grey',
+ alignItems: 'center',
+ justifyContent: 'center',
+ grow: true,
+})`
+ cursor: pointer;
+`
+
+const FooterButton = ({
+ icon,
+ label,
+ onClick,
+}: {
+ icon: any,
+ label: string,
+ onClick: Function,
+}) => (
+
+ {icon}
+
+ {label}
+
+
+)
+
+type Props = {
+ address: string,
+ addressVerified?: boolean,
+ amount?: string,
+ onCopy: Function,
+ onPrint: Function,
+ onShare: Function,
+ onVerify: Function,
+ t: T,
+ withBadge: boolean,
+ withFooter: boolean,
+ withQRCode: boolean,
+ withVerify: boolean,
+}
+
+class CurrentAddress extends PureComponent {
+ static defaultProps = {
+ addressVerified: null,
+ amount: null,
+ onCopy: noop,
+ onPrint: noop,
+ onShare: noop,
+ onVerify: noop,
+ withBadge: false,
+ withFooter: false,
+ withQRCode: false,
+ withVerify: false,
+ }
+
+ render() {
+ const {
+ address,
+ addressVerified,
+ amount,
+ onCopy,
+ onPrint,
+ onShare,
+ onVerify,
+ t,
+ withBadge,
+ withFooter,
+ withQRCode,
+ withVerify,
+ ...props
+ } = this.props
+
+ const notValid = addressVerified === false
+
+ return (
+
+
+ {withQRCode && (
+
+
+
+ )}
+
+
+ {address}
+
+
+ {withBadge && (
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam blandit velit egestas leo
+ tincidunt
+
+
+ )}
+ {withFooter && (
+
+ )}
+
+ )
+ }
+}
+
+export default translate()(CurrentAddress)
diff --git a/src/components/CurrentAddress/stories.js b/src/components/CurrentAddress/stories.js
new file mode 100644
index 00000000..188b4c00
--- /dev/null
+++ b/src/components/CurrentAddress/stories.js
@@ -0,0 +1,22 @@
+// @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
new file mode 100644
index 00000000..a4c5e94e
--- /dev/null
+++ b/src/components/DeviceConfirm/index.js
@@ -0,0 +1,178 @@
+// @flow
+
+import React from 'react'
+import styled, { keyframes } from 'styled-components'
+
+import { rgba } from 'styles/helpers'
+
+import Box from 'components/base/Box'
+
+import IconCheck from 'icons/Check'
+import IconCross from 'icons/Cross'
+
+const pulseAnimation = p => keyframes`
+ 0% {
+ box-shadow: 0 0 0 1px ${rgba(p.theme.colors.wallet, 0.4)};
+ }
+ 70% {
+ 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)};
+ }
+`
+
+const Wrapper = styled(Box).attrs({
+ color: p => (p.notValid ? 'alertRed' : 'wallet'),
+ relative: true,
+})`
+ padding-top: ${p => (p.notValid ? 0 : 30)}px;
+ transition: color ease-in-out 0.1s;
+`
+
+const WrapperIcon = styled(Box)`
+ color: ${p => (p.notValid ? p.theme.colors.alertRed : p.theme.colors.positiveGreen)};
+ position: absolute;
+ left: ${p => (p.notValid ? 152 : 193)}px;
+ bottom: 16px;
+
+ svg {
+ transition: color ease-in-out 0.1s;
+ }
+`
+
+const Check = ({ notValid }: { notValid: boolean }) => (
+
+ {notValid ? : }
+
+)
+
+const PushButton = styled(Box)`
+ background: linear-gradient(to bottom, #ffffff, ${p => p.theme.colors.wallet});
+ bottom: 48px;
+ height: 28px;
+ left: 205px;
+ position: absolute;
+ width: 1px;
+
+ &: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;
+ height: 9px;
+ left: 50%;
+ margin-bottom: -4px;
+ margin-left: -4px;
+ position: absolute;
+ width: 9px;
+ z-index: 1;
+ }
+`
+
+type Props = {
+ notValid: boolean,
+}
+
+const SVG = (
+
+)
+
+const DeviceConfirm = (props: Props) => (
+
+ {!props.notValid && }
+
+ {SVG}
+
+)
+
+DeviceConfirm.defaultProps = {
+ notValid: false,
+}
+
+export default DeviceConfirm
diff --git a/src/components/DeviceConfirm/stories.js b/src/components/DeviceConfirm/stories.js
new file mode 100644
index 00000000..60d53d65
--- /dev/null
+++ b/src/components/DeviceConfirm/stories.js
@@ -0,0 +1,11 @@
+// @flow
+
+import React from 'react'
+import { storiesOf } from '@storybook/react'
+import { boolean } from '@storybook/addon-knobs'
+
+import DeviceConfirm from 'components/DeviceConfirm'
+
+const stories = storiesOf('Components', module)
+
+stories.add('DeviceConfirm', () => )
diff --git a/src/components/DeviceMonitNew/index.js b/src/components/DeviceMonitNew/index.js
index 7d7f8b93..193b69d0 100644
--- a/src/components/DeviceMonitNew/index.js
+++ b/src/components/DeviceMonitNew/index.js
@@ -140,7 +140,7 @@ class DeviceMonit extends PureComponent {
if (render) {
return render({
appStatus,
- coinType: (account && account.coinType) || coinType,
+ coinType: account ? account.coinType : coinType,
devices,
deviceSelected: deviceStatus === 'connected' ? deviceSelected : null,
deviceStatus,
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) => (