Browse Source

fix(activity): grouped data by day and redesign payment, invoice and transaction components

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
e85a9b2dd1
  1. 2
      app/components/Wallet/ReceiveModal.js
  2. 7
      app/components/Wallet/Wallet.scss
  3. 72
      app/reducers/activity.js
  4. 41
      app/routes/activity/components/Activity.js
  5. 14
      app/routes/activity/components/Activity.scss
  6. 23
      app/routes/activity/components/components/Activity.scss
  7. 45
      app/routes/activity/components/components/Invoice/Invoice.js
  8. 84
      app/routes/activity/components/components/Payment/Payment.js
  9. 24
      app/routes/activity/components/components/Transaction/Transaction.js
  10. 3
      app/routes/activity/containers/ActivityContainer.js

2
app/components/Wallet/ReceiveModal.js

@ -106,7 +106,7 @@ ReceiveModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
pubkey: PropTypes.string, pubkey: PropTypes.string,
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
alias: PropTypes.string.isRequired, alias: PropTypes.string,
closeReceiveModal: PropTypes.func.isRequired closeReceiveModal: PropTypes.func.isRequired
} }

7
app/components/Wallet/Wallet.scss

@ -125,16 +125,17 @@
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
color: $white; color: $white;
background: $spaceblue; background: #31343F;
padding: 15px; padding: 10px;
width: 100px; width: 100px;
text-align: center; text-align: center;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
opacity: 0.5;
transition: all 0.25s; transition: all 0.25s;
&:hover { &:hover {
opacity: 0.5; opacity: 1;
} }
&:nth-child(1) { &:nth-child(1) {

72
app/reducers/activity.js

@ -10,8 +10,7 @@ const initialState = {
{ key: 'ALL_ACTIVITY', name: 'All' }, { key: 'ALL_ACTIVITY', name: 'All' },
{ key: 'SENT_ACTIVITY', name: 'Sent' }, { key: 'SENT_ACTIVITY', name: 'Sent' },
{ key: 'REQUESTED_ACTIVITY', name: 'Requested' }, { key: 'REQUESTED_ACTIVITY', name: 'Requested' },
{ key: 'PENDING_ACTIVITY', name: 'Pending' }, { key: 'PENDING_ACTIVITY', name: 'Pending' }
{ key: 'FUNDED_ACTIVITY', name: 'Funding Transactions' }
], ],
modal: { modal: {
modalType: null, modalType: null,
@ -128,8 +127,30 @@ const allActivity = createSelector(
return false return false
}) })
return searchedArr.sort((a, b) => { // return searchedArr.sort((a, b) => {
// this will return the correct timestamp to use when sorting (time_stamp, creation_date, or settle_date) // // this will return the correct timestamp to use when sorting (time_stamp, creation_date, or settle_date)
// function returnTimestamp(transaction) {
// // if on-chain txn
// if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) { return transaction.time_stamp }
// // if invoice that has been paid
// if (transaction.settled) { return transaction.settle_date }
// // if invoice that has not been paid or an LN payment
// return transaction.creation_date
// }
// const aTimestamp = returnTimestamp(a)
// const bTimestamp = returnTimestamp(b)
// // console.log('aTimestamp: ', aTimestamp)
// // console.log('bTimestamp: ', bTimestamp)
// console.log('date: ', new Date(aTimestamp * 1000))
// return bTimestamp - aTimestamp
// })
if (!searchedArr.length) { return [] }
const groups = searchedArr.reduce((groups, el) => {
function returnTimestamp(transaction) { function returnTimestamp(transaction) {
// if on-chain txn // if on-chain txn
if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) { return transaction.time_stamp } if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) { return transaction.time_stamp }
@ -139,11 +160,34 @@ const allActivity = createSelector(
return transaction.creation_date return transaction.creation_date
} }
const aTimestamp = returnTimestamp(a) const months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'June', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
const bTimestamp = returnTimestamp(b)
return bTimestamp - aTimestamp const d = new Date(returnTimestamp(el) * 1000)
const date = d.getDate()
const title = `${months[d.getMonth()]} ${date}, ${d.getFullYear()}`
if (!groups[title]) {
groups[title] = []
}
groups[title].push({
el
})
return groups
}, {})
const groupArrays = Object.keys(groups).map((title) => {
return {
title,
activity: groups[title]
}
}) })
console.log('groupArrays: ', groupArrays)
return groupArrays.sort((a, b) => new Date(b.title).getTime() - new Date(a.title).getTime())
} }
) )
@ -171,23 +215,11 @@ const pendingActivity = createSelector(
invoices => invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice)) invoices => invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice))
) )
const fundedActivity = createSelector(
transactionsSelector,
channelsSelector,
(transactions, channels) => {
const fundingTxIds = channels.map(channel => channel.channel_point.split(':')[0])
const fundingTxs = transactions.filter(transaction => fundingTxIds.includes(transaction.tx_hash))
return fundingTxs.sort((a, b) => b.time_stamp - a.time_stamp)
}
)
const FILTERS = { const FILTERS = {
ALL_ACTIVITY: allActivity, ALL_ACTIVITY: allActivity,
SENT_ACTIVITY: sentActivity, SENT_ACTIVITY: sentActivity,
REQUESTED_ACTIVITY: invoiceActivity, REQUESTED_ACTIVITY: invoiceActivity,
PENDING_ACTIVITY: pendingActivity, PENDING_ACTIVITY: pendingActivity
FUNDED_ACTIVITY: fundedActivity
} }
activitySelectors.currentActivity = createSelector( activitySelectors.currentActivity = createSelector(

41
app/routes/activity/components/Activity.js

@ -27,17 +27,40 @@ class Activity extends Component {
} }
renderActivity(activity) { renderActivity(activity) {
const { ticker, currentTicker, showActivityModal } = this.props const {
ticker,
currentTicker,
showActivityModal,
network,
currencyName
} = this.props
if (Object.prototype.hasOwnProperty.call(activity, 'block_hash')) { if (Object.prototype.hasOwnProperty.call(activity, 'block_hash')) {
// activity is an on-chain tx // activity is an on-chain tx
return <Transaction transaction={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} /> return (
<Transaction
transaction={activity}
ticker={ticker}
currentTicker={currentTicker}
showActivityModal={showActivityModal}
currencyName={currencyName}
/>
)
} else if (Object.prototype.hasOwnProperty.call(activity, 'payment_request')) { } else if (Object.prototype.hasOwnProperty.call(activity, 'payment_request')) {
// activity is an LN invoice // activity is an LN invoice
return <Invoice invoice={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} /> return <Invoice invoice={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} />
} }
// activity is an LN payment // activity is an LN payment
return <Payment payment={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} /> return (
<Payment
payment={activity}
ticker={ticker}
currentTicker={currentTicker}
showActivityModal={showActivityModal}
nodes={network.nodes}
currencyName={currencyName}
/>
)
} }
render() { render() {
@ -51,9 +74,12 @@ class Activity extends Component {
changeFilter, changeFilter,
currentActivity, currentActivity,
network,
walletProps walletProps
} = this.props } = this.props
console.log('network: ', network)
if (!balance.channelBalance || !balance.walletBalance) { return <LoadingBolt /> } if (!balance.channelBalance || !balance.walletBalance) { return <LoadingBolt /> }
return ( return (
@ -78,9 +104,14 @@ class Activity extends Component {
</header> </header>
<ul className={`${styles.activityContainer} ${filterPulldown && styles.pulldown}`}> <ul className={`${styles.activityContainer} ${filterPulldown && styles.pulldown}`}>
{ {
currentActivity.map((activity, index) => ( currentActivity.map((activityBlock, index) => (
<li className={styles.activity} key={index}> <li className={styles.activity} key={index}>
{this.renderActivity(activity)} <h2>{activityBlock.title}</h2>
<ul>
{
activityBlock.activity.map(activity => <li>{this.renderActivity(activity.el)}</li>)
}
</ul>
</li> </li>
)) ))
} }

14
app/routes/activity/components/Activity.scss

@ -96,10 +96,16 @@
} }
.activity { .activity {
padding: 0 40px; position: relative;
padding: 0 60px;
&:hover { margin-bottom: 30px;
h2 {
color: $white;
font-size: 10px;
font-weight: bold;
border-bottom: 0.2px solid #A0A0A0;
padding: 10px 0;
} }
.left, .center, .right { .left, .center, .right {

23
app/routes/activity/components/components/Activity.scss

@ -15,6 +15,17 @@
&.unpaid { &.unpaid {
opacity: 0.5; opacity: 0.5;
} }
.pendingIcon {
position: absolute;
left: -5%;
top: 30%;
svg {
width: 10.5px;
height: 10.5px;
}
}
} }
.clock { .clock {
@ -65,7 +76,14 @@
} }
.title { .title {
margin-bottom: 5px; margin-bottom: 10px;
font-size: 14px;
font-family: Roboto;
}
.subtitle {
opacity: 0.5;
font-size: 10px;
} }
.icon, h3, span { .icon, h3, span {
@ -118,14 +136,13 @@
.amount { .amount {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1;
text-align: right; text-align: right;
font-size: 12px; font-size: 12px;
color: $white; color: $white;
span { span {
&:nth-child(1) { &:nth-child(1) {
margin-bottom: 5px; margin-bottom: 10px;
} }
&:nth-child(2) { &:nth-child(2) {

45
app/routes/activity/components/components/Invoice/Invoice.js

@ -2,6 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Moment from 'react-moment' import Moment from 'react-moment'
import 'moment-timezone' import 'moment-timezone'
import { btc } from 'utils'
import Isvg from 'react-inlinesvg' import Isvg from 'react-inlinesvg'
import { FaBolt } from 'react-icons/lib/fa' import { FaBolt } from 'react-icons/lib/fa'
import Value from 'components/Value' import Value from 'components/Value'
@ -13,35 +15,22 @@ const Invoice = ({
invoice, ticker, currentTicker, showActivityModal invoice, ticker, currentTicker, showActivityModal
}) => ( }) => (
<div className={`${styles.container} ${!invoice.settled && styles.unpaid}`} onClick={() => showActivityModal('INVOICE', { invoice })}> <div className={`${styles.container} ${!invoice.settled && styles.unpaid}`} onClick={() => showActivityModal('INVOICE', { invoice })}>
<div className={styles.date}> {
<section> !invoice.settled && (
{ <div className={styles.pendingIcon}>
invoice.settled ? <Isvg src={checkmarkIcon} />
<Isvg src={checkmarkIcon} /> </div>
: )
<i className='hint--top' data-hint='Request has not been paid'> }
<Isvg src={clockIcon} />
</i>
}
</section>
<section>
<Moment format='MMM'>{invoice.creation_date * 1000}</Moment> <Moment format='D'>{invoice.creation_date * 1000}</Moment>
</section>
</div>
<div className={styles.data}> <div className={styles.data}>
<div className={styles.title}> <div className={styles.title}>
<i className={`${styles.icon} hint--top`} data-hint='Lightning Network request'>
<FaBolt />
</i>
<h3> <h3>
{ invoice.settled ? 'Received' : 'Requested' } { invoice.settled ? 'Received payment' : 'Requested payment' }
</h3> </h3>
<span>
{ticker.currency}
</span>
</div> </div>
<div className={styles.subtitle}> <div className={styles.subtitle}>
{invoice.r_hash.toString('hex')} <Moment format='h:mm a'>{invoice.settled ? invoice.settled_date * 1000 : invoice.creation_date * 1000}</Moment>
</div> </div>
</div> </div>
<div className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}> <div className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}>
@ -53,12 +42,10 @@ const Invoice = ({
currentTicker={currentTicker} currentTicker={currentTicker}
/> />
</span> </span>
<span className='hint--bottom' data-hint='Invoice fee'> <span>
<Value <span>
value={invoice.fee} ${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)}
currency={ticker.currency} </span>
currentTicker={currentTicker}
/>
</span> </span>
</div> </div>
</div> </div>

84
app/routes/activity/components/components/Payment/Payment.js

@ -1,7 +1,10 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import find from 'lodash/find'
import Moment from 'react-moment' import Moment from 'react-moment'
import 'moment-timezone' import 'moment-timezone'
import { btc } from 'utils'
import Isvg from 'react-inlinesvg' import Isvg from 'react-inlinesvg'
import { FaBolt } from 'react-icons/lib/fa' import { FaBolt } from 'react-icons/lib/fa'
import Value from 'components/Value' import Value from 'components/Value'
@ -9,52 +12,47 @@ import checkmarkIcon from 'icons/check_circle.svg'
import styles from '../Activity.scss' import styles from '../Activity.scss'
const Payment = ({ const Payment = ({
payment, ticker, currentTicker, showActivityModal payment, ticker, currentTicker, showActivityModal, nodes, currencyName
}) => ( }) => {
<div className={styles.container} onClick={() => showActivityModal('PAYMENT', { payment })}> console.log('nodes: ', nodes)
<div className={styles.date}>
<section> const displayNodeName = (pubkey) => {
<Isvg src={checkmarkIcon} /> const node = find(nodes, n => pubkey === n.pub_key)
</section>
<section> if (node && node.alias.length) { return node.alias }
<Moment format='MMM'>{payment.creation_date * 1000}</Moment> <Moment format='D'>{payment.creation_date * 1000}</Moment>
</section> return pubkey.substring(0, 10)
</div> }
<div className={styles.data}>
<div className={styles.title}> return (
<i className={`${styles.icon} hint--top`} data-hint='Lightning Network payment'> <div className={styles.container} onClick={() => showActivityModal('PAYMENT', { payment })}>
<FaBolt /> <div className={styles.data}>
</i> <div className={styles.title}>
<h3> <h3>
Sent {displayNodeName(payment.path[payment.path.length - 1])}
</h3> </h3>
<span> </div>
{ticker.currency} <div className={styles.subtitle}>
</span> <Moment format='h:mm a'>{payment.creation_date * 1000}</Moment>
</div>
</div> </div>
<div className={styles.subtitle}> <div className={styles.amount}>
{payment.payment_hash.toString('hex')} <span className='hint--top' data-hint='Payment amount'>
<i className={styles.minus}>-</i>
<Value
value={payment.value}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<i> {currencyName}</i>
</span>
<span className='hint--bottom' data-hint='Payment fee'>
${btc.convert('sats', 'usd', payment.value, currentTicker.price_usd)}
</span>
</div> </div>
</div> </div>
<div className={styles.amount}> )
<span className='hint--top' data-hint='Payment amount'> }
<i className={styles.minus}>-</i>
<Value
value={payment.value}
currency={ticker.currency}
currentTicker={currentTicker}
/>
</span>
<span className='hint--bottom' data-hint='Payment fee'>
<Value
value={payment.fee}
currency={ticker.currency}
currentTicker={currentTicker}
/>
</span>
</div>
</div>
)
Payment.propTypes = { Payment.propTypes = {
payment: PropTypes.object.isRequired, payment: PropTypes.object.isRequired,

24
app/routes/activity/components/components/Transaction/Transaction.js

@ -2,6 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Moment from 'react-moment' import Moment from 'react-moment'
import 'moment-timezone' import 'moment-timezone'
import { btc } from 'utils'
import Isvg from 'react-inlinesvg' import Isvg from 'react-inlinesvg'
import { FaChain } from 'react-icons/lib/fa' import { FaChain } from 'react-icons/lib/fa'
import Value from 'components/Value' import Value from 'components/Value'
@ -12,28 +14,14 @@ const Transaction = ({
transaction, ticker, currentTicker, showActivityModal transaction, ticker, currentTicker, showActivityModal
}) => ( }) => (
<div className={styles.container} onClick={() => showActivityModal('TRANSACTION', { transaction })}> <div className={styles.container} onClick={() => showActivityModal('TRANSACTION', { transaction })}>
<div className={styles.date}>
<section>
<Isvg src={checkmarkIcon} />
</section>
<section>
<Moment format='MMM'>{transaction.time_stamp * 1000}</Moment> <Moment format='D'>{transaction.time_stamp * 1000}</Moment>
</section>
</div>
<div className={styles.data}> <div className={styles.data}>
<div className={styles.title}> <div className={styles.title}>
<i className={`${styles.icon} hint--top`} data-hint='On-chain transaction'>
<FaChain />
</i>
<h3> <h3>
{ transaction.amount > 0 ? 'Received' : 'Sent' } { transaction.amount > 0 ? 'Received' : 'Sent' }
</h3> </h3>
<span>
{ticker.currency}
</span>
</div> </div>
<div className={styles.subtitle}> <div className={styles.subtitle}>
{transaction.tx_hash} <Moment format='h:mm a'>{transaction.time_stamp * 1000}</Moment>
</div> </div>
</div> </div>
<div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}> <div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}>
@ -46,11 +34,7 @@ const Transaction = ({
/> />
</span> </span>
<span className='hint--bottom' data-hint='Transaction fee'> <span className='hint--bottom' data-hint='Transaction fee'>
<Value ${btc.convert('sats', 'usd', transaction.amount, currentTicker.price_usd)}
value={transaction.total_fees}
currency={ticker.currency}
currentTicker={currentTicker}
/>
</span> </span>
</div> </div>
</div> </div>

3
app/routes/activity/containers/ActivityContainer.js

@ -60,10 +60,13 @@ const mapStateToProps = state => ({
ticker: state.ticker, ticker: state.ticker,
network: state.network,
paymentModalOpen: paymentSelectors.paymentModalOpen(state), paymentModalOpen: paymentSelectors.paymentModalOpen(state),
invoiceModalOpen: invoiceSelectors.invoiceModalOpen(state), invoiceModalOpen: invoiceSelectors.invoiceModalOpen(state),
currentTicker: tickerSelectors.currentTicker(state), currentTicker: tickerSelectors.currentTicker(state),
currencyName: tickerSelectors.currencyName(state),
currentActivity: activitySelectors.currentActivity(state)(state), currentActivity: activitySelectors.currentActivity(state)(state),
nonActiveFilters: activitySelectors.nonActiveFilters(state), nonActiveFilters: activitySelectors.nonActiveFilters(state),

Loading…
Cancel
Save