Browse Source

Bugfix account.unit to refresh account page when you change it

this also start splitting a bit the AccountPage component

as CalculateBalance will memoize things, we want inner part to be potentially re-render if they depend on other things, we do this by connecting them to redux. redux idea is to connect leafs instead of connecting the whole tree when possible, so this fit this paradigm to me.
master
Gaëtan Renaudeau 7 years ago
parent
commit
c83beeb782
  1. 125
      src/components/AccountPage/AccountBalanceSummaryHeader.js
  2. 100
      src/components/AccountPage/AccountHeaderActions.js
  3. 160
      src/components/AccountPage/index.js
  4. 1
      src/components/BalanceSummary/index.js
  5. 10
      src/components/CalculateBalance.js

125
src/components/AccountPage/AccountBalanceSummaryHeader.js

@ -0,0 +1,125 @@
// @flow
import React, { PureComponent } from 'react'
import { createStructuredSelector } from 'reselect'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import { saveSettings } from 'actions/settings'
import { accountSelector } from 'reducers/accounts'
import { counterValueCurrencySelector, selectedTimeRangeSelector } from 'reducers/settings'
import type { TimeRange } from 'reducers/settings'
import {
BalanceTotal,
BalanceSinceDiff,
BalanceSincePercent,
} from 'components/BalanceSummary/BalanceInfos'
import Box from 'components/base/Box'
import FormattedVal from 'components/base/FormattedVal'
import PillsDaysCount from 'components/PillsDaysCount'
type OwnProps = {
isAvailable: boolean,
totalBalance: number,
sinceBalance: number,
refBalance: number,
accountId: string, // eslint-disable-line
}
type Props = OwnProps & {
counterValue: Currency,
t: T,
account: Account,
saveSettings: ({ selectedTimeRange: TimeRange }) => *,
selectedTimeRange: TimeRange,
}
const mapStateToProps = createStructuredSelector({
account: accountSelector,
counterValue: counterValueCurrencySelector,
selectedTimeRange: selectedTimeRangeSelector,
})
const mapDispatchToProps = {
saveSettings,
}
class AccountBalanceSummaryHeader extends PureComponent<Props> {
handleChangeSelectedTime = item => {
this.props.saveSettings({ selectedTimeRange: item.key })
}
render() {
const {
account,
t,
counterValue,
selectedTimeRange,
isAvailable,
totalBalance,
sinceBalance,
refBalance,
} = this.props
return (
<Box flow={4} mb={2}>
<Box horizontal>
<BalanceTotal
showCryptoEvenIfNotAvailable
isAvailable={isAvailable}
totalBalance={account.balance}
unit={account.unit}
>
<FormattedVal
animateTicker
disableRounding
alwaysShowSign={false}
color="warmGrey"
unit={counterValue.units[0]}
fontSize={6}
showCode
val={totalBalance}
/>
</BalanceTotal>
<Box>
<PillsDaysCount selected={selectedTimeRange} onChange={this.handleChangeSelectedTime} />
</Box>
</Box>
<Box horizontal justifyContent="center" flow={7}>
<BalanceSincePercent
isAvailable={isAvailable}
t={t}
alignItems="center"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
since={selectedTimeRange}
/>
<BalanceSinceDiff
isAvailable={isAvailable}
t={t}
counterValue={counterValue}
alignItems="center"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
since={selectedTimeRange}
/>
</Box>
</Box>
)
}
}
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
),
translate(), // FIXME t() is not even needed directly here. should be underlying component responsability to inject it
)(AccountBalanceSummaryHeader)

100
src/components/AccountPage/AccountHeaderActions.js

@ -0,0 +1,100 @@
// @flow
import React, { PureComponent, Fragment } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import styled from 'styled-components'
import type { Account } from '@ledgerhq/live-common/lib/types'
import Tooltip from 'components/base/Tooltip'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants'
import type { T } from 'types/common'
import { rgba } from 'styles/helpers'
import { openModal } from 'reducers/modals'
import IconAccountSettings from 'icons/AccountSettings'
import IconReceive from 'icons/Receive'
import IconSend from 'icons/Send'
import Box, { Tabbable } from 'components/base/Box'
import Button from 'components/base/Button'
const ButtonSettings = styled(Tabbable).attrs({
cursor: 'pointer',
align: 'center',
justify: 'center',
borderRadius: 1,
})`
width: 40px;
height: 40px;
&:hover {
color: ${p => (p.disabled ? '' : p.theme.colors.dark)};
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.2))};
}
&:active {
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.3))};
}
`
const mapStateToProps = null
const mapDispatchToProps = {
openModal,
}
type OwnProps = {
account: Account,
}
type Props = OwnProps & {
t: T,
openModal: Function,
}
class AccountHeaderActions extends PureComponent<Props> {
render() {
const { account, openModal, t } = this.props
return (
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{account.operations.length > 0 && (
<Fragment>
<Button small primary onClick={() => openModal(MODAL_SEND, { account })}>
<Box horizontal flow={1} alignItems="center">
<IconSend size={12} />
<Box>{t('app:send.title')}</Box>
</Box>
</Button>
<Button small primary onClick={() => openModal(MODAL_RECEIVE, { account })}>
<Box horizontal flow={1} alignItems="center">
<IconReceive size={12} />
<Box>{t('app:receive.title')}</Box>
</Box>
</Button>
</Fragment>
)}
<Tooltip render={() => t('app:account.settings.title')}>
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Box justifyContent="center">
<IconAccountSettings size={16} />
</Box>
</ButtonSettings>
</Tooltip>
</Box>
)
}
}
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
),
translate(),
)(AccountHeaderActions)

160
src/components/AccountPage/index.js

@ -5,19 +5,8 @@ import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import { Redirect } from 'react-router'
import styled from 'styled-components'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount'
import Tooltip from 'components/base/Tooltip'
import TrackPage from 'analytics/TrackPage'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants'
import type { T } from 'types/common'
import { rgba } from 'styles/helpers'
import { saveSettings } from 'actions/settings'
import { accountSelector } from 'reducers/accounts'
import {
counterValueCurrencySelector,
@ -26,47 +15,19 @@ import {
timeRangeDaysByKey,
} from 'reducers/settings'
import type { TimeRange } from 'reducers/settings'
import { openModal } from 'reducers/modals'
import IconAccountSettings from 'icons/AccountSettings'
import IconReceive from 'icons/Receive'
import IconSend from 'icons/Send'
import TrackPage from 'analytics/TrackPage'
import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount'
import BalanceSummary from 'components/BalanceSummary'
import {
BalanceTotal,
BalanceSinceDiff,
BalanceSincePercent,
} from 'components/BalanceSummary/BalanceInfos'
import Box, { Tabbable } from 'components/base/Box'
import Button from 'components/base/Button'
import FormattedVal from 'components/base/FormattedVal'
import PillsDaysCount from 'components/PillsDaysCount'
import Box from 'components/base/Box'
import OperationsList from 'components/OperationsList'
import StickyBackToTop from 'components/StickyBackToTop'
import AccountHeader from './AccountHeader'
import AccountHeaderActions from './AccountHeaderActions'
import AccountBalanceSummaryHeader from './AccountBalanceSummaryHeader'
import EmptyStateAccount from './EmptyStateAccount'
const ButtonSettings = styled(Tabbable).attrs({
cursor: 'pointer',
align: 'center',
justify: 'center',
borderRadius: 1,
})`
width: 40px;
height: 40px;
&:hover {
color: ${p => (p.disabled ? '' : p.theme.colors.dark)};
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.2))};
}
&:active {
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.3))};
}
`
const mapStateToProps = (state, props) => ({
account: accountSelector(state, { accountId: props.match.params.id }),
counterValue: counterValueCurrencySelector(state),
@ -74,74 +35,54 @@ const mapStateToProps = (state, props) => ({
selectedTimeRange: selectedTimeRangeSelector(state),
})
const mapDispatchToProps = {
openModal,
saveSettings,
}
const mapDispatchToProps = null
type Props = {
counterValue: Currency,
t: T,
account?: Account,
openModal: Function,
saveSettings: ({ selectedTimeRange: TimeRange }) => *,
selectedTimeRange: TimeRange,
}
class AccountPage extends PureComponent<Props> {
handleChangeSelectedTime = item => {
this.props.saveSettings({ selectedTimeRange: item.key })
renderBalanceSummaryHeader = ({ isAvailable, totalBalance, sinceBalance, refBalance }) => {
const { account } = this.props
if (!account) return null
return (
<AccountBalanceSummaryHeader
accountId={account.id}
isAvailable={isAvailable}
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
/>
)
}
_cacheBalance = null
render() {
const { account, openModal, t, counterValue, selectedTimeRange } = this.props
const { account, t, counterValue, selectedTimeRange } = this.props
const daysCount = timeRangeDaysByKey[selectedTimeRange]
// Don't even throw if we jumped in wrong account route
if (!account) {
return <Redirect to="/" />
}
return (
// Force re-render account page, for avoid animation
// `key` forces re-render account page when going an another account (skip animations)
<Box key={account.id}>
<TrackPage
category="Account"
currency={account.currency.id}
operationsLength={account.operations.length}
/>
<SyncOneAccountOnMount priority={10} accountId={account.id} />
<Box horizontal mb={5} flow={4}>
<AccountHeader account={account} />
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{account.operations.length > 0 && (
<Fragment>
<Button small primary onClick={() => openModal(MODAL_SEND, { account })}>
<Box horizontal flow={1} alignItems="center">
<IconSend size={12} />
<Box>{t('app:send.title')}</Box>
</Box>
</Button>
<Button small primary onClick={() => openModal(MODAL_RECEIVE, { account })}>
<Box horizontal flow={1} alignItems="center">
<IconReceive size={12} />
<Box>{t('app:receive.title')}</Box>
</Box>
</Button>
</Fragment>
)}
<Tooltip render={() => t('app:account.settings.title')}>
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Box justifyContent="center">
<IconAccountSettings size={16} />
</Box>
</ButtonSettings>
</Tooltip>
</Box>
<AccountHeaderActions account={account} />
</Box>
{account.operations.length > 0 ? (
<Fragment>
<Box mb={7}>
@ -152,59 +93,12 @@ class AccountPage extends PureComponent<Props> {
counterValue={counterValue}
daysCount={daysCount}
selectedTimeRange={selectedTimeRange}
renderHeader={({ isAvailable, totalBalance, sinceBalance, refBalance }) => (
<Box flow={4} mb={2}>
<Box horizontal>
<BalanceTotal
showCryptoEvenIfNotAvailable
isAvailable={isAvailable}
totalBalance={account.balance}
unit={account.unit}
>
<FormattedVal
animateTicker
disableRounding
alwaysShowSign={false}
color="warmGrey"
unit={counterValue.units[0]}
fontSize={6}
showCode
val={totalBalance}
/>
</BalanceTotal>
<Box>
<PillsDaysCount
selected={selectedTimeRange}
onChange={this.handleChangeSelectedTime}
/>
</Box>
</Box>
<Box horizontal justifyContent="center" flow={7}>
<BalanceSincePercent
isAvailable={isAvailable}
t={t}
alignItems="center"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
since={selectedTimeRange}
/>
<BalanceSinceDiff
isAvailable={isAvailable}
t={t}
counterValue={counterValue}
alignItems="center"
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
since={selectedTimeRange}
/>
</Box>
</Box>
)}
renderHeader={this.renderBalanceSummaryHeader}
/>
</Box>
<OperationsList account={account} title={t('app:account.lastOperations')} />
<StickyBackToTop />
</Fragment>
) : (

1
src/components/BalanceSummary/index.js

@ -35,6 +35,7 @@ const BalanceSummary = ({
selectedTimeRange,
}: Props) => {
const account = accounts.length === 1 ? accounts[0] : undefined
// FIXME This nesting 😱
return (
<Card p={0} py={5}>
<CalculateBalance accounts={accounts} daysCount={daysCount}>

10
src/components/CalculateBalance.js

@ -32,6 +32,7 @@ type Props = OwnProps & {
balanceStart: number,
balanceEnd: number,
isAvailable: boolean,
hash: string,
}
const mapStateToProps = (state: State, props: OwnProps) => {
@ -71,19 +72,20 @@ const mapStateToProps = (state: State, props: OwnProps) => {
({ ...item, originalValue: originalValues[i] || 0 }),
)
const balanceEnd = balanceHistory[balanceHistory.length - 1].value
return {
isAvailable,
balanceHistory,
balanceStart: balanceHistory[0].value,
balanceEnd: balanceHistory[balanceHistory.length - 1].value,
balanceEnd,
hash: `${balanceHistory.length}_${balanceEnd}`,
}
}
const hash = ({ balanceHistory, balanceEnd }) => `${balanceHistory.length}_${balanceEnd}`
class CalculateBalance extends Component<Props> {
shouldComponentUpdate(nextProps) {
return hash(nextProps) !== hash(this.props)
return nextProps.hash !== this.props.hash
}
render() {
const { children } = this.props

Loading…
Cancel
Save