You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

322 lines
10 KiB

// @flow
import React, { Fragment, Component } from 'react'
import { connect } from 'react-redux'
import { openURL } from 'helpers/linking'
import { translate } from 'react-i18next'
import styled from 'styled-components'
import moment from 'moment'
import { getOperationAmountNumber } from '@ledgerhq/live-common/lib/helpers/operation'
import { getAccountOperationExplorer } from '@ledgerhq/live-common/lib/explorers'
import uniq from 'lodash/uniq'
import TrackPage from 'analytics/TrackPage'
import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import type { T, CurrencySettings } from 'types/common'
import { MODAL_OPERATION_DETAILS } from 'config/constants'
import { getMarketColor } from 'styles/helpers'
import Box from 'components/base/Box'
import GradientBox from 'components/GradientBox'
import GrowScroll from 'components/base/GrowScroll'
import Button from 'components/base/Button'
import Bar from 'components/base/Bar'
import FormattedVal from 'components/base/FormattedVal'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal'
import Text from 'components/base/Text'
import CopyWithFeedback from 'components/base/CopyWithFeedback'
import { createStructuredSelector, createSelector } from 'reselect'
import { accountSelector } from 'reducers/accounts'
import { currencySettingsForAccountSelector, marketIndicatorSelector } from 'reducers/settings'
import IconChevronRight from 'icons/ChevronRight'
import CounterValue from 'components/CounterValue'
import ConfirmationCheck from 'components/OperationsList/ConfirmationCheck'
import Ellipsis from '../base/Ellipsis'
const OpDetailsTitle = styled(Box).attrs({
ff: 'Museo Sans|ExtraBold',
fontSize: 2,
color: 'black',
textTransform: 'uppercase',
mb: 1,
})`
letter-spacing: 2px;
`
export const GradientHover = styled(Box).attrs({
align: 'center',
color: 'wallet',
})`
background: white;
position: absolute;
top: 0;
right: 0;
bottom: 0;
padding-left: 20px;
background: linear-gradient(to right, rgba(255, 255, 255, 0), #ffffff 20%);
`
const OpDetailsData = styled(Box).attrs({
ff: 'Open Sans',
color: 'smoke',
fontSize: 4,
relative: true,
})`
${GradientHover} {
display: none;
}
&:hover ${GradientHover} {
display: flex;
}
`
const B = styled(Bar).attrs({
color: 'lightGrey',
size: 1,
})``
const operationSelector = createSelector(
accountSelector,
(_, { operationId }) => operationId,
(account: Account, operationId: string): ?Operation => {
if (!account) return null
const maybeOp = account.operations.find(op => op.id === operationId)
if (maybeOp) return maybeOp
const maybeOpPending = account.pendingOperations.find(op => op.id === operationId)
return maybeOpPending
},
)
const mapStateToProps = createStructuredSelector({
marketIndicator: marketIndicatorSelector,
account: accountSelector,
operation: operationSelector,
currencySettings: createSelector(
state => state,
accountSelector,
(state, account) => (account ? currencySettingsForAccountSelector(state, { account }) : null),
),
})
type Props = {
t: T,
operation: ?Operation,
account: ?Account,
currencySettings: ?CurrencySettings,
onClose: () => void,
marketIndicator: *,
}
const OperationDetails = connect(mapStateToProps)((props: Props) => {
const { t, onClose, operation, account, currencySettings, marketIndicator } = props
if (!operation || !account || !currencySettings) return null
const { hash, date, senders, type, fee, recipients } = operation
const { name, unit, currency } = account
const amount = getOperationAmountNumber(operation)
const isNegative = operation.type === 'OUT'
const marketColor = getMarketColor({
marketIndicator,
isNegative,
})
const confirmations = operation.blockHeight ? account.blockHeight - operation.blockHeight : 0
const isConfirmed = confirmations >= currencySettings.confirmationsNb
const url = getAccountOperationExplorer(account, operation)
const uniqueSenders = uniq(senders)
return (
<ModalBody onClose={onClose}>
<TrackPage category="Modal" name="OperationDetails" />
<ModalTitle>{t('app:operationDetails.title')}</ModalTitle>
<ModalContent relative style={{ height: 500 }} px={0} pb={0}>
<GrowScroll px={5} pt={1} pb={8}>
<Box flow={3}>
<Box alignItems="center" mt={1}>
<ConfirmationCheck
marketColor={marketColor}
isConfirmed={isConfirmed}
style={{
transform: 'scale(1.5)',
}}
t={t}
type={type}
withTooltip={false}
/>
<Box my={4} alignItems="center">
<Box>
<FormattedVal
color={amount < 0 ? 'smoke' : undefined}
unit={unit}
alwaysShowSign
showCode
val={amount}
fontSize={7}
disableRounding
/>
</Box>
<Box mt={1}>
<CounterValue
color="grey"
fontSize={5}
date={date}
currency={currency}
value={amount}
/>
</Box>
</Box>
</Box>
<Box horizontal flow={2}>
<Box flex={1}>
<OpDetailsTitle>{t('app:operationDetails.account')}</OpDetailsTitle>
<OpDetailsData>{name}</OpDetailsData>
</Box>
<Box flex={1}>
<OpDetailsTitle>{t('app:operationDetails.date')}</OpDetailsTitle>
<OpDetailsData>{moment(date).format('LLL')}</OpDetailsData>
</Box>
</Box>
<B />
<Box horizontal flow={2}>
<Box flex={1}>
<OpDetailsTitle>{t('app:operationDetails.fees')}</OpDetailsTitle>
{fee ? (
<Fragment>
<OpDetailsData>
<FormattedVal unit={unit} showCode val={fee} color="smoke" />
</OpDetailsData>
</Fragment>
) : (
<OpDetailsData>{t('app:operationDetails.noFees')}</OpDetailsData>
)}
</Box>
<Box flex={1}>
<OpDetailsTitle>{t('app:operationDetails.status')}</OpDetailsTitle>
<OpDetailsData color={isConfirmed ? 'positiveGreen' : null} horizontal flow={1}>
<Box>
{isConfirmed
? t('app:operationDetails.confirmed')
: t('app:operationDetails.notConfirmed')}
</Box>
<Box>{`(${confirmations})`}</Box>
</OpDetailsData>
</Box>
</Box>
<B />
<Box>
<OpDetailsTitle>{t('app:operationDetails.identifier')}</OpDetailsTitle>
<OpDetailsData>
<Ellipsis canSelect>{hash}</Ellipsis>
<GradientHover>
<CopyWithFeedback text={hash} />
</GradientHover>
</OpDetailsData>
</Box>
<B />
<Box>
<OpDetailsTitle>{t('app:operationDetails.from')}</OpDetailsTitle>
<DataList lines={uniqueSenders} t={t} />
</Box>
<B />
<Box>
<OpDetailsTitle>{t('app:operationDetails.to')}</OpDetailsTitle>
<DataList lines={recipients} t={t} />
</Box>
</Box>
</GrowScroll>
<GradientBox />
</ModalContent>
{url && (
<ModalFooter horizontal justify="flex-end" flow={2}>
<Button primary onClick={() => openURL(url)}>
{t('app:operationDetails.viewOperation')}
</Button>
</ModalFooter>
)}
</ModalBody>
)
})
type ModalRenderProps = {
data: {
account: string,
operation: string,
},
onClose: Function,
}
const OperationDetailsWrapper = ({ t }: { t: T }) => (
<Modal
name={MODAL_OPERATION_DETAILS}
render={(props: ModalRenderProps) => {
const { data, onClose } = props
return <OperationDetails t={t} {...data} onClose={onClose} />
}}
/>
)
export default translate()(OperationDetailsWrapper)
const More = styled(Text).attrs({
ff: p => (p.ff ? p.ff : 'Museo Sans|Bold'),
fontSize: p => (p.fontSize ? p.fontSize : 2),
color: p => (p.color ? p.color : 'dark'),
tabIndex: 0,
})`
text-transform: ${p => (!p.textTransform ? 'auto' : 'uppercase')};
cursor: pointer;
outline: none;
`
export class DataList extends Component<{ lines: string[], t: T }, *> {
state = {
showMore: false,
}
onClick = () => {
this.setState(({ showMore }) => ({ showMore: !showMore }))
}
render() {
const { lines, t } = this.props
const { showMore } = this.state
// Hardcoded for now
const numToShow = 2
const shouldShowMore = lines.length > 3
return (
<Box>
{(shouldShowMore ? lines.slice(0, numToShow) : lines).map(line => (
<OpDetailsData key={line}>
{line}
<GradientHover>
<CopyWithFeedback text={line} />
</GradientHover>
</OpDetailsData>
))}
{shouldShowMore &&
!showMore && (
<Box onClick={this.onClick} py={1}>
<More fontSize={4} color="wallet" ff="Open Sans|SemiBold" mt={1}>
<IconChevronRight size={12} style={{ marginRight: 5 }} />
{t('app:operationDetails.showMore', { recipients: lines.length - numToShow })}
</More>
</Box>
)}
{showMore &&
lines.slice(numToShow).map(line => <OpDetailsData key={line}>{line}</OpDetailsData>)}
{shouldShowMore &&
showMore && (
<Box onClick={this.onClick} py={1}>
<More fontSize={4} color="wallet" ff="Open Sans|SemiBold" mt={1}>
<IconChevronRight size={12} style={{ marginRight: 5 }} />
{t('app:operationDetails.showLess')}
</More>
</Box>
)}
</Box>
)
}
}