Browse Source

Merge pull request #373 from LN-Zap/feature/new-activity-design

Feature/new activity design
renovate/lint-staged-8.x
JimmyMow 7 years ago
committed by GitHub
parent
commit
7d699bb0e3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .eslintrc
  2. 2
      app/components/Wallet/ReceiveModal.js
  3. 7
      app/components/Wallet/Wallet.scss
  4. 95
      app/reducers/activity.js
  5. 43
      app/routes/activity/components/Activity.js
  6. 14
      app/routes/activity/components/Activity.scss
  7. 23
      app/routes/activity/components/components/Activity.scss
  8. 47
      app/routes/activity/components/components/Invoice/Invoice.js
  9. 87
      app/routes/activity/components/components/Payment/Payment.js
  10. 27
      app/routes/activity/components/components/Transaction/Transaction.js
  11. 3
      app/routes/activity/containers/ActivityContainer.js

3
.eslintrc

@ -46,7 +46,8 @@
"array": false,
"object": true
}],
"prefer-promise-reject-errors": 0
"prefer-promise-reject-errors": 0,
"no-param-reassign": [2, { "props": false }]
},
"plugins": [
"flowtype",

2
app/components/Wallet/ReceiveModal.js

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

7
app/components/Wallet/Wallet.scss

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

95
app/reducers/activity.js

@ -10,8 +10,7 @@ const initialState = {
{ key: 'ALL_ACTIVITY', name: 'All' },
{ key: 'SENT_ACTIVITY', name: 'Sent' },
{ key: 'REQUESTED_ACTIVITY', name: 'Requested' },
{ key: 'PENDING_ACTIVITY', name: 'Pending' },
{ key: 'FUNDED_ACTIVITY', name: 'Funding Transactions' }
{ key: 'PENDING_ACTIVITY', name: 'Pending' }
],
modal: {
modalType: null,
@ -105,13 +104,58 @@ const searchSelector = state => state.activity.searchText
const paymentsSelector = state => state.payment.payments
const invoicesSelector = state => state.invoice.invoices
const transactionsSelector = state => state.transaction.transactions
const channelsSelector = state => state.channels.channels
const invoiceExpired = (invoice) => {
const expiresAt = (parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10))
return expiresAt < (Date.now() / 1000)
}
// helper function that returns invoice, payment or transaction timestamp
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
}
// getMonth() returns the month in 0 index (0 for Jan), so we create an arr of the
// string representation we want for the UI
const months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'June', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
// groups the data by day
function groupData(data) {
return data.reduce((arr, el) => {
const d = new Date(returnTimestamp(el) * 1000)
const date = d.getDate()
const title = `${months[d.getMonth()]} ${date}, ${d.getFullYear()}`
if (!arr[title]) { arr[title] = [] }
arr[title].push({ el })
return arr
}, {})
}
// takes the result of groupData and returns an array
function groupArray(data) {
return Object.keys(data).map(title => ({ title, activity: data[title] }))
}
// sorts data form new to old according to the timestamp
function sortNewToOld(data) {
return data.sort((a, b) => new Date(b.title).getTime() - new Date(a.title).getTime())
}
// take in a dataset and return an array grouped by day
function groupAll(data) {
const groups = groupData(data)
const groupArrays = groupArray(groups)
return sortNewToOld(groupArrays)
}
const allActivity = createSelector(
searchSelector,
paymentsSelector,
@ -128,66 +172,33 @@ const allActivity = createSelector(
return false
})
return searchedArr.sort((a, b) => {
// 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)
if (!searchedArr.length) { return [] }
return bTimestamp - aTimestamp
})
return groupAll(searchedArr)
}
)
const invoiceActivity = createSelector(
invoicesSelector,
invoices => invoices
invoices => groupAll(invoices)
)
const sentActivity = createSelector(
transactionsSelector,
paymentsSelector,
(transactions, payments) => {
const sentTransactions = transactions.filter(transaction => transaction.amount < 0)
return [...sentTransactions, ...payments].sort((a, b) => {
const aTimestamp = Object.prototype.hasOwnProperty.call(a, 'time_stamp') ? a.time_stamp : a.creation_date
const bTimestamp = Object.prototype.hasOwnProperty.call(b, 'time_stamp') ? b.time_stamp : b.creation_date
return bTimestamp - aTimestamp
})
}
(transactions, payments) => groupAll([...transactions.filter(transaction => transaction.amount < 0), ...payments])
)
const pendingActivity = createSelector(
invoicesSelector,
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)
}
invoices => groupAll(invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice)))
)
const FILTERS = {
ALL_ACTIVITY: allActivity,
SENT_ACTIVITY: sentActivity,
REQUESTED_ACTIVITY: invoiceActivity,
PENDING_ACTIVITY: pendingActivity,
FUNDED_ACTIVITY: fundedActivity
PENDING_ACTIVITY: pendingActivity
}
activitySelectors.currentActivity = createSelector(

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

@ -27,17 +27,40 @@ class Activity extends Component {
}
renderActivity(activity) {
const { ticker, currentTicker, showActivityModal } = this.props
const {
ticker,
currentTicker,
showActivityModal,
network,
currencyName
} = this.props
if (Object.prototype.hasOwnProperty.call(activity, 'block_hash')) {
// 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')) {
// activity is an LN invoice
return <Invoice invoice={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} />
}
// 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() {
@ -78,9 +101,14 @@ class Activity extends Component {
</header>
<ul className={`${styles.activityContainer} ${filterPulldown && styles.pulldown}`}>
{
currentActivity.map((activity, index) => (
currentActivity.map((activityBlock, index) => (
<li className={styles.activity} key={index}>
{this.renderActivity(activity)}
<h2>{activityBlock.title}</h2>
<ul>
{
activityBlock.activity.map((activity, i) => <li key={i}>{this.renderActivity(activity.el)}</li>)
}
</ul>
</li>
))
}
@ -99,6 +127,7 @@ Activity.propTypes = {
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
network: PropTypes.object.isRequired,
showActivityModal: PropTypes.func.isRequired,
changeFilter: PropTypes.func.isRequired,
@ -106,7 +135,9 @@ Activity.propTypes = {
activity: PropTypes.object.isRequired,
currentActivity: PropTypes.array.isRequired,
balance: PropTypes.object.isRequired,
walletProps: PropTypes.object.isRequired
walletProps: PropTypes.object.isRequired,
currencyName: PropTypes.string.isRequired
}
export default Activity

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

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

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

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

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

@ -2,46 +2,33 @@ import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import { btc } from 'utils'
import Isvg from 'react-inlinesvg'
import { FaBolt } from 'react-icons/lib/fa'
import Value from 'components/Value'
import checkmarkIcon from 'icons/check_circle.svg'
import clockIcon from 'icons/clock.svg'
import styles from '../Activity.scss'
const Invoice = ({
invoice, ticker, currentTicker, showActivityModal
}) => (
<div className={`${styles.container} ${!invoice.settled && styles.unpaid}`} onClick={() => showActivityModal('INVOICE', { invoice })}>
<div className={styles.date}>
<section>
{
invoice.settled ?
<Isvg src={checkmarkIcon} />
:
<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>
{
!invoice.settled && (
<div className={styles.pendingIcon}>
<Isvg src={checkmarkIcon} />
</div>
)
}
<div className={styles.data}>
<div className={styles.title}>
<i className={`${styles.icon} hint--top`} data-hint='Lightning Network request'>
<FaBolt />
</i>
<h3>
{ invoice.settled ? 'Received' : 'Requested' }
{ invoice.settled ? 'Received payment' : 'Requested payment' }
</h3>
<span>
{ticker.currency}
</span>
</div>
<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 className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}>
@ -53,12 +40,10 @@ const Invoice = ({
currentTicker={currentTicker}
/>
</span>
<span className='hint--bottom' data-hint='Invoice fee'>
<Value
value={invoice.fee}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<span>
<span>
${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)}
</span>
</span>
</div>
</div>

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

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

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

@ -2,38 +2,23 @@ import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import Isvg from 'react-inlinesvg'
import { FaChain } from 'react-icons/lib/fa'
import { btc } from 'utils'
import Value from 'components/Value'
import checkmarkIcon from 'icons/check_circle.svg'
import styles from '../Activity.scss'
const Transaction = ({
transaction, ticker, currentTicker, showActivityModal
}) => (
<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.title}>
<i className={`${styles.icon} hint--top`} data-hint='On-chain transaction'>
<FaChain />
</i>
<h3>
{ transaction.amount > 0 ? 'Received' : 'Sent' }
</h3>
<span>
{ticker.currency}
</span>
</div>
<div className={styles.subtitle}>
{transaction.tx_hash}
<Moment format='h:mm a'>{transaction.time_stamp * 1000}</Moment>
</div>
</div>
<div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}>
@ -46,11 +31,7 @@ const Transaction = ({
/>
</span>
<span className='hint--bottom' data-hint='Transaction fee'>
<Value
value={transaction.total_fees}
currency={ticker.currency}
currentTicker={currentTicker}
/>
${btc.convert('sats', 'usd', transaction.amount, currentTicker.price_usd)}
</span>
</div>
</div>

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

@ -63,11 +63,14 @@ const mapStateToProps = state => ({
ticker: state.ticker,
network: state.network,
paymentModalOpen: paymentSelectors.paymentModalOpen(state),
invoiceModalOpen: invoiceSelectors.invoiceModalOpen(state),
currentTicker: tickerSelectors.currentTicker(state),
currentCurrencyFilters: tickerSelectors.currentCurrencyFilters(state),
currencyName: tickerSelectors.currencyName(state),
currentActivity: activitySelectors.currentActivity(state)(state),

Loading…
Cancel
Save