Browse Source

Add chart in AccoutnPage

master
Loëck Vézien 7 years ago
parent
commit
e2e1052372
No known key found for this signature in database GPG Key ID: CBCDCE384E853AC4
  1. 2
      package.json
  2. 12
      src/components/AccountPage/AccountHeader.js
  3. 116
      src/components/AccountPage/index.js
  4. 127
      src/components/BalanceSummary/BalanceInfos.js
  5. 101
      src/components/BalanceSummary/index.js
  6. 68
      src/components/DashboardPage/index.js
  7. 36
      src/components/PillsDaysCount.js
  8. 8
      src/components/base/Chart/index.js
  9. 14
      src/components/base/GrowScroll/index.js
  10. 13
      src/components/layout/Default.js
  11. 4
      src/helpers/btc.js
  12. 16
      yarn.lock

2
package.json

@ -49,7 +49,7 @@
"@fortawesome/fontawesome-free-solid": "^5.0.7",
"@fortawesome/react-fontawesome": "^0.0.17",
"@ledgerhq/common": "^4.2.0",
"@ledgerhq/currencies": "^4.2.2",
"@ledgerhq/currencies": "^4.3.0-beta.f8165b69",
"@ledgerhq/hw-app-btc": "^4.2.2",
"@ledgerhq/hw-app-eth": "^4.2.0",
"@ledgerhq/hw-transport": "^4.2.0",

12
src/components/AccountPage/AccountHeader.js

@ -17,6 +17,14 @@ const CurName = styled(Text).attrs({
letter-spacing: 1px;
`
const AccountName = styled(Text).attrs({
color: 'dark',
ff: 'Museo Sans',
fontSize: 7,
})`
line-height: 1;
`
type Props = {
account: Account,
}
@ -34,9 +42,7 @@ class AccountHeader extends PureComponent<Props> {
)}
<Box>
<CurName>{account.currency.name}</CurName>
<Text ff="Museo Sans|Regular" fontSize={7} color="dark">
{account.name}
</Text>
<AccountName>{account.name}</AccountName>
</Box>
</Box>
)

116
src/components/AccountPage/index.js

@ -11,25 +11,26 @@ import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'constants'
import type { MapStateToProps } from 'react-redux'
import type { T, Account } from 'types/common'
import { formatBTC } from 'helpers/format'
import { getAccountById } from 'reducers/accounts'
import { openModal } from 'reducers/modals'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
import IconControls from 'icons/Controls'
import Text from 'components/base/Text'
import TransactionsList from 'components/TransactionsList'
import IconArrowUp from 'icons/ArrowUp'
import IconArrowDown from 'icons/ArrowDown'
import AccountHeader from './AccountHeader'
import IconArrowUp from 'icons/ArrowUp'
type Props = {
t: T,
account?: Account,
openModal: Function,
}
import BalanceSummary from 'components/BalanceSummary'
import {
BalanceTotal,
BalanceSinceDiff,
BalanceSincePercent,
} from 'components/BalanceSummary/BalanceInfos'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import FormattedVal from 'components/base/FormattedVal'
import PillsDaysCount from 'components/PillsDaysCount'
import TransactionsList from 'components/TransactionsList'
import AccountHeader from './AccountHeader'
const mapStateToProps: MapStateToProps<*, *, *> = (state, props) => ({
account: getAccountById(state, props.match.params.id),
@ -39,9 +40,32 @@ const mapDispatchToProps = {
openModal,
}
class AccountPage extends PureComponent<Props> {
type Props = {
t: T,
account?: Account,
openModal: Function,
}
type State = {
selectedTime: string,
daysCount: number,
}
class AccountPage extends PureComponent<Props, State> {
state = {
selectedTime: 'week',
daysCount: 7,
}
handleChangeSelectedTime = item =>
this.setState({
selectedTime: item.key,
daysCount: item.value,
})
render() {
const { account, openModal, t } = this.props
const { selectedTime, daysCount } = this.state
// Don't even throw if we jumped in wrong account route
if (!account) {
@ -49,19 +73,19 @@ class AccountPage extends PureComponent<Props> {
}
return (
<Box flow={7}>
<Box horizontal>
<Box>
<Box horizontal mb={5}>
<AccountHeader account={account} />
<Box horizontal alignItems="center" justifyContent="flex-end" grow flow={2}>
<Button primary onClick={() => openModal(MODAL_SEND, { account })}>
<Box horizontal flow={2} alignItems="center">
<IconArrowUp height={16} width={16} />
<Box horizontal flow={1} alignItems="center">
<IconArrowUp width={12} />
<Box>{t('send:title')}</Box>
</Box>
</Button>
<Button primary onClick={() => openModal(MODAL_RECEIVE, { account })}>
<Box horizontal flow={2} alignItems="center">
<IconArrowDown height={16} width={16} />
<Box horizontal flow={1} alignItems="center">
<IconArrowDown width={12} />
<Box>{t('receive:title')}</Box>
</Box>
</Button>
@ -75,9 +99,55 @@ class AccountPage extends PureComponent<Props> {
</Button>
</Box>
</Box>
<Card style={{ height: 435 }} alignItems="center" justifyContent="center">
<Text fontSize={5}>{formatBTC(account.balance)}</Text>
</Card>
<Box mb={7}>
<BalanceSummary
chartColor={account.currency.color}
chartId={`account-chart-${account.id}`}
accounts={[account]}
selectedTime={selectedTime}
daysCount={daysCount}
renderHeader={({ totalBalance, sinceBalance }) => (
<Box flow={4} mb={2}>
<Box horizontal>
<BalanceTotal totalBalance={account.balance} unit={account.unit}>
<Box mt={1}>
<FormattedVal
alwaysShowSign={false}
color="warmGrey"
fiat="USD"
fontSize={6}
showCode
style={{ lineHeight: 1 }}
val={totalBalance}
/>
</Box>
</BalanceTotal>
<Box>
<PillsDaysCount
selectedTime={selectedTime}
onChange={this.handleChangeSelectedTime}
/>
</Box>
</Box>
<Box horizontal justifyContent="center" flow={7}>
<BalanceSincePercent
alignItems="center"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
since={selectedTime}
/>
<BalanceSinceDiff
fiat="USD"
alignItems="center"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
since={selectedTime}
/>
</Box>
</Box>
)}
/>
</Box>
<TransactionsList
title={t('account:lastOperations')}
transactions={account.transactions}

127
src/components/BalanceSummary/BalanceInfos.js

@ -3,58 +3,111 @@
import React from 'react'
import styled from 'styled-components'
import type { Unit } from '@ledgerhq/currencies'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import FormattedVal from 'components/base/FormattedVal'
type Props = {
fiat: string,
since: string,
totalBalance: number,
sinceBalance: number,
}
const Sub = styled(Text).attrs({
ff: 'Open Sans',
color: 'warnGrey',
fontSize: 4,
})``
type BalanceSinceProps = {
since: string,
totalBalance: number,
sinceBalance: number,
}
type BalanceTotalProps = {
children?: any,
fiat?: string,
totalBalance: number,
unit?: Unit,
}
type Props = {
fiat: string,
} & BalanceSinceProps
export function BalanceSincePercent(props: BalanceSinceProps) {
const { totalBalance, sinceBalance, since, ...otherProps } = props
return (
<Box {...otherProps}>
<FormattedVal
isPercent
val={Math.floor((totalBalance - sinceBalance) / sinceBalance * 100)}
alwaysShowSign
fontSize={7}
/>
<Sub>since one {since}</Sub>
</Box>
)
}
export function BalanceSinceDiff(props: Props) {
const { totalBalance, sinceBalance, since, fiat, ...otherProps } = props
return (
<Box {...otherProps}>
<FormattedVal
fiat={fiat}
alwaysShowSign
showCode
val={totalBalance - sinceBalance}
fontSize={7}
/>
<Sub>since one {since}</Sub>
</Box>
)
}
export function BalanceTotal(props: BalanceTotalProps) {
const { fiat, totalBalance, children, unit } = props
return (
<Box grow>
<FormattedVal
alwaysShowSign={false}
color="dark"
fiat={fiat}
fontSize={8}
showCode
style={{ lineHeight: 1 }}
unit={unit}
val={totalBalance}
/>
{children}
</Box>
)
}
BalanceTotal.defaultProps = {
fiat: undefined,
children: null,
unit: undefined,
}
function BalanceInfos(props: Props) {
const { fiat, totalBalance, since, sinceBalance } = props
return (
<Box horizontal alignItems="flex-end" flow={7}>
<Box grow>
<FormattedVal
fiat={fiat}
val={totalBalance}
alwaysShowSign={false}
showCode
fontSize={8}
color="dark"
style={{ lineHeight: 1 }}
/>
<BalanceTotal fiat={fiat} totalBalance={totalBalance}>
<Sub>{'Total balance'}</Sub>
</Box>
<Box alignItems="flex-end">
<FormattedVal
isPercent
val={Math.floor((totalBalance - sinceBalance) / sinceBalance * 100)}
alwaysShowSign
fontSize={7}
/>
<Sub>since one {since}</Sub>
</Box>
<Box alignItems="flex-end">
<FormattedVal
fiat="USD"
alwaysShowSign
showCode
val={totalBalance - sinceBalance}
fontSize={7}
/>
<Sub>since one {since}</Sub>
</Box>
</BalanceTotal>
<BalanceSincePercent
alignItems="flex-end"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
since={since}
/>
<BalanceSinceDiff
fiat="USD"
alignItems="flex-end"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
since={since}
/>
</Box>
)
}

101
src/components/BalanceSummary/index.js

@ -3,7 +3,7 @@
import React, { Fragment } from 'react'
import moment from 'moment'
import { formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies'
import { formatShort, formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies'
import type { Accounts } from 'types/common'
@ -13,54 +13,67 @@ import { AreaChart } from 'components/base/Chart'
import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance'
import BalanceInfos from './BalanceInfos'
type Props = {
chartColor: string,
chartId: string,
accounts: Accounts,
selectedTime: string,
daysCount: number,
renderHeader: null | Function,
}
const BalanceSummary = ({ accounts, selectedTime, daysCount }: Props) => (
<Card flow={3} p={0} py={6}>
<CalculateBalance
accounts={accounts}
daysCount={daysCount}
render={({ allBalances, totalBalance, sinceBalance }) => (
<Fragment>
<Box px={6}>
<BalanceInfos
fiat="USD"
totalBalance={totalBalance}
since={selectedTime}
sinceBalance={sinceBalance}
/>
</Box>
<Box ff="Open Sans" fontSize={4} color="graphite">
<AreaChart
color="#5286f7"
data={allBalances}
height={250}
id="dashboard-chart"
padding={{
top: space[6],
bottom: space[6],
left: space[6] * 2,
right: space[6],
}}
strokeWidth={2}
renderLabels={d =>
formatCurrencyUnit(getFiatUnit('USD'), d.y * 100, {
showCode: true,
})
}
renderTickX={t => moment(t).format('MMM. D')}
/>
</Box>
</Fragment>
)}
/>
</Card>
)
const BalanceSummary = ({
chartColor,
chartId,
accounts,
selectedTime,
daysCount,
renderHeader,
}: Props) => {
const unit = getFiatUnit('USD')
return (
<Card p={0} py={6}>
<CalculateBalance
accounts={accounts}
daysCount={daysCount}
render={({ allBalances, totalBalance, sinceBalance }) => (
<Fragment>
{renderHeader !== null && (
<Box px={6}>
{renderHeader({
totalBalance,
selectedTime,
sinceBalance,
})}
</Box>
)}
<Box ff="Open Sans" fontSize={4} color="graphite">
<AreaChart
color={chartColor}
data={allBalances}
height={250}
id={chartId}
padding={{
top: space[6],
bottom: space[6],
left: space[6] * 2,
right: space[6],
}}
strokeWidth={2}
renderLabels={d =>
formatCurrencyUnit(unit, d.y * 100, {
showCode: true,
})
}
renderTickX={t => moment(t).format('MMM. D')}
renderTickY={t => formatShort(unit, t)}
/>
</Box>
</Fragment>
)}
/>
</Card>
)
}
export default BalanceSummary

68
src/components/DashboardPage/index.js

@ -18,9 +18,10 @@ import { getVisibleAccounts } from 'reducers/accounts'
import { updateOrderAccounts } from 'actions/accounts'
import { saveSettings } from 'actions/settings'
import BalanceInfos from 'components/BalanceSummary/BalanceInfos'
import BalanceSummary from 'components/BalanceSummary'
import Box from 'components/base/Box'
import Pills from 'components/base/Pills'
import PillsDaysCount from 'components/PillsDaysCount'
import Text from 'components/base/Text'
import TransactionsList from 'components/TransactionsList'
@ -47,17 +48,12 @@ type State = {
accountsChunk: Array<Array<Account | null>>,
allTransactions: Array<Object>,
selectedTime: string,
daysCount: number,
}
const ACCOUNTS_BY_LINE = 3
const ALL_TRANSACTIONS_LIMIT = 10
const itemsTimes = [
{ key: 'week', value: 7 },
{ key: 'month', value: 30 },
{ key: 'year', value: 365 },
]
const getAllTransactions = accounts => {
const allTransactions = accounts.reduce((result, account) => {
const transactions = get(account, 'transactions', [])
@ -92,18 +88,7 @@ class DashboardPage extends PureComponent<Props, State> {
accountsChunk: getAccountsChunk(this.props.accounts),
allTransactions: getAllTransactions(this.props.accounts),
selectedTime: 'week',
}
componentWillMount() {
this._itemsTimes = itemsTimes.map(item => ({
...item,
value: item.value,
label: this.props.t(`time:${item.key}`),
}))
}
componentDidMount() {
this._mounted = true
daysCount: 7,
}
componentWillReceiveProps(nextProps) {
@ -115,37 +100,22 @@ class DashboardPage extends PureComponent<Props, State> {
}
}
componentWillUnmount() {
this._mounted = false
}
getDaysCount() {
const { selectedTime } = this.state
const selectedTimeItems = this._itemsTimes.find(i => i.key === selectedTime)
return selectedTimeItems && selectedTimeItems.value ? selectedTimeItems.value : 7
}
handleChangeSelectedTime = item =>
this.setState({
selectedTime: item.key,
daysCount: item.value,
})
_mounted = false
_itemsTimes = []
render() {
const { push, accounts, t } = this.props
const { accountsChunk, allTransactions, selectedTime } = this.state
const { accountsChunk, allTransactions, selectedTime, daysCount } = this.state
const daysCount = this.getDaysCount()
const totalAccounts = accounts.length
return (
<Box flow={7}>
<Box horizontal alignItems="flex-end">
<Box>
<Box grow>
<Text color="dark" ff="Museo Sans" fontSize={7}>
{t('dashboard:greetings', { name: 'Khalil' })}
</Text>
@ -155,17 +125,27 @@ class DashboardPage extends PureComponent<Props, State> {
: t('dashboard:noAccounts')}
</Text>
</Box>
<Box ml="auto">
<Pills
items={this._itemsTimes}
activeKey={selectedTime}
onChange={this.handleChangeSelectedTime}
/>
<Box>
<PillsDaysCount selectedTime={selectedTime} onChange={this.handleChangeSelectedTime} />
</Box>
</Box>
{totalAccounts > 0 && (
<Fragment>
<BalanceSummary accounts={accounts} selectedTime={selectedTime} daysCount={daysCount} />
<BalanceSummary
chartId="dashboard-chart"
chartColor="#5286f7"
accounts={accounts}
selectedTime={selectedTime}
daysCount={daysCount}
renderHeader={({ totalBalance, selectedTime, sinceBalance }) => (
<BalanceInfos
fiat="USD"
totalBalance={totalBalance}
since={selectedTime}
sinceBalance={sinceBalance}
/>
)}
/>
<Box flow={4}>
<Box horizontal alignItems="flex-end">
<Text color="dark" ff="Museo Sans" fontSize={6}>

36
src/components/PillsDaysCount.js

@ -0,0 +1,36 @@
// @flow
import React from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Pills from 'components/base/Pills'
type Props = {
selectedTime: string,
onChange: Function,
t: T,
}
const itemsTimes = [
{ key: 'week', value: 7 },
{ key: 'month', value: 30 },
{ key: 'year', value: 365 },
]
function PillsDaysCount(props: Props) {
const { selectedTime, onChange, t } = props
return (
<Pills
items={itemsTimes.map(item => ({
...item,
label: t(`time:${item.key}`),
}))}
activeKey={selectedTime}
onChange={onChange}
/>
)
}
export default translate()(PillsDaysCount)

8
src/components/base/Chart/index.js

@ -242,7 +242,7 @@ export class AreaChart extends PureComponent<Chart> {
return (
<WrapperChart
height={height}
render={({ width, isAnimationActive }) => (
render={({ width }) => (
<Fragment>
{getLinearGradient({
linearGradient,
@ -259,11 +259,12 @@ export class AreaChart extends PureComponent<Chart> {
containerComponent={AreaChartContainer}
>
<VictoryAxis
animate={false}
tickCount={6}
tickFormat={renderTickX}
style={{
axis: {
stroke: colors.lightGrey,
stroke: colors.fog,
},
tickLabels: {
...tickLabelsStyle,
@ -277,7 +278,7 @@ export class AreaChart extends PureComponent<Chart> {
tickFormat={renderTickY}
style={{
grid: {
stroke: colors.lightGrey,
stroke: colors.fog,
strokeDasharray: 5,
},
axis: {
@ -290,7 +291,6 @@ export class AreaChart extends PureComponent<Chart> {
}}
/>
<VictoryArea
animate={isAnimationActive ? { duration: ANIMATION_DURATION } : null}
data={data}
x="name"
y="value"

14
src/components/base/GrowScroll/index.js

@ -14,28 +14,42 @@ type Props = {
full: boolean,
maxHeight?: number,
onUpdate: Function,
onScroll: Function,
}
class GrowScroll extends PureComponent<Props> {
static defaultProps = {
full: false,
onUpdate: noop,
onScroll: noop,
}
componentDidMount() {
this.handleUpdate(this.props)
if (this._scrollbar) {
this._scrollbar.addListener(this.handleScroll)
}
}
componentWillReceiveProps(nextProps: Props) {
this.handleUpdate(nextProps)
}
componentWillUnmount() {
if (this._scrollbar) {
this._scrollbar.removeListener(this.handleScroll)
}
}
handleUpdate = (props: Props) => {
if (this._scrollbar) {
props.onUpdate(this._scrollbar)
}
}
handleScroll = this.props.onScroll
_scrollbar = undefined
render() {

13
src/components/layout/Default.js

@ -48,6 +48,17 @@ class Default extends Component<Props> {
clearTimeout(this._timeout)
}
handleScroll = () => {
document.querySelectorAll('.tippy-popper').forEach((p: any) => {
const instance = p._tippy
if (instance.state.visible) {
instance.popperInstance.disableEventListeners()
instance.hide(0)
}
})
}
_timeout = undefined
_scrollContainer = null
@ -67,7 +78,7 @@ class Default extends Component<Props> {
<Box shrink grow bg="lightGrey" color="grey" relative>
<TopBar />
<UpdateNotifier />
<Container innerRef={n => (this._scrollContainer = n)}>
<Container innerRef={n => (this._scrollContainer = n)} onScroll={this.handleScroll}>
<Route path="/" exact component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
<Route path="/account/:id" component={AccountPage} />

4
src/helpers/btc.js

@ -154,7 +154,9 @@ export async function getAccount({
try {
txs = await ledger.getTransactions(listAddresses, transactionsOpts)
txs = txs.filter(t => !allTxsHash.includes(t.hash)).reverse()
} catch (e) {} // eslint-disable-line no-empty
} catch (e) {
console.log('getTransactions', e) // eslint-disable-line no-console
}
const hasTransactions = txs.length > 0

16
yarn.lock

@ -165,10 +165,12 @@
redux "^3.7.2"
redux-thunk "^2.2.0"
"@ledgerhq/currencies@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@ledgerhq/currencies/-/currencies-4.2.2.tgz#3fadbdff2b4f7adce9ba255a407bd3999aea3fdc"
"@ledgerhq/currencies@^4.3.0-beta.f8165b69":
version "4.3.0-beta.f8165b69"
resolved "https://registry.yarnpkg.com/@ledgerhq/currencies/-/currencies-4.3.0-beta.f8165b69.tgz#bf85e210a1172ec3cc6821af75d39fdd60627963"
dependencies:
lodash "^4.17.5"
numeral "^2.0.6"
querystring "^0.2.0"
"@ledgerhq/hw-app-btc@^4.2.2":
@ -3422,8 +3424,8 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
duplexify@^3.4.2, duplexify@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
version "3.5.4"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4"
dependencies:
end-of-stream "^1.0.0"
inherits "^2.0.1"
@ -7059,6 +7061,10 @@ number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
numeral@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506"
nwmatcher@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c"

Loading…
Cancel
Save