Browse Source

Merge pull request #32 from LN-Zap/feature/listpayments

Feature/listpayments
renovate/lint-staged-8.x
jackmallers 7 years ago
committed by GitHub
parent
commit
050f272309
  1. 17
      app/lnd/methods/index.js
  2. 145
      app/reducers/activity.js
  3. 6
      app/reducers/index.js
  4. 11
      app/reducers/ipc.js
  5. 25
      app/reducers/payment.js
  6. 92
      app/reducers/transaction.js
  7. 132
      app/routes/activity/components/Activity.js
  8. 85
      app/routes/activity/components/Activity.scss
  9. 131
      app/routes/activity/components/components/Activity.scss
  10. 74
      app/routes/activity/components/components/Invoice/Invoice.js
  11. 0
      app/routes/activity/components/components/Invoice/Invoice.scss
  12. 3
      app/routes/activity/components/components/Invoice/index.js
  13. 110
      app/routes/activity/components/components/Invoices.js
  14. 125
      app/routes/activity/components/components/Invoices.scss
  15. 41
      app/routes/activity/components/components/Modal.js
  16. 61
      app/routes/activity/components/components/Modal/Invoice/Invoice.js
  17. 75
      app/routes/activity/components/components/Modal/Invoice/Invoice.scss
  18. 3
      app/routes/activity/components/components/Modal/Invoice/index.js
  19. 57
      app/routes/activity/components/components/Modal/Modal.js
  20. 44
      app/routes/activity/components/components/Modal/Payment/Payment.js
  21. 55
      app/routes/activity/components/components/Modal/Payment/Payment.scss
  22. 3
      app/routes/activity/components/components/Modal/Payment/index.js
  23. 54
      app/routes/activity/components/components/Modal/Transaction/Transaction.js
  24. 63
      app/routes/activity/components/components/Modal/Transaction/Transaction.scss
  25. 3
      app/routes/activity/components/components/Modal/Transaction/index.js
  26. 3
      app/routes/activity/components/components/Modal/index.js
  27. 64
      app/routes/activity/components/components/Payment/Payment.js
  28. 0
      app/routes/activity/components/components/Payment/Payment.scss
  29. 3
      app/routes/activity/components/components/Payment/index.js
  30. 112
      app/routes/activity/components/components/Payments.js
  31. 102
      app/routes/activity/components/components/Payments.scss
  32. 64
      app/routes/activity/components/components/Transaction/Transaction.js
  33. 111
      app/routes/activity/components/components/Transaction/Transaction.scss
  34. 3
      app/routes/activity/components/components/Transaction/index.js
  35. 20
      app/routes/activity/containers/ActivityContainer.js
  36. 6
      app/routes/app/components/App.js
  37. 10
      app/routes/app/components/components/Form/Form.js
  38. 11
      app/routes/app/components/components/Form/components/Pay/Pay.js
  39. 4
      app/routes/app/containers/AppContainer.js
  40. 2
      app/tooltip.scss
  41. 2
      package.json
  42. 15
      yarn.lock

17
app/lnd/methods/index.js

@ -49,6 +49,12 @@ export default function (lnd, event, msg, data) {
)
.catch(error => console.log('channels error: ', error))
break
case 'transactions':
// Data looks like { transactions: [] }
walletController.getTransactions(lnd)
.then(transactionsData => event.sender.send('receiveTransactions', transactionsData))
.catch(error => console.log('transactions error: ', error))
break
case 'payments':
// Data looks like { payments: [] }
paymentsController.listPayments(lnd)
@ -80,7 +86,12 @@ export default function (lnd, event, msg, data) {
.then(newinvoice =>
event.sender.send(
'createdInvoice',
Object.assign(newinvoice, { memo: data.memo, value: data.value, r_hash: new Buffer(newinvoice.r_hash, 'hex').toString('hex') })
Object.assign(newinvoice, {
memo: data.memo,
value: data.value,
r_hash: new Buffer(newinvoice.r_hash, 'hex').toString('hex'),
creation_date: Date.now() / 1000
})
)
)
.catch(error => console.log('addInvoice error: ', error))
@ -96,8 +107,8 @@ export default function (lnd, event, msg, data) {
// Transaction looks like { txid: String }
// { amount, addr } = data
walletController.sendCoins(lnd, data)
.then(({ txid }) => event.sender.send('sendSuccessful', { amount: data.amount, addr: data.addr, txid }))
.catch(error => event.sender.send('sendCoinsError', { error }))
.then(({ txid }) => event.sender.send('transactionSuccessful', { amount: data.amount, addr: data.addr, txid }))
.catch(error => event.sender.send('transactionError', { error }))
break
case 'openChannel':
// Response is empty. Streaming updates on channel status and updates

145
app/reducers/activity.js

@ -0,0 +1,145 @@
import { createSelector } from 'reselect'
// ------------------------------------
// Initial State
// ------------------------------------
const initialState = {
filterPulldown: false,
filter: { key: 'ALL_ACTIVITY', name: 'All Activity' },
filters: [
{ key: 'ALL_ACTIVITY', name: 'All Activity' },
{ key: 'LN_ACTIVITY', name: 'LN Activity' },
{ key: 'PAYMENT_ACTIVITY', name: 'LN Payments' },
{ key: 'INVOICE_ACTIVITY', name: 'LN Invoices' },
{ key: 'TRANSACTION_ACTIVITY', name: 'On-chain Activity' }
],
modal: {
modalType: null,
modalProps: {}
}
}
// ------------------------------------
// Constants
// ------------------------------------
export const SHOW_ACTIVITY_MODAL = 'SHOW_ACTIVITY_MODAL'
export const HIDE_ACTIVITY_MODAL = 'HIDE_ACTIVITY_MODAL'
export const CHANGE_FILTER = 'CHANGE_FILTER'
export const TOGGLE_PULLDOWN = 'TOGGLE_PULLDOWN'
// ------------------------------------
// Actions
// ------------------------------------
export function showActivityModal(modalType, modalProps) {
return {
type: SHOW_ACTIVITY_MODAL,
modalType,
modalProps
}
}
export function hideActivityModal() {
return {
type: HIDE_ACTIVITY_MODAL
}
}
export function changeFilter(filter) {
return {
type: CHANGE_FILTER,
filter
}
}
export function toggleFilterPulldown() {
return {
type: TOGGLE_PULLDOWN
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[SHOW_ACTIVITY_MODAL]: (state, { modalType, modalProps }) => ({ ...state, modal: { modalType, modalProps } }),
[HIDE_ACTIVITY_MODAL]: state => ({ ...state, modal: { modalType: null, modalProps: {} } }),
[CHANGE_FILTER]: (state, { filter }) => ({ ...state, filter, filterPulldown: false }),
[TOGGLE_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown })
}
// ------------------------------------
// Selectors
// ------------------------------------
const activitySelectors = {}
const filtersSelector = state => state.activity.filters
const filterSelector = state => state.activity.filter
const paymentsSelector = state => state.payment.payments
const invoicesSelector = state => state.invoice.invoices
const transactionsSelector = state => state.transaction.transactions
const allActivity = createSelector(
paymentsSelector,
invoicesSelector,
transactionsSelector,
(payments, invoices, transactions) => [...payments, ...invoices, ...transactions].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
})
)
const lnActivity = createSelector(
paymentsSelector,
invoicesSelector,
(payments, invoices) => [...payments, ...invoices].sort((a, b) => b.creation_date - a.creation_date)
)
const paymentActivity = createSelector(
paymentsSelector,
payments => payments
)
const invoiceActivity = createSelector(
invoicesSelector,
invoices => invoices
)
const transactionActivity = createSelector(
transactionsSelector,
transactions => transactions
)
const FILTERS = {
ALL_ACTIVITY: allActivity,
LN_ACTIVITY: lnActivity,
PAYMENT_ACTIVITY: paymentActivity,
INVOICE_ACTIVITY: invoiceActivity,
TRANSACTION_ACTIVITY: transactionActivity
}
activitySelectors.currentActivity = createSelector(
filterSelector,
filter => FILTERS[filter.key]
)
activitySelectors.nonActiveFilters = createSelector(
filtersSelector,
filterSelector,
(filters, filter) => filters.filter(f => f.key !== filter.key)
)
export { activitySelectors }
// ------------------------------------
// Reducer
// ------------------------------------
export default function activityReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

6
app/reducers/index.js

@ -11,6 +11,8 @@ import form from './form'
import invoice from './invoice'
import modal from './modal'
import address from './address'
import transaction from './transaction'
import activity from './activity'
const rootReducer = combineReducers({
router,
@ -23,7 +25,9 @@ const rootReducer = combineReducers({
form,
invoice,
modal,
address
address,
transaction,
activity
})
export default rootReducer

11
app/reducers/ipc.js

@ -19,9 +19,10 @@ import {
pushclosechannelstatus
} from './channels'
import { receivePayments, paymentSuccessful, sendSuccessful, sendCoinsError } from './payment'
import { receivePayments, paymentSuccessful } from './payment'
import { receiveInvoices, createdInvoice, receiveFormInvoice } from './invoice'
import { receiveBalance } from './balance'
import { receiveTransactions, transactionSuccessful, transactionError } from './transaction'
// Import all receiving IPC event handlers and pass them into createIpc
const ipc = createIpc({
@ -40,8 +41,6 @@ const ipc = createIpc({
receiveBalance,
paymentSuccessful,
sendSuccessful,
sendCoinsError,
channelSuccessful,
pushchannelupdated,
@ -58,7 +57,11 @@ const ipc = createIpc({
disconnectSuccess,
receiveAddress,
receiveCryptocurrency
receiveCryptocurrency,
receiveTransactions,
transactionSuccessful,
transactionError
})
export default ipc

25
app/reducers/payment.js

@ -1,8 +1,6 @@
import { createSelector } from 'reselect'
import { ipcRenderer } from 'electron'
import { btc, usd } from '../utils'
import { setForm, resetForm } from './form'
import { showModal } from './modal'
// ------------------------------------
// Constants
@ -66,32 +64,19 @@ export const payInvoice = paymentRequest => (dispatch) => {
ipcRenderer.send('lnd', { msg: 'sendPayment', data: { paymentRequest } })
}
export const sendCoins = ({ value, addr, currency, rate }) => (dispatch) => {
const amount = currency === 'usd' ? btc.btcToSatoshis(usd.usdToBtc(value, rate)) : btc.btcToSatoshis(value)
dispatch(sendPayment())
ipcRenderer.send('lnd', { msg: 'sendCoins', data: { amount, addr } })
}
// Receive IPC event for successful payment
// TODO: Add payment to state, not a total re-fetch
export const paymentSuccessful = () => fetchPayments()
export const sendSuccessful = (event, { amount, addr, txid }) => (dispatch) => {
export const paymentSuccessful = () => (dispatch) => {
// Close the form modal once the payment was succesful
dispatch(setForm({ modalOpen: false }))
// Show successful payment state
dispatch(showModal('SUCCESSFUL_SEND_COINS', { txid, amount, addr }))
// TODO: Add successful on-chain payment to payments list once payments list supports on-chain and LN
// dispatch({ type: PAYMENT_SUCCESSFULL, payment: { amount, addr, txid, pending: true } })
dispatch({ type: PAYMENT_SUCCESSFULL })
// Refetch payments (TODO: dont do a full refetch, rather append new tx to list)
dispatch(fetchPayments())
// Reset the payment form
dispatch(resetForm())
}
export const sendCoinsError = () => (dispatch) => {
dispatch({ type: PAYMENT_FAILED })
}
// ------------------------------------
// Action Handlers

92
app/reducers/transaction.js

@ -0,0 +1,92 @@
import { ipcRenderer } from 'electron'
import { btc, usd } from '../utils'
import { setForm, resetForm } from './form'
import { showModal } from './modal'
// ------------------------------------
// Constants
// ------------------------------------
export const GET_TRANSACTIONS = 'GET_TRANSACTIONS'
export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS'
export const SEND_TRANSACTION = 'SEND_TRANSACTION'
export const TRANSACTION_SUCCESSFULL = 'TRANSACTION_SUCCESSFULL'
export const TRANSACTION_FAILED = 'TRANSACTION_FAILED'
// ------------------------------------
// Actions
// ------------------------------------
export function getTransactions() {
return {
type: GET_TRANSACTIONS
}
}
export function sendTransaction() {
return {
type: SEND_TRANSACTION
}
}
// Send IPC event for payments
export const fetchTransactions = () => (dispatch) => {
dispatch(getTransactions())
ipcRenderer.send('lnd', { msg: 'transactions' })
}
// Receive IPC event for payments
export const receiveTransactions = (event, { transactions }) => dispatch => dispatch({ type: RECEIVE_TRANSACTIONS, transactions })
export const sendCoins = ({ value, addr, currency, rate }) => (dispatch) => {
const amount = currency === 'usd' ? btc.btcToSatoshis(usd.usdToBtc(value, rate)) : btc.btcToSatoshis(value)
dispatch(sendTransaction())
ipcRenderer.send('lnd', { msg: 'sendCoins', data: { amount, addr } })
}
// Receive IPC event for successful payment
// TODO: Add payment to state, not a total re-fetch
export const transactionSuccessful = (event, { amount, addr, txid }) => (dispatch) => {
// Get the new list of transactions (TODO dont do an entire new fetch)
fetchTransactions()
// Close the form modal once the payment was succesful
dispatch(setForm({ modalOpen: false }))
// Show successful payment state
dispatch(showModal('SUCCESSFUL_SEND_COINS', { txid, amount, addr }))
// TODO: Add successful on-chain payment to payments list once payments list supports on-chain and LN
// dispatch({ type: PAYMENT_SUCCESSFULL, payment: { amount, addr, txid, pending: true } })
dispatch({ type: TRANSACTION_SUCCESSFULL })
// Reset the payment form
dispatch(resetForm())
}
export const transactionError = () => (dispatch) => {
dispatch({ type: TRANSACTION_FAILED })
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[GET_TRANSACTIONS]: state => ({ ...state, transactionLoading: true }),
[SEND_TRANSACTION]: state => ({ ...state, sendingTransaction: true }),
[RECEIVE_TRANSACTIONS]: (state, { transactions }) => ({ ...state, transactionLoading: false, transactions }),
[TRANSACTION_SUCCESSFULL]: state => ({ ...state, sendingTransaction: false }),
[TRANSACTION_FAILED]: state => ({ ...state, sendingTransaction: false })
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
sendingTransaction: false,
transactionLoading: false,
transactions: []
}
export default function transactionReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

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

@ -1,52 +1,80 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { MdSearch } from 'react-icons/lib/md'
import Payments from './components/Payments'
import Invoices from './components/Invoices'
import { FaAngleDown } from 'react-icons/lib/fa'
import Invoice from './components/Invoice'
import Payment from './components/Payment'
import Transaction from './components/Transaction'
import Modal from './components/Modal'
import styles from './Activity.scss'
class Activity extends Component {
constructor(props, context) {
super(props, context)
this.state = {
tab: 1
}
this.renderActivity = this.renderActivity.bind(this)
}
componentWillMount() {
const { fetchPayments, fetchInvoices } = this.props
const { fetchPayments, fetchInvoices, fetchTransactions } = this.props
fetchPayments()
fetchInvoices()
fetchTransactions()
}
renderActivity(activity) {
const { ticker, currentTicker, showActivityModal } = 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} />
} 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} />
}
render() {
const { tab } = this.state
const {
ticker,
searchInvoices,
invoices,
invoice: { invoicesSearchText, invoice, invoiceLoading },
payment: { payment, payments, paymentLoading },
setPayment,
setInvoice,
paymentModalOpen,
invoiceModalOpen,
currentTicker
invoice: { invoicesSearchText, invoiceLoading },
payment: { paymentLoading },
currentTicker,
activity: { modal, filter, filterPulldown },
hideActivityModal,
changeFilter,
toggleFilterPulldown,
currentActivity,
nonActiveFilters
} = this.props
if (invoiceLoading || paymentLoading) { return <div>Loading...</div> }
return (
<div>
<Modal
modalType={modal.modalType}
modalProps={modal.modalProps}
hideActivityModal={hideActivityModal}
ticker={ticker}
currentTicker={currentTicker}
/>
<div className={styles.search}>
<label className={`${styles.label} ${styles.input}`} htmlFor='invoiceSearch'>
<MdSearch />
</label>
<input
value={tab === 1 ? '' : invoicesSearchText}
onChange={event => (tab === 1 ? null : searchInvoices(event.target.value))}
value={invoicesSearchText}
onChange={event => searchInvoices(event.target.value)}
className={`${styles.text} ${styles.input}`}
placeholder={tab === 1 ? 'Search transactions by amount, public key, channel' : 'Search requests by memo'}
placeholder='Search by amount, hash, memo, etc'
type='text'
id='invoiceSearch'
/>
@ -54,41 +82,30 @@ class Activity extends Component {
<div className={styles.activities}>
<header className={styles.header}>
<span
className={`${styles.title} ${tab === 1 ? styles.active : null}`}
onClick={() => this.setState({ tab: 1 })}
>
Payments
</span>
<span
className={`${styles.title} ${tab === 2 ? styles.active : null}`}
onClick={() => this.setState({ tab: 2 })}
>
Requests
</span>
<section>
<h2 onClick={toggleFilterPulldown}>
{filter.name} <span className={filterPulldown ? styles.pulldown : ''}><FaAngleDown /></span>
</h2>
<ul className={`${styles.filters} ${filterPulldown ? styles.active : ''}`}>
{
nonActiveFilters.map(f =>
(<li key={f.key} onClick={() => changeFilter(f)}>
{f.name}
</li>)
)
}
</ul>
</section>
</header>
<div className={styles.activityContainer}>
<ul className={`${styles.activityContainer} ${filterPulldown ? styles.pulldown : ''}`}>
{
tab === 1 ?
<Payments
payment={payment}
payments={payments}
ticker={ticker}
setPayment={setPayment}
paymentModalOpen={paymentModalOpen}
currentTicker={currentTicker}
/>
:
<Invoices
invoice={invoice}
invoices={invoices}
ticker={ticker}
setInvoice={setInvoice}
invoiceModalOpen={invoiceModalOpen}
currentTicker={currentTicker}
/>
currentActivity.map((activity, index) => (
<li className={styles.activity} key={index}>
{this.renderActivity(activity)}
</li>
))
}
</div>
</ul>
</div>
</div>
)
@ -98,16 +115,19 @@ class Activity extends Component {
Activity.propTypes = {
fetchPayments: PropTypes.func.isRequired,
fetchInvoices: PropTypes.func.isRequired,
fetchTransactions: PropTypes.func.isRequired,
ticker: PropTypes.object.isRequired,
searchInvoices: PropTypes.func.isRequired,
invoices: PropTypes.array.isRequired,
invoice: PropTypes.object.isRequired,
payment: PropTypes.object.isRequired,
setPayment: PropTypes.func.isRequired,
setInvoice: PropTypes.func.isRequired,
paymentModalOpen: PropTypes.bool.isRequired,
invoiceModalOpen: PropTypes.bool.isRequired,
currentTicker: PropTypes.object.isRequired
currentTicker: PropTypes.object.isRequired,
showActivityModal: PropTypes.func.isRequired,
hideActivityModal: PropTypes.func.isRequired,
changeFilter: PropTypes.func.isRequired,
toggleFilterPulldown: PropTypes.func.isRequired,
activity: PropTypes.object.isRequired,
currentActivity: PropTypes.array.isRequired,
nonActiveFilters: PropTypes.array.isRequired
}
export default Activity

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

@ -34,39 +34,68 @@
background: $lightgrey;
.header {
width: 75%;
padding: 60px 0 20px 0;
margin: 0 auto;
section {
position: relative;
margin-left: auto;
margin-right: auto;
padding-left: 100px;
padding-right: 100px;
max-width: 964px;
}
h2, h2 span {
cursor: pointer;
transition: color 0.25s;
&:hover {
color: lighten($darkestgrey, 10%);
}
}
.title {
display: inline-block;
margin: 60px 0 20px 0;
padding: 5px 10px;
background: $lightgrey;
color: $darkestgrey;
h2, .filters li {
text-transform: uppercase;
letter-spacing: 1.5px;
color: $darkestgrey;
font-size: 14px;
font-weight: 400;
letter-spacing: 1.6px;
cursor: pointer;
transition: all 0.5s;
&:first-child {
border-right: 1px solid $darkestgrey;
}
&.active {
color: $main;
font-weight: bold;
}
font-weight: 400;
}
h2 span.pulldown {
color: $main;
}
.filters {
display: none;
&.active {
display: block;
position: absolute;
bottom: -100px;
z-index: 10;
li {
margin: 5px 0;
cursor: pointer;
&:hover {
color: $main;
}
}
}
}
}
}
.activityContainer {
background: $white;
padding: 20px 0;
transition: opacity 0.25s;
&.pulldown {
opacity: 0.15;
}
}
.activityList {
@ -76,9 +105,13 @@
}
.activity {
padding: 35px 10px;
border-bottom: 1px solid $grey;
font-size: 14px;
padding: 0 100px;
&:hover {
background-color: #f0f0f0;
transition-delay: 0s;
outline: $grey solid 1px;
}
.left, .center, .right {
display: inline-block;

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

@ -0,0 +1,131 @@
@import '../../../../variables.scss';
.container {
display: flex;
flex-direction: row;
cursor: pointer;
max-width: 960px;
margin: 0 auto;
height: 76px;
align-items: center;
border-bottom: 1px solid $grey;
font-size: 14px;
transition: background-color .1s linear;
transition-delay: .1s;
color: $darkestgrey;
position: relative;
}
.clock {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: -100px;
width: 100px;
height: 77px;
svg {
font-size: 18px;
}
}
.date {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-right: 50px;
time {
text-transform: uppercase;
&:nth-child(1) {
display: flex;
align-items: center;
height: 38px;
font-size: 18px;
margin-bottom: 5px;
}
&:nth-child(2) {
font-size: 12px;
}
}
}
.data {
display: flex;
flex-direction: column;
flex: 6;
justify-content: space-evenly;
.title {
margin-bottom: 5px;
}
.icon, h3, span {
vertical-align: middle;
}
h3, span {
font-size: 18px;
font-weight: bold;
}
.icon {
display: inline-block;
flex: none;
position: relative;
width: 36px;
height: 36px;
border: 1px solid $darkestgrey;
border-radius: 50%;
margin-right: 15px;
svg {
color: $main;
font-size: 16px;
vertical-align: middle;
display: flex;
top: 0;
left: 50%;
height: 100%;
margin: 0 auto;
}
}
h3 {
display: inline-block;
&:after {
content: " ";
display: inline-block;
width: 3px;
}
}
span {
text-transform: uppercase;
}
}
.amount {
display: flex;
flex-direction: column;
flex: 1;
text-align: right;
font-size: 16px;
&.positive span:nth-child(1) {
font-weight: bold;
color: $main;
}
span {
&:nth-child(2) {
font-size: 12px;
}
}
}

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

@ -0,0 +1,74 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import { FaBolt, FaClockO } from 'react-icons/lib/fa'
import { btc } from '../../../../../utils'
import styles from '../Activity.scss'
const Invoice = ({ invoice, ticker, currentTicker, showActivityModal }) => (
<div className={styles.container} onClick={() => showActivityModal('INVOICE', { invoice })}>
{
!invoice.settled ?
<div className={styles.clock}>
<i className='hint--top' data-hint='Request has not been paid'>
<FaClockO />
</i>
</div>
:
null
}
<div className={styles.date}>
<Moment format='D'>
{invoice.creation_date * 1000}
</Moment>
<Moment format='MMMM'>
{invoice.creation_date * 1000}
</Moment>
</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' }
</h3>
<span>
{ticker.currency}
</span>
</div>
<div className={styles.subtitle}>
{invoice.r_hash.toString('hex')}
</div>
</div>
<div className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}>
<span className='hint--top' data-hint='Invoice amount'>
+
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(invoice.value, currentTicker.price_usd)
:
btc.satoshisToBtc(invoice.value)
}
</span>
<span className='hint--bottom' data-hint='Invoice fee'>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(invoice.fee, currentTicker.price_usd)
:
btc.satoshisToBtc(invoice.fee)
}
</span>
</div>
</div>
)
Invoice.propTypes = {
invoice: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
showActivityModal: PropTypes.func.isRequired
}
export default Invoice

0
app/routes/activity/components/components/Invoice/Invoice.scss

3
app/routes/activity/components/components/Invoice/index.js

@ -0,0 +1,3 @@
import Invoice from './Invoice'
export default Invoice

110
app/routes/activity/components/components/Invoices.js

@ -1,110 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import { MdCheck } from 'react-icons/lib/md'
import QRCode from 'qrcode.react'
import Modal from './Modal'
import CurrencyIcon from '../../../../components/CurrencyIcon'
import { btc } from '../../../../utils'
import styles from './Invoices.scss'
const Invoices = ({
invoice,
invoices,
ticker,
setInvoice,
invoiceModalOpen,
currentTicker
}) => (
<div>
<Modal isOpen={invoiceModalOpen} resetObject={setInvoice}>
{
invoice ?
<div className={styles.invoiceModal}>
<h3>{invoice.memo}</h3>
<h1>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} styles={{ verticalAlign: 'top' }} />
<span className={styles.value}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(invoice.value, currentTicker.price_usd)
:
btc.satoshisToBtc(invoice.value)
}
</span>
</h1>
<div className={styles.qrcode}>
<QRCode value={invoice.payment_request} size={200} />
<input
readOnly
className={styles.paymentRequest}
onClick={event => event.target.select()}
defaultValue={invoice.payment_request}
/>
</div>
<div className={styles.settled}>
{
invoice.settled ?
<p><MdCheck style={{ verticalAlign: 'top' }} /> Paid</p>
:
<p>Not Paid</p>
}
</div>
<p className={styles.date}>
Created on
<Moment format='MMM Do'>{invoice.creation_date * 1000}</Moment>
</p>
</div>
:
null
}
</Modal>
<ul className={styles.invoices}>
<li className={styles.invoiceTitles}>
<div className={styles.left}>
<div>Payment Request</div>
</div>
<div className={styles.center}>
<div>Memo</div>
</div>
<div className={styles.right}>
<div>Amount</div>
</div>
</li>
{
invoices.map((invoiceItem, index) => (
<li key={index} className={styles.invoice} onClick={() => setInvoice(invoiceItem)}>
<div className={styles.left}>
<div className={styles.path}>{`${invoiceItem.payment_request.substring(0, 75)}...`}</div>
</div>
<div className={styles.center}>
<div>{invoiceItem.memo}</div>
</div>
<div className={styles.right}>
<div className={invoiceItem.settled ? styles.settled : null}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(invoiceItem.value, currentTicker.price_usd)
:
btc.satoshisToBtc(invoiceItem.value)
}
</div>
</div>
</li>
))
}
</ul>
</div>
)
Invoices.propTypes = {
invoice: PropTypes.object,
invoices: PropTypes.array.isRequired,
ticker: PropTypes.object.isRequired,
setInvoice: PropTypes.func.isRequired,
invoiceModalOpen: PropTypes.bool.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Invoices

125
app/routes/activity/components/components/Invoices.scss

@ -1,125 +0,0 @@
@import '../../../../variables.scss';
.invoiceModal {
padding: 40px;
h3 {
font-size: 24px;
color: $black;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
}
h1 {
text-align: center;
color: $main;
margin: 20px 20px 60px 0;
svg {
font-size: 30px;
vertical-align: top;
}
span svg[data-icon='ltc'] {
width: 30px;
height: 30px;
vertical-align: top;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
.value {
font-size: 60px;
}
}
.qrcode {
text-align: center;
.paymentRequest {
text-align: center;
font-size: 0.5vw;
margin-top: 20px;
padding: 5px;
border-radius: 5px;
background: $lightgrey;
border: 1px solid $darkgrey;
display: block;
width: 100%;
}
}
.settled {
text-align: center;
color: $main;
text-transform: uppercase;
font-size: 20px;
margin: 30px 0;
font-weight: bold;
svg {
line-height: 20px;
}
}
.date {
text-align: center;
time {
margin-left: 3px;
}
}
}
.invoices {
width: 75%;
margin: 0 auto;
background: $white;
}
.invoice, .invoiceTitles {
display: flex;
flex-direction: 'row';
padding: 35px 10px;
border-bottom: 1px solid $grey;
font-size: 14px;
.left {
flex: 7;
}
.center {
flex: 2;
}
.right {
flex: 1;
}
.settled {
color: $main;
font-weight: bold;
}
}
.invoiceTitles {
border: none;
.left, .center, .right {
color: $black;
text-transform: uppercase;
font-size: 16px;
font-weight: bold;
}
}
.invoice {
cursor: pointer;
&:hover {
background: #f0f0f0;
}
}

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

@ -1,41 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
const Modal = ({ isOpen, resetObject, children }) => {
const customStyles = {
overlay: {
cursor: 'pointer'
},
content: {
top: 'auto',
left: '20%',
right: '0',
bottom: 'auto',
width: '40%',
margin: '50px auto'
}
}
return (
<ReactModal
isOpen={isOpen}
contentLabel='No Overlay Click Modal'
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={() => resetObject(null)}
parentSelector={() => document.body}
style={customStyles}
>
{children}
</ReactModal>
)
}
Modal.propTypes = {
isOpen: PropTypes.bool.isRequired,
resetObject: PropTypes.func.isRequired,
children: PropTypes.object
}
export default Modal

61
app/routes/activity/components/components/Modal/Invoice/Invoice.js

@ -0,0 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import QRCode from 'qrcode.react'
import { MdCheck } from 'react-icons/lib/md'
import CurrencyIcon from '../../../../../../components/CurrencyIcon'
import { btc } from '../../../../../../utils'
import styles from './Invoice.scss'
const Invoice = ({ invoice, ticker, currentTicker }) => (
<div className={styles.container}>
<h3>{invoice.memo}</h3>
<h1>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} styles={{ verticalAlign: 'top' }} />
<span className={styles.value}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(invoice.value, currentTicker.price_usd)
:
btc.satoshisToBtc(invoice.value)
}
</span>
</h1>
<div className={styles.qrcode}>
<QRCode value={invoice.payment_request} size={200} />
<input
readOnly
className={styles.paymentRequest}
onClick={event => event.target.select()}
defaultValue={invoice.payment_request}
/>
</div>
<div className={styles.settled}>
{
invoice.settled ?
<p><MdCheck style={{ verticalAlign: 'top' }} /> Paid</p>
:
<p>Not Paid</p>
}
</div>
<p className={styles.date}>
Created on
<Moment format='MMM Do'>{invoice.creation_date * 1000}</Moment>
</p>
</div>
)
Invoice.propTypes = {
invoice: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Invoice

75
app/routes/activity/components/components/Modal/Invoice/Invoice.scss

@ -0,0 +1,75 @@
@import '../../../../../../variables.scss';
.container {
padding: 40px;
h3 {
font-size: 24px;
color: $black;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
}
h1 {
text-align: center;
color: $main;
margin: 20px 20px 60px 0;
svg {
font-size: 30px;
vertical-align: top;
}
span svg[data-icon='ltc'] {
width: 30px;
height: 30px;
vertical-align: top;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
.value {
font-size: 60px;
}
}
.qrcode {
text-align: center;
.paymentRequest {
text-align: center;
font-size: 0.5vw;
margin-top: 20px;
padding: 5px;
border-radius: 5px;
background: $lightgrey;
border: 1px solid $darkgrey;
display: block;
width: 100%;
}
}
.settled {
text-align: center;
color: $main;
text-transform: uppercase;
font-size: 20px;
margin: 30px 0;
font-weight: bold;
svg {
line-height: 20px;
}
}
.date {
text-align: center;
time {
margin-left: 3px;
}
}
}

3
app/routes/activity/components/components/Modal/Invoice/index.js

@ -0,0 +1,3 @@
import Invoice from './Invoice'
export default Invoice

57
app/routes/activity/components/components/Modal/Modal.js

@ -0,0 +1,57 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
import Transaction from './Transaction'
import Payment from './Payment'
import Invoice from './Invoice'
const Modal = ({ modalType, modalProps, hideActivityModal, ticker, currentTicker }) => {
const MODAL_COMPONENTS = {
TRANSACTION: Transaction,
PAYMENT: Payment,
INVOICE: Invoice
}
const customStyles = {
overlay: {
cursor: 'pointer'
},
content: {
top: 'auto',
left: '20%',
right: '0',
bottom: 'auto',
width: '40%',
margin: '50px auto'
}
}
if (!modalType) { return null }
const SpecificModal = MODAL_COMPONENTS[modalType]
return (
<ReactModal
isOpen
ariaHideApp
shouldCloseOnOverlayClick
contentLabel='No Overlay Click Modal'
onRequestClose={() => hideActivityModal()}
parentSelector={() => document.body}
style={customStyles}
>
<SpecificModal {...modalProps} ticker={ticker} currentTicker={currentTicker} />
</ReactModal>
)
}
Modal.propTypes = {
modalType: PropTypes.string,
modalProps: PropTypes.object.isRequired,
hideActivityModal: PropTypes.func.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Modal

44
app/routes/activity/components/components/Modal/Payment/Payment.js

@ -0,0 +1,44 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import CurrencyIcon from '../../../../../../components/CurrencyIcon'
import { btc } from '../../../../../../utils'
import styles from './Payment.scss'
const Payment = ({ payment, ticker, currentTicker }) => (
<div className={styles.container}>
<h3>{payment.payment_hash}</h3>
<h1>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} styles={{ verticalAlign: 'top' }} />
<span className={styles.value}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(payment.value, currentTicker.price_usd)
:
btc.satoshisToBtc(payment.value)
}
</span>
</h1>
<dl>
<dt>Fee</dt>
<dd>{payment.fee}</dd>
<dt>Date</dt>
<dd>
<Moment format='MMM Do'>{payment.creation_date * 1000}</Moment>
</dd>
</dl>
</div>
)
Payment.propTypes = {
payment: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Payment

55
app/routes/activity/components/components/Modal/Payment/Payment.scss

@ -0,0 +1,55 @@
@import '../../../../../../variables.scss';
.container {
padding: 40px;
h1 {
text-align: center;
color: $main;
margin: 60px 30px 60px 0;
svg {
font-size: 30px;
vertical-align: top;
}
span svg[data-icon='ltc'] {
width: 30px;
height: 30px;
vertical-align: top;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
.value {
font-size: 80px;
}
}
h3 {
font-size: 14px;
text-align: center;
color: $black;
font-weight: bold;
}
dt {
text-align: left;
float: left;
clear: left;
font-weight: 500;
padding: 20px 35px 19px 0;
color: $black;
font-weight: bold;
}
dd {
text-align: right;
font-weight: 400;
padding: 19px 0;
margin-left: 0;
border-top: 1px solid $darkgrey;
}
}

3
app/routes/activity/components/components/Modal/Payment/index.js

@ -0,0 +1,3 @@
import Payment from './Payment'
export default Payment

54
app/routes/activity/components/components/Modal/Transaction/Transaction.js

@ -0,0 +1,54 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import CurrencyIcon from '../../../../../../components/CurrencyIcon'
import { btc } from '../../../../../../utils'
import styles from './Transaction.scss'
const Transaction = ({ transaction, ticker, currentTicker }) => (
<div className={styles.container}>
<h2>
{
transaction.amount < 0 ?
'Sent'
:
'Received'
}
</h2>
<h3>{transaction.tx_hash}</h3>
<h1>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} styles={{ verticalAlign: 'top' }} />
<span className={styles.value}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(transaction.amount, currentTicker.price_usd)
:
btc.satoshisToBtc(transaction.amount)
}
</span>
</h1>
<dl>
<dt>Confirmations</dt>
<dd>{transaction.num_confirmations}</dd>
<dt>Fee</dt>
<dd>{transaction.total_fees}</dd>
<dt>Date</dt>
<dd>
<Moment format='MMM Do'>{transaction.time_stamp * 1000}</Moment>
</dd>
</dl>
</div>
)
Transaction.propTypes = {
transaction: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Transaction

63
app/routes/activity/components/components/Modal/Transaction/Transaction.scss

@ -0,0 +1,63 @@
@import '../../../../../../variables.scss';
.container {
padding: 40px;
h1 {
text-align: center;
color: $main;
margin: 60px 30px 60px 0;
svg {
font-size: 30px;
vertical-align: top;
}
span svg[data-icon='ltc'] {
width: 30px;
height: 30px;
vertical-align: top;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
.value {
font-size: 75px;
}
}
h3 {
font-size: 14px;
text-align: center;
color: $black;
font-weight: bold;
}
h2 {
text-align: center;
margin-bottom: 30px;
text-transform: uppercase;
letter-spacing: 1.5px;
font-size: 24px;
}
dt {
text-align: left;
float: left;
clear: left;
font-weight: 500;
padding: 20px 35px 19px 0;
color: $black;
font-weight: bold;
}
dd {
text-align: right;
font-weight: 400;
padding: 19px 0;
margin-left: 0;
border-top: 1px solid $darkgrey;
}
}

3
app/routes/activity/components/components/Modal/Transaction/index.js

@ -0,0 +1,3 @@
import Transaction from './Transaction'
export default Transaction

3
app/routes/activity/components/components/Modal/index.js

@ -0,0 +1,3 @@
import Modal from './Modal'
export default Modal

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

@ -0,0 +1,64 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import { FaBolt } from 'react-icons/lib/fa'
import { btc } from '../../../../../utils'
import styles from '../Activity.scss'
const Payment = ({ payment, ticker, currentTicker, showActivityModal }) => (
<div className={styles.container} onClick={() => showActivityModal('PAYMENT', { payment })}>
<div className={styles.date}>
<Moment format='D'>
{payment.creation_date * 1000}
</Moment>
<Moment format='MMMM'>
{payment.creation_date * 1000}
</Moment>
</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>
</div>
<div className={styles.subtitle}>
{payment.payment_hash.toString('hex')}
</div>
</div>
<div className={styles.amount}>
<span className='hint--top' data-hint='Payment amount'>
-
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(payment.value, currentTicker.price_usd)
:
btc.satoshisToBtc(payment.value)
}
</span>
<span className='hint--bottom' data-hint='Payment fee'>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(payment.fee, currentTicker.price_usd)
:
btc.satoshisToBtc(payment.fee)
}
</span>
</div>
</div>
)
Payment.propTypes = {
payment: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
showActivityModal: PropTypes.func.isRequired
}
export default Payment

0
app/routes/activity/components/components/Payment/Payment.scss

3
app/routes/activity/components/components/Payment/index.js

@ -0,0 +1,3 @@
import Payment from './Payment'
export default Payment

112
app/routes/activity/components/components/Payments.js

@ -1,112 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import Modal from './Modal'
import CurrencyIcon from '../../../../components/CurrencyIcon'
import { btc } from '../../../../utils'
import styles from './Payments.scss'
const Payments = ({
payment,
payments,
ticker,
setPayment,
paymentModalOpen,
currentTicker
}) => (
<div>
<Modal isOpen={paymentModalOpen} resetObject={setPayment}>
{
payment ?
<div className={styles.paymentModal}>
<h3>{payment.payment_hash}</h3>
<h1>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} styles={{ verticalAlign: 'top' }} />
<span className={styles.value}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(payment.value, currentTicker.price_usd)
:
btc.satoshisToBtc(payment.value)
}
</span>
</h1>
<dl>
<dt>Fee</dt>
<dd>{payment.fee}</dd>
<dt>Date</dt>
<dd>
<Moment format='MMM Do'>{payment.creation_date * 1000}</Moment>
</dd>
</dl>
</div>
:
null
}
</Modal>
<ul className={styles.payments}>
<li className={styles.paymentTitles}>
<div className={styles.left}>
<div>Public Key</div>
</div>
<div className={styles.center}>
<div>Date</div>
</div>
<div className={styles.center}>
<div>Fee</div>
</div>
<div className={styles.right}>
<div>Amount</div>
</div>
</li>
{
payments.map((paymentItem, index) =>
(
<li key={index} className={styles.payment} onClick={() => setPayment(paymentItem)}>
<div className={styles.left}>
<div className={styles.path}>{paymentItem.path[0]}</div>
</div>
<div className={styles.center}>
<div className={styles.date}>
<Moment format='MMM Do'>{paymentItem.creation_date * 1000}</Moment>
</div>
</div>
<div className={styles.right}>
<span className={styles.fee}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(paymentItem.fee, currentTicker.price_usd)
:
btc.satoshisToBtc(paymentItem.fee)
}
</span>
</div>
<div className={styles.right}>
<span className={styles.value}>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(paymentItem.value, currentTicker.price_usd)
:
btc.satoshisToBtc(paymentItem.value)
}
</span>
</div>
</li>
)
)
}
</ul>
</div>
)
Payments.propTypes = {
payment: PropTypes.object,
payments: PropTypes.array.isRequired,
ticker: PropTypes.object.isRequired,
setPayment: PropTypes.func.isRequired,
paymentModalOpen: PropTypes.bool.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Payments

102
app/routes/activity/components/components/Payments.scss

@ -1,102 +0,0 @@
@import '../../../../variables.scss';
.paymentModal {
padding: 40px;
h1 {
text-align: center;
color: $main;
margin: 60px 30px 60px 0;
svg {
font-size: 30px;
vertical-align: top;
}
span svg[data-icon='ltc'] {
width: 30px;
height: 30px;
vertical-align: top;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
.value {
font-size: 80px;
}
}
h3 {
font-size: 14px;
text-align: center;
color: $black;
font-weight: bold;
}
dt {
text-align: left;
float: left;
clear: left;
font-weight: 500;
padding: 20px 35px 19px 0;
color: $black;
font-weight: bold;
}
dd {
text-align: right;
font-weight: 400;
padding: 19px 0;
margin-left: 0;
border-top: 1px solid $darkgrey;
}
}
.payments {
width: 75%;
margin: 0 auto;
background: $white;
}
.payment, .paymentTitles {
display: flex;
flex-direction: 'row';
padding: 35px 10px;
border-bottom: 1px solid $grey;
font-size: 14px;
.left, .center, .right {
display: inline-block;
vertical-align: top;
}
.center, .right {
flex: 1;
}
.left {
flex: 7;
}
}
.paymentTitles {
border: none;
.left, .center, .right {
color: $black;
text-transform: uppercase;
font-size: 16px;
font-weight: bold;
}
}
.payment {
cursor: pointer;
&:hover {
background: #f0f0f0;
}
}

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

@ -0,0 +1,64 @@
import React from 'react'
import PropTypes from 'prop-types'
import Moment from 'react-moment'
import 'moment-timezone'
import { FaChain } from 'react-icons/lib/fa'
import { btc } from '../../../../../utils'
import styles from '../Activity.scss'
const Transaction = ({ transaction, ticker, currentTicker, showActivityModal }) => (
<div className={styles.container} onClick={() => showActivityModal('TRANSACTION', { transaction })}>
<div className={styles.date}>
<Moment format='D'>
{transaction.time_stamp * 1000}
</Moment>
<Moment format='MMMM'>
{transaction.time_stamp * 1000}
</Moment>
</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}
</div>
</div>
<div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}>
<span className='hint--top' data-hint='Transaction amount'>
{ transaction.amount > 0 ? '+' : '' }
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(transaction.amount, currentTicker.price_usd)
:
btc.satoshisToBtc(transaction.amount)
}
</span>
<span className='hint--bottom' data-hint='Transaction fee'>
{
ticker.currency === 'usd' ?
btc.satoshisToUsd(transaction.total_fees, currentTicker.price_usd)
:
btc.satoshisToBtc(transaction.total_fees)
}
</span>
</div>
</div>
)
Transaction.propTypes = {
transaction: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
showActivityModal: PropTypes.func.isRequired
}
export default Transaction

111
app/routes/activity/components/components/Transaction/Transaction.scss

@ -0,0 +1,111 @@
@import '../../../../../variables.scss';
.container {
display: flex;
flex-direction: row;
cursor: pointer;
max-width: 960px;
margin: 0 auto;
height: 76px;
align-items: center;
border-bottom: 1px solid $grey;
font-size: 14px;
transition: background-color .1s linear;
transition-delay: .1s;
color: $darkestgrey;
position: relative;
}
.icon {
display: block;
margin-right: 20px;
flex: none;
position: relative;
width: 36px;
height: 36px;
border: 1px solid $darkestgrey;
border-radius: 50%;
svg {
color: $main;
font-size: 16px;
vertical-align: middle;
display: flex;
top: 0;
left: 50%;
height: 100%;
margin: 0 auto;
}
}
.data {
display: flex;
flex-direction: row;
flex: 6 0;
.title {
flex: 8 0;
}
.subtitle {
padding-left: 20px;
flex: 2 0;
}
}
.subtitle, .date {
text-align: center;
}
.date {
padding-left: 20px;
flex: 1 0;
}
.amount {
flex: 1 0;
padding-left: 20px;
&.negative {
font-weight: 200;
}
&.positive {
color: $main;
font-weight: 500;
}
section {
display: inline-block;
vertical-align: top;
text-align: right;
font-size: 0;
margin: 0;
padding: 0;
&:nth-child(1) {
width: 20%;
}
&:nth-child(2) {
width: 80%;
}
}
svg {
font-size: 20px;
}
span {
display: block;
&:nth-child(1) {
font-size: 14px;
}
&:nth-child(2) {
font-size: 10px;
}
}
}

3
app/routes/activity/components/components/Transaction/index.js

@ -0,0 +1,3 @@
import Transaction from './Transaction'
export default Transaction

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

@ -11,6 +11,14 @@ import {
fetchPayments,
paymentSelectors
} from '../../../reducers/payment'
import { fetchTransactions } from '../../../reducers/transaction'
import {
showActivityModal,
hideActivityModal,
changeFilter,
toggleFilterPulldown,
activitySelectors
} from '../../../reducers/activity'
import Activity from '../components/Activity'
const mapDispatchToProps = {
@ -18,7 +26,12 @@ const mapDispatchToProps = {
setInvoice,
fetchPayments,
fetchInvoices,
searchInvoices
fetchTransactions,
searchInvoices,
showActivityModal,
hideActivityModal,
changeFilter,
toggleFilterPulldown
}
const mapStateToProps = state => ({
@ -34,7 +47,10 @@ const mapStateToProps = state => ({
paymentModalOpen: paymentSelectors.paymentModalOpen(state),
invoiceModalOpen: invoiceSelectors.invoiceModalOpen(state),
currentTicker: tickerSelectors.currentTicker(state)
currentTicker: tickerSelectors.currentTicker(state),
currentActivity: activitySelectors.currentActivity(state)(state),
nonActiveFilters: activitySelectors.nonActiveFilters(state)
})
export default connect(mapStateToProps, mapDispatchToProps)(Activity)

6
app/routes/app/components/App.js

@ -27,7 +27,7 @@ class App extends Component {
setMessage,
setPubkey,
setPaymentRequest,
payment,
transaction: { sendingTransaction },
peers,
setCurrency,
setForm,
@ -60,10 +60,10 @@ class App extends Component {
setMessage={setMessage}
setPubkey={setPubkey}
setPaymentRequest={setPaymentRequest}
payment={payment}
peers={peers}
ticker={ticker}
form={form}
sendingTransaction={sendingTransaction}
createInvoice={createInvoice}
payInvoice={payInvoice}
sendCoins={sendCoins}
@ -102,7 +102,7 @@ App.propTypes = {
setMessage: PropTypes.func.isRequired,
setPubkey: PropTypes.func.isRequired,
setPaymentRequest: PropTypes.func.isRequired,
payment: PropTypes.object.isRequired,
transaction: PropTypes.object.isRequired,
peers: PropTypes.array,
setCurrency: PropTypes.func.isRequired,
setForm: PropTypes.func.isRequired,

10
app/routes/app/components/components/Form/Form.js

@ -7,7 +7,6 @@ import styles from './Form.scss'
const Form = ({
form: { formType, amount, onchainAmount, message, payment_request },
payment: { sendingPayment },
setAmount,
setOnchainAmount,
setMessage,
@ -22,7 +21,8 @@ const Form = ({
formInvoice,
currentTicker,
isOnchain,
isLn
isLn,
sendingTransaction
}) => (
<div className={`${styles.formContainer} ${isOpen ? styles.open : ''}`}>
<div className={styles.container}>
@ -33,7 +33,7 @@ const Form = ({
{
formType === 'pay' ?
<Pay
sendingPayment={sendingPayment}
sendingTransaction={sendingTransaction}
invoiceAmount={formInvoice.amount}
onchainAmount={onchainAmount}
setOnchainAmount={setOnchainAmount}
@ -71,7 +71,6 @@ const Form = ({
)
Form.propTypes = {
payment: PropTypes.object.isRequired,
form: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
setAmount: PropTypes.func.isRequired,
@ -87,7 +86,8 @@ Form.propTypes = {
formInvoice: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired
isLn: PropTypes.bool.isRequired,
sendingTransaction: PropTypes.bool.isRequired
}
export default Form

11
app/routes/app/components/components/Form/components/Pay/Pay.js

@ -16,7 +16,7 @@ class Pay extends Component {
render() {
const {
sendingPayment,
sendingTransaction,
invoiceAmount,
onchainAmount,
setOnchainAmount,
@ -43,7 +43,7 @@ class Pay extends Component {
return (
<div>
{
sendingPayment ?
sendingTransaction ?
<LoadingBolt />
:
null
@ -127,8 +127,11 @@ class Pay extends Component {
}
Pay.propTypes = {
sendingPayment: PropTypes.bool.isRequired,
invoiceAmount: PropTypes.string.isRequired,
sendingTransaction: PropTypes.bool.isRequired,
invoiceAmount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
onchainAmount: PropTypes.string.isRequired,
setOnchainAmount: PropTypes.func.isRequired,
payment_request: PropTypes.string.isRequired,

4
app/routes/app/containers/AppContainer.js

@ -5,7 +5,8 @@ import { fetchBalance } from '../../../reducers/balance'
import { fetchInfo } from '../../../reducers/info'
import { createInvoice, fetchInvoice } from '../../../reducers/invoice'
import { hideModal } from '../../../reducers/modal'
import { payInvoice, sendCoins } from '../../../reducers/payment'
import { payInvoice } from '../../../reducers/payment'
import { sendCoins } from '../../../reducers/transaction'
import { fetchChannels } from '../../../reducers/channels'
import {
setForm,
@ -42,6 +43,7 @@ const mapStateToProps = state => ({
ticker: state.ticker,
balance: state.balance,
payment: state.payment,
transaction: state.transaction,
form: state.form,
invoice: state.invoice,
modal: state.modal,

2
app/tooltip.scss

@ -130,4 +130,4 @@
.hint--left:hover:before {
-webkit-transform: translateX(-8px);
transform: translateX(-8px);
}
}

2
package.json

@ -185,7 +185,7 @@
},
"dependencies": {
"axios": "^0.16.2",
"bitcoinjs-lib": "^3.1.1",
"bitcoinjs-lib": "^3.2.0",
"bitcore-lib": "^0.14.0",
"devtron": "^1.4.0",
"electron-debug": "^1.2.0",

15
yarn.lock

@ -1472,6 +1472,10 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bech32@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/bech32/-/bech32-0.0.3.tgz#736747c4a6531c5d8937d0400498de30e93b2f9c"
beeper@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
@ -1505,10 +1509,11 @@ bitcoin-ops@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.3.0.tgz#6b126b585537bc679b02ed499f14450cffc37e13"
bitcoinjs-lib@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-3.1.1.tgz#c2b1efe455992be88f6be70b5f6fe1a93b9abd90"
bitcoinjs-lib@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-3.2.0.tgz#23d435523b26b15ce58d539172c1c2cbc83141a1"
dependencies:
bech32 "0.0.3"
bigi "^1.4.0"
bip66 "^1.1.0"
bitcoin-ops "^1.3.0"
@ -1520,7 +1525,7 @@ bitcoinjs-lib@^3.1.1:
pushdata-bitcoin "^1.0.1"
randombytes "^2.0.1"
safe-buffer "^5.0.1"
typeforce "^1.8.7"
typeforce "^1.11.3"
varuint-bitcoin "^1.0.4"
wif "^2.0.1"
@ -8705,7 +8710,7 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typeforce@^1.8.7:
typeforce@^1.11.3:
version "1.11.4"
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.11.4.tgz#07ce3abcce836761243ae483dce5bc609786205c"
dependencies:

Loading…
Cancel
Save