Browse Source

Merge pull request #147 from meriadec/master

Use common cryptocurrencies definitions, handle multi currencies
master
Loëck Vézien 7 years ago
committed by GitHub
parent
commit
e31a78f2fe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      package.json
  2. 11
      src/components/AccountPage.js
  3. 76
      src/components/DashboardPage/AccountCard.js
  4. 5
      src/components/DashboardPage/BalanceInfos.js
  5. 6
      src/components/DashboardPage/index.js
  6. 5
      src/components/SelectAccount/stories.js
  7. 45
      src/components/SideBar/index.js
  8. 14
      src/components/TransactionsList/index.js
  9. 36
      src/components/base/FormattedVal.js
  10. 76
      src/components/modals/AddAccount/index.js
  11. 10
      src/icons/currencies/Bitcoin.js
  12. 15
      src/internals/usb/wallet/index.js
  13. 67
      src/stories/currencies.stories.js
  14. 6
      src/types/common.js
  15. 2
      yarn.lock

2
package.json

@ -45,7 +45,7 @@
"@fortawesome/fontawesome-free-regular": "^5.0.6",
"@fortawesome/fontawesome-free-solid": "^5.0.6",
"@fortawesome/react-fontawesome": "^0.0.17",
"@ledgerhq/common": "4.2.0",
"@ledgerhq/common": "^4.2.0",
"@ledgerhq/currencies": "^4.2.0",
"@ledgerhq/hw-app-btc": "^4.2.0",
"@ledgerhq/hw-app-eth": "^4.2.0",

11
src/components/AccountPage.js

@ -39,6 +39,13 @@ const mapDispatchToProps = {
openModal,
}
function enrichTransactionsWithAccount(transactions, account) {
return transactions.map(t => ({
...t,
account,
}))
}
class AccountPage extends PureComponent<Props> {
render() {
const { account, accountData, openModal, t } = this.props
@ -100,7 +107,9 @@ class AccountPage extends PureComponent<Props> {
</Box>
</Box>
<Card p={0} px={4} title={t('AccountPage.lastOperations')}>
<TransactionsList transactions={accountData.transactions} />
<TransactionsList
transactions={enrichTransactionsWithAccount(accountData.transactions, account)}
/>
</Card>
</Fragment>
)}

76
src/components/DashboardPage/AccountCard.js

@ -1,11 +1,10 @@
// @flow
import React from 'react'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Account } from 'types/common'
import IconCurrencyBitcoin from 'icons/currencies/Bitcoin'
import { AreaChart } from 'components/base/Chart'
import Bar from 'components/base/Bar'
import Box, { Card } from 'components/base/Box'
@ -19,43 +18,46 @@ const AccountCard = ({
account: Account,
data: Array<Object>,
onClick: Function,
}) => (
<Card p={4} flow={4} flex={1} style={{ cursor: 'pointer' }} onClick={onClick}>
<Box horizontal ff="Open Sans|SemiBold" flow={3} alignItems="center">
<Box alignItems="center" justifyContent="center" style={{ color: '#fcb653' }}>
<IconCurrencyBitcoin height={20} width={20} />
</Box>
<Box>
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="warmGrey">
{account.type}
}) => {
const Icon = getIconByCoinType(account.currency.coinType)
return (
<Card p={4} flow={4} flex={1} style={{ cursor: 'pointer' }} onClick={onClick}>
<Box horizontal ff="Open Sans|SemiBold" flow={3} alignItems="center">
<Box alignItems="center" justifyContent="center" style={{ color: '#fcb653' }}>
{Icon && <Icon size={20} />}
</Box>
<Box fontSize={4} color="dark">
{account.name}
<Box>
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="warmGrey">
{account.unit.code}
</Box>
<Box fontSize={4} color="dark">
{account.name}
</Box>
</Box>
</Box>
</Box>
<Bar size={1} color="argile" />
<Box grow justifyContent="center" color="dark">
{account.data && (
<FormattedVal
alwaysShowSign={false}
color="dark"
currency={account.type}
showCode
val={account.data.balance}
/>
)}
</Box>
<AreaChart
tiny
id={`account-chart-${account.id}`}
color="#fcb653"
height={52}
data={data}
strokeWidth={1}
linearGradient={[[5, 0.4], [80, 0]]}
/>
</Card>
)
<Bar size={1} color="argile" />
<Box grow justifyContent="center" color="dark">
{account.data && (
<FormattedVal
alwaysShowSign={false}
color="dark"
unit={account.unit}
showCode
val={account.data.balance}
/>
)}
</Box>
<AreaChart
tiny
id={`account-chart-${account.id}`}
color="#fcb653"
height={52}
data={data}
strokeWidth={1}
linearGradient={[[5, 0.4], [80, 0]]}
/>
</Card>
)
}
export default AccountCard

5
src/components/DashboardPage/BalanceInfos.js

@ -3,6 +3,7 @@
import React from 'react'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import type { MapStateToProps } from 'react-redux'
@ -33,7 +34,7 @@ function BalanceInfos(props: Props) {
<Box grow>
<FormattedVal
val={totalBalance}
currency="BTC"
unit={getDefaultUnitByCoinType(0)}
alwaysShowSign={false}
showCode
fontSize={8}
@ -47,7 +48,7 @@ function BalanceInfos(props: Props) {
<Sub>{'since one week'}</Sub>
</Box>
<Box alignItems="flex-end">
<FormattedVal currency="USD" alwaysShowSign showCode val={6132.23} fontSize={7} />
<FormattedVal fiat="USD" alwaysShowSign showCode val={6132.23} fontSize={7} />
<Sub>{'since one week'}</Sub>
</Box>
</Box>

6
src/components/DashboardPage/index.js

@ -96,11 +96,7 @@ const getAllTransactions = accounts => {
...result,
...transactions.map(t => ({
...t,
account: {
id: account.id,
name: account.name,
type: account.type,
},
account,
})),
]

5
src/components/SelectAccount/stories.js

@ -3,6 +3,7 @@
import React, { PureComponent } from 'react'
import { storiesOf } from '@storybook/react'
import Chance from 'chance'
import { getCurrencyByCoinType, getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import { SelectAccount } from 'components/SelectAccount'
@ -12,7 +13,9 @@ const stories = storiesOf('SelectAccount', module)
const accounts = [...Array(20)].map(() => ({
id: chance.string(),
name: chance.name(),
type: 'BTC',
coinType: 0,
currency: getCurrencyByCoinType(0),
unit: getDefaultUnitByCoinType(0),
data: {
address: chance.string(),
balance: chance.floating({ min: 0, max: 20 }),

45
src/components/SideBar/index.js

@ -5,6 +5,7 @@ import { compose } from 'redux'
import { translate } from 'react-i18next'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_ADD_ACCOUNT } from 'constants'
@ -19,7 +20,6 @@ import IconArrowDown from 'icons/ArrowDown'
import IconArrowUp from 'icons/ArrowUp'
import IconSettings from 'icons/Settings'
import IconPlus from 'icons/Plus'
import IconCurrencyBitcoin from 'icons/currencies/Bitcoin'
import Box, { Tabbable } from 'components/base/Box'
import FormattedVal from 'components/base/FormattedVal'
@ -99,26 +99,29 @@ class SideBar extends PureComponent<Props> {
</Tooltip>
</CapsSubtitle>
<GrowScroll pb={4} px={4} flow={2}>
{accounts.map(account => (
<Item
big
desc={
<FormattedVal
alwaysShowSign={false}
color="warmGrey"
currency={account.type}
showCode
val={account.data ? account.data.balance : 0}
/>
}
iconActiveColor="#fcb653"
icon={<IconCurrencyBitcoin height={16} width={16} />}
key={account.id}
linkTo={`/account/${account.id}`}
>
{account.name}
</Item>
))}
{accounts.map(account => {
const Icon = getIconByCoinType(account.currency.coinType)
return (
<Item
big
desc={
<FormattedVal
alwaysShowSign={false}
color="warmGrey"
unit={account.unit}
showCode
val={account.data ? account.data.balance : 0}
/>
}
iconActiveColor={account.currency.color}
icon={Icon ? <Icon size={16} /> : null}
key={account.id}
linkTo={`/account/${account.id}`}
>
{account.name}
</Item>
)
})}
</GrowScroll>
</Box>
</Box>

14
src/components/TransactionsList/index.js

@ -6,6 +6,7 @@ import moment from 'moment'
import get from 'lodash/get'
import noop from 'lodash/noop'
import isEqual from 'lodash/isEqual'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Transaction as TransactionType } from 'types/common'
@ -14,8 +15,6 @@ import Defer from 'components/base/Defer'
import FormattedVal from 'components/base/FormattedVal'
import Text from 'components/base/Text'
import IconCurrencyBitcoin from 'icons/currencies/Bitcoin'
const DATE_COL_SIZE = 80
const ACCOUNT_COL_SIZE = 150
const AMOUNT_COL_SIZE = 150
@ -79,6 +78,7 @@ const Transaction = ({
tx: TransactionType,
}) => {
const time = moment(tx.received_at)
const Icon = getIconByCoinType(get(tx, 'account.currency.coinType'))
return (
<TransactionRaw>
<Cell size={DATE_COL_SIZE} justifyContent="space-between">
@ -96,7 +96,7 @@ const Transaction = ({
onClick={() => onAccountClick && onAccountClick(tx.account)}
>
<Box alignItems="center" justifyContent="center" style={{ color: '#fcb653' }}>
<IconCurrencyBitcoin height={16} width={16} />
{Icon && <Icon size={16} />}
</Box>
<Box ff="Open Sans|SemiBold" fontSize={4} color="dark">
{tx.account.name}
@ -121,7 +121,13 @@ const Transaction = ({
</Box>
</Cell>
<Cell size={AMOUNT_COL_SIZE} justifyContent="flex-end">
<FormattedVal val={tx.balance} currency="BTC" showCode fontSize={4} alwaysShowSign />
<FormattedVal
val={tx.balance}
unit={get(tx, 'account.unit')}
showCode
fontSize={4}
alwaysShowSign
/>
</Cell>
</TransactionRaw>
)

36
src/components/base/FormattedVal.js

@ -3,7 +3,8 @@
import React from 'react'
import styled from 'styled-components'
import { formatCurrencyUnit } from '@ledgerhq/currencies'
import { formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies'
import type { Unit } from '@ledgerhq/currencies'
import Text from 'components/base/Text'
@ -12,31 +13,18 @@ const T = styled(Text).attrs({
color: p => (p.isNegative ? p.theme.colors.grenade : p.theme.colors.green),
})``
const currencies = {
BTC: {
name: 'bitcoin',
code: 'BTC',
symbol: 'b',
magnitude: 8,
},
USD: {
name: 'dollar',
code: 'USD',
symbol: '$',
magnitude: 0,
},
}
type Props = {
val: number,
fiat?: string | null,
isPercent?: boolean,
currency?: any,
unit?: Unit | null,
alwaysShowSign?: boolean,
showCode?: boolean,
}
function FormattedVal(props: Props) {
const { val, isPercent, currency, alwaysShowSign, showCode, ...p } = props
const { val, fiat, isPercent, alwaysShowSign, showCode, ...p } = props
let { unit } = props
const isNegative = val < 0
@ -45,11 +33,12 @@ function FormattedVal(props: Props) {
if (isPercent) {
text = `${alwaysShowSign ? (isNegative ? '- ' : '+ ') : ''}${val} %`
} else {
const curr = currency ? currencies[currency.toUpperCase()] : null
if (!curr) {
return `[invalid currency ${currency || '(null)'}]`
if (fiat) {
unit = getFiatUnit(fiat)
} else if (!unit) {
return ''
}
text = formatCurrencyUnit(curr, val, {
text = formatCurrencyUnit(unit, val, {
alwaysShowSign,
showCode,
})
@ -63,10 +52,11 @@ function FormattedVal(props: Props) {
}
FormattedVal.defaultProps = {
currency: null,
unit: null,
isPercent: false,
alwaysShowSign: false,
showCode: false,
fiat: null,
}
export default FormattedVal

76
src/components/modals/AddAccount/index.js

@ -6,6 +6,9 @@ import { compose } from 'redux'
import { translate } from 'react-i18next'
import { ipcRenderer } from 'electron'
import differenceBy from 'lodash/differenceBy'
import { listCurrencies, getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import type { Currency } from '@ledgerhq/currencies'
import { MODAL_ADD_ACCOUNT } from 'constants'
@ -30,25 +33,24 @@ import CreateAccount from './CreateAccount'
import ImportAccounts from './ImportAccounts'
import RestoreAccounts from './RestoreAccounts'
const currencies = [
{
key: 'btc',
name: 'Bitcoin',
},
]
const currencies = listCurrencies().map(currency => ({
key: currency.coinType,
name: currency.name,
data: currency,
}))
const Steps = {
chooseWallet: (props: Object) => (
chooseCurrency: (props: Object) => (
<form onSubmit={props.onSubmit}>
<Box flow={3}>
<Box flow={1}>
<Label>{props.t('common.currency')}</Label>
<Select
placeholder={props.t('common.chooseWalletPlaceholder')}
onChange={item => props.onChangeInput('wallet')(item.key)}
onChange={item => props.onChangeCurrency(item.data)}
renderSelected={item => item.name}
items={currencies}
value={currencies.find(c => c.key === props.value.wallet)}
value={props.currency ? currencies.find(c => c.key === props.currency.coinType) : null}
/>
</Box>
<Box horizontal justifyContent="flex-end">
@ -62,7 +64,7 @@ const Steps = {
connectDevice: (props: Object) => (
<Box>
<Box>Connect your Ledger: {props.connected ? 'ok' : 'ko'}</Box>
<Box>Start {props.wallet.toUpperCase()} App on your Ledger: ko</Box>
<Box>Start {props.currency.name} App on your Ledger: ko</Box>
</Box>
),
inProgress: (props: Object) => (
@ -95,11 +97,7 @@ const Steps = {
},
}
type InputValue = {
wallet: string,
}
type Step = 'chooseWallet' | 'connectDevice' | 'inProgress' | 'listAccounts'
type Step = 'chooseCurrency' | 'connectDevice' | 'inProgress' | 'listAccounts'
type Props = {
t: T,
@ -113,8 +111,8 @@ type Props = {
}
type State = {
inputValue: InputValue,
step: Step,
currency: Currency | null,
accounts: Accounts,
progress: null | Object,
}
@ -133,12 +131,10 @@ const mapDispatchToProps = {
}
const defaultState = {
inputValue: {
wallet: '',
},
currency: null,
accounts: [],
progress: null,
step: 'chooseWallet',
step: 'chooseCurrency',
}
class AddAccountModal extends PureComponent<Props, State> {
@ -176,36 +172,36 @@ class AddAccountModal extends PureComponent<Props, State> {
getWalletInfos() {
const { currentDevice, accounts } = this.props
const { inputValue } = this.state
const { currency } = this.state
if (currentDevice === null) {
if (currentDevice === null || currency === null) {
return
}
sendEvent('usb', 'wallet.getAccounts', {
pathDevice: currentDevice.path,
wallet: inputValue.wallet,
coinType: currency.coinType,
currentAccounts: accounts.map(acc => acc.id),
})
}
getStepProps() {
const { currentDevice, archivedAccounts, canCreateAccount, updateAccount, t } = this.props
const { inputValue, step, progress, accounts } = this.state
const { currency, step, progress, accounts } = this.state
const props = (predicate, props) => (predicate ? props : {})
return {
...props(step === 'chooseWallet', {
...props(step === 'chooseCurrency', {
t,
value: inputValue,
currency,
onChangeCurrency: this.handleChangeCurrency,
onSubmit: this.handleSubmit,
onChangeInput: this.handleChangeInput,
}),
...props(step === 'connectDevice', {
t,
connected: currentDevice !== null,
wallet: inputValue.wallet,
currency,
}),
...props(step === 'inProgress', {
t,
@ -247,23 +243,11 @@ class AddAccountModal extends PureComponent<Props, State> {
handleImportAccounts = accounts => accounts.forEach(account => this.addAccount(account))
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
...prev.inputValue,
[key]: value,
},
}))
handleChangeCurrency = (currency: Currency) => this.setState({ currency })
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
const { inputValue } = this.state
if (inputValue.wallet.trim() === '') {
return
}
this.setState({
step: 'connectDevice',
})
@ -277,13 +261,19 @@ class AddAccountModal extends PureComponent<Props, State> {
})
addAccount = ({ id, name, ...data }) => {
const { inputValue } = this.state
const { currency } = this.state
const { addAccount } = this.props
if (currency === null) {
return
}
addAccount({
id,
name,
type: inputValue.wallet,
coinType: currency.coinType,
currency,
unit: getDefaultUnitByCoinType(currency.coinType),
data,
})
}

10
src/icons/currencies/Bitcoin.js

@ -1,10 +0,0 @@
import React from 'react'
export default props => (
<svg viewBox="0 0 16 16" {...props}>
<path
fill="currentColor"
d="M7.61970553 0h1.70000005v3.05846154H7.61970553V0zm1.70000005 0v3.05846154H7.61970553V0h1.70000005zM7.81303887 12.9415385h1.70000004V16H7.81303887v-3.0584615zm1.70000004 0V16H7.81303887v-3.0584615h1.70000004zM4.4063722 0h1.70000005v3.05846154H4.4063722V0zm1.70000005 0v3.05846154H4.4063722V0h1.70000005zM4.59970553 12.9415385h1.70000005V16H4.59970553v-3.0584615zm1.70000005 0V16H4.59970553v-3.0584615h1.70000005zM2.6663722 8.81307695V1.94999998h.85000002l6.13827333.00156806c1.80778725.10996034 3.21199965 1.5663186 3.11839335 3.25458581l-.0017039.27851419c.110634 1.74464117-1.2981636 3.21696546-3.16829611 3.32840891H2.6663722zm.85000002 0l.85000003-.85000003v4.31926938l5.60765891.000255c.95057664-.0249932 1.67559124-.7265334 1.65900774-1.5564475l.0002249-.3703244c.0187355-.81415185-.7068616-1.51805139-1.63689158-1.54275245h-6.48zm7.55666668-3.27461541l.0012684-.37872715c.042116-.77004688-.6196246-1.45636138-1.47126841-1.50973437H4.36637225v3.46311513l5.20484808.0002342c.89861507-.06354201 1.55224847-.7529014 1.50181857-1.57488781zM4.36637225 7.11311515l5.18540306.00150901c.00432951-.00026159.15996192-.00067981.46689729-.00125468 1.859875.04881191 3.3551536 1.49937703 3.3143663 3.26201512l-.0001732.3336104c.0359164 1.7788337-1.4578252 3.2242112-3.33649348 3.2733127H2.6663722V7.1130769h.85000002l.85000003.00003825zm0 .00017966v.84978211l-.85000003-.85000002.85000003.00021791z"
/>
</svg>
)

15
src/internals/usb/wallet/index.js

@ -4,30 +4,31 @@ import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import getAllAccounts, { verifyAddress } from './accounts'
async function getAllAccountsByWallet({ pathDevice, wallet, currentAccounts, onProgress }) {
async function getAllAccountsByCoinType({ pathDevice, coinType, currentAccounts, onProgress }) {
const transport = await CommNodeHid.open(pathDevice)
if (wallet === 'btc') {
// 0: BTC
if (coinType === 0) {
return getAllAccounts({ transport, currentAccounts, onProgress })
}
throw new Error('invalid wallet')
throw new Error('invalid coinType')
}
export default (sendEvent: Function) => ({
getAccounts: async ({
pathDevice,
wallet,
coinType,
currentAccounts,
}: {
pathDevice: string,
wallet: string,
coinType: number,
currentAccounts: Array<*>,
}) => {
try {
const data = await getAllAccountsByWallet({
const data = await getAllAccountsByCoinType({
pathDevice,
wallet,
coinType,
currentAccounts,
onProgress: progress => sendEvent('wallet.getAccounts.progress', progress, { kill: false }),
})

67
src/stories/currencies.stories.js

@ -0,0 +1,67 @@
// @flow
import React, { Fragment } from 'react'
import { storiesOf } from '@storybook/react'
import { listCurrencies } from '@ledgerhq/currencies'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Currency } from '@ledgerhq/currencies'
const stories = storiesOf('currencies', module)
const currencies: Array<Currency> = listCurrencies()
stories.add('currencies list', () => (
<div>
<table border="1">
<thead>
<tr>
<td>{'coin type'}</td>
<td>{'name'}</td>
<td>{'color'}</td>
<td>{'icon'}</td>
<td>{'units'}</td>
</tr>
</thead>
<tbody>
{currencies.map(cur => {
const Icon = getIconByCoinType(cur.coinType)
return (
<tr key={cur.coinType}>
<td>{cur.coinType}</td>
<td>{cur.name}</td>
<td>
{cur.color ? (
<Fragment>
<div
style={{
width: 50,
height: 25,
backgroundColor: cur.color,
}}
/>
<div>{cur.color}</div>
</Fragment>
) : (
'-'
)}
</td>
<td>{Icon ? <Icon size={30} /> : '-'}</td>
<td>
{cur.units && (
<ul style={{ paddingRight: 10 }}>
{cur.units.map(unit => (
<li key={unit.code}>
{unit.code} ({unit.magnitude})
</li>
))}
</ul>
)}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
))

6
src/types/common.js

@ -1,5 +1,7 @@
// @flow
import type { Unit, Currency } from '@ledgerhq/currencies'
export type Device = {
vendorId: string,
productId: string,
@ -32,7 +34,9 @@ export type Account = {
data?: AccountData,
id: string,
name: string,
type: string,
coinType: number,
currency: Currency,
unit: Unit,
}
export type Accounts = Array<Account>

2
yarn.lock

@ -150,7 +150,7 @@
dependencies:
humps "^2.0.1"
"@ledgerhq/common@4.2.0":
"@ledgerhq/common@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/common/-/common-4.2.0.tgz#b4e831f2d4b6b9550660141ae4f3aee0198a0987"
dependencies:

Loading…
Cancel
Save