Browse Source

Merge pull request #287 from meriadec/master

Settings complete redesign
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
9393048b50
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .circleci/config.yml
  2. 32
      package.json
  3. 12
      src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap
  4. 94
      src/components/CurrentAddress/index.js
  5. 3
      src/components/CurrentAddress/stories.js
  6. 6
      src/components/DashboardPage/index.js
  7. 6
      src/components/IsUnlocked.js
  8. 4
      src/components/OperationsList/index.js
  9. 3
      src/components/SelectAccount/index.js
  10. 60
      src/components/SelectCurrency/index.js
  11. 31
      src/components/SelectCurrency/stories.js
  12. 98
      src/components/SettingsPage/Display.js
  13. 88
      src/components/SettingsPage/Money.js
  14. 137
      src/components/SettingsPage/PasswordModal.js
  15. 124
      src/components/SettingsPage/Profile.js
  16. 120
      src/components/SettingsPage/SettingsSection.js
  17. 148
      src/components/SettingsPage/index.js
  18. 62
      src/components/SettingsPage/sections/About.js
  19. 72
      src/components/SettingsPage/sections/Currencies.js
  20. 120
      src/components/SettingsPage/sections/Display.js
  21. 172
      src/components/SettingsPage/sections/Profile.js
  22. 0
      src/components/SettingsPage/sections/Tools.js
  23. 10
      src/components/SideBar/Item.js
  24. 23
      src/components/TopBar.js
  25. 7
      src/components/base/Box/index.js
  26. 126
      src/components/base/Button/index.js
  27. 11
      src/components/base/Button/stories.js
  28. 15
      src/components/base/CheckBox/index.js
  29. 12
      src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap
  30. 27
      src/components/base/Input/index.js
  31. 35
      src/components/base/InputPassword/index.js
  32. 1
      src/components/base/Label.js
  33. 74
      src/components/base/Modal/ConfirmModal.js
  34. 12
      src/components/base/Modal/index.js
  35. 60
      src/components/base/Modal/stories.js
  36. 15
      src/components/base/Select/index.js
  37. 68
      src/components/base/Tabs/index.js
  38. 31
      src/components/base/Tabs/stories.js
  39. 1
      src/components/layout/Default.js
  40. 7
      src/components/layout/Print.js
  41. 6
      src/components/modals/Receive/04-step-receive-funds.js
  42. 12
      src/icons/Currencies.js
  43. 12
      src/icons/Display.js
  44. 12
      src/icons/Help.js
  45. 1
      src/internals/usb/manager/helpers.js
  46. 4
      src/reducers/accounts.js
  47. 6
      src/reducers/settings.js
  48. 17
      src/types/common.js
  49. 2
      static/i18n/en/common.yml
  50. 1
      static/i18n/en/currentAddress.yml
  51. 57
      static/i18n/en/settings.yml
  52. 329
      yarn.lock

2
.circleci/config.yml

@ -20,7 +20,7 @@ jobs:
command: yarn flow-typed
- run:
name: Temporary remove broken flow definitions
command: rm flow-typed/npm/{react-i18next_v7.x.x.js,react-redux_v5.x.x.js,redux_v3.x.x.js}
command: rm flow-typed/npm/{react-i18next_v7.x.x.js,react-redux_v5.x.x.js,redux_v3.x.x.js,styled-components_v3.x.x.js}
- run:
name: Lint
command: yarn lint

32
package.json

@ -57,11 +57,11 @@
"bs58check": "^2.1.1",
"color": "^3.0.0",
"cross-env": "^5.1.4",
"d3": "^5.0.0",
"d3": "^5.1.0",
"debug": "^3.1.0",
"downshift": "^1.31.7",
"electron-store": "^1.3.0",
"electron-updater": "^2.21.4",
"electron-updater": "^2.21.8",
"fuse.js": "^3.2.0",
"history": "^4.7.2",
"i18next": "^11.2.2",
@ -74,8 +74,8 @@
"qs": "^6.5.1",
"raven": "^2.5.0",
"raven-js": "^3.24.1",
"react": "^16.3.1",
"react-dom": "^16.3.1",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-flip-ticker": "^0.3.0",
"react-i18next": "^7.6.0",
"react-mortal": "^3.2.0",
@ -92,7 +92,7 @@
"smooth-scrollbar": "^8.2.7",
"source-map": "0.7.2",
"source-map-support": "^0.5.4",
"styled-components": "^3.2.5",
"styled-components": "^3.2.6",
"styled-system": "^2.2.1",
"tippy.js": "^2.5.2",
"ws": "^5.1.1",
@ -105,24 +105,24 @@
"@babel/preset-flow": "7.0.0-beta.42",
"@babel/preset-react": "7.0.0-beta.42",
"@babel/preset-stage-0": "7.0.0-beta.42",
"@storybook/addon-actions": "^3.4.1",
"@storybook/addon-knobs": "^3.4.1",
"@storybook/addon-links": "^3.4.1",
"@storybook/addon-options": "^3.4.1",
"@storybook/addons": "^3.4.1",
"@storybook/react": "^3.4.1",
"@storybook/addon-actions": "^3.4.2",
"@storybook/addon-knobs": "^3.4.2",
"@storybook/addon-links": "^3.4.2",
"@storybook/addon-options": "^3.4.2",
"@storybook/addons": "^3.4.2",
"@storybook/react": "^3.4.2",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^8.2.3",
"babel-jest": "^22.4.3",
"babel-loader": "^8.0.0-beta.2",
"babel-plugin-module-resolver": "^3.1.1",
"babel-plugin-styled-components": "^1.5.0",
"chalk": "^2.3.1",
"chalk": "^2.4.0",
"chance": "^1.0.13",
"concurrently": "^3.5.1",
"dotenv": "^5.0.1",
"electron": "1.8.4",
"electron-builder": "^20.8.1",
"electron-builder": "^20.9.0",
"electron-devtools-installer": "^2.2.3",
"electron-rebuild": "^1.7.3",
"electron-webpack": "^2.0.1",
@ -142,10 +142,10 @@
"js-yaml": "^3.10.0",
"lint-staged": "^7.0.4",
"node-loader": "^0.6.0",
"prettier": "^1.12.0",
"prettier": "^1.12.1",
"react-hot-loader": "^4.0.1",
"react-test-renderer": "^16.3.1",
"webpack": "^4.5.0",
"react-test-renderer": "^16.3.2",
"webpack": "^4.6.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-cli": "^2.0.14",
"yaml-loader": "^0.5.0"

12
src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap

@ -2,7 +2,7 @@
exports[`components CounterValue basic 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ USD 10.00
@ -11,7 +11,7 @@ exports[`components CounterValue basic 1`] = `
exports[`components CounterValue specifying ticker different from default 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ USD 5.00
@ -20,7 +20,7 @@ exports[`components CounterValue specifying ticker different from default 1`] =
exports[`components CounterValue using countervalue different from default 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ EUR 0.42
@ -29,7 +29,7 @@ exports[`components CounterValue using countervalue different from default 1`] =
exports[`components CounterValue with time travel whith date in countervalues 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ USD 20.00
@ -38,7 +38,7 @@ exports[`components CounterValue with time travel whith date in countervalues 1`
exports[`components CounterValue with time travel whith date not in countervalues 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ USD 0.00
@ -47,7 +47,7 @@ exports[`components CounterValue with time travel whith date not in countervalue
exports[`components CounterValue without countervalues populated 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ USD 0.00

94
src/components/CurrentAddress/index.js

@ -1,7 +1,7 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import { Trans, translate } from 'react-i18next'
import styled from 'styled-components'
import noop from 'lodash/noop'
@ -25,20 +25,12 @@ import IconShield from 'icons/Shield'
const Container = styled(Box).attrs({
borderRadius: 1,
alignItems: 'center',
bg: p => (p.withQRCode ? 'lightGrey' : 'transparent'),
py: 5,
bg: p =>
p.withQRCode ? (p.notValid ? rgba(p.theme.colors.alertRed, 0.02) : 'lightGrey') : 'transparent',
py: 4,
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%;
border: ${p => (p.notValid ? `1px dashed ${rgba(p.theme.colors.alertRed, 0.5)}` : 'none')};
`
const Address = styled(Box).attrs({
@ -63,11 +55,17 @@ const Label = styled(Box).attrs({
fontSize: 4,
flow: 1,
horizontal: true,
})``
})`
strong {
color: ${p => p.theme.colors.dark};
font-weight: 600;
border-bottom: 1px dashed ${p => p.theme.colors.dark};
}
`
const Footer = styled(Box).attrs({
horizontal: true,
mt: 5,
mt: 4,
})`
text-transform: uppercase;
width: 100%;
@ -77,9 +75,20 @@ const FooterButtonWrapper = styled(Box).attrs({
color: 'grey',
alignItems: 'center',
justifyContent: 'center',
grow: true,
borderRadius: 1,
})`
cursor: pointer;
height: 55px;
width: 55px;
&:hover {
background-color: rgba(100, 144, 241, 0.1);
color: ${p => p.theme.colors.dark};
svg {
color: ${p => p.theme.colors.wallet};
}
}
`
const FooterButton = ({
@ -91,15 +100,18 @@ const FooterButton = ({
label: string,
onClick: Function,
}) => (
<FooterButtonWrapper onClick={onClick}>
{icon}
<Box fontSize={3} ff="Museo Sans|Bold" mt={1}>
{label}
</Box>
</FooterButtonWrapper>
<Box grow alignItems="center" justifyContent="center">
<FooterButtonWrapper onClick={onClick}>
{icon}
<Box fontSize={3} ff="Museo Sans|Bold" mt={1}>
{label}
</Box>
</FooterButtonWrapper>
</Box>
)
type Props = {
accountName?: string,
address: string,
addressVerified?: boolean,
amount?: string,
@ -130,6 +142,7 @@ class CurrentAddress extends PureComponent<Props> {
render() {
const {
accountName,
address,
addressVerified,
amount,
@ -149,20 +162,27 @@ class CurrentAddress extends PureComponent<Props> {
return (
<Container withQRCode={withQRCode} notValid={notValid} {...props}>
<WrapperAddress notValid={notValid} grow>
{withQRCode && (
<Box mb={4}>
<QRCode size={150} data={`bitcoin:${address}${amount ? `?amount=${amount}` : ''}`} />
</Box>
)}
<Label>
<Box>{t('currentAddress:label')}</Box>
<IconInfoCircle size={12} />
</Label>
<Address withQRCode={withQRCode} notValid={notValid}>
{address}
</Address>
</WrapperAddress>
{withQRCode && (
<Box mb={4}>
<QRCode size={120} data={`bitcoin:${address}${amount ? `?amount=${amount}` : ''}`} />
</Box>
)}
<Label>
<Box>
{accountName ? (
<Trans i18nKey="currentAddress:labelFrom" parent="div">
{'Address from '}
<strong>{accountName}</strong>
</Trans>
) : (
t('currentAddress:label')
)}
</Box>
<IconInfoCircle size={12} />
</Label>
<Address withQRCode={withQRCode} notValid={notValid}>
{address}
</Address>
{withBadge && (
<Box horizontal flow={2} mt={2} alignItems="center">
<Box color={notValid ? 'alertRed' : 'wallet'}>
@ -186,7 +206,7 @@ class CurrentAddress extends PureComponent<Props> {
)}
/>
<Print
data={{ address, amount }}
data={{ address, amount, accountName }}
render={(print, isLoading) => (
<FooterButton
icon={<IconPrint size={16} />}

3
src/components/CurrentAddress/stories.js

@ -2,7 +2,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { boolean } from '@storybook/addon-knobs'
import { boolean, text } from '@storybook/addon-knobs'
import CurrentAddress from 'components/CurrentAddress'
@ -12,6 +12,7 @@ const stories = storiesOf('Components', module)
stories.add('CurrentAddress', () => (
<CurrentAddress
accountName={text('accountName', '')}
address={accounts[0].address}
addressVerified={boolean('addressVerified', true)}
withBadge={boolean('withBadge', false)}

6
src/components/DashboardPage/index.js

@ -33,6 +33,7 @@ import AccountCard from './AccountCard'
import AccountsOrder from './AccountsOrder'
const mapStateToProps = state => ({
username: state.settings.username,
accounts: getVisibleAccounts(state),
counterValue: getCounterValueCode(state),
})
@ -48,6 +49,7 @@ type Props = {
accounts: Account[],
push: Function,
counterValue: string,
username: string,
}
type State = {
@ -109,7 +111,7 @@ class DashboardPage extends PureComponent<Props, State> {
_cacheBalance = null
render() {
const { push, accounts, t, counterValue } = this.props
const { push, accounts, t, counterValue, username } = this.props
const { accountsChunk, selectedTime, daysCount } = this.state
const totalAccounts = accounts.length
@ -119,7 +121,7 @@ class DashboardPage extends PureComponent<Props, State> {
<Box horizontal alignItems="flex-end">
<Box grow>
<Text color="dark" ff="Museo Sans" fontSize={7}>
{t('dashboard:greetings', { name: 'Khalil' })}
{t('dashboard:greetings', { name: username })}
</Text>
<Text color="grey" fontSize={5} ff="Museo Sans|Light">
{totalAccounts > 0

6
src/components/IsUnlocked.js

@ -1,10 +1,10 @@
// @flow
import bcrypt from 'bcryptjs'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import bcrypt from 'bcryptjs'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import type { Settings, T } from 'types/common'
@ -25,7 +25,7 @@ import { isLocked, unlock } from 'reducers/application'
import { getCounterValueCode } from 'reducers/settings'
import Box from 'components/base/Box'
import Input from 'components/base/Input'
import InputPassword from 'components/base/InputPassword'
type InputValue = {
password: string,
@ -141,7 +141,7 @@ class IsUnlocked extends Component<Props, State> {
<Box sticky alignItems="center" justifyContent="center" onClick={this.handleFocusInput}>
<form onSubmit={this.handleSubmit}>
<Box>
<Input
<InputPassword
autoFocus
innerRef={(n: any) => (this._input = n)}
placeholder={t('common:password')}

4
src/components/OperationsList/index.js

@ -16,6 +16,8 @@ import type { Account, Operation as OperationType } from '@ledgerhq/wallet-commo
import noop from 'lodash/noop'
import keyBy from 'lodash/keyBy'
import { rgba } from 'styles/helpers'
import type { T } from 'types/common'
import { MODAL_OPERATION_DETAILS } from 'config/constants'
@ -71,7 +73,7 @@ const OperationRaw = styled(Box).attrs({
}
&:hover {
background: ${p => p.theme.colors.lightFog};
background: ${p => rgba(p.theme.colors.wallet, 0.04)};
}
`

3
src/components/SelectAccount/index.js

@ -3,9 +3,10 @@
import React from 'react'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import noop from 'lodash/noop'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import noop from 'lodash/noop'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import type { T } from 'types/common'

60
src/components/SelectCurrency/index.js

@ -0,0 +1,60 @@
// @flow
import React from 'react'
import { translate } from 'react-i18next'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import { listCurrencies } from '@ledgerhq/currencies'
import noop from 'lodash/noop'
import type { Currency } from '@ledgerhq/currencies'
import type { T } from 'types/common'
import Select from 'components/base/Select'
import Box from 'components/base/Box'
const renderItem = a => {
const { color, name, coinType } = a
const Icon = getIconByCoinType(coinType)
return (
<Box grow horizontal alignItems="center" flow={2}>
{Icon && (
<Box style={{ width: 16, height: 16, color }}>
<Icon size={16} />
</Box>
)}
<Box grow ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{name}
</Box>
</Box>
)
}
const currencies = listCurrencies()
type Props = {
onChange: Function,
value?: Currency,
t: T,
}
const SelectCurrency = ({ onChange, value, t, ...props }: Props) => (
<Select
{...props}
value={value}
renderSelected={renderItem}
renderItem={renderItem}
keyProp="coinType"
items={currencies.sort((a, b) => (a.name < b.name ? -1 : 1))}
placeholder={t('common:selectCurrency')}
fontSize={4}
onChange={onChange}
/>
)
SelectCurrency.defaultProps = {
onChange: noop,
value: undefined,
}
export default translate()(SelectCurrency)

31
src/components/SelectCurrency/stories.js

@ -0,0 +1,31 @@
// @flow
import React, { Component } from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import SelectCurrency from 'components/SelectCurrency'
const stories = storiesOf('Components', module)
class Wrapper extends Component<any, any> {
state = {
value: '',
}
handleChange = item => {
this.setState({ value: item })
action('onChange')(item)
}
render() {
const { render } = this.props
const { value } = this.state
return render({ onChange: this.handleChange, value })
}
}
stories.add('SelectCurrency', () => (
<Wrapper render={({ onChange, value }) => <SelectCurrency onChange={onChange} value={value} />} />
))

98
src/components/SettingsPage/Display.js

@ -1,98 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import type { SettingsDisplay, T } from 'types/common'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
import Label from 'components/base/Label'
import Select from 'components/base/Select'
type InputValue = SettingsDisplay
type Props = {
t: T,
settings: SettingsDisplay,
onSaveSettings: Function,
}
type State = {
inputValue: InputValue,
}
class TabProfile extends PureComponent<Props, State> {
state = {
inputValue: {
language: this.props.settings.language,
},
}
getDatas() {
const { t } = this.props
return {
languages: [
{
key: 'en',
name: t('language:en'),
},
{
key: 'fr',
name: t('language:fr'),
},
],
}
}
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
...prev.inputValue,
[key]: value,
},
}))
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
const { onSaveSettings } = this.props
const { inputValue } = this.state
onSaveSettings({
...inputValue,
})
}
render() {
const { t } = this.props
const { inputValue } = this.state
const { languages } = this.getDatas()
const currentLanguage = languages.find(l => l.key === inputValue.language)
return (
<form onSubmit={this.handleSubmit}>
<Card flow={3}>
<Box flow={1}>
<Label>{t('settings:display.language')}</Label>
<Select
onChange={item => this.handleChangeInput('language')(item.key)}
renderSelected={item => item && item.name}
value={currentLanguage}
items={languages}
/>
</Box>
<Box horizontal justifyContent="flex-end">
<Button primary type="submit">
{t('common:save')}
</Button>
</Box>
</Card>
</form>
)
}
}
export default TabProfile

88
src/components/SettingsPage/Money.js

@ -1,88 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import { getFiatUnit } from '@ledgerhq/currencies'
import type { SettingsMoney, T } from 'types/common'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
import Label from 'components/base/Label'
import Select from 'components/base/Select'
const counterValues = ['USD', 'EUR', 'JPY', 'GBP'].sort().map(c => {
const { name } = getFiatUnit(c)
return {
key: c,
name,
}
})
type InputValue = SettingsMoney
type Props = {
t: T,
settings: SettingsMoney,
onSaveSettings: Function,
}
type State = {
inputValue: InputValue,
}
class TabProfile extends PureComponent<Props, State> {
state = {
inputValue: {
counterValue: this.props.settings.counterValue,
},
}
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
...prev.inputValue,
[key]: value,
},
}))
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
const { onSaveSettings } = this.props
const { inputValue } = this.state
onSaveSettings({
...inputValue,
})
}
render() {
const { t } = this.props
const { inputValue } = this.state
const currentCounterValues = counterValues.find(l => l.key === inputValue.counterValue)
return (
<form onSubmit={this.handleSubmit}>
<Card flow={3}>
<Box flow={1}>
<Label>{t('settings:display.counterValue')}</Label>
<Select
onChange={item => this.handleChangeInput('counterValue')(item.key)}
renderSelected={item => item && item.name}
value={currentCounterValues}
items={counterValues}
/>
</Box>
<Box horizontal justifyContent="flex-end">
<Button primary type="submit">
{t('common:save')}
</Button>
</Box>
</Card>
</form>
)
}
}
export default TabProfile

137
src/components/SettingsPage/PasswordModal.js

@ -0,0 +1,137 @@
// @flow
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import bcrypt from 'bcryptjs'
import { unlock } from 'reducers/application'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import InputPassword from 'components/base/InputPassword'
import Label from 'components/base/Label'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal'
import type { T } from 'types/common'
const mapDispatchToProps = {
unlock,
}
type Props = {
t: T,
onClose: Function,
unlock: Function,
isPasswordEnabled: boolean,
currentPasswordHash: string,
onChangePassword: Function,
}
type State = {
currentPassword: string,
newPassword: string,
}
const INITIAL_STATE = {
currentPassword: '',
newPassword: '',
}
class PasswordModal extends PureComponent<Props, State> {
state = INITIAL_STATE
handleSave = (e: SyntheticEvent<HTMLFormElement>) => {
if (e) {
e.preventDefault()
}
if (!this.isValid()) {
return
}
const { currentPassword, newPassword } = this.state
const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props
if (isPasswordEnabled) {
if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) {
return
}
onChangePassword(newPassword)
} else {
onChangePassword(newPassword)
}
}
handleInputChange = key => value => this.setState({ [key]: value })
handleReset = () => this.setState(INITIAL_STATE)
isValid = () => {
const { newPassword } = this.state
return newPassword
}
render() {
const { t, isPasswordEnabled, onClose, ...props } = this.props
const { currentPassword, newPassword } = this.state
const isValid = this.isValid()
return (
<Modal
{...props}
onHide={this.handleReset}
onClose={onClose}
render={({ onClose }) => (
<form onSubmit={this.handleSave}>
<ModalBody onClose={onClose}>
<ModalTitle>{t('settings:profile.passwordModalTitle')}</ModalTitle>
<ModalContent>
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{t('settings:profile.passwordModalSubtitle')}
</Box>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
{t('settings:profile.passwordModalDesc')}
<Box px={7} mt={4} flow={3}>
{isPasswordEnabled && (
<Box flow={1}>
<Label htmlFor="password">
{t('settings:profile.passwordModalPasswordInput')}
</Label>
<InputPassword
type="password"
placeholder={t('settings:profile.passwordModalPasswordInput')}
autoFocus
id="password"
onChange={this.handleInputChange('currentPassword')}
value={currentPassword}
/>
</Box>
)}
<Box flow={1}>
{isPasswordEnabled && (
<Label htmlFor="newPassword">
{t('settings:profile.passwordModalNewPasswordInput')}
</Label>
)}
<InputPassword
placeholder={t('settings:profile.passwordModalNewPasswordInput')}
id="newPassword"
onChange={this.handleInputChange('newPassword')}
value={newPassword}
withStrength
/>
</Box>
</Box>
</Box>
</ModalContent>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}>
<Button onClick={onClose}>{t('common:cancel')}</Button>
<Button primary onClick={this.handleSave} disabled={!isValid}>
{t('settings:profile.passwordModalSave')}
</Button>
</ModalFooter>
</ModalBody>
</form>
)}
/>
)
}
}
export default connect(null, mapDispatchToProps)(PasswordModal)

124
src/components/SettingsPage/Profile.js

@ -1,124 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import bcrypt from 'bcryptjs'
import get from 'lodash/get'
import set from 'lodash/set'
import { setEncryptionKey } from 'helpers/db'
import type { SettingsProfile, T } from 'types/common'
import { unlock } from 'reducers/application'
import Box, { Card } from 'components/base/Box'
import Input from 'components/base/Input'
import CheckBox from 'components/base/CheckBox'
import Button from 'components/base/Button'
import Label from 'components/base/Label'
type InputValue = SettingsProfile
type Props = {
t: T,
settings: SettingsProfile,
onSaveSettings: Function,
unlock: Function,
}
type State = {
inputValue: InputValue,
}
const mapDispatchToProps = {
unlock,
}
class TabProfile extends PureComponent<Props, State> {
state = {
inputValue: {
password: {
...this.props.settings.password,
value: undefined,
},
},
}
handleChangeInput = (key: string) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
...set(prev.inputValue, key, value),
},
}))
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
const { onSaveSettings, unlock } = this.props
const { inputValue } = this.state
const settings = {
...inputValue,
password: {
...inputValue.password,
value: '',
},
}
const password = get(inputValue, 'password', {})
if (password.state === true && password.value.trim() !== '') {
settings.password.value = bcrypt.hashSync(password.value, 8)
setEncryptionKey('accounts', password.value)
} else {
setEncryptionKey('accounts', undefined)
}
unlock()
onSaveSettings(settings)
}
render() {
const { t } = this.props
const { inputValue } = this.state
const isPasswordChecked = get(inputValue, 'password.state', false)
return (
<form onSubmit={this.handleSubmit}>
<Card flow={3}>
<label>
<Box
horizontal
alignItems="center"
flow={2}
style={{ cursor: 'pointer' }}
onClick={() => this.handleChangeInput('password.state')(!isPasswordChecked)}
>
<CheckBox isChecked={isPasswordChecked} />
<div>{t('settings:profile.protectWithPassword')}</div>
</Box>
</label>
{get(inputValue, 'password.state') === true && (
<Box flow={1}>
<Label>{t('settings:profile.password')}</Label>
<Input
value={get(inputValue, 'password.value', '')}
onChange={this.handleChangeInput('password.value')}
type="password"
/>
</Box>
)}
<Box horizontal justifyContent="flex-end">
<Button primary type="submit">
{t('common:save')}
</Button>
</Box>
</Card>
</form>
)
}
}
export default connect(null, mapDispatchToProps)(TabProfile)

120
src/components/SettingsPage/SettingsSection.js

@ -0,0 +1,120 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import { rgba } from 'styles/helpers'
import Box, { Card } from 'components/base/Box'
export const SettingsSection = styled(Card).attrs({ p: 0 })``
const SettingsSectionHeaderContainer = styled(Box).attrs({
p: 4,
horizontal: true,
align: 'center',
})`
border-bottom: 1px solid ${p => p.theme.colors.lightFog};
line-height: normal;
`
const RoundIconContainer = styled(Box).attrs({
align: 'center',
justify: 'center',
bg: p => rgba(p.theme.colors.wallet, 0.2),
color: 'wallet',
})`
height: 30px;
width: 30px;
border-radius: 50%;
`
export const SettingsSectionBody = styled(Box)`
> * + * {
&:after {
background: ${p => p.theme.colors.lightFog};
content: '';
display: block;
height: 1px;
left: ${p => p.theme.space[4]}px;
position: absolute;
right: ${p => p.theme.space[4]}px;
top: 0;
}
}
`
export function SettingsSectionHeader({
title,
desc,
icon,
renderRight,
}: {
title: string,
desc: string,
icon: any,
renderRight?: any,
}) {
return (
<SettingsSectionHeaderContainer>
<RoundIconContainer mr={3}>{icon}</RoundIconContainer>
<Box grow>
<Box ff="Museo Sans|Regular" color="dark">
{title}
</Box>
<Box ff="Open Sans" fontSize={3}>
{desc}
</Box>
</Box>
{renderRight && (
<Box alignItems="center" justifyContent="flex-end">
{renderRight}
</Box>
)}
</SettingsSectionHeaderContainer>
)
}
SettingsSectionHeader.defaultProps = {
renderRight: undefined,
}
const SettingsSectionRowContainer = styled(Box).attrs({
p: 4,
horizontal: true,
align: 'center',
relative: true,
})`
cursor: ${p => (p.onClick ? 'pointer' : '')};
`
export function SettingsSectionRow({
title,
desc,
children,
onClick,
}: {
title: string,
desc: string,
children?: any,
onClick?: ?Function,
}) {
return (
<SettingsSectionRowContainer onClick={onClick}>
<Box grow>
<Box ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{title}
</Box>
<Box ff="Open Sans" fontSize={3} color="grey">
{desc}
</Box>
</Box>
<Box>{children}</Box>
</SettingsSectionRowContainer>
)
}
SettingsSectionRow.defaultProps = {
children: null,
onClick: null,
}

148
src/components/SettingsPage/index.js

@ -4,8 +4,9 @@ import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import moment from 'moment'
import { Switch, Route } from 'react-router'
import type { RouterHistory, Match, Location } from 'react-router'
import type { Settings, T } from 'types/common'
import type { SaveSettings } from 'actions/settings'
import type { FetchCounterValues } from 'actions/counterValues'
@ -13,14 +14,13 @@ import type { FetchCounterValues } from 'actions/counterValues'
import { saveSettings } from 'actions/settings'
import { fetchCounterValues } from 'actions/counterValues'
import Pills from 'components/base/Pills'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import Tabs from 'components/base/Tabs'
import TabDisplay from './Display'
import TabProfile from './Profile'
import TabTools from './Tools'
import TabMoney from './Money'
import SectionDisplay from './sections/Display'
import SectionCurrencies from './sections/Currencies'
import SectionProfile from './sections/Profile'
import SectionAbout from './sections/About'
const mapStateToProps = state => ({
settings: state.settings,
@ -32,33 +32,80 @@ const mapDispatchToProps = {
}
type Props = {
fetchCounterValues: FetchCounterValues,
history: RouterHistory,
i18n: Object,
location: Location,
match: Match,
saveSettings: SaveSettings,
settings: Settings,
fetchCounterValues: FetchCounterValues,
t: T,
}
type State = {
tab: number,
tab: Object,
}
class SettingsPage extends PureComponent<Props, State> {
state = {
tab: 0,
constructor(props) {
super(props)
this._items = [
{
key: 'display',
label: props.t('settings:tabs.display'),
value: p => () => <SectionDisplay {...p} />,
},
{
key: 'currencies',
label: props.t('settings:tabs.currencies'),
value: p => () => <SectionCurrencies {...p} />,
},
{
key: 'profile',
label: props.t('settings:tabs.profile'),
value: p => () => <SectionProfile {...p} />,
},
{
key: 'about',
label: props.t('settings:tabs.about'),
value: p => () => <SectionAbout {...p} />,
},
]
this.state = {
tab: this.getCurrentTab({ url: props.match.url, pathname: props.location.pathname }),
}
}
handleChangeTab = (tab: number) => this.setState({ tab })
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.setState({
tab: this.getCurrentTab({
url: nextProps.match.url,
pathname: nextProps.location.pathname,
}),
})
}
}
handleSaveSettings = newSettings => {
const { fetchCounterValues, saveSettings, i18n, settings } = this.props
getCurrentTab = ({ url, pathname }) =>
this._items.find(i => `${url}/${i.key}` === pathname) || this._items[0]
saveSettings(newSettings)
_items = []
if (newSettings.language !== settings.language) {
i18n.changeLanguage(newSettings.language)
moment.locale(newSettings.language)
handleChangeTab = (item: any) => {
const { match, history, location } = this.props
const url = `${match.url}/${item.key}`
if (location.pathname !== url) {
history.push(`${match.url}/${item.key}`)
}
}
handleSaveSettings = newSettings => {
const { fetchCounterValues, saveSettings, settings } = this.props
saveSettings(newSettings)
if (newSettings.counterValue !== settings.counterValue) {
fetchCounterValues()
@ -66,62 +113,29 @@ class SettingsPage extends PureComponent<Props, State> {
}
render() {
const { settings, t } = this.props
const { match, settings, t, i18n, saveSettings } = this.props
const { tab } = this.state
const props = {
t,
settings,
onSaveSettings: this.handleSaveSettings,
saveSettings,
i18n,
}
const defaultItem = this._items[0]
return (
<Box flow={6}>
<Text fontSize={7}>{t('settings:title')}</Text>
<Tabs
index={tab}
onTabClick={this.handleChangeTab}
items={[
{
key: 'display',
title: t('settings:tabs.display'),
render: () => <TabDisplay {...props} />,
},
{
key: 'money',
title: t('settings:tabs.money'),
render: () => <TabMoney {...props} />,
},
{
key: 'material',
isDisabled: true,
title: t('settings:tabs.material'),
render: () => <div>{'Matériel'}</div>,
},
{
key: 'app',
isDisabled: true,
title: t('settings:tabs.app'),
render: () => <div>{'App (beta)'}</div>,
},
{
key: 'tools',
title: t('settings:tabs.tools'),
render: () => <TabTools {...props} />,
},
{
key: 'blockchain',
isDisabled: true,
title: t('settings:tabs.blockchain'),
render: () => <div>{'Blockchain'}</div>,
},
{
key: 'profile',
title: t('settings:tabs.profile'),
render: () => <TabProfile {...props} />,
},
]}
/>
<Box>
<Box ff="Museo Sans|Regular" color="dark" fontSize={7} mb={5}>
{t('settings:title')}
</Box>
<Pills mb={4} items={this._items} activeKey={tab.key} onChange={this.handleChangeTab} />
<Switch>
{this._items.map(i => (
<Route key={i.key} path={`${match.url}/${i.key}`} render={i.value && i.value(props)} />
))}
<Route render={defaultItem.value && defaultItem.value(props)} />
</Switch>
</Box>
)
}

62
src/components/SettingsPage/sections/About.js

@ -0,0 +1,62 @@
// @flow
import React, { PureComponent } from 'react'
import { shell } from 'electron'
import type { T } from 'types/common'
import IconHelp from 'icons/Help'
import IconChevronRight from 'icons/ChevronRight'
import {
SettingsSection as Section,
SettingsSectionHeader as Header,
SettingsSectionBody as Body,
SettingsSectionRow as Row,
} from '../SettingsSection'
type Props = {
t: T,
}
class SectionAbout extends PureComponent<Props> {
handleOpenLink = (url: string) => () => shell.openExternal(url)
render() {
const { t } = this.props
return (
<Section>
<Header
icon={<IconHelp size={16} />}
title={t('settings:tabs.about')}
desc="Lorem ipsum dolor sit amet"
/>
<Body>
<Row
onClick={this.handleOpenLink('http://google.com')}
title={t('settings:about.faq')}
desc={t('settings:about.faqDesc')}
>
<IconChevronRight size={16} />
</Row>
<Row
onClick={this.handleOpenLink('http://google.com')}
title={t('settings:about.contactUs')}
desc={t('settings:about.contactUsDesc')}
>
<IconChevronRight size={16} />
</Row>
<Row
onClick={this.handleOpenLink('http://google.com')}
title={t('settings:about.terms')}
desc={t('settings:about.termsDesc')}
>
<IconChevronRight size={16} />
</Row>
</Body>
</Section>
)
}
}
export default SectionAbout

72
src/components/SettingsPage/sections/Currencies.js

@ -0,0 +1,72 @@
// @flow
import React, { PureComponent } from 'react'
import { listCurrencies } from '@ledgerhq/currencies'
import type { Currency } from '@ledgerhq/currencies'
import type { T } from 'types/common'
import SelectCurrency from 'components/SelectCurrency'
import IconCurrencies from 'icons/Currencies'
import {
SettingsSection as Section,
SettingsSectionHeader as Header,
SettingsSectionBody as Body,
SettingsSectionRow as Row,
} from '../SettingsSection'
type Props = {
t: T,
}
type State = {
currency: Currency,
}
class TabCurrencies extends PureComponent<Props, State> {
state = {
currency: listCurrencies()[0],
}
handleChangeCurrency = (currency: Currency) => this.setState({ currency })
render() {
const { t } = this.props
const { currency } = this.state
return (
<Section>
<Header
icon={<IconCurrencies size={16} />}
title={t('settings:tabs.currencies')}
desc="Lorem ipsum dolor sit amet"
renderRight={
<SelectCurrency small value={currency} onChange={this.handleChangeCurrency} />
}
/>
<Body>
<Row
title={t('settings:currencies.confirmationsToSpend')}
desc={t('settings:currencies.confirmationsToSpendDesc')}
/>
<Row
title={t('settings:currencies.confirmationsNb')}
desc={t('settings:currencies.confirmationsNbDesc')}
/>
<Row
title={t('settings:currencies.transactionsFees')}
desc={t('settings:currencies.transactionsFeesDesc')}
/>
<Row
title={t('settings:currencies.explorer')}
desc={t('settings:currencies.explorerDesc')}
/>
</Body>
</Section>
)
}
}
export default TabCurrencies

120
src/components/SettingsPage/sections/Display.js

@ -0,0 +1,120 @@
// @flow
import React, { PureComponent } from 'react'
import moment from 'moment'
import { listFiats } from '@ledgerhq/currencies'
import type { Settings, T } from 'types/common'
import Select from 'components/base/Select'
import IconDisplay from 'icons/Display'
import {
SettingsSection as Section,
SettingsSectionHeader as Header,
SettingsSectionBody as Body,
SettingsSectionRow as Row,
} from '../SettingsSection'
const fiats = listFiats().map(fiat => ({
key: fiat.code,
fiat,
name: `${fiat.name} - ${fiat.code}${fiat.symbol ? ` (${fiat.symbol})` : ''}`,
}))
type Props = {
t: T,
settings: Settings,
saveSettings: Function,
i18n: Object,
}
type State = {
cachedLanguageKey: string,
cachedCounterValue: ?Object,
}
class TabProfile extends PureComponent<Props, State> {
state = {
cachedLanguageKey: this.props.settings.language,
cachedCounterValue: fiats.find(fiat => fiat.fiat.code === this.props.settings.counterValue),
}
getDatas() {
const { t } = this.props
return {
languages: [{ key: 'en', name: t('language:en') }, { key: 'fr', name: t('language:fr') }],
}
}
handleChangeCounterValue = (item: Object) => {
const { saveSettings } = this.props
this.setState({ cachedCounterValue: item.fiat })
window.requestIdleCallback(() => {
saveSettings({ counterValue: item.fiat.code })
})
}
handleChangeLanguage = (languageKey: string) => {
const { i18n, saveSettings } = this.props
this.setState({ cachedLanguageKey: languageKey })
window.requestIdleCallback(() => {
i18n.changeLanguage(languageKey)
moment.locale(languageKey)
saveSettings({ language: languageKey })
})
}
render() {
const { t } = this.props
const { cachedLanguageKey, cachedCounterValue } = this.state
const { languages } = this.getDatas()
const currentLanguage = languages.find(l => l.key === cachedLanguageKey)
return (
<Section>
<Header
icon={<IconDisplay size={16} />}
title={t('settings:tabs.display')}
desc="Lorem ipsum dolor sit amet"
/>
<Body>
<Row
title={t('settings:display.counterValue')}
desc={t('settings:display.counterValueDesc')}
>
<Select
searchable
fuseOptions={{ keys: ['name'] }}
style={{ minWidth: 250 }}
small
onChange={item => this.handleChangeCounterValue(item)}
itemToString={item => (item ? item.name : '')}
renderSelected={item => item && item.name}
items={fiats}
value={cachedCounterValue}
/>
</Row>
<Row title={t('settings:display.language')} desc={t('settings:display.languageDesc')}>
<Select
style={{ minWidth: 130 }}
small
onChange={item => this.handleChangeLanguage(item.key)}
renderSelected={item => item && item.name}
value={currentLanguage}
items={languages}
/>
</Row>
<Row title={t('settings:display.region')} desc={t('settings:display.regionDesc')}>
{'-'}
</Row>
<Row title={t('settings:display.stock')} desc={t('settings:display.stockDesc')}>
{'-'}
</Row>
</Body>
</Section>
)
}
}
export default TabProfile

172
src/components/SettingsPage/sections/Profile.js

@ -0,0 +1,172 @@
// @flow
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { remote } from 'electron'
import bcrypt from 'bcryptjs'
import type { Settings, T } from 'types/common'
import debounce from 'lodash/debounce'
import { unlock } from 'reducers/application'
import db, { setEncryptionKey } from 'helpers/db'
import Input from 'components/base/Input'
import CheckBox from 'components/base/CheckBox'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import { ConfirmModal } from 'components/base/Modal'
import IconUser from 'icons/User'
import PasswordModal from '../PasswordModal'
import {
SettingsSection as Section,
SettingsSectionHeader as Header,
SettingsSectionBody as Body,
SettingsSectionRow as Row,
} from '../SettingsSection'
const mapDispatchToProps = {
unlock,
}
type Props = {
t: T,
settings: Settings,
unlock: Function,
saveSettings: Function,
}
type State = {
isHardResetModalOpened: boolean,
isPasswordModalOpened: boolean,
username: string,
}
class TabProfile extends PureComponent<Props, State> {
state = {
username: this.props.settings.username,
isHardResetModalOpened: false,
isPasswordModalOpened: false,
}
setPassword = password => {
const { saveSettings, unlock } = this.props
window.requestIdleCallback(() => {
setEncryptionKey('accounts', password)
const hash = password ? bcrypt.hashSync(password, 8) : undefined
saveSettings({
password: {
isEnabled: hash !== undefined,
value: hash,
},
})
unlock()
})
}
debounceSaveUsername = debounce(
v => this.props.saveSettings({ username: v.trim() || 'Anonymous' }),
250,
)
handleChangeUsername = username => {
this.setState({ username })
this.debounceSaveUsername(username)
}
handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true })
handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false })
handleOpenPasswordModal = () => this.setState({ isPasswordModalOpened: true })
handleClosePasswordModal = () => this.setState({ isPasswordModalOpened: false })
handleHardReset = () => {
db.resetAll()
remote.app.relaunch()
remote.app.exit()
}
handleChangePasswordCheck = isChecked => {
if (isChecked) {
this.handleOpenPasswordModal()
} else {
this.setPassword(undefined)
}
}
handleChangePassword = (password: ?string) => {
if (password) {
this.setPassword(password)
this.handleClosePasswordModal()
}
}
render() {
const { t, settings } = this.props
const { username, isHardResetModalOpened, isPasswordModalOpened } = this.state
const isPasswordEnabled = settings.password.isEnabled === true
return (
<Section>
<Header
icon={<IconUser size={16} />}
title={t('settings:tabs.profile')}
desc="Lorem ipsum dolor sit amet"
/>
<Body>
<Row title={t('settings:profile.username')} desc={t('settings:profile.usernameDesc')}>
<Input
small
placeholder={t('settings:profile.username')}
onChange={this.handleChangeUsername}
value={username}
/>
</Row>
<Row title={t('settings:profile.password')} desc={t('settings:profile.passwordDesc')}>
<Box horizontal flow={2} align="center">
{isPasswordEnabled && (
<Button onClick={this.handleOpenPasswordModal}>
{t('settings:profile.changePassword')}
</Button>
)}
<CheckBox isChecked={isPasswordEnabled} onChange={this.handleChangePasswordCheck} />
</Box>
</Row>
<Row title={t('settings:profile.sync')} desc={t('settings:profile.syncDesc')}>
<Button primary>{t('settings:profile.sync')}</Button>
</Row>
<Row title={t('settings:profile.export')} desc={t('settings:profile.exportDesc')}>
<Button primary>{t('settings:profile.export')}</Button>
</Row>
<Row title={t('settings:profile.reset')} desc={t('settings:profile.resetDesc')}>
<Button danger onClick={this.handleOpenHardResetModal}>
{t('settings:profile.resetButton')}
</Button>
</Row>
</Body>
<ConfirmModal
isDanger
isOpened={isHardResetModalOpened}
onClose={this.handleCloseHardResetModal}
onReject={this.handleCloseHardResetModal}
onConfirm={this.handleHardReset}
title={t('settings:hardResetModal.title')}
subTitle={t('settings:hardResetModal.subTitle')}
desc={t('settings:hardResetModal.desc')}
/>
<PasswordModal
t={t}
isOpened={isPasswordModalOpened}
onClose={this.handleClosePasswordModal}
onChangePassword={this.handleChangePassword}
isPasswordEnabled={isPasswordEnabled}
currentPasswordHash={settings.password.value}
/>
</Section>
)
}
}
export default connect(null, mapDispatchToProps)(TabProfile)

0
src/components/SettingsPage/Tools.js → src/components/SettingsPage/sections/Tools.js

10
src/components/SideBar/Item.js

@ -3,7 +3,7 @@
import React from 'react'
import styled from 'styled-components'
import { compose } from 'redux'
import { withRouter } from 'react-router'
import { matchPath, withRouter } from 'react-router'
import { push } from 'react-router-redux'
import { connect } from 'react-redux'
@ -77,7 +77,13 @@ function Item({
openModal,
}: Props) {
const { pathname } = location
const isActive = pathname === linkTo
const isActive = linkTo
? linkTo === '/'
? linkTo === pathname
: matchPath(pathname, {
path: linkTo,
})
: false
return (
<Container
big={big}

23
src/components/TopBar.js

@ -5,8 +5,10 @@ import { compose } from 'redux'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { withRouter } from 'react-router'
import { ipcRenderer } from 'electron'
import type { Location, RouterHistory } from 'react-router'
import type { T } from 'types/common'
import { rgba } from 'styles/helpers'
@ -75,6 +77,7 @@ const DropDownItem = styled(DDItem).attrs({
`
const mapStateToProps = state => ({
username: state.settings.username,
hasAccounts: getAccounts(state).length > 0,
hasPassword: hasPassword(state),
})
@ -84,10 +87,13 @@ const mapDispatchToProps = {
}
type Props = {
t: T,
hasAccounts: boolean,
hasPassword: boolean,
history: RouterHistory,
location: Location,
lock: Function,
t: T,
username: string,
}
type State = {
@ -145,7 +151,7 @@ class TopBar extends PureComponent<Props, State> {
handleLock = () => this.props.lock()
render() {
const { hasPassword, hasAccounts, t } = this.props
const { location, hasPassword, history, hasAccounts, username, t } = this.props
const { sync } = this.state
return (
@ -171,6 +177,13 @@ class TopBar extends PureComponent<Props, State> {
key: 'profile',
label: t('common:editProfile'),
icon: <IconUser size={16} />,
onClick: () => {
const url = '/settings/profile'
if (location.pathname !== url) {
history.push(url)
}
},
},
...(hasPassword
? [
@ -197,7 +210,7 @@ class TopBar extends PureComponent<Props, State> {
justifyContent="center"
offsetTop={-2}
>
<Box>{'Khalil Benihoud'}</Box>
<Box>{username}</Box>
<IconAngleDown size={12} />
</DropDown>
</Box>
@ -207,4 +220,6 @@ class TopBar extends PureComponent<Props, State> {
}
}
export default compose(connect(mapStateToProps, mapDispatchToProps), translate())(TopBar)
export default compose(withRouter, connect(mapStateToProps, mapDispatchToProps), translate())(
TopBar,
)

7
src/components/base/Box/index.js

@ -11,12 +11,18 @@ import {
fontSize,
justifyContent,
space,
style,
} from 'styled-system'
import fontFamily from 'styles/styled/fontFamily'
import Text from 'components/base/Text'
const textAlign = style({
prop: 'textAlign',
cssProperty: 'textAlign',
})
const Box = styled.div`
${alignItems};
${borderRadius};
@ -27,6 +33,7 @@ const Box = styled.div`
${fontSize};
${justifyContent};
${space};
${textAlign};
display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};

126
src/components/base/Button/index.js

@ -9,36 +9,90 @@ import { darken, lighten } from 'styles/helpers'
import fontFamily from 'styles/styled/fontFamily'
const buttonStyles = {
primary: {
default: p => `
background: ${p.disabled ? p.theme.colors.lightFog : p.theme.colors.wallet};
color: ${p.disabled ? p.theme.colors.grey : p.theme.colors.white};
`,
hover: p => `
background: ${darken(p.theme.colors.wallet, 0.05)};
`,
active: p => `
background: ${darken(p.theme.colors.wallet, 0.05)};
`,
},
danger: {
default: p => `
background: ${p.disabled ? p.theme.colors.lightFog : p.theme.colors.alertRed};
color: ${p.disabled ? p.theme.colors.grey : p.theme.colors.white};
`,
hover: p => `
background: ${lighten(p.theme.colors.alertRed, 0.2)};
`,
active: p => `
background: ${lighten(p.theme.colors.alertRed, 0.2)};
`,
},
outline: {
default: p => `
background: transparent;
border: 1px solid ${p.theme.colors.wallet};
color: ${p.theme.colors.wallet};
`,
active: p => `
color: ${darken(p.theme.colors.wallet, 0.1)};
border-color: ${darken(p.theme.colors.wallet, 0.1)};
`,
},
icon: {
default: () => `
font-size: ${fontSize[3]}px;
padding-left: ${space[1]}px;
padding-right: ${space[1]}px;
`,
},
}
function getStyles(props, state) {
let output = ``
for (const s in buttonStyles) {
if (buttonStyles.hasOwnProperty(s) && props[s] === true) {
const style = buttonStyles[s][state]
if (style) {
output += style(props)
}
}
}
return output
}
const Base = styled.button.attrs({
ff: 'Museo Sans|Regular',
fontSize: p => p.fontSize || 3,
px: p => (p.primary ? (p.small ? 2 : 3) : 2),
px: 2,
color: 'grey',
bg: 'transparent',
})`
${space};
${color};
${fontSize};
${fontWeight};
${fontFamily};
border: none;
border-radius: ${p => p.theme.radii[1]}px;
border: ${p => (p.outline ? `1px solid ${p.theme.colors.wallet}` : 'none')};
color: ${p => (p.outline ? p.theme.colors.wallet : '')};
cursor: ${p => (p.disabled ? 'default' : 'pointer')};
height: ${p => (p.small ? 30 : 36)}px;
pointer-events: ${p => (p.disabled ? 'none' : '')};
outline: none;
&:hover {
background: ${p => (p.disabled ? '' : p.primary ? lighten(p.theme.colors.wallet, 0.05) : '')};
${p => getStyles(p, 'default')};
&:hover,
&:focus {
${p => getStyles(p, 'hover')};
}
&:active {
color: ${p =>
p.primary
? ''
: p.outline
? darken(p.theme.colors.wallet, 0.1)
: darken(p.theme.colors.grey, 0.2)};
border-color: ${p => (p.outline ? darken(p.theme.colors.wallet, 0.1) : '')};
background: ${p => (p.primary ? darken(p.theme.colors.wallet, 0.1) : '')};
${p => getStyles(p, 'active')};
}
`
@ -46,54 +100,17 @@ type Props = {
children?: any,
icon?: string,
primary?: boolean,
danger?: boolean,
disabled?: boolean,
onClick?: Function,
small?: boolean,
}
function getProps({ disabled, icon, primary }: Object) {
const props = (predicate, props, defaults = {}) => (predicate ? props : defaults)
return {
color: 'grey',
...props(
icon,
{
fontSize: 3,
px: 1,
},
{
fontSize: 4,
px: 3,
},
),
...props(
primary,
{
color: 'white',
bg: 'wallet',
},
{
bg: 'transparent',
},
),
...props(disabled, {
color: 'grey',
bg: 'lightFog',
}),
}
}
const Button = (props: Props) => {
const { onClick, children, primary, disabled } = props
const { onClick, children, disabled } = props
return (
<Base
{...getProps({ primary, disabled })}
{...props}
disabled={disabled}
onClick={disabled ? undefined : onClick}
>
<Base {...props} onClick={disabled ? undefined : onClick}>
{children}
</Base>
)
@ -106,6 +123,7 @@ Button.defaultProps = {
onClick: noop,
primary: false,
small: false,
danger: false,
}
export default Button

11
src/components/base/Button/stories.js

@ -47,6 +47,17 @@ stories.add('Button', () => (
</Button>
</Td>
</tr>
<tr>
<Td>danger</Td>
<Td>
<Button danger>Danger button</Button>
</Td>
<Td>
<Button danger disabled>
Danger button
</Button>
</Td>
</tr>
<tr>
<Td>outline</Td>
<Td>

15
src/components/base/CheckBox/index.js

@ -7,30 +7,29 @@ import styled from 'styled-components'
import { Tabbable } from 'components/base/Box'
const Base = styled(Tabbable).attrs({
bg: p => (p.isChecked ? 'wallet' : 'fog'),
bg: p => (p.isChecked ? 'wallet' : 'lightFog'),
horizontal: true,
align: 'center',
})`
backround: red;
width: 50px;
height: 24px;
border-radius: 16px;
height: 26px;
border-radius: 13px;
transition: 250ms linear background-color;
cursor: pointer;
&:focus {
box-shadow: rgba(0, 0, 0, 0.1) 0 2px 2px;
outline: none;
}
`
const Ball = styled.div`
width: 22px;
height: 22px;
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.15);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2);
transition: 250ms ease-in-out transform;
transform: translate3d(${p => (p.isChecked ? '28px' : '0')}, 0, 0);
transform: translate3d(${p => (p.isChecked ? '27px' : '3px')}, 0, 0);
`
type Props = {

12
src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap

@ -2,7 +2,7 @@
exports[`components FormattedVal renders a fiat 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
20.00
@ -11,7 +11,7 @@ exports[`components FormattedVal renders a fiat 1`] = `
exports[`components FormattedVal renders a formatted val 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
4
@ -20,7 +20,7 @@ exports[`components FormattedVal renders a formatted val 1`] = `
exports[`components FormattedVal renders a percent 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
30 %
@ -29,7 +29,7 @@ exports[`components FormattedVal renders a percent 1`] = `
exports[`components FormattedVal shows code 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
BTC 4
@ -38,7 +38,7 @@ exports[`components FormattedVal shows code 1`] = `
exports[`components FormattedVal shows sign 1`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 cCejoE"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 grmBGS"
color="#66be54"
>
+ 4
@ -47,7 +47,7 @@ exports[`components FormattedVal shows sign 1`] = `
exports[`components FormattedVal shows sign 2`] = `
<div
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 kwFiWV"
className="s1c17x4y-0 jLdFhK s1xoa5y3-0 kyprQb"
color="#ea2e49"
>
- 4

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

@ -16,17 +16,17 @@ const Container = styled(Box).attrs({
border-radius: ${p => p.theme.radii[1]}px;
border: 1px solid ${p => (p.isFocus ? p.theme.colors.wallet : p.theme.colors.fog)};
box-shadow: ${p => (p.isFocus ? `rgba(0, 0, 0, 0.05) 0 2px 2px` : 'none')};
height: 40px;
height: ${p => (p.small ? '34' : '40')}px;
`
const Base = styled.input.attrs({
ff: p => p.ff || 'Open Sans|SemiBold',
ff: p => (p.ff || p.small ? 'Open Sans' : 'Open Sans|SemiBold'),
fontSize: 4,
})`
${fontFamily};
${fontSize};
border: 0;
color: ${p => p.theme.colors.dark};
color: ${p => p.theme.colors.graphite};
height: 100%;
outline: none;
padding: 0;
@ -65,6 +65,7 @@ type Props = {
renderLeft?: any,
renderRight?: any,
containerProps?: Object,
small?: boolean,
}
type State = {
@ -77,6 +78,7 @@ class Input extends PureComponent<Props, State> {
onFocus: noop,
renderLeft: null,
renderRight: null,
small: false,
}
state = {
@ -93,34 +95,41 @@ class Input extends PureComponent<Props, State> {
handleClick = () => this._input && this._input.focus()
handleFocus = () => {
handleFocus = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { onFocus } = this.props
this.setState({
isFocus: true,
})
onFocus()
onFocus(e)
}
handleBlur = () => {
handleBlur = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { onBlur } = this.props
this.setState({
isFocus: false,
})
onBlur()
onBlur(e)
}
_input = null
render() {
const { isFocus } = this.state
const { renderLeft, renderRight, containerProps } = this.props
const { renderLeft, renderRight, containerProps, small } = this.props
return (
<Container onClick={this.handleClick} isFocus={isFocus} shrink {...containerProps}>
<Container
onClick={this.handleClick}
isFocus={isFocus}
shrink
{...containerProps}
small={small}
>
{renderLeft}
<Box px={3} grow shrink>
<Base
{...this.props}
small={small}
innerRef={n => (this._input = n)}
onFocus={this.handleFocus}
onBlur={this.handleBlur}

35
src/components/base/InputPassword/index.js

@ -1,6 +1,6 @@
// @flow
import React, { PureComponent } from 'react'
import React, { Fragment, PureComponent } from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import zxcvbn from 'zxcvbn'
@ -50,6 +50,7 @@ type Props = {
onChange: Function,
t: T,
value: string,
withStrength: boolean,
}
class InputPassword extends PureComponent<Props, State> {
@ -84,7 +85,7 @@ class InputPassword extends PureComponent<Props, State> {
}
render() {
const { t, value, maxLength } = this.props
const { t, value, maxLength, withStrength } = this.props
const { passwordStrength, inputType } = this.state
const hasValue = value.trim() !== ''
@ -102,19 +103,23 @@ class InputPassword extends PureComponent<Props, State> {
</InputRight>
}
/>
<Box flow={1} horizontal>
{[0, 1, 2, 3, 4].map(v => (
<Strength
key={v}
warning={passwordStrength <= 1}
activated={hasValue && passwordStrength >= v}
/>
))}
</Box>
{hasValue && (
<Warning passwordStrength={passwordStrength}>
{t(`password:warning_${passwordStrength}`)}
</Warning>
{withStrength && (
<Fragment>
<Box flow={1} horizontal>
{[0, 1, 2, 3, 4].map(v => (
<Strength
key={v}
warning={passwordStrength <= 1}
activated={hasValue && passwordStrength >= v}
/>
))}
</Box>
{hasValue && (
<Warning passwordStrength={passwordStrength}>
{t(`password:warning_${passwordStrength}`)}
</Warning>
)}
</Fragment>
)}
</Box>
)

1
src/components/base/Label.js

@ -8,6 +8,7 @@ export default styled.label.attrs({
ff: 'Museo Sans|Regular',
color: 'grey',
align: 'center',
display: 'block',
})`
${alignItems};
${color};

74
src/components/base/Modal/ConfirmModal.js

@ -0,0 +1,74 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index'
type Props = {
isOpened: boolean,
isDanger: boolean,
title: string,
subTitle: string,
desc: string,
confirmText: string,
cancelText: string,
onReject: Function,
onConfirm: Function,
t: T,
}
class ConfirmModal extends PureComponent<Props> {
render() {
const {
isOpened,
title,
subTitle,
desc,
confirmText,
cancelText,
isDanger,
onReject,
onConfirm,
t,
...props
} = this.props
const realConfirmText = confirmText || t('common:confirm')
const realCancelText = cancelText || t('common:cancel')
return (
<Modal
isOpened={isOpened}
{...props}
render={({ onClose }) => (
<ModalBody onClose={onClose}>
<ModalTitle>{title}</ModalTitle>
<ModalContent>
{subTitle && (
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{subTitle}
</Box>
)}
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center">
{desc}
</Box>
</ModalContent>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}>
<Button onClick={onReject}>{realCancelText}</Button>
<Button onClick={onConfirm} primary={!isDanger} danger={isDanger}>
{realConfirmText}
</Button>
</ModalFooter>
</ModalBody>
)}
/>
)
}
}
export default translate()(ConfirmModal)

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

@ -21,6 +21,7 @@ import GrowScroll from 'components/base/GrowScroll'
import Defer from 'components/base/Defer'
export { default as ModalBody } from './ModalBody'
export { default as ConfirmModal } from './ConfirmModal'
const springConfig = {
stiffness: 320,
@ -54,8 +55,6 @@ const mapDispatchToProps: Function = (dispatch, { name, onClose = noop }): * =>
const Container = styled(Box).attrs({
color: 'grey',
alignItems: 'center',
justifyContent: 'flex-start',
sticky: true,
style: p => ({
pointerEvents: p.isVisible ? 'auto' : 'none',
@ -78,8 +77,6 @@ const Backdrop = styled(Box).attrs({
const Wrapper = styled(Tabbable).attrs({
bg: 'transparent',
flow: 4,
mt: 100,
mb: 100,
style: p => ({
opacity: p.op,
transform: `scale3d(${p.scale}, ${p.scale}, ${p.scale})`,
@ -174,12 +171,7 @@ export class Modal extends Component<Props> {
{(m, isVisible, isAnimated) => (
<Container isVisible={isVisible} onClick={preventBackdropClick ? undefined : onClose}>
<Backdrop op={m.opacity} />
<GrowScroll
alignItems="center"
full
justifyContent="flex-start"
style={{ height: '100%' }}
>
<GrowScroll alignItems="center" full justifyContent="center" style={{ height: '100%' }}>
<Wrapper
op={m.opacity}
scale={m.scale}

60
src/components/base/Modal/stories.js

@ -2,29 +2,49 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { boolean } from '@storybook/addon-knobs'
import { action } from '@storybook/addon-actions'
import { boolean, text } from '@storybook/addon-knobs'
import { Modal, ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal'
import {
Modal,
ModalBody,
ModalTitle,
ModalContent,
ModalFooter,
ConfirmModal,
} from 'components/base/Modal'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
const stories = storiesOf('Components/base', module)
stories.add('Modal', () => {
const isOpened = boolean('isOpened', true)
return (
<Modal
isOpened={isOpened}
render={({ onClose }) => (
<ModalBody onClose={onClose}>
<ModalTitle>{'modal title'}</ModalTitle>
<ModalContent>{'this is the modal content'}</ModalContent>
<ModalFooter horizontal align="center">
<Box grow>{'modal footer'}</Box>
<Button primary>{'Next'}</Button>
</ModalFooter>
</ModalBody>
)}
/>
)
})
stories.add('Modal', () => (
<Modal
isOpened={boolean('isOpened', true)}
render={({ onClose }) => (
<ModalBody onClose={onClose}>
<ModalTitle>{'modal title'}</ModalTitle>
<ModalContent>{'this is the modal content'}</ModalContent>
<ModalFooter horizontal align="center">
<Box grow>{'modal footer'}</Box>
<Button primary>{'Next'}</Button>
</ModalFooter>
</ModalBody>
)}
/>
))
stories.add('ConfirmModal', () => (
<ConfirmModal
isOpened
isDanger={boolean('isDanger', false)}
title={text('title', 'Hard reset')}
subTitle={text('subTitle', 'Are you sure houston?')}
desc={text(
'desc',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam. In eget ipsum arcu donec finibus',
)}
onConfirm={action('onConfirm')}
onReject={action('onReject')}
/>
))

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

@ -34,20 +34,21 @@ type Props = {
searchable?: boolean,
value?: Object | null,
disabled: boolean,
small?: boolean,
}
const Container = styled(Box).attrs({ relative: true, color: 'graphite' })``
const TriggerBtn = styled(Box).attrs({
alignItems: 'center',
ff: 'Open Sans|SemiBold',
ff: p => (p.small ? 'Open Sans' : 'Open Sans|SemiBold'),
flow: 2,
fontSize: 4,
fontSize: p => (p.small ? 3 : 4),
horizontal: true,
px: 3,
})`
${space};
height: 40px;
height: ${p => (p.small ? '34' : '40')}px;
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;
@ -149,6 +150,7 @@ class Select extends PureComponent<Props> {
static defaultProps = {
bg: undefined,
disabled: false,
small: false,
fakeFocusRight: false,
flatLeft: false,
flatRight: false,
@ -251,6 +253,7 @@ class Select extends PureComponent<Props> {
renderSelected,
searchable,
value,
small,
...props
} = this.props
@ -276,7 +279,7 @@ class Select extends PureComponent<Props> {
if (disabled) {
return (
<Container {...getRootProps({ refKey: 'innerRef' })}>
<TriggerBtn disabled bg={props.bg} tabIndex={0}>
<TriggerBtn disabled bg={props.bg} tabIndex={0} small={small}>
{renderSelectedItem({ selectedItem, renderSelected, placeholder })}
</TriggerBtn>
</Container>
@ -294,10 +297,11 @@ class Select extends PureComponent<Props> {
{searchable ? (
<Box grow>
<Input
small
keepEvent
{...getInputProps({ placeholder })}
onClick={openMenu}
renderRight={<AngleDown mr={2} />}
{...getInputProps({ placeholder })}
/>
</Box>
) : (
@ -308,6 +312,7 @@ class Select extends PureComponent<Props> {
flatLeft={flatLeft}
flatRight={flatRight}
tabIndex={0}
small={small}
>
<Box grow>
{renderSelectedItem({ selectedItem, renderSelected, placeholder })}

68
src/components/base/Tabs/index.js

@ -1,68 +0,0 @@
// @flow
import React, { Fragment } from 'react'
import styled from 'styled-components'
import type { Element } from 'react'
import Box, { Tabbable } from 'components/base/Box'
const WrapperTab = styled(Box).attrs({
horizontal: true,
})`
border-bottom: 1px solid ${p => p.theme.colors.fog};
`
const Tab = styled(Tabbable).attrs({
flex: 1,
pb: 2,
alignItems: 'center',
justifyContent: 'center',
fontSize: 3,
})`
border-bottom: 2px solid transparent;
border-bottom-color: ${p => (p.isActive ? p.theme.colors.wallet : '')};
color: ${p =>
p.isActive
? p.theme.colors.wallet
: p.isDisabled
? p.theme.colors.grey
: p.theme.colors.graphite};
margin-bottom: -1px;
outline: none;
cursor: ${p => (p.isActive ? 'default' : p.isDisabled ? 'not-allowed' : 'pointer')};
max-width: 200px;
`
type Item = {
key: string | number,
isDisabled?: boolean,
title: string | Element<any>,
render: () => Element<any>,
}
type Props = {
items: Array<Item>,
index: number,
onTabClick: number => void,
}
const Tabs = ({ items, index, onTabClick }: Props) => (
<Fragment>
<WrapperTab>
{items.map((item, i) => (
<Tab
key={item.key}
isDisabled={item.isDisabled}
isActive={index === i}
onClick={item.isDisabled ? void 0 : () => onTabClick(i)}
>
{item.title}
</Tab>
))}
</WrapperTab>
{items[index] && items[index].render()}
</Fragment>
)
export default Tabs

31
src/components/base/Tabs/stories.js

@ -1,31 +0,0 @@
import React from 'react'
import { number } from '@storybook/addon-knobs'
import { action } from '@storybook/addon-actions'
import { storiesOf } from '@storybook/react'
import Tabs from 'components/base/Tabs'
const stories = storiesOf('Components/base', module)
stories.add('Tabs', () => (
<Tabs
index={number('index', 0, {
min: 0,
max: 1,
})}
onTabClick={action('onTabClick')}
items={[
{
key: 'first',
title: 'first tab',
render: () => <div>{'first tab content'}</div>,
},
{
key: 'second',
title: 'second tab',
render: () => <div>{'second tab content'}</div>,
},
]}
/>
))

1
src/components/layout/Default.js

@ -6,6 +6,7 @@ import { ipcRenderer } from 'electron'
import styled from 'styled-components'
import { Route, withRouter } from 'react-router'
import { translate } from 'react-i18next'
import type { Location } from 'react-router'
import * as modals from 'components/modals'

7
src/components/layout/Print.js

@ -31,12 +31,13 @@ class Print extends PureComponent<any> {
if (!data) {
return null
}
const { address, amount } = data
const { address, amount, accountName } = data
return (
<CurrentAddress
innerRef={n => (this._node = n)}
amount={amount}
accountName={accountName}
address={address}
amount={amount}
innerRef={n => (this._node = n)}
withQRCode
/>
)

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

@ -8,7 +8,6 @@ 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 = {
@ -22,10 +21,6 @@ type Props = {
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
@ -36,6 +31,7 @@ export default (props: Props) => (
/>
</Box>
<CurrentAddress
accountName={props.account && props.account.name}
address={props.account && props.account.address}
addressVerified={props.addressVerified}
amount={props.amount}

12
src/icons/Currencies.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="M8 5.581c-3.865 0-7-1.083-7-2.79C1 1.083 4.135 0 8 0s7 1.083 7 2.79c0 1.708-3.135 2.791-7 2.791zm-7-2.79c0-.309.241-.558.538-.558.298 0 .539.25.539.558v10.418c0 .28.516.704 1.517 1.05 1.142.396 2.714.625 4.406.625 1.692 0 3.264-.23 4.406-.625 1.001-.346 1.517-.77 1.517-1.05V2.791c0-.309.241-.558.539-.558.297 0 .538.25.538.558v10.418C15 14.921 11.88 16 8 16s-7-1.08-7-2.79V2.79zM13.923 8c0-.308.241-.558.539-.558.297 0 .538.25.538.558 0 1.711-3.12 2.79-7 2.79S1 9.712 1 8c0-.308.241-.558.538-.558.298 0 .539.25.539.558 0 .28.516.704 1.517 1.05 1.142.395 2.714.624 4.406.624 1.692 0 3.264-.229 4.406-.624 1.001-.346 1.517-.77 1.517-1.05zM8 4.465c1.682 0 3.254-.23 4.399-.625 1.004-.347 1.524-.772 1.524-1.05 0-.277-.52-.702-1.524-1.048-1.145-.396-2.717-.626-4.399-.626s-3.254.23-4.399.626c-1.004.346-1.524.771-1.524 1.049 0 .277.52.702 1.524 1.049 1.145.395 2.717.625 4.399.625z"
/>
</svg>
)

12
src/icons/Display.js

@ -0,0 +1,12 @@
// @flow
import React from 'react'
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" width={size} height={size} {...p}>
<path
fill="currentColor"
d="M3.2 2.26c-.552 0-1 .471-1 1.051v6.547c0 .58.448 1.05 1 1.05h9.6c.552 0 1-.47 1-1.05V3.31c0-.58-.448-1.05-1-1.05H3.2zm5.4 9.909v1.57h1.96c.331 0 .6.283.6.63 0 .349-.269.631-.6.631H5.44c-.331 0-.6-.282-.6-.63 0-.348.269-.63.6-.63H7.4v-1.571H3.2c-1.215 0-2.2-1.035-2.2-2.311V3.31C1 2.035 1.985 1 3.2 1h9.6C14.015 1 15 2.035 15 3.311v6.547c0 1.276-.985 2.311-2.2 2.311H8.6z"
/>
</svg>
)

12
src/icons/Help.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="M3.3 4.007a6.167 6.167 0 1 0 .707-.707l2.135 2.135a3.167 3.167 0 1 1-.707.707L3.3 4.007zM8 15.167A7.167 7.167 0 1 1 8 .833a7.167 7.167 0 0 1 0 14.334zm0-5a2.167 2.167 0 1 0 0-4.334 2.167 2.167 0 0 0 0 4.334zm1.387-4.054c0-.128.048-.256.146-.353l2.353-2.354a.5.5 0 0 1 .708.708L10.24 6.467a.5.5 0 0 1-.707-.707l2.827-2.827a.5.5 0 0 1 .707.707L10.24 6.467a.5.5 0 0 1-.853-.354zm.146 4.127a.5.5 0 0 1 .707-.707l2.827 2.827a.5.5 0 1 1-.707.707L9.533 10.24zM3.64 13.067a.5.5 0 1 1-.707-.707L5.76 9.533a.5.5 0 1 1 .707.707L3.64 13.067z"
/>
</svg>
)

1
src/internals/usb/manager/helpers.js

@ -214,6 +214,7 @@ export async function getFirmwareInfo(transport: Transport<*>) {
*/
function log(namespace: string, str: string = '', color?: string) {
namespace = namespace.padEnd(15)
// $FlowFixMe
const coloredNamespace = color ? chalk[color](namespace) : namespace
if (__DEV__) {
console.log(`${chalk.bold(`> ${coloredNamespace}`)} ${str}`) // eslint-disable-line no-console

4
src/reducers/accounts.js

@ -94,10 +94,10 @@ export function canCreateAccount(state: State): boolean {
// such a simple thing, let's put any, right? I don't care.
export function serializeAccounts(accounts: any): Account[] {
// ensure that accounts are always wrapped in data key
if (accounts.length && !accounts[0].data) {
if (accounts && accounts.length && !accounts[0].data) {
accounts = accounts.map(account => ({ data: account }))
}
return accounts.map(accountModel.decode)
return accounts ? accounts.map(accountModel.decode) : []
}
export function deserializeAccounts(accounts: Account[]) {

6
src/reducers/settings.js

@ -10,11 +10,13 @@ import type { Settings } from 'types/common'
export type SettingsState = Object
const defaultState: SettingsState = {
username: 'Anonymous',
counterValue: 'USD',
language: 'en',
orderAccounts: 'balance|asc',
password: {
state: false,
isEnabled: false,
value: '',
},
}
@ -34,7 +36,7 @@ const handlers: Object = {
}
export const hasPassword = (state: Object) =>
get(state.settings, 'password.state', defaultState.password.state)
get(state.settings, 'password.isEnabled', defaultState.password.isEnabled)
export const getCounterValueCode = (state: Object) =>
get(state.settings, 'counterValue', defaultState.counterValue)

17
src/types/common.js

@ -12,23 +12,16 @@ export type Devices = Array<Device>
// -------------------- Settings
export type SettingsProfile = {
export type Settings = {
language: string,
username: string,
counterValue: string,
password: {
state: boolean,
isEnabled: boolean,
value: string,
},
}
export type SettingsDisplay = {
language: string,
}
export type SettingsMoney = {
counterValue: string,
}
export type Settings = SettingsProfile & SettingsDisplay & SettingsMoney
export type T = (?string, ?Object) => string
// -------------------- Manager

2
static/i18n/en/common.yml

@ -1,8 +1,10 @@
ok: Okay
confirm: Confirm
cancel: Cancel
chooseWalletPlaceholder: Choose a wallet...
currency: Currency
selectAccount: Select an account
selectCurrency: Select an currency
sortBy: Sort by
search: Search
save: Save

1
static/i18n/en/currentAddress.yml

@ -1 +1,2 @@
label: Current address
labelFrom: Address from <1><0>{{accountName}}</0></1>

57
static/i18n/en/settings.yml

@ -1,16 +1,55 @@
title: Settings
tabs:
display: Display
money: Money
material: Material
app: App (beta)
tools: Tools
blockchain: Blockchain
currencies: Currencies
profile: Profile
about: About
display:
language: Language
counterValue: Counter Value
orderAccounts: Order accounts
language: Interface language
languageDesc: Lorem ipsum dolor sit amet
counterValue: Countervalue
counterValueDesc: Lorem ipsum dolor sit amet
region: Region
regionDesc: Lorem ipsum dolor sit amet
stock: Stock market indicators
stockDesc: Lorem ipsum dolor sit amet
currencies:
confirmationsToSpend: Confirmations to spend
confirmationsToSpendDesc: Lorem ipsum dolor sit amet
confirmationsNb: Number of confirmations
confirmationsNbDesc: Lorem ipsum dolor sit amet
transactionsFees: Transactions fees
transactionsFeesDesc: Lorem ipsum dolor sit amet
explorer: Blockchain explorer
explorerDesc: Lorem ipsum dolor sit amet
profile:
protectWithPassword: Protect local data with a password
username: Username
usernameDesc: Lorem ipsum dolor sit amet
password: Password
passwordDesc: Lorem ipsum dolor sit amet
changePassword: Change password
passwordModalTitle: Password
passwordModalSubtitle: Set a password to lock your application
passwordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam.
passwordModalPasswordInput: Current password
passwordModalNewPasswordInput: New password
passwordModalRepeatPasswordInput: Repeat password
passwordModalSave: Save
sync: Sync accounts
syncDesc: Lorem ipsum dolor sit amet
export: Export logs
exportDesc: Lorem ipsum dolor sit amet
reset: Reset application
resetDesc: Lorem ipsum dolor sit amet
resetButton: Hard reset
about:
faq: FAQ
faqDesc: Lorem ipsum dolor sit amet
contactUs: Contact us
contactUsDesc: Lorem ipsum dolor sit amet
terms: Terms and Privacy policy
termsDesc: Lorem ipsum dolor sit amet
hardResetModal:
title: Hard reset
subTitle: Are you sure houston?
desc: Lorem ipsum dolor sit amet

329
yarn.lock

@ -1002,11 +1002,11 @@
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
"@storybook/addon-actions@3.4.1", "@storybook/addon-actions@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.4.1.tgz#6ec5dc337e4af301d2f0f8fbc0ad2dee812cfa29"
"@storybook/addon-actions@3.4.2", "@storybook/addon-actions@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.4.2.tgz#ab45c37f57027257530577a9c1af6896ffb40156"
dependencies:
"@storybook/components" "3.4.1"
"@storybook/components" "3.4.2"
babel-runtime "^6.26.0"
deep-equal "^1.0.1"
glamor "^2.20.40"
@ -1017,11 +1017,11 @@
react-inspector "^2.2.2"
uuid "^3.2.1"
"@storybook/addon-knobs@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-3.4.1.tgz#9c23abf62944280d2d681f0a356fea67683a892f"
"@storybook/addon-knobs@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-3.4.2.tgz#31baf535cafa9740d96b2f93197d688fa30b97f2"
dependencies:
"@storybook/components" "3.4.1"
"@storybook/components" "3.4.2"
babel-runtime "^6.26.0"
deep-equal "^1.0.1"
global "^4.3.2"
@ -1034,58 +1034,58 @@
react-textarea-autosize "^5.2.1"
util-deprecate "^1.0.2"
"@storybook/addon-links@3.4.1", "@storybook/addon-links@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.4.1.tgz#b5c5e942b8865b2489d8d1537699316d0b154b1b"
"@storybook/addon-links@3.4.2", "@storybook/addon-links@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.4.2.tgz#09531263bd332669807b2e94cc70bd15e173f421"
dependencies:
"@storybook/components" "3.4.1"
"@storybook/components" "3.4.2"
babel-runtime "^6.26.0"
global "^4.3.2"
prop-types "^15.6.1"
"@storybook/addon-options@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-3.4.1.tgz#309f46c298b110442824b066e73df6dee17fc8b9"
"@storybook/addon-options@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-3.4.2.tgz#5acc5f8e6c00f803bb0f69e86b9e4b4c942fa283"
dependencies:
babel-runtime "^6.26.0"
"@storybook/addons@3.4.1", "@storybook/addons@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.4.1.tgz#ff80dc65a87607681b811a8cc4ff92124643f5ac"
"@storybook/addons@3.4.2", "@storybook/addons@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.4.2.tgz#77f0d5cb3b9443da6765600d83cd5acda6115af6"
"@storybook/channel-postmessage@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.4.1.tgz#17a84b8df1613196554f0450733a0b13d0954907"
"@storybook/channel-postmessage@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.4.2.tgz#08476f2b5b04ae83714137f6916e142c92222513"
dependencies:
"@storybook/channels" "3.4.1"
"@storybook/channels" "3.4.2"
global "^4.3.2"
json-stringify-safe "^5.0.1"
"@storybook/channels@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.4.1.tgz#13536c0211f0b49d24b63c5e7cef87b11ae43f85"
"@storybook/channels@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.4.2.tgz#da26b3116fae1f83ccdb44881537cd9a073aefe9"
"@storybook/client-logger@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-3.4.1.tgz#5b24ea0fa105f0683a5e9797a41466e4267d2c7f"
"@storybook/client-logger@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-3.4.2.tgz#33a7b5c924e2960f9c129a934fc905ed1b271017"
"@storybook/components@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-3.4.1.tgz#74e3bcf9590cb314a4845d2f3264aada79856c1f"
"@storybook/components@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-3.4.2.tgz#1124cf22a54e36aa07c291608c652746919cf375"
dependencies:
glamor "^2.20.40"
glamorous "^4.12.1"
prop-types "^15.6.1"
"@storybook/core@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-3.4.1.tgz#5ddbe754faa95c48ba272cfa30ca2e3272481aeb"
"@storybook/core@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-3.4.2.tgz#d74b9232ef404e3dfc7c19539473699e83a2800a"
dependencies:
"@storybook/addons" "3.4.1"
"@storybook/channel-postmessage" "3.4.1"
"@storybook/client-logger" "3.4.1"
"@storybook/node-logger" "3.4.1"
"@storybook/ui" "3.4.1"
"@storybook/addons" "3.4.2"
"@storybook/channel-postmessage" "3.4.2"
"@storybook/client-logger" "3.4.2"
"@storybook/node-logger" "3.4.2"
"@storybook/ui" "3.4.2"
autoprefixer "^7.2.6"
babel-runtime "^6.26.0"
chalk "^2.3.2"
@ -1117,9 +1117,9 @@
"@storybook/react-simple-di" "^1.2.1"
babel-runtime "6.x.x"
"@storybook/node-logger@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-3.4.1.tgz#d1bddbaa5bcf995b97d408e46c1c2125d04b6e58"
"@storybook/node-logger@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-3.4.2.tgz#1b5d909a1ab611b89da68216a81c4b22bf668c36"
dependencies:
npmlog "^4.1.2"
@ -1155,18 +1155,18 @@
dependencies:
babel-runtime "^6.5.0"
"@storybook/react@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.4.1.tgz#db2b1c7980d9ed084aff4e28c5017e182b04bf3d"
dependencies:
"@storybook/addon-actions" "3.4.1"
"@storybook/addon-links" "3.4.1"
"@storybook/addons" "3.4.1"
"@storybook/channel-postmessage" "3.4.1"
"@storybook/client-logger" "3.4.1"
"@storybook/core" "3.4.1"
"@storybook/node-logger" "3.4.1"
"@storybook/ui" "3.4.1"
"@storybook/react@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.4.2.tgz#d48952a01cc2fa6bb143a1042bfa178f7d634ec3"
dependencies:
"@storybook/addon-actions" "3.4.2"
"@storybook/addon-links" "3.4.2"
"@storybook/addons" "3.4.2"
"@storybook/channel-postmessage" "3.4.2"
"@storybook/client-logger" "3.4.2"
"@storybook/core" "3.4.2"
"@storybook/node-logger" "3.4.2"
"@storybook/ui" "3.4.2"
airbnb-js-shims "^1.4.1"
babel-loader "^7.1.4"
babel-plugin-macros "^2.2.0"
@ -1199,11 +1199,11 @@
webpack "^3.11.0"
webpack-hot-middleware "^2.21.2"
"@storybook/ui@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.4.1.tgz#2f0109d806d7922e45b7f4dea67584d61536df46"
"@storybook/ui@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.4.2.tgz#8e6bcb6ebdf88e7d7bb3d7dfaf4151620daca0ad"
dependencies:
"@storybook/components" "3.4.1"
"@storybook/components" "3.4.2"
"@storybook/mantra-core" "^1.7.2"
"@storybook/podda" "^1.2.3"
"@storybook/react-komposer" "^2.0.3"
@ -1427,6 +1427,10 @@ app-builder-bin-linux@1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/app-builder-bin-linux/-/app-builder-bin-linux-1.8.3.tgz#4bf638a7bd29365e5534d2ba554baf1350fb4a87"
app-builder-bin-linux@1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/app-builder-bin-linux/-/app-builder-bin-linux-1.8.5.tgz#bf001d3ef347e1179680a1760fea83d0832fc344"
app-builder-bin-mac@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin-mac/-/app-builder-bin-mac-1.7.2.tgz#c4ee0d950666c97c12a45ac74ec6396be3357644"
@ -1435,6 +1439,10 @@ app-builder-bin-mac@1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/app-builder-bin-mac/-/app-builder-bin-mac-1.8.3.tgz#8e2c63e9d822fce2eee8db2f9f817d7b68532df7"
app-builder-bin-mac@1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/app-builder-bin-mac/-/app-builder-bin-mac-1.8.5.tgz#31282504d232081a9de94d377736bf904bbfbd53"
app-builder-bin-win@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz#7acac890782f4118f09941b343ba06c56452a6f6"
@ -1443,6 +1451,10 @@ app-builder-bin-win@1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/app-builder-bin-win/-/app-builder-bin-win-1.8.3.tgz#3598ec1c523dd197e8bb5dfeab3e2fe70905ae79"
app-builder-bin-win@1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/app-builder-bin-win/-/app-builder-bin-win-1.8.5.tgz#d7eefc1dff6052e137a3c5d68cd6e68ba862054b"
app-builder-bin@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-1.7.2.tgz#daf67060a6bad8f5f611a0d2876d9db897a83f06"
@ -1459,6 +1471,14 @@ app-builder-bin@1.8.3:
app-builder-bin-mac "1.8.3"
app-builder-bin-win "1.8.3"
app-builder-bin@1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-1.8.5.tgz#ac0c0fad3c348ff3bde367e49b25cf5edc414407"
optionalDependencies:
app-builder-bin-linux "1.8.5"
app-builder-bin-mac "1.8.5"
app-builder-bin-win "1.8.5"
app-root-path@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46"
@ -3178,6 +3198,25 @@ builder-util@5.7.4, builder-util@^5.6.7, builder-util@^5.7.0, builder-util@^5.7.
stat-mode "^0.2.2"
temp-file "^3.1.1"
builder-util@5.7.6, builder-util@^5.7.6:
version "5.7.6"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-5.7.6.tgz#2480c8a233ab7ab05a4f1689892cc39819e768bb"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.8.5"
bluebird-lst "^1.0.5"
builder-util-runtime "^4.2.0"
chalk "^2.3.2"
debug "^3.1.0"
fs-extra-p "^4.5.2"
is-ci "^1.1.0"
js-yaml "^3.11.0"
lazy-val "^1.0.3"
semver "^5.5.0"
source-map-support "^0.5.4"
stat-mode "^0.2.2"
temp-file "^3.1.1"
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -3362,6 +3401,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52"
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
@ -4321,9 +4368,9 @@ d3-zoom@1:
d3-selection "1"
d3-transition "1"
d3@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.0.0.tgz#b373ce8ccc953cbe09cef4c1e7e2223b42441df9"
d3@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.1.0.tgz#2506c4f070fe8a5a1e9a16308caacf96ae51f522"
dependencies:
d3-array "1"
d3-axis "1"
@ -4579,6 +4626,19 @@ dmg-builder@4.1.3:
parse-color "^1.0.0"
sanitize-filename "^1.6.1"
dmg-builder@4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-4.1.5.tgz#f8dc24cd911e0e4a8cdcf9c8a2c829317403985a"
dependencies:
bluebird-lst "^1.0.5"
builder-util "^5.7.6"
electron-builder-lib "~20.9.0"
fs-extra-p "^4.5.2"
iconv-lite "^0.4.21"
js-yaml "^3.11.0"
parse-color "^1.0.0"
sanitize-filename "^1.6.1"
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@ -4785,6 +4845,35 @@ electron-builder-lib@20.8.1:
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder-lib@20.9.0, electron-builder-lib@~20.9.0:
version "20.9.0"
resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.9.0.tgz#bb4725a796e80d15c5512e50a1ed0be08fea0e4f"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.8.5"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.5"
builder-util "5.7.6"
builder-util-runtime "4.2.0"
chromium-pickle-js "^0.2.0"
debug "^3.1.0"
ejs "^2.5.8"
electron-osx-sign "0.4.10"
electron-publish "20.9.0"
fs-extra-p "^4.5.2"
hosted-git-info "^2.6.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 "^3.0.1"
read-config-file "3.0.0"
sanitize-filename "^1.6.1"
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder-lib@~20.6.2:
version "20.6.2"
resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.6.2.tgz#34f38b6172c05f90d34b6b5ed2f2b6922e731a39"
@ -4814,7 +4903,7 @@ electron-builder-lib@~20.6.2:
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder@^20.0.4, electron-builder@^20.8.1:
electron-builder@^20.0.4:
version "20.8.1"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.8.1.tgz#3d19607a7f7d3ee7f3e110a6fc66c720ed1d2cc0"
dependencies:
@ -4833,6 +4922,25 @@ electron-builder@^20.0.4, electron-builder@^20.8.1:
update-notifier "^2.4.0"
yargs "^11.0.0"
electron-builder@^20.9.0:
version "20.9.0"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.9.0.tgz#3010d5fbd927c0e1d5c4db6984d797f4b87cd239"
dependencies:
bluebird-lst "^1.0.5"
builder-util "5.7.6"
builder-util-runtime "4.2.0"
chalk "^2.3.2"
dmg-builder "4.1.5"
electron-builder-lib "20.9.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.5.0"
yargs "^11.0.0"
electron-devtools-installer@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-2.2.3.tgz#58b9a4ec507377bc46e091cd43714188e0c369be"
@ -4909,6 +5017,18 @@ electron-publish@20.8.1:
lazy-val "^1.0.3"
mime "^2.2.0"
electron-publish@20.9.0:
version "20.9.0"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-20.9.0.tgz#095c02fe39674079d90a29eb404dbc894188ca16"
dependencies:
bluebird-lst "^1.0.5"
builder-util "^5.7.6"
builder-util-runtime "^4.2.0"
chalk "^2.3.2"
fs-extra-p "^4.5.2"
lazy-val "^1.0.3"
mime "^2.3.1"
electron-rebuild@^1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.7.3.tgz#24ae06ad9dd61cb7e4d688961f49118c40a110eb"
@ -4938,9 +5058,9 @@ electron-to-chromium@^1.3.40:
version "1.3.40"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.40.tgz#1fbd6d97befd72b8a6f921dc38d22413d2f6fddf"
electron-updater@^2.21.4:
version "2.21.4"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-2.21.4.tgz#56326defc8072e78e339cc656838ac78e708f50c"
electron-updater@^2.21.8:
version "2.21.8"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-2.21.8.tgz#3881fff1fc7c57a66be0665bd7ffe02b7ecb5566"
dependencies:
bluebird-lst "^1.0.5"
builder-util-runtime "~4.2.0"
@ -6752,6 +6872,12 @@ iconv-lite@0.4, iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
iconv-lite@^0.4.21:
version "0.4.21"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"
dependencies:
safer-buffer "^2.1.0"
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@ -8013,9 +8139,6 @@ 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"
@ -8522,6 +8645,10 @@ mime@^2.0.3, mime@^2.1.0, mime@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b"
mime@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
@ -9795,9 +9922,9 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.0.tgz#d26fc5894b9230de97629b39cae225b503724ce8"
prettier@^1.12.1:
version "1.12.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325"
prettier@^1.5.3:
version "1.11.1"
@ -10135,9 +10262,9 @@ react-docgen@^3.0.0-beta11:
node-dir "^0.1.10"
recast "^0.12.6"
react-dom@^16.3.1:
version "16.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.1.tgz#6a3c90a4fb62f915bdbcf6204422d93a7d4ca573"
react-dom@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
@ -10208,6 +10335,10 @@ react-is@^16.3.1:
version "16.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.1.tgz#ee66e6d8283224a83b3030e110056798488359ba"
react-is@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22"
react-modal@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.3.2.tgz#b13da9490653a7c76bc0e9600323eb1079c620e7"
@ -10311,14 +10442,14 @@ react-style-proptype@^3.0.0:
dependencies:
prop-types "^15.5.4"
react-test-renderer@^16.3.1:
version "16.3.1"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.1.tgz#d9257936d8535bd40f57f3d5a84e7b0452fb17f2"
react-test-renderer@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.2.tgz#3d1ed74fda8db42521fdf03328e933312214749a"
dependencies:
fbjs "^0.8.16"
object-assign "^4.1.1"
prop-types "^15.6.0"
react-is "^16.3.1"
react-is "^16.3.2"
react-textarea-autosize@^5.2.1:
version "5.2.1"
@ -10356,9 +10487,9 @@ react@^16.0.0, react@^16.2.0:
object-assign "^4.1.1"
prop-types "^15.6.0"
react@^16.3.1:
version "16.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.1.tgz#4a2da433d471251c69b6033ada30e2ed1202cfd8"
react@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
@ -10985,6 +11116,10 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
safer-buffer@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
sane@^2.0.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/sane/-/sane-2.4.1.tgz#29f991208cf28636720efdc584293e7fd66663a5"
@ -11015,7 +11150,7 @@ schema-utils@^0.3.0:
dependencies:
ajv "^5.0.0"
schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4.5:
schema-utils@^0.4.0, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
dependencies:
@ -11707,9 +11842,9 @@ style-loader@^0.20.3:
loader-utils "^1.1.0"
schema-utils "^0.4.5"
styled-components@^3.2.5:
version "3.2.5"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.5.tgz#b5d5d7d618ab240ff10602b5ca5886b8db3d0a0d"
styled-components@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.6.tgz#99e6e75a746bdedd295a17e03dd1493055a1cc3b"
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
@ -11717,6 +11852,7 @@ styled-components@^3.2.5:
hoist-non-react-statics "^2.5.0"
is-plain-object "^2.0.1"
prop-types "^15.5.4"
react-is "^16.3.1"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
supports-color "^3.2.3"
@ -12304,6 +12440,21 @@ update-notifier@^2.4.0:
semver-diff "^2.0.0"
xdg-basedir "^3.0.0"
update-notifier@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
dependencies:
boxen "^1.2.1"
chalk "^2.0.1"
configstore "^3.0.0"
import-lazy "^2.1.0"
is-ci "^1.0.10"
is-installed-globally "^0.1.0"
is-npm "^1.0.0"
latest-version "^3.0.0"
semver-diff "^2.0.0"
xdg-basedir "^3.0.0"
upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
@ -12755,9 +12906,9 @@ webpack@^3.11.0:
webpack-sources "^1.0.1"
yargs "^8.0.2"
webpack@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.5.0.tgz#1e6f71e148ead02be265ff2879c9cd6bb30b8848"
webpack@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.6.0.tgz#363eafa733710eb0ed28c512b2b9b9f5fb01e69b"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^3.0.0"
@ -12773,7 +12924,7 @@ webpack@^4.5.0:
mkdirp "~0.5.0"
neo-async "^2.5.0"
node-libs-browser "^2.0.0"
schema-utils "^0.4.2"
schema-utils "^0.4.4"
tapable "^1.0.0"
uglifyjs-webpack-plugin "^1.2.4"
watchpack "^1.5.0"

Loading…
Cancel
Save