Browse Source

Merge pull request #30 from LN-Zap/feature/sendcoins

Feature/sendcoins
renovate/lint-staged-8.x
jackmallers 7 years ago
committed by GitHub
parent
commit
090e35afdc
  1. 50
      app/animated_checkmark.scss
  2. 32
      app/app.global.scss
  3. 6
      app/components/AnimatedCheckmark/AnimatedCheckmark.js
  4. 4
      app/components/AnimatedCheckmark/checkmark.svg
  5. 3
      app/components/AnimatedCheckmark/index.js
  6. 15
      app/components/LoadingBolt/LoadingBolt.js
  7. 29
      app/components/LoadingBolt/LoadingBolt.scss
  8. 3
      app/components/LoadingBolt/index.js
  9. 2
      app/lnd/config/index.js
  10. 28
      app/lnd/methods/index.js
  11. 40
      app/reducers/form.js
  12. 2
      app/reducers/index.js
  13. 4
      app/reducers/ipc.js
  14. 47
      app/reducers/modal.js
  15. 33
      app/reducers/payment.js
  16. 60
      app/routes/activity/components/components/Payments.js
  17. 24
      app/routes/app/components/App.js
  18. 147
      app/routes/app/components/components/Form/Form.js
  19. 61
      app/routes/app/components/components/Form/Form.scss
  20. 146
      app/routes/app/components/components/Form/components/Pay/Pay.js
  21. 142
      app/routes/app/components/components/Form/components/Pay/Pay.scss
  22. 3
      app/routes/app/components/components/Form/components/Pay/index.js
  23. 68
      app/routes/app/components/components/Form/components/Request/Request.js
  24. 110
      app/routes/app/components/components/Form/components/Request/Request.scss
  25. 3
      app/routes/app/components/components/Form/components/Request/index.js
  26. 42
      app/routes/app/components/components/ModalRoot/ModalRoot.js
  27. 42
      app/routes/app/components/components/ModalRoot/ModalRoot.scss
  28. 40
      app/routes/app/components/components/ModalRoot/SuccessfulSendCoins/SuccessfulSendCoins.js
  29. 37
      app/routes/app/components/components/ModalRoot/SuccessfulSendCoins/SuccessfulSendCoins.scss
  30. 3
      app/routes/app/components/components/ModalRoot/SuccessfulSendCoins/index.js
  31. 3
      app/routes/app/components/components/ModalRoot/index.js
  32. 17
      app/routes/app/containers/AppContainer.js
  33. 3
      app/variables.scss
  34. 6
      package.json
  35. 47
      resources/cloudbolt.svg
  36. 5
      resources/thunderstorm.svg
  37. 7
      test/reducers/__snapshots__/form.spec.js.snap
  38. 9
      test/reducers/__snapshots__/payment.spec.js.snap
  39. 86
      yarn.lock

50
app/animated_checkmark.scss

@ -0,0 +1,50 @@
$curve: cubic-bezier(0.650, 0.000, 0.450, 1.000);
.checkmark__circle {
stroke-dasharray: 166;
stroke-dashoffset: 166;
stroke-width: 2;
stroke-miterlimit: 10;
stroke: $main;
fill: none;
animation: stroke .6s $curve forwards;
}
.checkmark {
width: 56px;
height: 56px;
border-radius: 50%;
stroke-width: 2;
stroke: #fff;
stroke-miterlimit: 10;
box-shadow: inset 0px 0px 0px $main;
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
}
.checkmark__check {
transform-origin: 50% 50%;
stroke-dasharray: 48;
stroke-dashoffset: 48;
animation: stroke .3s $curve .8s forwards;
}
@keyframes stroke {
100% {
stroke-dashoffset: 0;
}
}
@keyframes scale {
0%, 100% {
transform: none;
}
50% {
transform: scale3d(1.1, 1.1, 1);
}
}
@keyframes fill {
100% {
box-shadow: inset 0px 0px 0px 30px $main;
}
}

32
app/app.global.scss

@ -2,6 +2,7 @@
@import 'reset.scss';
@import 'variables.scss';
@import 'tooltip.scss';
@import 'animated_checkmark.scss';
@font-face {
font-family: 'Roboto';
@ -34,4 +35,35 @@ body {
&--before-close {
opacity: 0;
}
}
.load-draw-svg {
stroke-dasharray: 211;
stroke-dashoffset: 2110;
stroke-width: 2;
stroke-linecap: round;
animation: dash 15s linear infinite;
fill-opacity: 0;
stroke: $main;
}
@keyframes dash {
0% {
stroke-dashoffset: 2110;
opacity: 1;
stroke: $main;
}
15%{
opacity: 1;
stroke: $main;
}
70%{
opacity: 1;
stroke: $main;
}
100%{
stroke-dashoffset: 0;
opacity: 1;
stroke: darken($main, 10%);
}
}

6
app/components/AnimatedCheckmark/AnimatedCheckmark.js

@ -0,0 +1,6 @@
import React from 'react'
import Isvg from 'react-inlinesvg'
const AnimatedCheckmark = () => <Isvg src={'./components/AnimatedCheckmark/checkmark.svg'} />
export default AnimatedCheckmark

4
app/components/AnimatedCheckmark/checkmark.svg

@ -0,0 +1,4 @@
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
<path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>

After

Width:  |  Height:  |  Size: 238 B

3
app/components/AnimatedCheckmark/index.js

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

15
app/components/LoadingBolt/LoadingBolt.js

@ -0,0 +1,15 @@
import React from 'react'
import path from 'path'
import Isvg from 'react-inlinesvg'
import styles from './LoadingBolt.scss'
const LoadingBolt = () => (
<div className={styles.container}>
<div className={styles.content}>
<Isvg className={styles.bolt} src={path.join(__dirname, '..', 'resources/cloudbolt.svg')} />
<h1>loading</h1>
</div>
</div>
)
export default LoadingBolt

29
app/components/LoadingBolt/LoadingBolt.scss

@ -0,0 +1,29 @@
@import '../../variables.scss';
.container {
z-index: 1000;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: $white;
text-align: center;
}
.content {
position: relative;
top: calc(50% - 250px);
min-height: 250px;
.bolt svg {
height: 150px;
width: 150px;
}
h1 {
color: $main;
margin-top: 50px;
font-size: 25px;
}
}

3
app/components/LoadingBolt/index.js

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

2
app/lnd/config/index.js

@ -7,5 +7,5 @@ import { userInfo } from 'os'
export default {
lightningRpc: `${__dirname}/rpc.proto`,
lightningHost: 'localhost:10009',
cert: `/Users/${userInfo().username}/Library/Application\ Support/Lnd/tls.cert`
cert: `/Users/${userInfo().username}/Library/Application Support/Lnd/tls.cert`
}

28
app/lnd/methods/index.js

@ -7,16 +7,15 @@ import * as peersController from './peersController'
import * as paymentsController from './paymentsController'
import * as networkController from './networkController'
//TODO - GetChanInfo
//TODO - GetTransactions
//TODO - GetNodeInfo
//TODO - DescribeGraph
//TODO - GetNetworkInfo
//TODO - QueryRoutes
//TODO - DecodePayReq
//TODO - SendPayment
//TODO - DeleteAllPayments
// TODO - GetChanInfo
// TODO - GetTransactions
// TODO - GetNodeInfo
// TODO - DescribeGraph
// TODO - GetNetworkInfo
// TODO - QueryRoutes
// TODO - DecodePayReq
// TODO - SendPayment
// TODO - DeleteAllPayments
export default function (lnd, event, msg, data) {
@ -95,13 +94,10 @@ export default function (lnd, event, msg, data) {
break
case 'sendCoins':
// Transaction looks like { txid: String }
// { addr, amount } = data
// { amount, addr } = data
walletController.sendCoins(lnd, data)
.then((transaction) => {
console.log('transaction: ', transaction)
event.sender.send('sendSuccessful', { transaction })
})
.catch(error => console.log('sendcoins error: ', error))
.then(({ txid }) => event.sender.send('sendSuccessful', { amount: data.amount, addr: data.addr, txid }))
.catch(error => event.sender.send('sendCoinsError', { error }))
break
case 'openChannel':
// Response is empty. Streaming updates on channel status and updates

40
app/reducers/form.js

@ -1,8 +1,12 @@
import { createSelector } from 'reselect'
import bitcoin from 'bitcoinjs-lib'
// Initial State
const initialState = {
modalOpen: false,
formType: 'pay',
amount: '0',
onchainAmount: '0',
message: '',
pubkey: '',
payment_request: ''
@ -12,6 +16,7 @@ const initialState = {
// ------------------------------------
export const SET_FORM = 'SET_FORM'
export const SET_AMOUNT = 'SET_AMOUNT'
export const SET_ONCHAIN_AMOUNT = 'SET_ONCHAIN_AMOUNT'
export const SET_MESSAGE = 'SET_MESSAGE'
export const SET_PUBKEY = 'SET_PUBKEY'
export const SET_PAYMENT_REQUEST = 'SET_PAYMENT_REQUEST'
@ -35,6 +40,13 @@ export function setAmount(amount) {
}
}
export function setOnchainAmount(onchainAmount) {
return {
type: SET_ONCHAIN_AMOUNT,
onchainAmount
}
}
export function setMessage(message) {
return {
type: SET_MESSAGE,
@ -68,12 +80,40 @@ export function resetForm() {
const ACTION_HANDLERS = {
[SET_FORM]: (state, { modalOpen, formType }) => ({ ...state, modalOpen, formType }),
[SET_AMOUNT]: (state, { amount }) => ({ ...state, amount }),
[SET_ONCHAIN_AMOUNT]: (state, { onchainAmount }) => ({ ...state, onchainAmount }),
[SET_MESSAGE]: (state, { message }) => ({ ...state, message }),
[SET_PUBKEY]: (state, { pubkey }) => ({ ...state, pubkey }),
[SET_PAYMENT_REQUEST]: (state, { payment_request }) => ({ ...state, payment_request }),
[RESET_FORM]: () => (initialState)
}
// ------------------------------------
// Selector
// ------------------------------------
const formSelectors = {}
const paymentRequestSelector = state => state.form.payment_request
formSelectors.isOnchain = createSelector(
paymentRequestSelector,
(paymentRequest) => {
// TODO: work with bitcoin-js to fix p2wkh error and make testnet/mainnet dynamic
try {
bitcoin.address.toOutputScript(paymentRequest, bitcoin.networks.testnet)
return true
} catch (e) {
return false
}
}
)
// TODO: Add more robust logic to detect a LN payment request
formSelectors.isLn = createSelector(
paymentRequestSelector,
paymentRequest => paymentRequest.length === 124
)
export { formSelectors }
// ------------------------------------
// Reducer
// ------------------------------------

2
app/reducers/index.js

@ -9,6 +9,7 @@ import peers from './peers'
import channels from './channels'
import form from './form'
import invoice from './invoice'
import modal from './modal'
import address from './address'
const rootReducer = combineReducers({
@ -21,6 +22,7 @@ const rootReducer = combineReducers({
channels,
form,
invoice,
modal,
address
})

4
app/reducers/ipc.js

@ -19,7 +19,7 @@ import {
pushclosechannelstatus
} from './channels'
import { receivePayments, paymentSuccessful } from './payment'
import { receivePayments, paymentSuccessful, sendSuccessful, sendCoinsError } from './payment'
import { receiveInvoices, createdInvoice, receiveFormInvoice } from './invoice'
import { receiveBalance } from './balance'
@ -40,6 +40,8 @@ const ipc = createIpc({
receiveBalance,
paymentSuccessful,
sendSuccessful,
sendCoinsError,
channelSuccessful,
pushchannelupdated,

47
app/reducers/modal.js

@ -0,0 +1,47 @@
// ------------------------------------
// Initial State
// ------------------------------------
const initialState = {
modalType: null,
modalProps: {}
}
// ------------------------------------
// Constants
// ------------------------------------
export const SHOW_MODAL = 'SHOW_MODAL'
export const HIDE_MODAL = 'HIDE_MODAL'
// ------------------------------------
// Actions
// ------------------------------------
export function showModal(modalType, modalProps) {
return {
type: SHOW_MODAL,
modalType,
modalProps
}
}
export function hideModal() {
return {
type: HIDE_MODAL
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[SHOW_MODAL]: (state, { modalType, modalProps }) => ({ ...state, modalType, modalProps }),
[HIDE_MODAL]: () => initialState
}
// ------------------------------------
// Reducer
// ------------------------------------
export default function modalReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

33
app/reducers/payment.js

@ -1,5 +1,8 @@
import { createSelector } from 'reselect'
import { ipcRenderer } from 'electron'
import { btc, usd } from '../utils'
import { setForm, resetForm } from './form'
import { showModal } from './modal'
// ------------------------------------
// Constants
@ -10,6 +13,7 @@ export const GET_PAYMENTS = 'GET_PAYMENTS'
export const RECEIVE_PAYMENTS = 'RECEIVE_PAYMENTS'
export const SEND_PAYMENT = 'SEND_PAYMENT'
export const PAYMENT_SUCCESSFULL = 'PAYMENT_SUCCESSFULL'
export const PAYMENT_FAILED = 'PAYMENT_FAILED'
@ -62,10 +66,32 @@ 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) => {
// 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 })
// Reset the payment form
dispatch(resetForm())
}
export const sendCoinsError = () => (dispatch) => {
dispatch({ type: PAYMENT_FAILED })
}
// ------------------------------------
// Action Handlers
@ -73,10 +99,10 @@ export const paymentSuccessful = () => fetchPayments()
const ACTION_HANDLERS = {
[SET_PAYMENT]: (state, { payment }) => ({ ...state, payment }),
[GET_PAYMENTS]: state => ({ ...state, paymentLoading: true }),
[SEND_PAYMENT]: state => ({ ...state, sendingPayment: true }),
[RECEIVE_PAYMENTS]: (state, { payments }) => ({ ...state, paymentLoading: false, payments }),
[PAYMENT_SUCCESSFULL]: (state, { payment }) => (
{ ...state, paymentLoading: false, payments: [payment, ...state.payments] }
)
[PAYMENT_SUCCESSFULL]: state => ({ ...state, sendingPayment: false }),
[PAYMENT_FAILED]: state => ({ ...state, sendingPayment: false })
}
const paymentSelectors = {}
@ -93,6 +119,7 @@ export { paymentSelectors }
// Reducer
// ------------------------------------
const initialState = {
sendingPayment: false,
paymentLoading: false,
payments: [],
payment: null

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

@ -62,36 +62,38 @@ const Payments = ({
</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>
(
<li key={index} className={styles.payment} onClick={() => setPayment(paymentItem)}>
<div className={styles.left}>
<div className={styles.path}>{paymentItem.path[0]}</div>
</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>)
<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>

24
app/routes/app/components/App.js

@ -1,5 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ModalRoot from './components/ModalRoot'
import Form from './components/Form'
import Nav from './components/Nav'
import styles from './App.scss'
@ -15,11 +16,14 @@ class App extends Component {
render() {
const {
modal: { modalType, modalProps },
hideModal,
ticker,
balance,
invoice: { formInvoice },
form,
setAmount,
setOnchainAmount,
setMessage,
setPubkey,
setPaymentRequest,
@ -29,8 +33,11 @@ class App extends Component {
setForm,
createInvoice,
payInvoice,
sendCoins,
fetchInvoice,
currentTicker,
isOnchain,
isLn,
children
} = this.props
@ -38,10 +45,18 @@ class App extends Component {
return (
<div>
<ModalRoot
modalType={modalType}
modalProps={modalProps}
hideModal={hideModal}
currentTicker={currentTicker}
currency={ticker.currency}
/>
<Form
isOpen={form.modalOpen}
close={() => setForm({ modalOpen: false })}
setAmount={setAmount}
setOnchainAmount={setOnchainAmount}
setMessage={setMessage}
setPubkey={setPubkey}
setPaymentRequest={setPaymentRequest}
@ -51,9 +66,12 @@ class App extends Component {
form={form}
createInvoice={createInvoice}
payInvoice={payInvoice}
sendCoins={sendCoins}
fetchInvoice={fetchInvoice}
formInvoice={formInvoice}
currentTicker={currentTicker}
isOnchain={isOnchain}
isLn={isLn}
/>
<Nav
ticker={ticker}
@ -71,6 +89,8 @@ class App extends Component {
}
App.propTypes = {
modal: PropTypes.object.isRequired,
hideModal: PropTypes.func.isRequired,
fetchTicker: PropTypes.func.isRequired,
fetchBalance: PropTypes.func.isRequired,
ticker: PropTypes.object.isRequired,
@ -78,6 +98,7 @@ App.propTypes = {
invoice: PropTypes.object.isRequired,
form: PropTypes.object.isRequired,
setAmount: PropTypes.func.isRequired,
setOnchainAmount: PropTypes.func.isRequired,
setMessage: PropTypes.func.isRequired,
setPubkey: PropTypes.func.isRequired,
setPaymentRequest: PropTypes.func.isRequired,
@ -87,9 +108,12 @@ App.propTypes = {
setForm: PropTypes.func.isRequired,
createInvoice: PropTypes.func.isRequired,
payInvoice: PropTypes.func.isRequired,
sendCoins: PropTypes.func.isRequired,
fetchInvoice: PropTypes.func.isRequired,
fetchInfo: PropTypes.func.isRequired,
currentTicker: PropTypes.object,
isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired,
children: PropTypes.object.isRequired
}

147
app/routes/app/components/components/Form/Form.js

@ -1,13 +1,15 @@
import React from 'react'
import PropTypes from 'prop-types'
import { MdClose } from 'react-icons/lib/md'
import CurrencyIcon from '../../../../../components/CurrencyIcon'
import { btc } from '../../../../../utils'
import Pay from './components/Pay'
import Request from './components/Request'
import styles from './Form.scss'
const Form = ({
form: { formType, amount, message, payment_request },
form: { formType, amount, onchainAmount, message, payment_request },
payment: { sendingPayment },
setAmount,
setOnchainAmount,
setMessage,
setPaymentRequest,
ticker: { currency, crypto },
@ -15,110 +17,77 @@ const Form = ({
close,
createInvoice,
payInvoice,
sendCoins,
fetchInvoice,
formInvoice,
currentTicker
}) => {
const requestClicked = () => {
createInvoice(amount, message, currency, currentTicker.price_usd)
close()
}
const payClicked = () => {
payInvoice(payment_request)
close()
}
const paymentRequestOnChange = (payreq) => {
setPaymentRequest(payreq)
if (payreq.length === 124) { fetchInvoice(payreq) }
}
const calculateAmount = value => (currency === 'usd' ? btc.satoshisToUsd(value, currentTicker.price_usd) : btc.satoshisToBtc(value))
return (
<div className={`${styles.formContainer} ${isOpen ? styles.open : ''}`}>
<div className={styles.container}>
<div className={styles.esc} onClick={close}>
<MdClose />
</div>
<div className={styles.content}>
<section className={styles.amountContainer}>
<label htmlFor='amount'>
<CurrencyIcon currency={currency} crypto={crypto} />
</label>
<input
type='text'
size=''
style={
formType === 'pay' ?
{ width: '75%', fontSize: '85px' }
:
{ width: `${amount.length > 1 ? (amount.length * 15) - 5 : 25}%`, fontSize: `${190 - (amount.length ** 2)}px` }
}
value={formType === 'pay' ? calculateAmount(formInvoice.amount) : amount}
onChange={event => setAmount(event.target.value)}
readOnly={formType === 'pay'}
id='amount'
currentTicker,
isOnchain,
isLn
}) => (
<div className={`${styles.formContainer} ${isOpen ? styles.open : ''}`}>
<div className={styles.container}>
<div className={styles.esc} onClick={close}>
<MdClose />
</div>
<div className={styles.content}>
{
formType === 'pay' ?
<Pay
sendingPayment={sendingPayment}
invoiceAmount={formInvoice.amount}
onchainAmount={onchainAmount}
setOnchainAmount={setOnchainAmount}
amount={formInvoice.amount}
payment_request={payment_request}
setPaymentRequest={setPaymentRequest}
fetchInvoice={fetchInvoice}
payInvoice={payInvoice}
sendCoins={sendCoins}
currentTicker={currentTicker}
currency={currency}
crypto={crypto}
close={close}
isOnchain={isOnchain}
isLn={isLn}
/>
:
<Request
amount={amount}
setAmount={setAmount}
payment_request={payment_request}
setMessage={setMessage}
createInvoice={createInvoice}
message={message}
currentTicker={currentTicker}
currency={currency}
crypto={crypto}
close={close}
/>
</section>
{
formType === 'pay' ?
<section className={styles.inputContainer}>
<label htmlFor='paymentRequest'>Request:</label>
<input
type='text'
placeholder='Payment Request'
value={payment_request}
onChange={event => paymentRequestOnChange(event.target.value)}
id='paymentRequest'
/>
</section>
:
<section className={styles.inputContainer}>
<label htmlFor='message'>For:</label>
<input
type='text'
placeholder='Dinner, Rent, etc'
value={message}
onChange={event => setMessage(event.target.value)}
id='message'
/>
</section>
}
{
formType === 'pay' ?
<section className={styles.buttonGroup}>
<div className={styles.button} onClick={payClicked}>
Pay
</div>
</section>
:
<section className={styles.buttonGroup}>
<div className={styles.button} onClick={requestClicked}>
Request
</div>
</section>
}
</div>
}
</div>
</div>
)
}
</div>
)
Form.propTypes = {
payment: PropTypes.object.isRequired,
form: PropTypes.object.isRequired,
ticker: PropTypes.object.isRequired,
setAmount: PropTypes.func.isRequired,
setOnchainAmount: PropTypes.func.isRequired,
setMessage: PropTypes.func.isRequired,
setPaymentRequest: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
createInvoice: PropTypes.func.isRequired,
payInvoice: PropTypes.func.isRequired,
sendCoins: PropTypes.func.isRequired,
fetchInvoice: PropTypes.func.isRequired,
formInvoice: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired
currentTicker: PropTypes.object.isRequired,
isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired
}
export default Form

61
app/routes/app/components/components/Form/Form.scss

@ -128,67 +128,6 @@
}
}
.peersContainer {
width: 100%;
margin: 10px 0 50px 0;
h4 {
text-transform: uppercase;
color: $black;
letter-spacing: 2;
font-weight: bold;
margin-bottom: 20px;
padding: 10px 0;
}
.peers {
overflow-y: scroll;
width: 100%;
}
.peer {
position: relative;
width: 100%;
border-top: 1px solid $traditionalgrey;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
border-left: 1px solid transparent;
padding: 5px;
cursor: pointer;
transition: all 0.25s;
&:hover {
background: lighten($main, 20%);
border-radius: 6px;
border: 1px solid $main;
svg {
visibility: visible;
}
}
p {
margin: 3px 0;
}
.address {
font-size: 18px;
}
.pubkey {
font-size: 12px;
}
svg {
visibility: hidden;
position: absolute;
top: calc(50% - 8px);
right: 5%;
color: $black;
}
}
}
.buttonGroup {
width: 100%;
display: flex;

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

@ -0,0 +1,146 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { FaBolt, FaChain } from 'react-icons/lib/fa'
import CurrencyIcon from '../../../../../../../components/CurrencyIcon'
import LoadingBolt from '../../../../../../../components/LoadingBolt'
import { btc } from '../../../../../../../utils'
import styles from './Pay.scss'
class Pay extends Component {
componentDidUpdate(prevProps) {
const { isOnchain, isLn, fetchInvoice, payment_request } = this.props
if (isOnchain) { this.amountInput.focus() }
if ((prevProps.payment_request !== payment_request) && isLn) { fetchInvoice(payment_request) }
}
render() {
const {
sendingPayment,
invoiceAmount,
onchainAmount,
setOnchainAmount,
payment_request,
setPaymentRequest,
payInvoice,
sendCoins,
currentTicker,
currency,
crypto,
isOnchain,
isLn
} = this.props
const payClicked = () => {
if (!isOnchain && !isLn) { return }
if (isOnchain) { sendCoins({ value: onchainAmount, addr: payment_request, currency, rate: currentTicker.price_usd }) }
if (isLn) { payInvoice(payment_request) }
}
const calculateAmount = value => (currency === 'usd' ? btc.satoshisToUsd(value, currentTicker.price_usd) : btc.satoshisToBtc(value))
return (
<div>
{
sendingPayment ?
<LoadingBolt />
:
null
}
<div className={styles.container}>
<section className={`${styles.amountContainer} ${isLn ? styles.ln : ''}`}>
<label htmlFor='amount'>
<CurrencyIcon currency={currency} crypto={crypto} />
</label>
<input
type='text'
ref={input => this.amountInput = input} // eslint-disable-line
size=''
style={
isLn ?
{ width: '75%', fontSize: '85px' }
:
{ width: `${onchainAmount.length > 1 ? (onchainAmount.length * 15) - 5 : 25}%`, fontSize: `${190 - (onchainAmount.length ** 2)}px` }
}
value={isLn ? calculateAmount(invoiceAmount) : onchainAmount}
onChange={event => setOnchainAmount(event.target.value)}
id='amount'
readOnly={isLn}
/>
</section>
<div className={styles.inputContainer}>
<div className={styles.info}>
{(() => {
if (isOnchain) {
return (
<span>{`You're about to send ${onchainAmount} ${currency.toUpperCase()} on-chain which should take around 10 minutes`}</span>
)
} else if (isLn) {
return (
<span>{`You're about to send ${calculateAmount(invoiceAmount)} ${currency.toUpperCase()} over the Lightning Network which will be instant`}</span> // eslint-disable-line
)
}
return null
})()}
</div>
<aside className={styles.paymentIcon}>
{(() => {
if (isOnchain) {
return (
<i>
<span>on-chain</span>
<FaChain />
</i>
)
} else if (isLn) {
return (
<i>
<span>lightning network</span>
<FaBolt />
</i>
)
}
return null
})()}
</aside>
<section className={styles.input}>
<input
type='text'
placeholder='Payment request or bitcoin address'
value={payment_request}
onChange={event => setPaymentRequest(event.target.value)}
id='paymentRequest'
/>
</section>
</div>
<section className={styles.buttonGroup}>
<div className={styles.button} onClick={payClicked}>
Pay
</div>
</section>
</div>
</div>
)
}
}
Pay.propTypes = {
sendingPayment: PropTypes.bool.isRequired,
invoiceAmount: PropTypes.string.isRequired,
onchainAmount: PropTypes.string.isRequired,
setOnchainAmount: PropTypes.func.isRequired,
payment_request: PropTypes.string.isRequired,
setPaymentRequest: PropTypes.func.isRequired,
fetchInvoice: PropTypes.func.isRequired,
payInvoice: PropTypes.func.isRequired,
sendCoins: PropTypes.func.isRequired,
currentTicker: PropTypes.object.isRequired,
currency: PropTypes.string.isRequired,
crypto: PropTypes.string.isRequired,
isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired
}
export default Pay

142
app/routes/app/components/components/Form/components/Pay/Pay.scss

@ -0,0 +1,142 @@
@import '../../../../../../../variables.scss';
.container {
margin: 0 auto;
display: flex;
flex-direction: column;
height: 75vh;
justify-content: center;
align-items: center;
}
.amountContainer {
color: $main;
display: flex;
justify-content: center;
min-height: 120px;
margin-bottom: 20px;
min-height: 175px;
&.ln {
opacity: 0.75;
}
label, input[type=text] {
color: inherit;
display: inline-block;
vertical-align: top;
padding: 0;
}
label {
svg {
width: 85px;
height: 85px;
}
svg[data-icon='ltc'] {
margin-right: 10px;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
}
input[type=text] {
width: 100px;
font-size: 180px;
border: none;
outline: 0;
-webkit-appearance: none;
}
}
.inputContainer {
position: relative;
width: 100%;
padding: 40px 0;
cursor: pointer;
.info {
margin-bottom: 10px;
min-height: 19px;
}
.paymentIcon {
position: absolute;
width: 20%;
left: calc(-12.5% - 75px);
top: 42px;
color: $main;
font-size: 50px;
text-align: center;
span {
text-transform: uppercase;
display: block;
font-size: 12px;
font-weight: 200;
}
}
}
.input {
display: flex;
justify-content: center;
font-size: 18px;
height: auto;
min-height: 55px;
border: 1px solid $traditionalgrey;
border-radius: 6px;
position: relative;
padding: 0 20px;
label, input[type=text] {
font-size: inherit;
}
label {
padding-top: 19px;
padding-bottom: 12px;
color: $traditionalgrey;
}
input[type=text] {
width: 100%;
border: none;
outline: 0;
-webkit-appearance: none;
height: 55px;
padding: 0 10px;
}
}
.buttonGroup {
width: 100%;
display: flex;
flex-direction: row;
border-radius: 6px;
overflow: hidden;
.button {
cursor: pointer;
height: 55px;
min-height: 55px;
text-transform: none;
font-size: 18px;
transition: opacity .2s ease-out;
background: $main;
color: $white;
border: none;
font-weight: 500;
padding: 0;
width: 100%;
text-align: center;
line-height: 55px;
&:first-child {
border-right: 1px solid lighten($main, 20%);
}
}
}

3
app/routes/app/components/components/Form/components/Pay/index.js

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

68
app/routes/app/components/components/Form/components/Request/Request.js

@ -0,0 +1,68 @@
import React from 'react'
import PropTypes from 'prop-types'
import CurrencyIcon from '../../../../../../../components/CurrencyIcon'
import styles from './Request.scss'
const Request = ({
amount,
setAmount,
setMessage,
createInvoice,
message,
currentTicker,
currency,
crypto,
close
}) => {
const requestClicked = () => {
createInvoice(amount, message, currency, currentTicker.price_usd)
close()
}
return (
<div className={styles.container}>
<section className={styles.amountContainer}>
<label htmlFor='amount'>
<CurrencyIcon currency={currency} crypto={crypto} />
</label>
<input
type='text'
size=''
style={{ width: `${amount.length > 1 ? (amount.length * 15) - 5 : 25}%`, fontSize: `${190 - (amount.length ** 2)}px` }}
value={amount}
onChange={event => setAmount(event.target.value)}
id='amount'
/>
</section>
<section className={styles.inputContainer}>
<label htmlFor='paymentRequest'>Request:</label>
<input
type='text'
placeholder='Dinner, Rent, etc'
value={message}
onChange={event => setMessage(event.target.value)}
id='paymentRequest'
/>
</section>
<section className={styles.buttonGroup}>
<div className={styles.button} onClick={requestClicked}>
Request
</div>
</section>
</div>
)
}
Request.propTypes = {
amount: PropTypes.string.isRequired,
setAmount: PropTypes.func.isRequired,
setMessage: PropTypes.func.isRequired,
createInvoice: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
currentTicker: PropTypes.object.isRequired,
currency: PropTypes.string.isRequired,
crypto: PropTypes.string.isRequired,
close: PropTypes.func.isRequired
}
export default Request

110
app/routes/app/components/components/Form/components/Request/Request.scss

@ -0,0 +1,110 @@
@import '../../../../../../../variables.scss';
.container {
margin: 0 auto;
display: flex;
flex-direction: column;
height: 75vh;
justify-content: center;
align-items: center;
}
.amountContainer {
color: $main;
display: flex;
justify-content: center;
min-height: 120px;
margin-bottom: 20px;
label, input[type=text] {
color: inherit;
display: inline-block;
vertical-align: top;
padding: 0;
}
label {
svg {
width: 85px;
height: 85px;
}
svg[data-icon='ltc'] {
margin-right: 10px;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
}
input[type=text] {
width: 100px;
font-size: 180px;
border: none;
outline: 0;
-webkit-appearance: none;
}
}
.inputContainer {
width: 100%;
display: flex;
justify-content: center;
font-size: 18px;
height: auto;
min-height: 55px;
margin-bottom: 20px;
border: 1px solid $traditionalgrey;
border-radius: 6px;
position: relative;
padding: 0 20px;
label, input[type=text] {
font-size: inherit;
}
label {
padding-top: 19px;
padding-bottom: 12px;
color: $traditionalgrey;
}
input[type=text] {
width: 100%;
border: none;
outline: 0;
-webkit-appearance: none;
height: 55px;
padding: 0 10px;
}
}
.buttonGroup {
width: 100%;
display: flex;
flex-direction: row;
border-radius: 6px;
overflow: hidden;
.button {
cursor: pointer;
height: 55px;
min-height: 55px;
text-transform: none;
font-size: 18px;
transition: opacity .2s ease-out;
background: $main;
color: $white;
border: none;
font-weight: 500;
padding: 0;
width: 100%;
text-align: center;
line-height: 55px;
&:first-child {
border-right: 1px solid lighten($main, 20%);
}
}
}

3
app/routes/app/components/components/Form/components/Request/index.js

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

42
app/routes/app/components/components/ModalRoot/ModalRoot.js

@ -0,0 +1,42 @@
import React from 'react'
import PropTypes from 'prop-types'
import { MdClose } from 'react-icons/lib/md'
import SuccessfulSendCoins from './SuccessfulSendCoins'
import styles from './ModalRoot.scss'
const MODAL_COMPONENTS = {
SUCCESSFUL_SEND_COINS: SuccessfulSendCoins
/* other modals */
}
const ModalRoot = ({ modalType, modalProps, hideModal, currentTicker, currency }) => {
if (!modalType) { return null }
const SpecificModal = MODAL_COMPONENTS[modalType]
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.esc} onClick={hideModal}>
<MdClose />
</div>
<SpecificModal
{...modalProps}
hideModal={hideModal}
currentTicker={currentTicker}
currency={currency}
/>
</div>
</div>
)
}
ModalRoot.propTypes = {
modalType: PropTypes.string,
modalProps: PropTypes.object.isRequired,
hideModal: PropTypes.func.isRequired,
currentTicker: PropTypes.object.isRequired,
currency: PropTypes.string.isRequired
}
export default ModalRoot

42
app/routes/app/components/components/ModalRoot/ModalRoot.scss

@ -0,0 +1,42 @@
@import '../../../../../variables.scss';
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
background: #fff;
}
.content {
position: relative;
height: 100vh;
margin: 5%;
}
.esc {
position: absolute;
top: 0;
right: 0;
color: $darkestgrey;
cursor: pointer;
padding: 20px;
border-radius: 50%;
&:hover {
color: $bluegrey;
background: $darkgrey;
}
&:active {
color: $white;
background: $main;
}
svg {
width: 32px;
height: 32px;
}
}

40
app/routes/app/components/components/ModalRoot/SuccessfulSendCoins/SuccessfulSendCoins.js

@ -0,0 +1,40 @@
import { shell } from 'electron'
import React from 'react'
import PropTypes from 'prop-types'
import AnimatedCheckmark from '../../../../../../components/AnimatedCheckmark'
import { btc } from '../../../../../../utils'
import styles from './SuccessfulSendCoins.scss'
const SuccessfulSendCoins = ({ amount, addr, txid, hideModal, currentTicker, currency }) => {
const calculatedAmount = currency === 'usd' ? btc.satoshisToUsd(amount, currentTicker.price_usd) : btc.satoshisToBtc(amount)
return (
<div className={styles.container}>
<AnimatedCheckmark />
<h1>
You&nbsp;
<span className={styles.link} onClick={() => shell.openExternal(`https://testnet.smartbit.com.au/tx/${txid}`)}>sent</span>&nbsp;
<span className={styles.amount}>{calculatedAmount} {currency.toUpperCase()}</span>&nbsp;
to&nbsp;
<span className={styles.addr}>{addr}</span>
</h1>
<div className={styles.button} onClick={hideModal}>
Done
</div>
</div>
)
}
SuccessfulSendCoins.propTypes = {
amount: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]).isRequired,
addr: PropTypes.string.isRequired,
txid: PropTypes.string.isRequired,
hideModal: PropTypes.func.isRequired,
currentTicker: PropTypes.object.isRequired,
currency: PropTypes.string.isRequired
}
export default SuccessfulSendCoins

37
app/routes/app/components/components/ModalRoot/SuccessfulSendCoins/SuccessfulSendCoins.scss

@ -0,0 +1,37 @@
@import '../../../../../../variables.scss';
.container {
position: relative;
min-height: 250px;
top: calc(50% - 250px);
text-align: center;
h1 {
font-size: 20px;
margin: 50px 0;
.link {
cursor: pointer;
color: $main;
text-decoration: underline;
}
.amount, .addr {
font-weight: bold;
}
}
.button {
text-align: center;
border-radius: 8px;
background: $main;
padding: 20px 10px;
font-weight: bold;
cursor: pointer;
text-transform: uppercase;
letter-spacing: .2px;
color: $white;
width: 15%;
margin: 0 auto;
}
}

3
app/routes/app/components/components/ModalRoot/SuccessfulSendCoins/index.js

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

3
app/routes/app/components/components/ModalRoot/index.js

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

17
app/routes/app/containers/AppContainer.js

@ -4,14 +4,18 @@ import { fetchTicker, setCurrency, tickerSelectors } from '../../../reducers/tic
import { fetchBalance } from '../../../reducers/balance'
import { fetchInfo } from '../../../reducers/info'
import { createInvoice, fetchInvoice } from '../../../reducers/invoice'
import { payInvoice } from '../../../reducers/payment'
import { hideModal } from '../../../reducers/modal'
import { payInvoice, sendCoins } from '../../../reducers/payment'
import { fetchChannels } from '../../../reducers/channels'
import {
setForm,
setPaymentType,
setAmount,
setOnchainAmount,
setMessage,
setPubkey,
setPaymentRequest
setPaymentRequest,
formSelectors
} from '../../../reducers/form'
const mapDispatchToProps = {
@ -20,12 +24,16 @@ const mapDispatchToProps = {
fetchBalance,
fetchInfo,
setAmount,
setOnchainAmount,
setMessage,
setPubkey,
setPaymentRequest,
setForm,
setPaymentType,
createInvoice,
hideModal,
payInvoice,
sendCoins,
fetchChannels,
fetchInvoice
}
@ -36,8 +44,11 @@ const mapStateToProps = state => ({
payment: state.payment,
form: state.form,
invoice: state.invoice,
modal: state.modal,
currentTicker: tickerSelectors.currentTicker(state)
currentTicker: tickerSelectors.currentTicker(state),
isOnchain: formSelectors.isOnchain(state),
isLn: formSelectors.isLn(state)
})
export default connect(mapStateToProps, mapDispatchToProps)(App)

3
app/variables.scss

@ -11,4 +11,5 @@ $darkestgrey: #999999;
$bluegrey: #555459;
$green: #0bb634;
$red: #ff0b00;
$red: #ff0b00;
$curve: cubic-bezier(0.650, 0.000, 0.450, 1.000);

6
package.json

@ -8,7 +8,7 @@
"build-dll": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors",
"build-main": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors",
"build-renderer": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors",
"dev": "cross-env START_HOT=1 npm run start-renderer-dev",
"dev": "cross-env START_HOT=1 concurrently \"npm run start-renderer-dev\"",
"flow": "flow",
"flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true",
"lint": "eslint --cache --format=node_modules/eslint-formatter-pretty .",
@ -27,7 +27,8 @@
"test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/runTests.js",
"test-all": "npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e",
"test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/runTests.js e2e",
"test-watch": "npm test -- --watch"
"test-watch": "npm test -- --watch",
"lnd": "lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --neutrino.active --neutrino.connect=faucet.lightning.community:18333 --no-macaroons"
},
"browserslist": "electron 1.6",
"build": {
@ -184,6 +185,7 @@
},
"dependencies": {
"axios": "^0.16.2",
"bitcoinjs-lib": "^3.1.1",
"bitcore-lib": "^0.14.0",
"devtron": "^1.4.0",
"electron-debug": "^1.2.0",

47
resources/cloudbolt.svg

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="79.536px" height="79.536px" viewBox="0 0 79.536 79.536" style="enable-background:new 0 0 79.536 79.536;"
xml:space="preserve">
<g>
<g>
<path class="load-draw-svg" style="fill:#010002;" d="M78.967,29.396c0,8.124-6.582,14.7-14.706,14.7c-0.58,0-6.131,0-13.489,0
c-4.464,0-9.574,0-14.685,0c-12.896,0-25.626,0-26.942,0c-4.735,0-8.575-1.799-8.575-6.54c0-4.735,3.84-8.575,8.575-8.575
c0.857,0,1.675,0.171,2.47,0.409c0-0.067-0.021-0.132-0.021-0.202c0-5.525,4.479-9.999,10.004-9.999
c0.228,0,0.456,0.052,0.687,0.067c-0.013-0.233-0.075-0.451-0.075-0.684C22.209,8.313,30.522,0,40.788,0
c9.264,0,16.896,6.814,18.289,15.689c1.61-0.616,3.351-0.991,5.184-0.991C72.385,14.698,78.967,21.279,78.967,29.396z
M49.177,47.618H34.504c-4.306,4.329-11.283,11.34-11.363,11.34h11.757L23.146,79.536l26.45-23.52h-8.818L49.177,47.618z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

5
resources/thunderstorm.svg

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50" version="1.1" width="100" height="100">
<g id="surface1">
<path style="" class="thunderstorm-path" d="M 26 5 C 21.890625 5 18.4375 7.535156 16.90625 11.09375 C 16.605469 11.0625 16.3125 11 16 11 C 11.792969 11 8.320313 13.925781 7.34375 17.84375 C 4.210938 19.253906 2 22.351563 2 26 C 2 30.957031 6.042969 35 11 35 L 16.125 35 L 15.0625 37.625 L 14.53125 39 L 20.5625 39 L 18.0625 45.65625 L 16.9375 48.625 L 19.5625 46.8125 L 32.5625 37.90625 L 33 37.59375 L 33 36 L 28.71875 36 L 29.28125 35 L 39 35 C 43.957031 35 48 30.957031 48 26 C 48 22.417969 45.851563 19.382813 42.8125 17.9375 C 42.292969 13.710938 38.910156 10.433594 34.625 10.125 C 32.90625 7.097656 29.726563 5 26 5 Z M 26 7 C 29.148438 7 31.847656 8.804688 33.15625 11.4375 L 33.4375 12 L 34.0625 12 C 37.792969 12.023438 40.777344 14.941406 40.96875 18.625 L 41 19.28125 L 41.59375 19.5 C 44.164063 20.535156 46 23.046875 46 26 C 46 29.878906 42.878906 33 39 33 L 30.375 33 L 32.875 28.5 L 33.6875 27 L 19.3125 27 L 19.0625 27.625 L 16.90625 33 L 11 33 C 7.121094 33 4 29.878906 4 26 C 4 23.007813 5.871094 20.476563 8.5 19.46875 L 9.03125 19.28125 L 9.125 18.71875 C 9.726563 15.464844 12.5625 13 16 13 C 16.433594 13 16.855469 13.046875 17.28125 13.125 L 18.15625 13.28125 L 18.40625 12.46875 C 19.46875 9.300781 22.460938 7 26 7 Z M 20.6875 29 L 30.28125 29 L 26.125 36.5 L 25.3125 38 L 28.90625 38 L 21.03125 43.40625 L 22.9375 38.34375 L 23.4375 37 L 17.5 37 Z "/>
</g>
</svg>

7
test/reducers/__snapshots__/form.spec.js.snap

@ -6,6 +6,7 @@ Object {
"formType": "pay",
"message": "",
"modalOpen": false,
"onchainAmount": "0",
"payment_request": "",
"pubkey": "",
}
@ -17,6 +18,7 @@ Object {
"formType": "pay",
"message": "",
"modalOpen": false,
"onchainAmount": "0",
"payment_request": "",
"pubkey": "",
}
@ -28,6 +30,7 @@ Object {
"formType": "foo",
"message": "",
"modalOpen": true,
"onchainAmount": "0",
"payment_request": "",
"pubkey": "",
}
@ -39,6 +42,7 @@ Object {
"formType": "pay",
"message": "foo",
"modalOpen": false,
"onchainAmount": "0",
"payment_request": "",
"pubkey": "",
}
@ -50,6 +54,7 @@ Object {
"formType": "pay",
"message": "",
"modalOpen": false,
"onchainAmount": "0",
"payment_request": "foo",
"pubkey": "",
}
@ -61,6 +66,7 @@ Object {
"formType": "pay",
"message": "",
"modalOpen": false,
"onchainAmount": "0",
"payment_request": "",
"pubkey": "foo",
}
@ -72,6 +78,7 @@ Object {
"formType": "pay",
"message": "",
"modalOpen": false,
"onchainAmount": "0",
"payment_request": "",
"pubkey": "",
}

9
test/reducers/__snapshots__/payment.spec.js.snap

@ -5,6 +5,7 @@ Object {
"payment": null,
"paymentLoading": true,
"payments": Array [],
"sendingPayment": false,
}
`;
@ -12,9 +13,8 @@ exports[`reducers paymentReducer should correctly paymentSuccessful 1`] = `
Object {
"payment": null,
"paymentLoading": false,
"payments": Array [
"foo",
],
"payments": Array [],
"sendingPayment": false,
}
`;
@ -26,6 +26,7 @@ Object {
1,
2,
],
"sendingPayment": false,
}
`;
@ -34,6 +35,7 @@ Object {
"payment": "foo",
"paymentLoading": false,
"payments": Array [],
"sendingPayment": false,
}
`;
@ -42,5 +44,6 @@ Object {
"payment": null,
"paymentLoading": false,
"payments": Array [],
"sendingPayment": false,
}
`;

86
yarn.lock

@ -1452,6 +1452,12 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base-x@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.2.tgz#bf873861b7514279b7969f340929eab87c11d130"
dependencies:
safe-buffer "^5.0.1"
base64-js@1.2.0, base64-js@^1.0.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
@ -1474,6 +1480,10 @@ big.js@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
bigi@^1.1.0, bigi@^1.4.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825"
binary-extensions@^1.0.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774"
@ -1485,6 +1495,35 @@ binary-extensions@^1.0.0:
buffers "~0.1.1"
chainsaw "~0.1.0"
bip66@^1.1.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
dependencies:
safe-buffer "^5.0.1"
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"
dependencies:
bigi "^1.4.0"
bip66 "^1.1.0"
bitcoin-ops "^1.3.0"
bs58check "^2.0.0"
create-hash "^1.1.0"
create-hmac "^1.1.3"
ecurve "^1.0.0"
merkle-lib "^2.0.10"
pushdata-bitcoin "^1.0.1"
randombytes "^2.0.1"
safe-buffer "^5.0.1"
typeforce "^1.8.7"
varuint-bitcoin "^1.0.4"
wif "^2.0.1"
bitcore-lib@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.14.0.tgz#21cb2359fe7b997a3b7b773eb7d7275ae37d644e"
@ -1657,6 +1696,19 @@ bs58@=2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5"
bs58@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
dependencies:
base-x "^3.0.2"
bs58check@<3.0.0, bs58check@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.0.2.tgz#06f63b01c2fa6173033c90eb87f1fe3d2e13d89a"
dependencies:
bs58 "^4.0.0"
create-hash "^1.1.0"
bser@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169"
@ -2231,7 +2283,7 @@ create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2:
ripemd160 "^2.0.0"
sha.js "^2.4.0"
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.3, create-hmac@^1.1.4:
version "1.1.6"
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
dependencies:
@ -2791,6 +2843,12 @@ ecc-jsbn@~0.1.1:
dependencies:
jsbn "~0.1.0"
ecurve@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.5.tgz#d148e8fe50a674f983bb5bae09da0ea23e10535e"
dependencies:
bigi "^1.1.0"
editorconfig@^0.13.2:
version "0.13.2"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.13.2.tgz#8e57926d9ee69ab6cb999f027c2171467acceb35"
@ -5744,6 +5802,10 @@ merge@^1.1.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
merkle-lib@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -6997,6 +7059,12 @@ punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
pushdata-bitcoin@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz#15931d3cd967ade52206f523aa7331aef7d43af7"
dependencies:
bitcoin-ops "^1.3.0"
q@^1.1.2, q@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
@ -8637,6 +8705,12 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typeforce@^1.8.7:
version "1.11.4"
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.11.4.tgz#07ce3abcce836761243ae483dce5bc609786205c"
dependencies:
inherits "^2.0.1"
ua-parser-js@^0.7.9:
version "0.7.12"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
@ -8849,6 +8923,10 @@ value-equal@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.2.1.tgz#c220a304361fce6994dbbedaa3c7e1a1b895871d"
varuint-bitcoin@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.0.4.tgz#d812c5dae16e32f60544b6adee1d4be1307d0283"
vary@~1.1.0, vary@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
@ -9102,6 +9180,12 @@ widest-line@^1.0.0:
dependencies:
string-width "^1.0.1"
wif@^2.0.1:
version "2.0.6"
resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704"
dependencies:
bs58check "<3.0.0"
window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"

Loading…
Cancel
Save