Browse Source

Merge branch 'master' into release-notes

master
Thibaut 7 years ago
committed by GitHub
parent
commit
8f00c30fb8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      package.json
  2. 46
      src/api/Ethereum.js
  3. 6
      src/api/Fees.js
  4. 22
      src/api/network.js
  5. 6
      src/components/AccountPage/index.js
  6. 4
      src/components/BalanceSummary/index.js
  7. 2
      src/components/DashboardPage/AccountCard.js
  8. 6
      src/components/DashboardPage/index.js
  9. 185
      src/components/MainSideBar.js
  10. 37
      src/components/MainSideBar/AccountListItem.js
  11. 43
      src/components/MainSideBar/AddAccountButton.js
  12. 155
      src/components/MainSideBar/index.js
  13. 30
      src/components/OperationsList/index.js
  14. 6
      src/components/OperationsList/stories.js
  15. 8
      src/components/SelectCurrency/index.js
  16. 2
      src/components/base/Chart/helpers.js
  17. 6
      src/components/base/Modal/ModalBody.js
  18. 7
      src/components/base/Select/index.js
  19. 23
      src/components/base/SideBar/SideBarList.js
  20. 17
      src/components/base/SideBar/SideBarListItem.js
  21. 1
      src/components/base/SideBar/index.js
  22. 21
      src/components/base/SideBar/stories.js
  23. 2
      src/components/modals/OperationDetails.js
  24. 3
      src/config/constants.js
  25. 3
      src/helpers/derivations.js
  26. 5
      src/helpers/promise.js
  27. 3
      src/helpers/withLibcore.js
  28. 2
      src/main/app.js
  29. 2
      static/i18n/en/sidebar.yml
  30. 12
      static/i18n/fr/importAccounts.yml
  31. 3
      static/i18n/fr/releaseNotes.yml
  32. 4
      static/i18n/fr/settings.yml
  33. 2
      static/i18n/fr/sidebar.yml
  34. 6
      yarn.lock

4
package.json

@ -3,7 +3,7 @@
"productName": "Ledger Live", "productName": "Ledger Live",
"description": "Ledger Live - Desktop", "description": "Ledger Live - Desktop",
"repository": "https://github.com/LedgerHQ/ledger-live-desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop",
"version": "0.1.0-alpha.7", "version": "0.1.0-alpha.8",
"author": "Ledger", "author": "Ledger",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -41,7 +41,7 @@
"@ledgerhq/hw-app-xrp": "^4.13.0", "@ledgerhq/hw-app-xrp": "^4.13.0",
"@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0", "@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "1.4.5", "@ledgerhq/ledger-core": "1.6.0",
"@ledgerhq/live-common": "2.29.0", "@ledgerhq/live-common": "2.29.0",
"async": "^2.6.1", "async": "^2.6.1",
"axios": "^0.18.0", "axios": "^0.18.0",

46
src/api/Ethereum.js

@ -1,8 +1,7 @@
// @flow // @flow
import axios from 'axios'
import { retry } from 'helpers/promise'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import { blockchainBaseURL, userFriendlyError } from './Ledger' import network from './network'
import { blockchainBaseURL } from './Ledger'
export type Block = { height: number } // TODO more fields actually export type Block = { height: number } // TODO more fields actually
export type Tx = { export type Tx = {
@ -47,37 +46,40 @@ export const apiForCurrency = (currency: CryptoCurrency): API => {
} }
return { return {
async getTransactions(address, blockHash) { async getTransactions(address, blockHash) {
const { data } = await userFriendlyError( const { data } = await network({
retry( method: 'GET',
() => url: `${baseURL}/addresses/${address}/transactions`,
axios.get(`${baseURL}/addresses/${address}/transactions`, { params: { blockHash, noToken: 1 },
params: { blockHash, noToken: 1 }, })
}),
{ maxRetry: 3 },
),
)
return data return data
}, },
async getCurrentBlock() { async getCurrentBlock() {
const { data } = await userFriendlyError( const { data } = await network({
retry(() => axios.get(`${baseURL}/blocks/current`), { maxRetry: 3 }), method: 'GET',
) url: `${baseURL}/blocks/current`,
})
return data return data
}, },
async getAccountNonce(address) { async getAccountNonce(address) {
const { data } = await userFriendlyError( const { data } = await network({
retry(() => axios.get(`${baseURL}/addresses/${address}/nonce`), { maxRetry: 3 }), method: 'GET',
) url: `${baseURL}/addresses/${address}/nonce`,
})
return data[0].nonce return data[0].nonce
}, },
async broadcastTransaction(tx) { async broadcastTransaction(tx) {
const { data } = await userFriendlyError(axios.post(`${baseURL}/transactions/send`, { tx })) const { data } = await network({
method: 'POST',
url: `${baseURL}/transactions/send`,
data: { tx },
})
return data.result return data.result
}, },
async getAccountBalance(address) { async getAccountBalance(address) {
const { data } = await userFriendlyError( const { data } = await network({
retry(() => axios.get(`${baseURL}/addresses/${address}/balance`), { maxRetry: 3 }), method: 'GET',
) url: `${baseURL}/addresses/${address}/balance`,
})
return data[0].balance return data[0].balance
}, },
} }

6
src/api/Fees.js

@ -1,8 +1,8 @@
// @flow // @flow
import invariant from 'invariant' import invariant from 'invariant'
import axios from 'axios'
import type { Currency } from '@ledgerhq/live-common/lib/types' import type { Currency } from '@ledgerhq/live-common/lib/types'
import { blockchainBaseURL, userFriendlyError } from './Ledger' import { blockchainBaseURL } from './Ledger'
import network from './network'
export type Fees = { export type Fees = {
[_: string]: number, [_: string]: number,
@ -11,7 +11,7 @@ export type Fees = {
export const getEstimatedFees = async (currency: Currency): Promise<Fees> => { export const getEstimatedFees = async (currency: Currency): Promise<Fees> => {
const baseURL = blockchainBaseURL(currency) const baseURL = blockchainBaseURL(currency)
invariant(baseURL, `Fees for ${currency.id} are not supported`) invariant(baseURL, `Fees for ${currency.id} are not supported`)
const { data, status } = await userFriendlyError(axios.get(`${baseURL}/fees`)) const { data, status } = await network({ method: 'GET', url: `${baseURL}/fees` })
if (data) { if (data) {
return data return data
} }

22
src/api/network.js

@ -0,0 +1,22 @@
// @flow
import axios from 'axios'
import { GET_CALLS_RETRY, GET_CALLS_TIMEOUT } from 'config/constants'
import { userFriendlyError } from 'api/Ledger'
import { retry } from 'helpers/promise'
const doRequest = axios // TODO later introduce a way to run it in renderer based on a env, we will diverge this implementation
export default (arg: Object) => {
let promise
if (arg.method === 'GET') {
if (!('timeout' in arg)) {
arg.timeout = GET_CALLS_TIMEOUT
}
promise = retry(() => doRequest(arg), {
maxRetry: GET_CALLS_RETRY,
})
} else {
promise = doRequest(arg)
}
return userFriendlyError(promise)
}

6
src/components/AccountPage/index.js

@ -74,8 +74,8 @@ type State = {
class AccountPage extends PureComponent<Props, State> { class AccountPage extends PureComponent<Props, State> {
state = { state = {
selectedTime: 'week', selectedTime: 'month',
daysCount: 7, daysCount: 30,
} }
handleChangeSelectedTime = item => handleChangeSelectedTime = item =>
@ -180,7 +180,7 @@ class AccountPage extends PureComponent<Props, State> {
)} )}
/> />
</Box> </Box>
<OperationsList canShowMore account={account} title={t('account:lastOperations')} /> <OperationsList account={account} title={t('account:lastOperations')} />
</Fragment> </Fragment>
) : ( ) : (
<EmptyStateAccount account={account} /> <EmptyStateAccount account={account} />

4
src/components/BalanceSummary/index.js

@ -35,7 +35,7 @@ const BalanceSummary = ({
}: Props) => { }: Props) => {
const account = accounts.length === 1 ? accounts[0] : undefined const account = accounts.length === 1 ? accounts[0] : undefined
return ( return (
<Card p={0} py={6}> <Card p={0} py={5}>
<CalculateBalance accounts={accounts} daysCount={daysCount}> <CalculateBalance accounts={accounts} daysCount={daysCount}>
{({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => {({ isAvailable, balanceHistory, balanceStart, balanceEnd }) =>
!isAvailable ? null : ( !isAvailable ? null : (
@ -57,7 +57,7 @@ const BalanceSummary = ({
unit={account ? account.unit : null} unit={account ? account.unit : null}
color={chartColor} color={chartColor}
data={balanceHistory} data={balanceHistory}
height={250} height={200}
currency={counterValue} currency={counterValue}
tickXScale={selectedTime} tickXScale={selectedTime}
renderTickY={val => formatShort(counterValue.units[0], val)} renderTickY={val => formatShort(counterValue.units[0], val)}

2
src/components/DashboardPage/AccountCard.js

@ -41,7 +41,7 @@ class AccountCard extends PureComponent<{
</Box> </Box>
<Box> <Box>
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite"> <Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite">
{account.unit.code} {account.currency.name}
</Box> </Box>
<Box fontSize={4} color="dark"> <Box fontSize={4} color="dark">
{account.name} {account.name}

6
src/components/DashboardPage/index.js

@ -56,8 +56,9 @@ type State = {
class DashboardPage extends PureComponent<Props, State> { class DashboardPage extends PureComponent<Props, State> {
state = { state = {
selectedTime: 'week', // save to user preference?
daysCount: 7, selectedTime: 'month',
daysCount: 30,
} }
onAccountClick = account => this.props.push(`/account/${account.id}`) onAccountClick = account => this.props.push(`/account/${account.id}`)
@ -167,7 +168,6 @@ class DashboardPage extends PureComponent<Props, State> {
</Box> </Box>
{displayOperations && ( {displayOperations && (
<OperationsList <OperationsList
canShowMore
onAccountClick={this.onAccountClick} onAccountClick={this.onAccountClick}
accounts={accounts} accounts={accounts}
title={t('dashboard:recentActivity')} title={t('dashboard:recentActivity')}

185
src/components/MainSideBar.js

@ -1,185 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { withRouter } from 'react-router'
import { push } from 'react-router-redux'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import type { Location } from 'react-router'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import type { UpdateStatus } from 'reducers/update'
import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants'
import { rgba } from 'styles/helpers'
import { accountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals'
import { getUpdateStatus } from 'reducers/update'
import Tooltip from 'components/base/Tooltip'
import { SideBarList } from 'components/base/SideBar'
import Box, { Tabbable } from 'components/base/Box'
import Space from 'components/base/Space'
import FormattedVal from 'components/base/FormattedVal'
import IconManager from 'icons/Manager'
import IconPieChart from 'icons/PieChart'
import IconCirclePlus from 'icons/CirclePlus'
import IconReceive from 'icons/Receive'
import IconSend from 'icons/Send'
import IconExchange from 'icons/Exchange'
const mapStateToProps = state => ({
accounts: accountsSelector(state),
updateStatus: getUpdateStatus(state),
})
const mapDispatchToProps = {
push,
openModal,
}
type Props = {
t: T,
accounts: Account[],
location: Location,
push: string => void,
openModal: string => void,
updateStatus: UpdateStatus,
}
class MainSideBar extends PureComponent<Props> {
push(to: string) {
const { push } = this.props
const {
location: { pathname },
} = this.props
if (pathname === to) {
return
}
push(to)
}
render() {
const { t, accounts, openModal, location, updateStatus } = this.props
const { pathname } = location
const navigationItems = [
{
value: 'dashboard',
label: t('dashboard:title'),
icon: IconPieChart,
iconActiveColor: 'wallet',
onClick: () => this.push('/'),
isActive: pathname === '/',
hasNotif: updateStatus === 'downloaded',
},
{
value: 'send',
label: t('send:title'),
icon: IconSend,
iconActiveColor: 'wallet',
onClick: () => openModal(MODAL_SEND),
},
{
value: 'receive',
label: t('receive:title'),
icon: IconReceive,
iconActiveColor: 'wallet',
onClick: () => openModal(MODAL_RECEIVE),
},
{
value: 'manager',
label: t('sidebar:manager'),
icon: IconManager,
iconActiveColor: 'wallet',
onClick: () => this.push('/manager'),
isActive: pathname === '/manager',
},
{
value: 'exchange',
label: t('sidebar:exchange'),
icon: IconExchange,
iconActiveColor: 'wallet',
onClick: () => this.push('/exchange'),
isActive: pathname === '/exchange',
},
]
const accountsItems = accounts.map(account => {
const accountURL = `/account/${account.id}`
return {
value: account.id,
label: account.name,
desc: () => (
<FormattedVal
alwaysShowSign={false}
color="graphite"
unit={account.unit}
showCode
val={account.balance || 0}
/>
),
iconActiveColor: account.currency.color,
icon: getCryptoCurrencyIcon(account.currency),
onClick: () => this.push(accountURL),
isActive: pathname === accountURL,
}
})
return (
<Box bg="white" style={{ width: 230 }}>
<Space of={70} />
<SideBarList title={t('sidebar:menu')} items={navigationItems} />
<Space of={40} />
<SideBarList
scroll
title={t('sidebar:accounts')}
titleRight={
<Tooltip render={() => t('importAccounts:title')}>
<PlusWrapper onClick={() => openModal('importAccounts')}>
<IconCirclePlus size={16} />
</PlusWrapper>
</Tooltip>
}
items={accountsItems}
emptyText={t('emptyState:sidebar.text')}
/>
</Box>
)
}
}
const PlusWrapper = styled(Tabbable).attrs({
p: 1,
cursor: 'pointer',
borderRadius: 1,
})`
color: ${p => p.theme.colors.smoke};
&:hover {
color: ${p => p.theme.colors.dark};
}
border: 1px solid transparent;
&:focus {
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};
}
`
const decorate = compose(
withRouter,
translate(),
connect(
mapStateToProps,
mapDispatchToProps,
),
)
export default decorate(MainSideBar)

37
src/components/MainSideBar/AccountListItem.js

@ -0,0 +1,37 @@
// @flow
import React, { PureComponent } from 'react'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import FormattedVal from 'components/base/FormattedVal'
import { SideBarListItem } from 'components/base/SideBar'
export default class AccountListItem extends PureComponent<{
account: Account,
push: string => void,
isActive: boolean,
}> {
render() {
const { account, push, isActive } = this.props
const accountURL = `/account/${account.id}`
const item = {
label: account.name,
desc: () => (
<FormattedVal
alwaysShowSign={false}
color="graphite"
unit={account.unit}
showCode
val={account.balance || 0}
/>
),
iconActiveColor: account.currency.color,
icon: getCryptoCurrencyIcon(account.currency),
onClick: () => push(accountURL),
isActive,
}
return <SideBarListItem {...item} />
}
}

43
src/components/MainSideBar/AddAccountButton.js

@ -0,0 +1,43 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { Tabbable } from 'components/base/Box'
import Tooltip from 'components/base/Tooltip'
import IconCirclePlus from 'icons/CirclePlus'
import { rgba } from 'styles/helpers'
const PlusWrapper = styled(Tabbable).attrs({
p: 1,
cursor: 'pointer',
borderRadius: 1,
})`
color: ${p => p.theme.colors.smoke};
&:hover {
color: ${p => p.theme.colors.dark};
}
border: 1px solid transparent;
&:focus {
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};
}
`
export default class AddAccountButton extends PureComponent<{
onClick: () => void,
tooltipText: string,
}> {
render() {
const { onClick, tooltipText } = this.props
return (
<Tooltip render={() => tooltipText}>
<PlusWrapper onClick={onClick}>
<IconCirclePlus size={16} />
</PlusWrapper>
</Tooltip>
)
}
}

155
src/components/MainSideBar/index.js

@ -0,0 +1,155 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { withRouter } from 'react-router'
import { push } from 'react-router-redux'
import type { Location } from 'react-router'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import type { UpdateStatus } from 'reducers/update'
import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants'
import { accountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals'
import { getUpdateStatus } from 'reducers/update'
import { SideBarList, SideBarListItem } from 'components/base/SideBar'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import Space from 'components/base/Space'
import IconManager from 'icons/Manager'
import IconPieChart from 'icons/PieChart'
import IconReceive from 'icons/Receive'
import IconSend from 'icons/Send'
import IconExchange from 'icons/Exchange'
import AccountListItem from './AccountListItem'
import AddAccountButton from './AddAccountButton'
const mapStateToProps = state => ({
accounts: accountsSelector(state),
updateStatus: getUpdateStatus(state),
})
const mapDispatchToProps = {
push,
openModal,
}
type Props = {
t: T,
accounts: Account[],
location: Location,
push: string => void,
openModal: string => void,
updateStatus: UpdateStatus,
}
class MainSideBar extends PureComponent<Props> {
push = (to: string) => {
const { push } = this.props
const {
location: { pathname },
} = this.props
if (pathname === to) {
return
}
push(to)
}
handleClickDashboard = () => this.push('/')
handleOpenSendModal = () => this.props.openModal(MODAL_SEND)
handleOpenReceiveModal = () => this.props.openModal(MODAL_RECEIVE)
handleClickManager = () => this.push('/manager')
handleClickExchange = () => this.push('/exchange')
handleOpenImportModal = () => this.props.openModal('importAccounts')
render() {
const { t, accounts, location, updateStatus } = this.props
const { pathname } = location
const addAccountButton = (
<AddAccountButton
tooltipText={t('importAccounts:title')}
onClick={this.handleOpenImportModal}
/>
)
return (
<Box relative bg="white" style={{ width: 230 }}>
<GrowScroll>
<Space of={70} />
<SideBarList title={t('sidebar:menu')}>
<SideBarListItem
label={t('dashboard:title')}
icon={IconPieChart}
iconActiveColor={'wallet'}
onClick={this.handleClickDashboard}
isActive={pathname === '/'}
hasNotif={updateStatus === 'downloaded'}
/>
<SideBarListItem
label={t('send:title')}
icon={IconSend}
iconActiveColor={'wallet'}
onClick={this.handleOpenSendModal}
/>
<SideBarListItem
label={t('receive:title')}
icon={IconReceive}
iconActiveColor={'wallet'}
onClick={this.handleOpenReceiveModal}
/>
<SideBarListItem
label={t('sidebar:manager')}
icon={IconManager}
iconActiveColor={'wallet'}
onClick={this.handleClickManager}
isActive={pathname === '/manager'}
/>
<SideBarListItem
label={t('sidebar:exchange')}
icon={IconExchange}
iconActiveColor={'wallet'}
onClick={this.handleClickExchange}
isActive={pathname === '/exchange'}
/>
</SideBarList>
<Space of={40} />
<SideBarList
title={t('sidebar:accounts', { count: accounts.length })}
titleRight={addAccountButton}
emptyText={t('emptyState:sidebar.text')}
>
{accounts.map(account => (
<AccountListItem
key={account.id}
account={account}
push={this.push}
isActive={pathname === `/account/${account.id}`}
/>
))}
</SideBarList>
<Space of={15} />
</GrowScroll>
</Box>
)
}
}
const decorate = compose(
withRouter,
translate(),
connect(
mapStateToProps,
mapDispatchToProps,
),
)
export default decorate(MainSideBar)

30
src/components/OperationsList/index.js

@ -52,8 +52,7 @@ const mapDispatchToProps = {
type Props = { type Props = {
account: Account, account: Account,
accounts: Account[], accounts: Account[],
canShowMore: boolean, openModal: (string, Object) => *,
openModal: Function,
t: T, t: T,
withAccount?: boolean, withAccount?: boolean,
title?: string, title?: string,
@ -66,10 +65,18 @@ type State = {
const initialState = { const initialState = {
nbToShow: 20, nbToShow: 20,
} }
const footerPlaceholder = (
<Box p={4} align="center">
<Text ff="Open Sans" fontSize={3}>
No more operations
</Text>
</Box>
)
export class OperationsList extends PureComponent<Props, State> { export class OperationsList extends PureComponent<Props, State> {
static defaultProps = { static defaultProps = {
withAccount: false, withAccount: false,
canShowMore: false,
} }
state = initialState state = initialState
@ -86,7 +93,7 @@ export class OperationsList extends PureComponent<Props, State> {
} }
render() { render() {
const { account, accounts, canShowMore, t, title, withAccount } = this.props const { account, accounts, t, title, withAccount } = this.props
const { nbToShow } = this.state const { nbToShow } = this.state
if (!account && !accounts) { if (!account && !accounts) {
@ -130,13 +137,14 @@ export class OperationsList extends PureComponent<Props, State> {
</Card> </Card>
</Box> </Box>
))} ))}
{canShowMore && {!groupedOperations.completed ? (
!groupedOperations.completed && ( <ShowMore onClick={this.fetchMoreOperations}>
<ShowMore onClick={this.fetchMoreOperations}> <span>{t('operationsList:showMore')}</span>
<span>{t('operationsList:showMore')}</span> <IconAngleDown size={12} />
<IconAngleDown size={12} /> </ShowMore>
</ShowMore> ) : (
)} footerPlaceholder
)}
</Box> </Box>
</Defer> </Defer>
) )

6
src/components/OperationsList/stories.js

@ -15,10 +15,6 @@ const account2 = genAccount('account2')
stories.add('OperationsList', () => ( stories.add('OperationsList', () => (
<Box bg="lightGrey" p={6} m={-4}> <Box bg="lightGrey" p={6} m={-4}>
<OperationsList <OperationsList accounts={[account1, account2]} withAccount={boolean('withAccount')} />
accounts={[account1, account2]}
canShowMore={boolean('canShowMore')}
withAccount={boolean('withAccount')}
/>
</Box> </Box>
)) ))

8
src/components/SelectCurrency/index.js

@ -14,7 +14,7 @@ import Select from 'components/base/Select'
import Box from 'components/base/Box' import Box from 'components/base/Box'
type OwnProps = { type OwnProps = {
onChange: Option => void, onChange: (?Option) => void,
currencies?: CryptoCurrency[], currencies?: CryptoCurrency[],
value?: CryptoCurrency, value?: CryptoCurrency,
placeholder: string, placeholder: string,
@ -30,7 +30,9 @@ const mapStateToProps = (state, props: OwnProps) => ({
}) })
const SelectCurrency = ({ onChange, value, t, placeholder, currencies, ...props }: Props) => { const SelectCurrency = ({ onChange, value, t, placeholder, currencies, ...props }: Props) => {
const options = currencies ? currencies.map(c => ({ ...c, value: c.id, label: c.name })) : [] const options = currencies
? currencies.map(c => ({ ...c, value: c.id, label: c.name, currency: c }))
: []
return ( return (
<Select <Select
value={value} value={value}
@ -38,7 +40,7 @@ const SelectCurrency = ({ onChange, value, t, placeholder, currencies, ...props
renderValue={renderOption} renderValue={renderOption}
options={options} options={options}
placeholder={placeholder || t('common:selectCurrency')} placeholder={placeholder || t('common:selectCurrency')}
onChange={onChange} onChange={item => onChange(item ? item.currency : null)}
{...props} {...props}
/> />
) )

2
src/components/base/Chart/helpers.js

@ -25,7 +25,7 @@ export function generateMargins(hideAxis) {
top: hideAxis ? 5 : 10, top: hideAxis ? 5 : 10,
bottom: hideAxis ? 5 : 30, bottom: hideAxis ? 5 : 30,
right: hideAxis ? 5 : 40, right: hideAxis ? 5 : 40,
left: hideAxis ? 5 : 80, left: hideAxis ? 5 : 70,
} }
// FIXME: Forced to "use" margins here to prevent babel/uglify to believe // FIXME: Forced to "use" margins here to prevent babel/uglify to believe

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

@ -55,13 +55,13 @@ class ModalBody extends PureComponent<Props, State> {
} }
const CloseContainer = styled(Box).attrs({ const CloseContainer = styled(Box).attrs({
p: 4, p: 2,
color: 'fog', color: 'fog',
})` })`
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
top: 0; top: 25px;
right: 0; right: 10px;
z-index: 1; z-index: 1;
&:hover { &:hover {

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

@ -11,7 +11,7 @@ type Props = {
// required // required
value: ?Option, value: ?Option,
options: Option[], options: Option[],
onChange: Option => void, onChange: (?Option) => void,
// custom renders // custom renders
renderOption: Option => Node, renderOption: Option => Node,
@ -40,6 +40,9 @@ class Select extends Component<Props> {
if (action === 'select-option') { if (action === 'select-option') {
onChange(value) onChange(value)
} }
if (action === 'pop-value') {
onChange(null)
}
} }
render() { render() {
@ -73,10 +76,10 @@ class Select extends Component<Props> {
isClearable={isClearable} isClearable={isClearable}
isSearchable={isSearchable} isSearchable={isSearchable}
blurInputOnSelect={false} blurInputOnSelect={false}
onChange={this.handleChange}
backspaceRemovesValue backspaceRemovesValue
menuShouldBlockScroll menuShouldBlockScroll
{...props} {...props}
onChange={this.handleChange}
/> />
) )
} }

23
src/components/base/SideBar/SideBarList.js

@ -1,28 +1,23 @@
// @flow // @flow
import React, { PureComponent, Fragment } from 'react' import React, { Component, Fragment } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import GrowScroll from 'components/base/GrowScroll' import GrowScroll from 'components/base/GrowScroll'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Space from 'components/base/Space' import Space from 'components/base/Space'
import SideBarListItem from './SideBarListItem'
import type { Item } from './SideBarListItem'
type Props = { type Props = {
items: Item[], children: any,
title?: Node | string, title?: Node | string,
activeValue?: string,
scroll?: boolean, scroll?: boolean,
titleRight?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ titleRight?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
emptyText?: string, emptyText?: string,
} }
class SideBarList extends PureComponent<Props> { class SideBarList extends Component<Props> {
render() { render() {
const { items, title, activeValue, scroll, titleRight, emptyText, ...props } = this.props const { children, title, scroll, titleRight, emptyText, ...props } = this.props
const ListWrapper = scroll ? GrowScroll : Box const ListWrapper = scroll ? GrowScroll : Box
return ( return (
<Fragment> <Fragment>
@ -35,15 +30,9 @@ class SideBarList extends PureComponent<Props> {
<Space of={20} /> <Space of={20} />
</Fragment> </Fragment>
)} )}
{items.length > 0 ? ( {children ? (
<ListWrapper flow={2} px={3} fontSize={3} {...props}> <ListWrapper flow={2} px={3} fontSize={3} {...props}>
{items.map(item => { {children}
const itemProps = {
item,
isActive: item.isActive || (!!activeValue && activeValue === item.value),
}
return <SideBarListItem key={item.value} {...itemProps} />
})}
</ListWrapper> </ListWrapper>
) : emptyText ? ( ) : emptyText ? (
<Box px={4} ff="Open Sans|Regular" fontSize={3} color="grey"> <Box px={4} ff="Open Sans|Regular" fontSize={3} color="grey">

17
src/components/base/SideBar/SideBarListItem.js

@ -7,7 +7,6 @@ import Box, { Tabbable } from 'components/base/Box'
import { rgba } from 'styles/helpers' import { rgba } from 'styles/helpers'
export type Item = { export type Item = {
value: string,
label: string | (Props => React$Element<any>), label: string | (Props => React$Element<any>),
desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
@ -18,20 +17,22 @@ export type Item = {
} }
export type Props = { export type Props = {
item: Item, label: string | (Props => React$Element<any>),
isActive: boolean, desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
iconActiveColor: ?string,
hasNotif?: boolean,
isActive?: boolean,
onClick?: void => void,
isActive?: boolean,
} }
class SideBarListItem extends PureComponent<Props> { class SideBarListItem extends PureComponent<Props> {
render() { render() {
const { const { icon: Icon, label, desc, iconActiveColor, hasNotif, onClick, isActive } = this.props
item: { icon: Icon, label, desc, iconActiveColor, hasNotif, onClick, value },
isActive,
} = this.props
return ( return (
<Container <Container
data-role="side-bar-item" data-role="side-bar-item"
data-roledata={value}
isActive={isActive} isActive={isActive}
iconActiveColor={iconActiveColor} iconActiveColor={iconActiveColor}
onClick={onClick} onClick={onClick}

1
src/components/base/SideBar/index.js

@ -1,3 +1,4 @@
// @flow // @flow
export { default as SideBarList } from './SideBarList' export { default as SideBarList } from './SideBarList'
export { default as SideBarListItem } from './SideBarListItem'

21
src/components/base/SideBar/stories.js

@ -2,9 +2,8 @@
import React from 'react' import React from 'react'
import { storiesOf } from '@storybook/react' import { storiesOf } from '@storybook/react'
import { select } from '@storybook/addon-knobs'
import { SideBarList } from 'components/base/SideBar' import { SideBarList, SideBarListItem } from 'components/base/SideBar'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import IconAccountSettings from 'icons/AccountSettings' import IconAccountSettings from 'icons/AccountSettings'
@ -17,26 +16,27 @@ const stories = storiesOf('Components/base/SideBar', module)
const SIDEBAR_ITEMS = [ const SIDEBAR_ITEMS = [
{ {
value: 'first', key: 'first',
label: 'First', label: 'First',
icon: IconAccountSettings, icon: IconAccountSettings,
iconActiveColor: '#ffae35', iconActiveColor: '#ffae35',
}, },
{ {
value: 'second', key: 'second',
label: 'Second', label: 'Second',
icon: IconPrint, icon: IconPrint,
iconActiveColor: '#0ebdcd', iconActiveColor: '#0ebdcd',
isActive: true,
}, },
{ {
value: 'third', key: 'third',
label: 'Third very very very very long text very long text very long', label: 'Third very very very very long text very long text very long',
icon: IconControls, icon: IconControls,
iconActiveColor: '#27a2db', iconActiveColor: '#27a2db',
hasNotif: true, hasNotif: true,
}, },
{ {
value: 'fourth', key: 'fourth',
label: () => ( label: () => (
<Box> <Box>
{'custom'} {'custom'}
@ -47,7 +47,7 @@ const SIDEBAR_ITEMS = [
iconActiveColor: '#3ca569', iconActiveColor: '#3ca569',
}, },
{ {
value: 'fifth', key: 'fifth',
label: 'Fifth', label: 'Fifth',
icon: IconExclamationCircle, icon: IconExclamationCircle,
iconActiveColor: '#0e76aa', iconActiveColor: '#0e76aa',
@ -55,8 +55,7 @@ const SIDEBAR_ITEMS = [
] ]
stories.add('SideBarList', () => ( stories.add('SideBarList', () => (
<SideBarList <SideBarList>
items={SIDEBAR_ITEMS} {SIDEBAR_ITEMS.map(item => <SideBarListItem key={item.key} {...item} />)}
activeValue={select('activeValue', [...SIDEBAR_ITEMS.map(i => i.value), null], 'third')} </SideBarList>
/>
)) ))

2
src/components/modals/OperationDetails.js

@ -160,7 +160,7 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
<Line> <Line>
<ColLeft>Fees</ColLeft> <ColLeft>Fees</ColLeft>
<ColRight> <ColRight>
<FormattedVal unit={unit} showCode val={fee} /> <FormattedVal unit={unit} showCode val={fee} color="dark" />
</ColRight> </ColRight>
</Line> </Line>
<B /> <B />

3
src/config/constants.js

@ -6,6 +6,9 @@ const intFromEnv = (key: string, def: number) => {
return def return def
} }
export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000)
export const GET_CALLS_RETRY = intFromEnv('GET_CALLS_RETRY', 2)
export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 2) export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 2)
export const SYNC_BOOT_DELAY = 2 * 1000 export const SYNC_BOOT_DELAY = 2 * 1000
export const SYNC_ALL_INTERVAL = 60 * 1000 export const SYNC_ALL_INTERVAL = 60 * 1000

3
src/helpers/derivations.js

@ -11,9 +11,12 @@ const ethLegacyMEW: Derivation = ({ x }) => `44'/60'/0'/${x}`
const etcLegacyMEW: Derivation = ({ x }) => `44'/60'/160720'/${x}` const etcLegacyMEW: Derivation = ({ x }) => `44'/60'/160720'/${x}`
const rippleLegacy: Derivation = ({ x }) => `44'/144'/0'/${x}'`
const legacyDerivations = { const legacyDerivations = {
ethereum: [ethLegacyMEW], ethereum: [ethLegacyMEW],
ethereum_classic: [etcLegacyMEW], ethereum_classic: [etcLegacyMEW],
ripple: [rippleLegacy],
} }
export const standardDerivation: Derivation = ({ currency, segwit, x }) => { export const standardDerivation: Derivation = ({ currency, segwit, x }) => {

5
src/helpers/promise.js

@ -1,7 +1,8 @@
// @flow // @flow
import logger from 'logger'
// small utilities for Promises // small utilities for Promises
import logger from 'logger'
export const delay = (ms: number): Promise<void> => new Promise(f => setTimeout(f, ms)) export const delay = (ms: number): Promise<void> => new Promise(f => setTimeout(f, ms))
const defaults = { const defaults = {
@ -21,7 +22,7 @@ export function retry<A>(f: () => Promise<A>, options?: $Shape<typeof defaults>)
} }
// In case of failure, wait the interval, retry the action // In case of failure, wait the interval, retry the action
return result.catch(e => { return result.catch(e => {
logger.warn('Promise#retry', e) logger.warn('retry failed', e.message)
return delay(interval).then(() => rec(remainingTry - 1, interval * intervalMultiplicator)) return delay(interval).then(() => rec(remainingTry - 1, interval * intervalMultiplicator))
}) })
} }

3
src/helpers/withLibcore.js

@ -1,9 +1,12 @@
// @flow // @flow
import invariant from 'invariant' import invariant from 'invariant'
import network from 'api/network'
const core = require('@ledgerhq/ledger-core') const core = require('@ledgerhq/ledger-core')
core.setHttpQueryImplementation(network)
let walletPoolInstance: ?Object = null let walletPoolInstance: ?Object = null
// TODO: `core` and `NJSWalletPool` should be typed // TODO: `core` and `NJSWalletPool` should be typed

2
src/main/app.js

@ -65,7 +65,7 @@ const defaultWindowOptions = {
} }
function createMainWindow() { function createMainWindow() {
const MIN_HEIGHT = 768 const MIN_HEIGHT = 720
const MIN_WIDTH = 1024 const MIN_WIDTH = 1024
const savedDimensions = db.getIn('settings', 'window.MainWindow.dimensions', {}) const savedDimensions = db.getIn('settings', 'window.MainWindow.dimensions', {})

2
static/i18n/en/sidebar.yml

@ -1,4 +1,4 @@
menu: Menu menu: Menu
accounts: Accounts accounts: Accounts ({{count}})
manager: Manager manager: Manager
exchange: Exchange exchange: Exchange

12
static/i18n/fr/importAccounts.yml

@ -1,7 +1,17 @@
--- ---
title: Import accounts title: Add accounts
breadcrumb: breadcrumb:
informations: Informations informations: Informations
connectDevice: Connect device connectDevice: Connect device
import: Import import: Import
finish: End finish: End
accountToImportSubtitle: Account to import
accountToImportSubtitle_plural: 'Accounts to import ({{count}})'
selectAll: Select all
unselectAll: Unselect all
createNewAccount: Create new account
retrySync: Retry sync
cta:
create: 'Create account'
import: 'Import account'
import_plural: 'Import accounts'

3
static/i18n/fr/releaseNotes.yml

@ -0,0 +1,3 @@
---
title: Release notes
version: Version {{versionNb}}

4
static/i18n/fr/settings.yml

@ -58,6 +58,10 @@ softResetModal:
title: Clean application cache title: Clean application cache
subTitle: Are you sure houston? subTitle: Are you sure houston?
desc: Lorem ipsum dolor sit amet desc: Lorem ipsum dolor sit amet
removeAccountModal:
title: Delete this account
subTitle: Are you sure houston?
desc: Lorem ipsum dolor sit amet
exportLogs: exportLogs:
title: Export Logs title: Export Logs
desc: Export Logs desc: Export Logs

2
static/i18n/fr/sidebar.yml

@ -1,5 +1,5 @@
--- ---
menu: Menu menu: Menu
accounts: Comptes accounts: Accounts ({{count}})
manager: Manager manager: Manager
exchange: Exchange exchange: Exchange

6
yarn.lock

@ -1502,9 +1502,9 @@
dependencies: dependencies:
events "^2.0.0" events "^2.0.0"
"@ledgerhq/ledger-core@1.4.5": "@ledgerhq/ledger-core@1.6.0":
version "1.4.5" version "1.6.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.4.5.tgz#d3cf00ac07307d419b09e4cc1de8a8764ef16338" resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.6.0.tgz#885184cfc14598487894de2b9893c4a08798b074"
dependencies: dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3" "@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6" "@ledgerhq/hw-transport-node-hid" "^4.7.6"

Loading…
Cancel
Save