Browse Source

Merge pull request #241 from loeck/master

Add new design for OperationsList
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
3379989daf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      package.json
  2. 1
      src/components/AccountPage/index.js
  3. 44
      src/components/OperationsList/ConfirmationCheck.js
  4. 239
      src/components/OperationsList/index.js
  5. 70
      src/components/OperationsList/stories.js
  6. 1
      src/styles/reset.js
  7. 2
      src/styles/theme.js
  8. 4
      static/i18n/en/operationsList.yml
  9. 6
      yarn.lock

2
package.json

@ -57,7 +57,7 @@
"cross-env": "^5.1.4",
"d3": "^4.13.0",
"debug": "^3.1.0",
"downshift": "^1.31.1",
"downshift": "^1.31.2",
"electron-store": "^1.3.0",
"electron-updater": "^2.21.1",
"fuse.js": "^3.2.0",

1
src/components/AccountPage/index.js

@ -158,7 +158,6 @@ class AccountPage extends PureComponent<Props, State> {
</Box>
<OperationsList
account={account}
minConfirmations={account.settings.minConfirmations}
operations={account.operations}
title={t('account:lastOperations')}
/>

44
src/components/OperationsList/ConfirmationCheck.js

@ -7,27 +7,52 @@ import { rgba } from 'styles/helpers'
import type { T } from 'types/common'
import IconArrowDown from 'icons/ArrowDown'
import IconArrowUp from 'icons/ArrowUp'
import IconClock from 'icons/Clock'
import Box from 'components/base/Box'
import Tooltip from 'components/base/Tooltip'
import IconCheck from 'icons/Check'
import IconClock from 'icons/Clock'
const Container = styled(Box).attrs({
bg: p => rgba(p.isConfirmed ? p.theme.colors.positiveGreen : p.theme.colors.grey, 0.1),
color: p => (p.isConfirmed ? p.theme.colors.positiveGreen : p.theme.colors.grey),
bg: p =>
p.isConfirmed
? rgba(p.type === 'from' ? p.theme.colors.positiveGreen : p.theme.colors.grey, 0.1)
: 'none',
color: p => (p.type === 'from' ? p.theme.colors.positiveGreen : p.theme.colors.grey),
align: 'center',
justify: 'center',
})`
width: 24px;
border: ${p =>
!p.isConfirmed
? `1px solid ${
p.type === 'from' ? p.theme.colors.positiveGreen : rgba(p.theme.colors.grey, 0.2)
}`
: 0};
border-radius: 50%;
position: relative;
height: 24px;
width: 24px;
`
const WrapperClock = styled(Box).attrs({
bg: 'white',
color: 'grey',
})`
border-radius: 50%;
position: absolute;
bottom: -4px;
right: -4px;
padding: 1px;
`
const ConfirmationCheck = ({
type,
confirmations,
minConfirmations,
t,
}: {
type: 'to' | 'from',
confirmations: number,
minConfirmations: number,
t: T,
@ -39,8 +64,13 @@ const ConfirmationCheck = ({
isConfirmed ? t('operationsList:confirmed') : t('operationsList:notConfirmed')
}
>
<Container isConfirmed={isConfirmed}>
{isConfirmed ? <IconCheck size={12} /> : <IconClock size={12} />}
<Container type={type} isConfirmed={isConfirmed}>
{type === 'from' ? <IconArrowDown size={12} /> : <IconArrowUp size={12} />}
{!isConfirmed && (
<WrapperClock>
<IconClock size={10} />
</WrapperClock>
)}
</Container>
</Tooltip>
)

239
src/components/OperationsList/index.js

@ -3,14 +3,20 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import moment from 'moment'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import get from 'lodash/get'
import noop from 'lodash/noop'
import isEqual from 'lodash/isEqual'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Account, Operation as OperationType, T } from 'types/common'
import { getCounterValue } from 'reducers/settings'
import IconAngleDown from 'icons/AngleDown'
import Box, { Card } from 'components/base/Box'
@ -19,19 +25,11 @@ import FormattedVal from 'components/base/FormattedVal'
import Text from 'components/base/Text'
import ConfirmationCheck from './ConfirmationCheck'
const DATE_COL_SIZE = 80
const DATE_COL_SIZE = 100
const ACCOUNT_COL_SIZE = 150
const AMOUNT_COL_SIZE = 150
const CONFIRMATION_COL_SIZE = 44
const Cap = styled(Text).attrs({
fontSize: 2,
color: 'graphite',
ff: 'Museo Sans|Bold',
})`
text-transform: uppercase;
`
const Day = styled(Text).attrs({
color: 'dark',
fontSize: 3,
@ -42,26 +40,15 @@ const Day = styled(Text).attrs({
`
const Hour = styled(Day).attrs({
color: 'graphite',
color: 'grey',
})``
const HeaderCol = ({ size, children, ...props }: { size?: number, children?: any }) => (
<Cell size={size} {...props}>
<Cap>{children}</Cap>
</Cell>
)
HeaderCol.defaultProps = {
size: undefined,
children: undefined,
}
const OperationRaw = styled(Box).attrs({
horizontal: true,
alignItems: 'center',
})`
cursor: pointer;
border-bottom: 1px solid ${p => p.theme.colors.fog};
border-bottom: 1px solid ${p => p.theme.colors.lightGrey};
height: 68px;
&:last-child {
@ -91,89 +78,124 @@ const ShowMore = styled(Box).attrs({
p: 4,
color: 'wallet',
})`
border-top: 1px solid ${p => p.theme.colors.fog};
cursor: pointer;
&:hover {
text-decoration: underline;
}
`
const AddressEllipsis = styled.div`
display: block;
flex-shrink: 1;
min-width: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`
const Address = ({ value }: { value: string }) => {
const addrSize = value.length / 2
const left = value.slice(0, 10)
const right = value.slice(-addrSize)
const middle = value.slice(10, -addrSize)
return (
<Box horizontal color="smoke" ff="Open Sans" fontSize={3}>
<div>{left}</div>
<AddressEllipsis>{middle}</AddressEllipsis>
<div>{right}</div>
</Box>
)
}
const Operation = ({
account,
t,
counterValue,
counterValues,
minConfirmations,
onAccountClick,
t,
tx,
withAccount,
minConfirmations,
}: {
account: Account | null,
t: T,
account: Account,
counterValue: string,
counterValues: Object | null,
minConfirmations: number,
onAccountClick?: Function,
t: T,
tx: OperationType,
withAccount?: boolean,
minConfirmations: number,
}) => {
const acc = account || tx.account
const { unit } = account
const time = moment(tx.receivedAt)
const Icon = getIconByCoinType(get(tx, 'account.currency.coinType'))
const Icon = getIconByCoinType(account.currency.coinType)
const type = tx.amount > 0 ? 'from' : 'to'
return (
<OperationRaw>
<Cell size={DATE_COL_SIZE} justifyContent="space-between">
<Cell size={CONFIRMATION_COL_SIZE} align="center" justify="flex-start">
<ConfirmationCheck
type={type}
minConfirmations={minConfirmations}
confirmations={tx.confirmations}
t={t}
/>
</Cell>
<Cell size={DATE_COL_SIZE} justifyContent="space-between" px={3}>
<Box>
<Day>{time.format('DD MMM')}</Day>
<Box ff="Open Sans|SemiBold" fontSize={3} color="smoke">
{t(`operationsList:${type}`)}
</Box>
<Hour>{time.format('HH:mm')}</Hour>
</Box>
</Cell>
{withAccount &&
acc && (
account && (
<Cell
size={ACCOUNT_COL_SIZE}
horizontal
flow={2}
style={{ cursor: 'pointer' }}
onClick={() => onAccountClick && onAccountClick(acc)}
onClick={() => onAccountClick && onAccountClick(account)}
>
<Box alignItems="center" justifyContent="center" style={{ color: acc.currency.color }}>
<Box
alignItems="center"
justifyContent="center"
style={{ color: account.currency.color }}
>
{Icon && <Icon size={16} />}
</Box>
<Box ff="Open Sans|SemiBold" fontSize={4} color="dark">
{acc.name}
<Box ff="Open Sans|SemiBold" fontSize={3} color="dark">
{account.name}
</Box>
</Cell>
)}
<Cell
grow
shrink
style={{
display: 'block',
}}
>
<Box ff="Open Sans" fontSize={3} color="graphite">
{tx.amount > 0 ? t('operationsList:from') : t('operationsList:to')}
</Box>
<Box
color="dark"
ff="Open Sans"
fontSize={3}
style={{
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
display: 'block',
}}
>
{tx.address}
</Box>
<Cell grow shrink style={{ display: 'block' }}>
<Address value={tx.address} />
</Cell>
<Cell size={AMOUNT_COL_SIZE} justifyContent="flex-end">
<FormattedVal val={tx.amount} unit={acc && acc.unit} showCode fontSize={4} alwaysShowSign />
</Cell>
<Cell size={CONFIRMATION_COL_SIZE} px={0} align="center" justify="flex-start">
<ConfirmationCheck
minConfirmations={minConfirmations}
confirmations={tx.confirmations}
t={t}
/>
<Cell size={AMOUNT_COL_SIZE}>
<Box alignItems="flex-end">
<FormattedVal
val={tx.amount}
unit={unit}
showCode
fontSize={4}
alwaysShowSign
color={tx.amount < 0 ? 'smoke' : 'positiveGreen'}
/>
{counterValues && (
<FormattedVal
val={counterValues[time.format('YYYY-MM-DD')] * (tx.amount / 10 ** unit.magnitude)}
fiat={counterValue}
showCode
fontSize={3}
alwaysShowSign
color="grey"
/>
)}
</Box>
</Cell>
</OperationRaw>
)
@ -184,10 +206,16 @@ Operation.defaultProps = {
withAccount: false,
}
const mapStateToProps = state => ({
counterValue: getCounterValue(state),
counterValues: state.counterValues,
})
type Props = {
account: Account | null,
account: Account,
canShowMore: boolean,
minConfirmations: number,
counterValue: string,
counterValues: Object,
onAccountClick?: Function,
operations: OperationType[],
t: T,
@ -195,21 +223,24 @@ type Props = {
withAccount?: boolean,
}
class OperationsList extends Component<Props> {
export class OperationsList extends Component<Props> {
static defaultProps = {
account: null,
onAccountClick: noop,
withAccount: false,
minConfirmations: 2,
canShowMore: false,
}
shouldComponentUpdate(nextProps: Props) {
if (this.props.canShowMore !== nextProps.canShowMore) {
if (this.props.account !== nextProps.account) {
return true
}
if (this.props.withAccount !== nextProps.withAccount) {
return true
}
if (this.props.minConfirmations !== nextProps.minConfirmations) {
if (this.props.canShowMore !== nextProps.canShowMore) {
return true
}
@ -228,7 +259,8 @@ class OperationsList extends Component<Props> {
const {
account,
canShowMore,
minConfirmations,
counterValue,
counterValues,
onAccountClick,
operations,
t,
@ -240,43 +272,40 @@ class OperationsList extends Component<Props> {
return (
<Defer>
<Card flow={1} title={title} p={0}>
<Box horizontal pt={4}>
<HeaderCol size={DATE_COL_SIZE}>{t('operationsList:date')}</HeaderCol>
{withAccount && (
<HeaderCol size={ACCOUNT_COL_SIZE}>{t('operationsList:account')}</HeaderCol>
)}
<HeaderCol grow>{t('operationsList:address')}</HeaderCol>
<HeaderCol size={AMOUNT_COL_SIZE} justifyContent="flex-end">
{t('operationsList:amount')}
</HeaderCol>
<HeaderCol size={CONFIRMATION_COL_SIZE} px={0} />
</Box>
<Box>
{operations.map(tx => (
<Operation
account={account}
t={t}
key={`{${tx.hash}${tx.account ? `-${tx.account.id}` : ''}`}
withAccount={withAccount}
onAccountClick={onAccountClick}
minConfirmations={minConfirmations}
tx={tx}
/>
))}
</Box>
<Box>
<Card flow={1} title={title} p={0}>
<Box>
{operations.map(tx => {
const acc = account || tx.account
const unit = getDefaultUnitByCoinType(acc.coinType)
const cValues = get(counterValues, `${unit.code}-${counterValue}.byDate`, null)
return (
<Operation
account={acc}
counterValue={counterValue}
counterValues={cValues}
key={`{${tx.hash}${acc ? `-${acc.id}` : ''}`}
minConfirmations={acc.settings.minConfirmations}
onAccountClick={onAccountClick}
t={t}
tx={tx}
withAccount={withAccount}
/>
)
})}
</Box>
</Card>
{canShowMore && (
<ShowMore>
<span>{t('operationsList:showMore')}</span>
<IconAngleDown size={12} />
</ShowMore>
)}
</Card>
</Box>
</Defer>
)
}
}
export default translate()(OperationsList)
export default compose(translate(), connect(mapStateToProps))(OperationsList)

70
src/components/OperationsList/stories.js

@ -1,23 +1,56 @@
// @flow
import React from 'react'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import { getCurrencyByCoinType, getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import { storiesOf } from '@storybook/react'
import { boolean } from '@storybook/addon-knobs'
import { translate } from 'react-i18next'
import OperationsList from 'components/OperationsList'
import { OperationsList } from 'components/OperationsList'
const stories = storiesOf('Components', module)
const unit = getDefaultUnitByCoinType(0)
const counterValue = 'USD'
const counterValues = {
'BTC-USD': {
byDate: {
'2018-01-09': 10000,
},
},
}
const operations = [
{
address: '5c6ea1716520c7d6e038d36a3223faced3c',
hash: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62',
amount: 130000000,
receivedAt: '2018-01-09T16:03:52Z',
confirmations: 1,
account: {
settings: {
minConfirmations: 10,
},
currency: getCurrencyByCoinType(0),
name: 'Account 1',
coinType: 0,
unit,
},
},
{
address: '5c6ea1716520c7d6e038d36a3223faced3c',
hash: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62',
amount: 130000000,
receivedAt: '2018-01-09T16:03:52Z',
confirmations: 11,
account: {
settings: {
minConfirmations: 10,
},
currency: getCurrencyByCoinType(0),
name: 'Account 1',
coinType: 0,
unit,
},
},
@ -26,12 +59,43 @@ const operations = [
hash: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4',
amount: -65000000,
receivedAt: '2018-01-09T16:02:40Z',
confirmations: 11,
account: {
settings: {
minConfirmations: 10,
},
currency: getCurrencyByCoinType(0),
name: 'Account 2',
coinType: 0,
unit,
},
},
{
address: '27416a48caab90fab053b507b8b6b9d4',
hash: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4',
amount: -65000000,
receivedAt: '2018-01-09T16:02:40Z',
confirmations: 1,
account: {
settings: {
minConfirmations: 10,
},
currency: getCurrencyByCoinType(0),
name: 'Account 2',
coinType: 0,
unit,
},
},
]
const OperationsListComp = translate()(OperationsList)
stories.add('OperationsList', () => (
<OperationsList operations={operations} canShowMore={boolean('canShowMore')} />
<OperationsListComp
counterValue={counterValue}
counterValues={counterValues}
operations={operations}
canShowMore={boolean('canShowMore')}
withAccount={boolean('withAccount')}
/>
))

1
src/styles/reset.js

@ -9,6 +9,7 @@ module.exports = `* {
min-width: 0;
/* it will surely make problem in the future... to be inspected. */
/* ;_; */
flex-shrink: 0;
}

2
src/styles/theme.js

@ -75,7 +75,7 @@ export const colors = {
identity: '#41ccb4',
lightGraphite: '#fafafa',
lightGrey: '#f9f9f9',
positiveGreen: '#96d071',
positiveGreen: '#66be54',
smoke: '#666666',
wallet: '#4b84ff',
white: '#ffffff',

4
static/i18n/en/operationsList.yml

@ -2,8 +2,8 @@ date: Date
account: Account
address: Address
amount: Amount
from: From
to: To
from: Receive funds
to: Sent funds
showMore: Show more
confirmed: Confirmed
notConfirmed: Not confirmed

6
yarn.lock

@ -3560,9 +3560,9 @@ dotenv@^5.0.0, dotenv@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef"
downshift@^1.31.1:
version "1.31.1"
resolved "https://registry.yarnpkg.com/downshift/-/downshift-1.31.1.tgz#86760be5d2ceb9f322746458fc085ea0b923805c"
downshift@^1.31.2:
version "1.31.2"
resolved "https://registry.yarnpkg.com/downshift/-/downshift-1.31.2.tgz#6f638e9720d7540d9dffa0b4c4587cf10811cf8c"
duplexer2@~0.1.4:
version "0.1.4"

Loading…
Cancel
Save