diff --git a/.storybook/config.js b/.storybook/config.js index 6ee5a24c..e9f57a69 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -3,7 +3,7 @@ import { configure, addDecorator } from '@storybook/react' import { withKnobs } from '@storybook/addon-knobs' import { setOptions } from '@storybook/addon-options' import { ThemeProvider } from 'styled-components' -import { I18nextProvider } from 'react-i18next' +import { I18nextProvider, translate } from 'react-i18next' import 'globals' diff --git a/src/components/AccountPage/index.js b/src/components/AccountPage/index.js index 4375d8dd..3a9d7fcf 100644 --- a/src/components/AccountPage/index.js +++ b/src/components/AccountPage/index.js @@ -79,19 +79,20 @@ class AccountPage extends PureComponent { - - ) } } -export default connect(mapStateToProps)(RequestAmount) +export default compose(connect(mapStateToProps), translate())(RequestAmount) diff --git a/src/components/RequestAmount/stories.js b/src/components/RequestAmount/stories.js index edb96339..88da2a92 100644 --- a/src/components/RequestAmount/stories.js +++ b/src/components/RequestAmount/stories.js @@ -3,7 +3,7 @@ import React from 'react' import { storiesOf } from '@storybook/react' import { action } from '@storybook/addon-actions' -import { text } from '@storybook/addon-knobs' +import { number } from '@storybook/addon-knobs' import { accounts } from 'components/SelectAccount/stories' @@ -20,10 +20,11 @@ const props = { stories.add('basic', () => ( k} onChange={action('onChange')} value={{ - left: text('left value', 0), - right: text('right value', 0), + left: number('left value', 0), + right: number('right value', 0), }} /> )) diff --git a/src/components/SelectAccount/stories.js b/src/components/SelectAccount/stories.js index a46156d3..be2ec8f3 100644 --- a/src/components/SelectAccount/stories.js +++ b/src/components/SelectAccount/stories.js @@ -14,7 +14,7 @@ export const accounts = [...Array(20)].map(() => ({ id: chance.string(), address: chance.string(), addresses: [], - balance: chance.integer({ min: 10000000, max: 2000000000 }), + balance: chance.integer({ min: 10000000000, max: 2000000000000 }), balanceByDay: {}, coinType: 1, currency: getCurrencyByCoinType(1), diff --git a/src/components/base/Button/index.js b/src/components/base/Button/index.js index 051ca74e..14b53bb0 100644 --- a/src/components/base/Button/index.js +++ b/src/components/base/Button/index.js @@ -12,7 +12,7 @@ import fontFamily from 'styles/styled/fontFamily' const Base = styled.button.attrs({ ff: 'Museo Sans|Regular', fontSize: 3, - px: 2, + px: p => (p.small ? 2 : 4), })` ${space}; ${color}; @@ -23,7 +23,7 @@ const Base = styled.button.attrs({ border: ${p => p.primary ? 'none' : `2px solid ${p.disabled ? 'transparent' : p.theme.colors.grey}`}; cursor: ${p => (p.disabled ? 'default' : 'pointer')}; - height: 30px; + height: ${p => (p.small ? 30 : 40)}px; outline: none; &:hover { @@ -46,6 +46,7 @@ type Props = { primary?: boolean, disabled?: boolean, onClick?: Function, + small?: boolean, } function getProps({ disabled, icon, primary }: Object) { @@ -98,10 +99,11 @@ const Button = (props: Props) => { Button.defaultProps = { children: undefined, - icon: undefined, disabled: undefined, - primary: false, + icon: undefined, onClick: noop, + primary: false, + small: false, } export default Button diff --git a/src/components/base/Input/index.js b/src/components/base/Input/index.js index dddf725a..a759f5e1 100644 --- a/src/components/base/Input/index.js +++ b/src/components/base/Input/index.js @@ -2,42 +2,67 @@ import React, { PureComponent } from 'react' import styled from 'styled-components' -import { space, fontSize } from 'styled-system' +import { fontSize } from 'styled-system' + +import noop from 'lodash/noop' import fontFamily from 'styles/styled/fontFamily' +import Box from 'components/base/Box' + +const Container = styled(Box).attrs({ + horizontal: true, +})` + background: ${p => p.theme.colors.white}; + border-radius: 3px; + border: 1px solid ${p => p.theme.colors.fog}; + box-shadow: ${p => (p.isFocus ? `rgba(0, 0, 0, 0.05) 0 2px 2px` : 'none')}; + height: 40px; +` + const Base = styled.input.attrs({ - px: 3, - ff: 'Open Sans|SemiBold', + ff: p => p.ff || 'Open Sans|SemiBold', fontSize: 4, })` - ${space}; ${fontFamily}; ${fontSize}; - height: 40px; - border: 1px solid ${p => p.theme.colors.fog}; - border-radius: 3px; - display: flex; + border: 0; + color: ${p => p.theme.colors.dark}; + height: 100%; + outline: none; + padding: 0; width: 100%; - color: ${p => p.theme.colors.graphite}; - background: ${p => p.theme.colors.white}; &::placeholder { color: ${p => p.theme.colors.fog}; } - - &:focus { - outline: none; - box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px; - } ` type Props = { - onChange?: Function, keepEvent?: boolean, + onBlur: Function, + onChange?: Function, + onFocus: Function, + renderLeft?: any, + renderRight?: any, +} + +type State = { + isFocus: boolean, } -class Input extends PureComponent { +class Input extends PureComponent { + static defaultProps = { + onBlur: noop, + onFocus: noop, + renderLeft: null, + renderRight: null, + } + + state = { + isFocus: false, + } + handleChange = (e: SyntheticInputEvent) => { const { onChange, keepEvent } = this.props @@ -46,8 +71,45 @@ class Input extends PureComponent { } } + handleClick = () => this._input && this._input.focus() + + handleFocus = () => { + const { onFocus } = this.props + this.setState({ + isFocus: true, + }) + onFocus() + } + + handleBlur = () => { + const { onBlur } = this.props + this.setState({ + isFocus: false, + }) + onBlur() + } + + _input = null + render() { - return + const { isFocus } = this.state + const { renderLeft, renderRight } = this.props + + return ( + + {renderLeft} + + (this._input = n)} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + onChange={this.handleChange} + /> + + {renderRight} + + ) } } diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js new file mode 100644 index 00000000..cb1f941d --- /dev/null +++ b/src/components/base/InputCurrency/index.js @@ -0,0 +1,132 @@ +// @flow + +import React, { PureComponent } from 'react' + +import { parseCurrencyUnit, formatCurrencyUnit } from '@ledgerhq/currencies' + +import noop from 'lodash/noop' +import isNaN from 'lodash/isNaN' + +import Input from 'components/base/Input' + +import type { Unit } from '@ledgerhq/currencies' + +function parseValue(value) { + return value.toString().replace(/,/, '.') +} + +function format(unit: Unit, value: Value) { + let v = value === '' ? 0 : Number(value) + v *= 10 ** unit.magnitude + return formatCurrencyUnit(unit, v, { + disableRounding: true, + showAllDigits: false, + }) +} + +function unformat(unit, value) { + if (value === 0 || value === '') { + return 0 + } + + let v = parseCurrencyUnit(unit, value.toString()) + v /= 10 ** unit.magnitude + + return v +} + +type Value = string | number + +type Props = { + onChange: Function, + value: Value, + unit: Unit, +} + +type State = { + isFocus: boolean, + value: Value, +} + +class InputCurrency extends PureComponent { + static defaultProps = { + onChange: noop, + value: 0, + } + + state = { + isFocus: false, + value: this.props.value, + } + + componentWillReceiveProps(nextProps: Props) { + if (this.props.value !== nextProps.value) { + const { isFocus } = this.state + const value = isFocus ? nextProps.value : format(nextProps.unit, nextProps.value) + this.setState({ + value, + }) + } + } + + handleChange = (v: Value) => { + v = parseValue(v) + + // Check if value is valid Number + if (isNaN(Number(v))) { + return + } + + this.emitOnChange(v) + this.setState({ + value: v, + }) + } + + handleBlur = () => { + const { unit } = this.props + const { value } = this.state + + const v = format(unit, value) + + this.setState({ + isFocus: false, + value: v, + }) + } + + handleFocus = () => { + const { unit } = this.props + + this.setState(prev => ({ + isFocus: true, + value: unformat(unit, prev.value), + })) + } + + emitOnChange = (v: Value) => { + const { onChange } = this.props + const { value } = this.state + + if (value.toString() !== v.toString()) { + onChange(v.toString()) + } + } + + render() { + const { value } = this.state + + return ( + + ) + } +} + +export default InputCurrency diff --git a/src/components/base/InputCurrency/stories.js b/src/components/base/InputCurrency/stories.js new file mode 100644 index 00000000..3752604a --- /dev/null +++ b/src/components/base/InputCurrency/stories.js @@ -0,0 +1,15 @@ +// @flow + +import React from 'react' +import { storiesOf } from '@storybook/react' +import { action } from '@storybook/addon-actions' + +import { getDefaultUnitByCoinType } from '@ledgerhq/currencies' + +import InputCurrency from 'components/base/InputCurrency' + +const stories = storiesOf('Components/InputCurrency', module) + +const unit = getDefaultUnitByCoinType(1) + +stories.add('basic', () => ) diff --git a/src/components/modals/Receive/index.js b/src/components/modals/Receive/index.js index 4db3f029..e8db5d57 100644 --- a/src/components/modals/Receive/index.js +++ b/src/components/modals/Receive/index.js @@ -7,14 +7,12 @@ import get from 'lodash/get' import { MODAL_RECEIVE } from 'constants' -// import { getCounterValue } from 'reducers/settings' - 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 Button from 'components/base/Button' import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal' import ReceiveBox from 'components/ReceiveBox' +import RequestAmount from 'components/RequestAmount' import SelectAccount from 'components/SelectAccount' import type { Account as AccountType, T } from 'types/common' @@ -25,12 +23,15 @@ type Props = { type State = { account: AccountType | null, - amount: string, + amount: Object, } const defaultState = { account: null, - amount: '', + amount: { + left: 0, + right: 0, + }, } class ReceiveModal extends PureComponent { @@ -62,13 +63,14 @@ class ReceiveModal extends PureComponent { ].map(v => ({ label: this.props.t(v) })) render() { - // const { amount } = this.state + const { t } = this.props + const { amount } = this.state return ( { + render={({ data, onClose }) => { const account = this.getAccount(data) return ( @@ -83,14 +85,13 @@ class ReceiveModal extends PureComponent { - - + )} diff --git a/src/reducers/counterValues.js b/src/reducers/counterValues.js index f7bfe042..ed714024 100644 --- a/src/reducers/counterValues.js +++ b/src/reducers/counterValues.js @@ -2,7 +2,12 @@ import { handleActions } from 'redux-actions' -export type CounterValuesState = {} +export type CounterValuesState = { + [string]: { + byDate: Object, + list: Array<[string, number]>, + }, +} const state: CounterValuesState = {} @@ -16,6 +21,13 @@ const handlers = { }), } +export function getLastCounterValueBySymbol( + symbol: string, + state: { counterValues: CounterValuesState }, +): number { + return state.counterValues[symbol].list[0][1] +} + export function serializeCounterValues(counterValues: Object) { return Object.keys(counterValues).reduce((result, key) => { const counterValue = counterValues[key].sort(([dateA], [dateB]) => (dateA < dateB ? 1 : -1)) diff --git a/static/i18n/en/common.yml b/static/i18n/en/common.yml index be0cbab5..78da797c 100644 --- a/static/i18n/en/common.yml +++ b/static/i18n/en/common.yml @@ -9,3 +9,4 @@ save: Save password: Password editProfile: Edit profile lockApplication: Lock application +max: Max diff --git a/yarn.lock b/yarn.lock index ea6e37ce..93286e05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3658,35 +3658,6 @@ ejs@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-builder-lib@20.4.0: - version "20.4.0" - resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.4.0.tgz#e9a6d34b4bc6ed848a39a9ee894a3da015474c2a" - dependencies: - "7zip-bin" "~3.1.0" - app-builder-bin "1.7.2" - async-exit-hook "^2.0.1" - bluebird-lst "^1.0.5" - builder-util "5.6.4" - builder-util-runtime "4.0.5" - chromium-pickle-js "^0.2.0" - debug "^3.1.0" - ejs "^2.5.7" - electron-osx-sign "0.4.8" - electron-publish "20.2.0" - fs-extra-p "^4.5.2" - hosted-git-info "^2.5.0" - is-ci "^1.1.0" - isbinaryfile "^3.0.2" - js-yaml "^3.11.0" - lazy-val "^1.0.3" - minimatch "^3.0.4" - normalize-package-data "^2.4.0" - plist "^2.1.0" - read-config-file "3.0.0" - sanitize-filename "^1.6.1" - semver "^5.5.0" - temp-file "^3.1.1" - electron-builder-lib@20.4.1: version "20.4.1" resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.4.1.tgz#8560563e21ca0596046eac398cad204154665a5e" @@ -3745,29 +3716,6 @@ electron-builder-lib@~20.2.0: semver "^5.5.0" temp-file "^3.1.1" -<<<<<<< HEAD -electron-builder@^20.4.0: -======= -electron-builder@^20.0.4: ->>>>>>> Clean RequestAmount - version "20.4.0" - resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.4.0.tgz#d1393719339c17dd7c2dd16d58b4e138ca6646ce" - dependencies: - bluebird-lst "^1.0.5" - builder-util "5.6.4" - builder-util-runtime "4.0.5" - chalk "^2.3.2" - dmg-builder "4.1.1" - electron-builder-lib "20.4.0" - electron-download-tf "4.3.4" - fs-extra-p "^4.5.2" - is-ci "^1.1.0" - lazy-val "^1.0.3" - read-config-file "3.0.0" - sanitize-filename "^1.6.1" - update-notifier "^2.3.0" - yargs "^11.0.0" - electron-builder@^20.4.1: version "20.4.1" resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.4.1.tgz#ec8b5ada929df8d7a4ab4b1685568be4e12bf8f3" @@ -6537,6 +6485,9 @@ ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs#7d37482: dependencies: axios "^0.17.1" bindings "^1.3.0" + electron "^1.8.2" + electron-builder "^20.0.4" + electron-rebuild "^1.7.3" nan "^2.6.2" prebuild-install "^2.2.2"