ilp-plugin-virtual
Version:
ILP virtual ledger plugin for directly transacting connectors
278 lines (237 loc) • 9.66 kB
JavaScript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { CSSTransitionGroup } from 'react-transition-group'
import {connect} from 'react-redux'
import moment from 'moment'
import TimeAgo from 'react-timeago'
import _ from 'lodash'
import AnimateOnChange from 'react-animate-on-change'
import { destinationChange, amountsChange } from 'redux/actions/send'
import AnimateEnterLeave from 'components/AnimateEnterLeave/AnimateEnterLeave'
import Amount from 'components/Amount/Amount'
import { contextualizePayment } from '../../utils/api'
import { getAccountName } from '../../utils/account'
import classNames from 'classnames/bind'
import styles from './ActivityPayment.scss'
const cx = classNames.bind(styles)
export default class ActivityPayment extends Component {
static propTypes = {
activity: PropTypes.object.isRequired,
config: PropTypes.object,
advancedMode: PropTypes.bool,
destinationChange: PropTypes.func.isRequired,
amountsChange: PropTypes.func.isRequired
}
static defaultProps = {
config: {}
}
state = {
showTransfers: false
}
componentWillMount () {
this.processActivity()
}
componentWillReceiveProps (nextProps) {
if (this.props.activity === nextProps.activity) return
this.processActivity(nextProps, true)
}
componentWillUnmount () {
clearInterval(this.streamInterval)
}
isActiveStream = (props = this.props) => {
// Not a stream
if (!props.activity.stream_id) return
// TODO:UX how about looking at the distance between payments in the stream
// to determine how many seconds we should wait before calling it a night
// instead of waiting 10 hardcoded seconds
return moment(props.activity.updated_at).add(10, 'second').isAfter(new Date())
}
processStream = (props = this.props) => {
// Not a stream
if (!props.activity.stream_id) return
const isActiveStream = this.isActiveStream(props)
// Stream status didn't change
if (this.state.isActiveStream === isActiveStream) return
// do async setState to not mess up other setStates in the process
setTimeout(() => {
this.setState({
...this.state,
isActiveStream
})
}, 10)
// keep the isActiveStream in sync
clearInterval(this.streamInterval)
if (isActiveStream) {
this.streamInterval = setInterval(() => {
this.processStream()
}, 5000)
}
}
processActivity = (props = this.props, update) => {
const firstPayment = props.activity.Payments[0]
const sourceAmount = _.reduce(props.activity.Payments, (sum, payment) => {
return sum + payment.source_amount
}, 0)
const destinationAmount = _.reduce(props.activity.Payments, (sum, payment) => {
return sum + payment.destination_amount
}, 0)
const payment = contextualizePayment({
source_identifier: firstPayment.source_identifier,
destination_identifier: firstPayment.destination_identifier,
source_amount: sourceAmount,
source_name: firstPayment.source_name,
source_image_url: firstPayment.source_image_url,
destination_amount: destinationAmount,
destination_name: firstPayment.destination_name,
destination_image_url: firstPayment.destination_image_url,
message: firstPayment.message,
// TODO:BEFORE_DEPLOY should this be first payment or last payment
recent_date: firstPayment.created_at,
transfers: props.activity.Payments
}, props.user)
// Handle the stream
this.processStream(props)
this.setState({
...this.state,
payment,
type: payment.counterpartyIdentifier === payment.destination_identifier ? 'outgoing' : 'incoming',
updating: update && true
})
update && setTimeout(() => {
this.setState({
...this.state,
updating: false
})
}, 200)
}
toggleTransfers = event => {
// Don't expand when clicking on links
if (event.target.nodeName === 'A') return
this.setState({
...this.state,
showTransfers: !this.state.showTransfers
})
}
handleCounterpartyClick = e => {
e.preventDefault()
this.props.destinationChange(this.state.payment.counterpartyIdentifier)
}
handleAmountClick = e => {
e.preventDefault()
this.props.amountsChange(this.state.type === 'outgoing' ? this.state.payment.source_amount : this.state.payment.destination_amount, null)
}
timeAgoFormatter = (value, unit, suffix) => {
if (unit !== 'second') {
return [value, unit + (value !== 1 ? 's' : ''), suffix].join(' ')
}
if (suffix === 'ago') {
return 'a few seconds ago'
}
if (suffix === 'from now') {
return 'in a few seconds'
}
}
render () {
const config = this.props.config
const { showTransfers, isActiveStream, payment, type } = this.state
const advancedMode = this.props.advancedMode
const profilePic = (type === 'outgoing' ? payment.destination_image_url : payment.source_image_url) || require('../../../static/placeholder.png')
const paymentAmount = type === 'outgoing' ? payment.source_amount : payment.destination_amount
// TODO payments grouping / message
return (
<div className={cx('ActivityPayment')} onClick={this.toggleTransfers} title='Click to expand'>
<div className='row row-mobile'>
<div className='col-xs-8'>
<img src={profilePic} className={cx('profilePic')} />
<div className={cx('description')}>
<div className={cx('counterpartyContainer')}>
{type === 'outgoing' &&
<span>
You paid <a href=''
className={cx('counterparty')} title={payment.counterpartyIdentifier}
onClick={this.handleCounterpartyClick}>
{payment.counterpartyName || getAccountName(payment.counterpartyIdentifier) || 'someone'}
</a>
</span>}
{type === 'incoming' &&
<span>
<a href='' className={cx('counterparty')}
title={payment.counterpartyIdentifier}
onClick={this.handleCounterpartyClick}>
{payment.counterpartyName || getAccountName(payment.counterpartyIdentifier) || 'someone'}
</a> paid you
</span>}
</div>
{advancedMode &&
<span className={cx('counterpartyIdentifier')}>{payment.counterpartyIdentifier}</span>}
{payment.message &&
<div className={cx('message')}>
{payment.message}
</div>}
<div className={cx('date')} title={moment(payment.recent_date).format('LLL')}>
{advancedMode && <span>{moment(payment.recent_date || payment.created_at).format('MMM D, YYYY LTS')} - </span>}
<TimeAgo date={payment.recent_date || payment.created_at} formatter={this.timeAgoFormatter} />
</div>
</div>
</div>
<div className='col-xs-4'>
{isActiveStream && <i className={cx('fa', 'fa-refresh', 'fa-spin', 'activeStream')} />}
{/* TODO Show both source and destination amounts */}
<AnimateOnChange
baseClassName={cx('amount', type)}
animationClassName={cx('updating')}
animate={!!this.state.updating}>
<Amount amount={paymentAmount} currencySymbol={config.currencySymbol} />
</AnimateOnChange>
<div className={cx('transfersCount')}>
{payment.transfers.length > 1 ? payment.transfers.length + ' transfers' : '1 transfer'}
</div>
</div>
</div>
<CSSTransitionGroup
transitionName={{
enter: cx('enter'),
enterActive: cx('enterActive'),
leave: cx('leave'),
leaveActive: cx('leaveActive'),
appear: cx('appear'),
appearActive: cx('appearActive')
}}
transitionAppear
transitionAppearTimeout={300}
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
component='div'
className={cx('row', 'row-mobile', 'transfersContainer')}>
{showTransfers &&
<div className={cx('col-sm-12')}>
<AnimateEnterLeave>
{payment.transfers && payment.transfers.map(transfer => {
return (
<div className={cx('row', 'transfer')} key={transfer.source_identifier + transfer.created_at}>
{advancedMode &&
<div className='col-xs-2'>
<a href={config.ledgerUri + '/transfers/' + transfer.transfer} className={cx('hash')}>{transfer.transfer && transfer.transfer.split('-')[0]}</a>
</div>}
<div className={cx(advancedMode ? 'col-xs-6' : 'col-xs-8', 'date')}>
{moment(transfer.created_at).format('MMM D, YYYY LTS')}
</div>
<div className={cx('col-xs-4', 'amount')}>
<Amount amount={type === 'outgoing' ? transfer.source_amount : transfer.destination_amount} currencySymbol={config.currencySymbol} />
</div>
</div>
)
})}
</AnimateEnterLeave>
</div>}
</CSSTransitionGroup>
</div>
)
}
}