Browse Source

feature(modal): modal for detailed payment/invoice

renovate/lint-staged-8.x
Jack Mallers 8 years ago
parent
commit
f56128d244
  1. 13
      app/app.global.scss
  2. 24
      app/reducers/invoice.js
  3. 24
      app/reducers/payment.js
  4. 3
      app/reducers/ticker.js
  5. 30
      app/routes/activity/components/Activity.js
  6. 83
      app/routes/activity/components/components/Invoices.js
  7. 47
      app/routes/activity/components/components/Invoices.scss
  8. 45
      app/routes/activity/components/components/Modal.js
  9. 139
      app/routes/activity/components/components/Payments.js
  10. 43
      app/routes/activity/components/components/Payments.scss
  11. 10
      app/routes/activity/containers/ActivityContainer.js
  12. 5
      package-lock.json
  13. 1
      package.json

13
app/app.global.scss

@ -21,4 +21,17 @@ body {
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: rgba(255,255,255,0);
font-family: 'Roboto';
}
.ReactModal__Overlay {
transition: opacity 500ms ease-in-out;
opacity: 0;
&--after-open {
opacity: 1;
}
&--before-close {
opacity: 0;
}
}

24
app/reducers/invoice.js

@ -1,8 +1,11 @@
import { createSelector } from 'reselect'
import { callApi } from '../api'
import { btc, usd } from '../utils'
// ------------------------------------
// Constants
// ------------------------------------
export const SET_INVOICE = 'SET_INVOICE'
export const GET_INVOICE = 'GET_INVOICE'
export const RECEIVE_INVOICE = 'RECEIVE_INVOICE'
@ -17,6 +20,13 @@ export const INVOICE_FAILED = 'INVOICE_FAILED'
// ------------------------------------
// Actions
// ------------------------------------
export function setInvoice(invoice) {
return {
type: SET_INVOICE,
invoice
}
}
export function getInvoice() {
return {
type: GET_INVOICE
@ -104,6 +114,8 @@ export function invoiceFailed() {
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[SET_INVOICE]: (state, { invoice }) => ({ ...state, invoice }),
[GET_INVOICE]: (state) => ({ ...state, invoiceLoading: true }),
[RECEIVE_INVOICE]: (state, { data }) => ({ ...state, invoiceLoading: false, data }),
@ -115,13 +127,23 @@ const ACTION_HANDLERS = {
[INVOICE_FAILED]: (state) => ({ ...state, invoiceLoading: false, data: null })
}
const invoiceSelectors = {}
const modalInvoiceSelector = state => state.invoice.invoice
invoiceSelectors.invoiceModalOpen = createSelector(
modalInvoiceSelector,
invoice => invoice ? true : false
)
export { invoiceSelectors }
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
invoiceLoading: false,
invoices: [],
invoice: {},
invoice: null,
data: {}
}

24
app/reducers/payment.js

@ -1,8 +1,11 @@
import { createSelector } from 'reselect'
import { callApi } from '../api'
import { btc } from '../utils'
// ------------------------------------
// Constants
// ------------------------------------
export const SET_PAYMENT = 'SET_PAYMENT'
export const GET_PAYMENTS = 'GET_PAYMENTS'
export const RECEIVE_PAYMENTS = 'RECEIVE_PAYMENTS'
@ -13,6 +16,13 @@ export const PAYMENT_FAILED = 'PAYMENT_FAILED'
// ------------------------------------
// Actions
// ------------------------------------
export function setPayment(payment) {
return {
type: SET_PAYMENT,
payment
}
}
export function getPayments() {
return {
type: GET_PAYMENTS
@ -76,16 +86,28 @@ export const payInvoice = (payment_request) => async (dispatch) => {
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[SET_PAYMENT]: (state, { payment }) => ({ ...state, payment }),
[GET_PAYMENTS]: (state) => ({ ...state, paymentLoading: true }),
[RECEIVE_PAYMENTS]: (state, { payments }) => ({ ...state, paymentLoading: false, payments })
}
const paymentSelectors = {}
const modalPaymentSelector = state => state.payment.payment
paymentSelectors.paymentModalOpen = createSelector(
modalPaymentSelector,
payment => payment ? true : false
)
export { paymentSelectors }
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
paymentLoading: false,
payments: []
payments: [],
payment: null
}
export default function paymentReducer(state = initialState, action) {

3
app/reducers/ticker.js

@ -1,3 +1,4 @@
import { createSelector } from 'reselect'
import { requestTicker } from '../api'
// ------------------------------------
// Constants
@ -43,7 +44,7 @@ export const fetchTicker = () => async (dispatch) => {
const ACTION_HANDLERS = {
[SET_CURRENCY]: (state, { currency }) => ({ ...state, currency }),
[GET_TICKER]: (state) => ({ ...state, tickerLoading: true }),
[RECIEVE_TICKER]: (state, { ticker }) => ({...state, btcTicker: ticker[0] })
[RECIEVE_TICKER]: (state, { ticker }) => ({...state, tickerLoading: false, btcTicker: ticker[0] })
}
// ------------------------------------

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

@ -10,7 +10,7 @@ class Activity extends Component {
constructor(props, context) {
super(props, context)
this.state = {
tab: 1
tab: 2
}
}
@ -25,10 +25,14 @@ class Activity extends Component {
const { tab } = this.state
const {
ticker,
invoice: { invoices, invoiceLoading },
payment: { payments, paymentLoading }
invoice: { invoice, invoices, invoiceLoading },
payment: { payment, payments, paymentLoading },
setPayment,
setInvoice,
paymentModalOpen,
invoiceModalOpen
} = this.props
if (invoiceLoading || paymentLoading) { return <div>Loading...</div> }
return (
<div>
@ -62,9 +66,23 @@ class Activity extends Component {
>
{
tab === 1 ?
<Payments key={1} payments={payments} ticker={ticker} />
<Payments
key={1}
payment={payment}
payments={payments}
ticker={ticker}
setPayment={setPayment}
paymentModalOpen={paymentModalOpen}
/>
:
<Invoices key={2} invoices={invoices} ticker={ticker} />
<Invoices
key={2}
invoice={invoice}
invoices={invoices}
ticker={ticker}
setInvoice={setInvoice}
invoiceModalOpen={invoiceModalOpen}
/>
}
</CSSTransitionGroup>
</div>

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

@ -2,28 +2,71 @@
import React, { Component } from 'react'
import Moment from 'react-moment'
import 'moment-timezone'
import { FaBitcoin, FaDollar } from 'react-icons/lib/fa'
import Modal from './Modal'
import { btc } from '../../../../utils'
import styles from './Invoices.scss'
class Invoices extends Component {
render() {
const { invoices, ticker } = this.props
const {
invoice,
invoices,
ticker,
setInvoice,
invoiceModalOpen
} = this.props
return (
<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((invoice, index) => (
<li key={index} className={styles.invoice}>
<div>
<Modal isOpen={invoiceModalOpen} resetObject={setInvoice}>
{
invoice ?
<div className={styles.invoiceModal}>
<h3>{invoice.memo}</h3>
<p className={styles.paymentRequest}>{invoice.payment_request}</p>
<h1>
{
ticker.currency === 'btc' ?
<FaBitcoin style={{ verticalAlign: 'top' }} />
:
<FaDollar style={{ verticalAlign: 'top' }} />
}
<span className={styles.value}>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(invoice.value)
:
btc.satoshisToUsd(invoice.value, ticker.btcTicker.price_usd)
}
</span>
</h1>
<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((invoice, index) =>
<li key={index} className={styles.invoice} onClick={() => setInvoice(invoice)}>
<div className={styles.left}>
<div className={styles.path}>{`${invoice.payment_request.substring(0, 75)}...`}</div>
</div>
@ -42,11 +85,11 @@ class Invoices extends Component {
</div>
</li>
)
)
}
</ul>
}
</ul>
</div>
)
}
}
export default Invoices
export default Invoices

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

@ -1,5 +1,44 @@
@import '../../../../variables.scss';
.invoiceModal {
padding: 40px;
h3 {
font-size: 24px;
color: $black;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
}
.paymentRequest {
text-align: center;
font-size: 8px;
}
h1 {
text-align: center;
color: $main;
margin: 60px 30px 60px 0;
svg {
font-size: 30px;
}
.value {
font-size: 80px;
}
}
.date {
text-align: center;
time {
margin-left: 3px;
}
}
}
.invoices {
width: 75%;
margin: 0 auto;
@ -41,3 +80,11 @@
font-weight: bold;
}
}
.invoice {
cursor: pointer;
&:hover {
background: lighten($grey, 5%);
}
}

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

@ -0,0 +1,45 @@
// @flow
import React, { Component } from 'react'
import ReactModal from 'react-modal'
import Moment from 'react-moment'
import 'moment-timezone'
const customStyles = {
overlay: {
cursor: 'pointer'
},
content : {
top: 'auto',
left: '20%',
right: '0',
bottom: 'auto',
width: '40%',
margin: '50px auto'
}
}
class Modal extends Component {
render() {
const {
isOpen,
resetObject,
children
} = this.props
return (
<ReactModal
isOpen={isOpen}
contentLabel="No Overlay Click Modal"
ariaHideApp={true}
shouldCloseOnOverlayClick={true}
onRequestClose={() => resetObject(null)}
parentSelector={() => document.body}
style={customStyles}
>
{children}
</ReactModal>
)
}
}
export default Modal

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

@ -2,65 +2,112 @@
import React, { Component } from 'react'
import Moment from 'react-moment'
import 'moment-timezone'
import { FaBitcoin, FaDollar } from 'react-icons/lib/fa'
import Modal from './Modal'
import { btc } from '../../../../utils'
import styles from './Payments.scss'
class Payments extends Component {
render() {
const { payments, ticker } = this.props
const {
payment,
payments,
ticker,
setPayment,
paymentModalOpen
} = this.props
return (
<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((payment, index) =>
<li key={index} className={styles.payment}>
<div className={styles.left}>
<div className={styles.path}>{payment.path[0]}</div>
<div>
<Modal isOpen={paymentModalOpen} resetObject={setPayment}>
{
payment ?
<div className={styles.paymentModal}>
<h3>{payment.payment_hash}</h3>
<h1>
{
ticker.currency === 'btc' ?
<FaBitcoin style={{ verticalAlign: 'top' }} />
:
<FaDollar style={{ verticalAlign: 'top' }} />
}
<span className={styles.value}>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(payment.value)
:
btc.satoshisToUsd(payment.value, ticker.btcTicker.price_usd)
}
</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>
<div className={styles.center}>
<div className={styles.date}>
<Moment format="MMMM Do">{payment.creation_date * 1000}</Moment>
:
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((payment, index) =>
<li key={index} className={styles.payment} onClick={() => setPayment(payment)}>
<div className={styles.left}>
<div className={styles.path}>{payment.path[0]}</div>
</div>
</div>
<div className={styles.right}>
<span className={styles.fee}>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(payment.fee)
:
btc.satoshisToUsd(payment.fee, ticker.btcTicker.price_usd)
}
</span>
</div>
<div className={styles.right}>
<span className={styles.value}>
<div className={styles.center}>
<div className={styles.date}>
<Moment format="MMMM Do">{payment.creation_date * 1000}</Moment>
</div>
</div>
<div className={styles.right}>
<span className={styles.fee}>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(payment.value)
btc.satoshisToBtc(payment.fee)
:
btc.satoshisToUsd(payment.value, ticker.btcTicker.price_usd)
btc.satoshisToUsd(payment.fee, ticker.btcTicker.price_usd)
}
</span>
</div>
</li>
)
}
</ul>
</span>
</div>
<div className={styles.right}>
<span className={styles.value}>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(payment.value)
:
btc.satoshisToUsd(payment.value, ticker.btcTicker.price_usd)
}
</span>
</div>
</li>
)
}
</ul>
</div>
)
}
}
export default Payments
export default Payments

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

@ -1,5 +1,48 @@
@import '../../../../variables.scss';
.paymentModal {
padding: 40px;
h1 {
text-align: center;
color: $main;
margin: 60px 30px 60px 0;
svg {
font-size: 30px;
}
.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;

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

@ -1,9 +1,11 @@
import { connect } from 'react-redux'
import { fetchInvoices } from '../../../reducers/invoice'
import { fetchPayments } from '../../../reducers/payment'
import { fetchInvoices, setInvoice, invoiceSelectors } from '../../../reducers/invoice'
import { setPayment, fetchPayments, paymentSelectors } from '../../../reducers/payment'
import Activity from '../components/Activity'
const mapDispatchToProps = {
setPayment,
setInvoice,
fetchPayments,
fetchInvoices
}
@ -12,7 +14,9 @@ const mapStateToProps = (state) => ({
activity: state.activity,
payment: state.payment,
invoice: state.invoice,
ticker: state.ticker
ticker: state.ticker,
paymentModalOpen: paymentSelectors.paymentModalOpen(state),
invoiceModalOpen: invoiceSelectors.invoiceModalOpen(state)
})
export default connect(mapStateToProps, mapDispatchToProps)(Activity)

5
package-lock.json

@ -12988,6 +12988,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"reselect": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz",
"integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc="
},
"resolve": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",

1
package.json

@ -203,6 +203,7 @@
"react-svg": "^2.1.21",
"redux": "^3.7.1",
"redux-thunk": "^2.2.0",
"reselect": "^3.0.1",
"satoshi-bitcoin": "^1.0.4",
"source-map-support": "^0.4.15"
},

Loading…
Cancel
Save