diff --git a/.eslintrc b/.eslintrc index 69ccec87..5ae6e558 100644 --- a/.eslintrc +++ b/.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", diff --git a/app/components/Wallet/ReceiveModal.js b/app/components/Wallet/ReceiveModal.js index 2d94bf27..e1145421 100644 --- a/app/components/Wallet/ReceiveModal.js +++ b/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 } diff --git a/app/components/Wallet/Wallet.scss b/app/components/Wallet/Wallet.scss index 7b9b8ecd..86a84bfd 100644 --- a/app/components/Wallet/Wallet.scss +++ b/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) { diff --git a/app/reducers/activity.js b/app/reducers/activity.js index c71cc19d..2c6d402d 100644 --- a/app/reducers/activity.js +++ b/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( diff --git a/app/routes/activity/components/Activity.js b/app/routes/activity/components/Activity.js index 7ea8b984..c9b964c4 100644 --- a/app/routes/activity/components/Activity.js +++ b/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 + return ( + + ) } else if (Object.prototype.hasOwnProperty.call(activity, 'payment_request')) { // activity is an LN invoice return } // activity is an LN payment - return + return ( + + ) } render() { @@ -78,9 +101,14 @@ class Activity extends Component {
    { - currentActivity.map((activity, index) => ( + currentActivity.map((activityBlock, index) => (
  • - {this.renderActivity(activity)} +

    {activityBlock.title}

    +
      + { + activityBlock.activity.map((activity, i) =>
    • {this.renderActivity(activity.el)}
    • ) + } +
  • )) } @@ -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 diff --git a/app/routes/activity/components/Activity.scss b/app/routes/activity/components/Activity.scss index c3f6fc63..f191bf3d 100644 --- a/app/routes/activity/components/Activity.scss +++ b/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 { diff --git a/app/routes/activity/components/components/Activity.scss b/app/routes/activity/components/components/Activity.scss index fd9b84c1..47a4e0f7 100644 --- a/app/routes/activity/components/components/Activity.scss +++ b/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) { diff --git a/app/routes/activity/components/components/Invoice/Invoice.js b/app/routes/activity/components/components/Invoice/Invoice.js index c4da0efe..7b7642e5 100644 --- a/app/routes/activity/components/components/Invoice/Invoice.js +++ b/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 }) => (
    showActivityModal('INVOICE', { invoice })}> -
    -
    - { - invoice.settled ? - - : - - - - } -
    -
    - {invoice.creation_date * 1000} {invoice.creation_date * 1000} -
    -
    + { + !invoice.settled && ( +
    + +
    + ) + } +
    - - -

    - { invoice.settled ? 'Received' : 'Requested' } + { invoice.settled ? 'Received payment' : 'Requested payment' }

    - - {ticker.currency} -
    - {invoice.r_hash.toString('hex')} + {invoice.settled ? invoice.settled_date * 1000 : invoice.creation_date * 1000}
    @@ -53,12 +40,10 @@ const Invoice = ({ currentTicker={currentTicker} /> - - + + + ${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)} +
    diff --git a/app/routes/activity/components/components/Payment/Payment.js b/app/routes/activity/components/components/Payment/Payment.js index 97f866b0..a21136af 100644 --- a/app/routes/activity/components/components/Payment/Payment.js +++ b/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 -}) => ( -
    showActivityModal('PAYMENT', { payment })}> -
    -
    - -
    -
    - {payment.creation_date * 1000} {payment.creation_date * 1000} -
    -
    -
    -
    - - - -

    - Sent -

    - - {ticker.currency} - + 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 ( +
    showActivityModal('PAYMENT', { payment })}> +
    +
    +

    + {displayNodeName(payment.path[payment.path.length - 1])} +

    +
    +
    + {payment.creation_date * 1000} +
    -
    - {payment.payment_hash.toString('hex')} +
    + + - + + {currencyName} + + + ${btc.convert('sats', 'usd', payment.value, currentTicker.price_usd)} +
    -
    - - - - - - - - -
    -
    -) + ) +} 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 } diff --git a/app/routes/activity/components/components/Transaction/Transaction.js b/app/routes/activity/components/components/Transaction/Transaction.js index e336a877..dcdad169 100644 --- a/app/routes/activity/components/components/Transaction/Transaction.js +++ b/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 }) => (
    showActivityModal('TRANSACTION', { transaction })}> -
    -
    - -
    -
    - {transaction.time_stamp * 1000} {transaction.time_stamp * 1000} -
    -
    - - -

    { transaction.amount > 0 ? 'Received' : 'Sent' }

    - - {ticker.currency} -
    - {transaction.tx_hash} + {transaction.time_stamp * 1000}
    0 ? styles.positive : styles.negative}`}> @@ -46,11 +31,7 @@ const Transaction = ({ /> - + ${btc.convert('sats', 'usd', transaction.amount, currentTicker.price_usd)}
    diff --git a/app/routes/activity/containers/ActivityContainer.js b/app/routes/activity/containers/ActivityContainer.js index 3fc3702e..c2d05424 100644 --- a/app/routes/activity/containers/ActivityContainer.js +++ b/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),