jackmallers
7 years ago
committed by
GitHub
34 changed files with 960 additions and 817 deletions
@ -0,0 +1,42 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import { MdClose } from 'react-icons/lib/md' |
||||
|
|
||||
|
import PayForm from './PayForm' |
||||
|
import RequestForm from './RequestForm' |
||||
|
|
||||
|
import styles from './Form.scss' |
||||
|
|
||||
|
const FORM_TYPES = { |
||||
|
PAY_FORM: PayForm, |
||||
|
REQUEST_FORM: RequestForm |
||||
|
} |
||||
|
|
||||
|
const Form = ({ formType, formProps, closeForm }) => { |
||||
|
if (!formType) { return null } |
||||
|
|
||||
|
const FormComponent = FORM_TYPES[formType] |
||||
|
return ( |
||||
|
<div className={`${styles.outtercontainer} ${formType && styles.open}`}> |
||||
|
<div className={styles.innercontainer}> |
||||
|
<div className={styles.esc} onClick={closeForm}> |
||||
|
<MdClose /> |
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.content}> |
||||
|
<FormComponent {...formProps} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
Form.propTypes = { |
||||
|
formType: PropTypes.string, |
||||
|
formProps: PropTypes.object.isRequired, |
||||
|
closeForm: PropTypes.func.isRequired |
||||
|
} |
||||
|
|
||||
|
export default Form |
@ -0,0 +1,59 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.outtercontainer { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
background: $white; |
||||
|
z-index: 0; |
||||
|
opacity: 0; |
||||
|
transition: all 0.5s; |
||||
|
|
||||
|
&.open { |
||||
|
opacity: 1; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.innercontainer { |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
width: 50%; |
||||
|
margin: 0 auto; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 75vh; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
@ -0,0 +1,123 @@ |
|||||
|
import React, { Component } from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import { FaBolt, FaChain } from 'react-icons/lib/fa' |
||||
|
import LoadingBolt from 'components/LoadingBolt' |
||||
|
import CurrencyIcon from 'components/CurrencyIcon' |
||||
|
|
||||
|
import styles from './PayForm.scss' |
||||
|
|
||||
|
class PayForm extends Component { |
||||
|
componentDidUpdate(prevProps) { |
||||
|
const { isOnchain, isLn, payform: { payInput }, fetchInvoice } = this.props |
||||
|
|
||||
|
// If on-chain, focus on amount to let user know it's editable
|
||||
|
if (isOnchain) { this.amountInput.focus() } |
||||
|
// If LN go retrieve invoice details
|
||||
|
if ((prevProps.payform.payInput !== payInput) && isLn) { |
||||
|
fetchInvoice(payInput) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { |
||||
|
payform: { amount, payInput }, |
||||
|
currency, |
||||
|
crypto, |
||||
|
|
||||
|
isOnchain, |
||||
|
isLn, |
||||
|
currentAmount, |
||||
|
inputCaption, |
||||
|
showPayLoadingScreen, |
||||
|
|
||||
|
setPayAmount, |
||||
|
setPayInput, |
||||
|
|
||||
|
onPaySubmit |
||||
|
} = this.props |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
{showPayLoadingScreen && <LoadingBolt />} |
||||
|
|
||||
|
<section className={`${styles.amountContainer} ${isLn ? styles.ln : ''}`}> |
||||
|
<label htmlFor='amount'> |
||||
|
<CurrencyIcon currency={currency} crypto={crypto} /> |
||||
|
</label> |
||||
|
<input |
||||
|
type='number' |
||||
|
min='0' |
||||
|
ref={input => this.amountInput = input} // eslint-disable-line
|
||||
|
size='' |
||||
|
style={ |
||||
|
isLn ? |
||||
|
{ width: '75%', fontSize: '85px' } |
||||
|
: |
||||
|
{ width: `${amount.length > 1 ? (amount.length * 15) - 5 : 25}%`, fontSize: `${190 - (amount.length ** 2)}px` } |
||||
|
} |
||||
|
value={currentAmount} |
||||
|
onChange={event => setPayAmount(event.target.value)} |
||||
|
id='amount' |
||||
|
readOnly={isLn} |
||||
|
/> |
||||
|
</section> |
||||
|
<div className={styles.inputContainer}> |
||||
|
<div className={styles.info}> |
||||
|
<span>{inputCaption}</span> |
||||
|
</div> |
||||
|
<aside className={styles.paymentIcon}> |
||||
|
{isOnchain && |
||||
|
<i> |
||||
|
<span>on-chain</span> |
||||
|
<FaChain /> |
||||
|
</i> |
||||
|
} |
||||
|
{isLn && |
||||
|
<i> |
||||
|
<span>lightning network</span> |
||||
|
<FaBolt /> |
||||
|
</i> |
||||
|
} |
||||
|
</aside> |
||||
|
<section className={styles.input}> |
||||
|
<input |
||||
|
type='text' |
||||
|
placeholder='Payment request or bitcoin address' |
||||
|
value={payInput} |
||||
|
onChange={event => setPayInput(event.target.value)} |
||||
|
id='paymentRequest' |
||||
|
/> |
||||
|
</section> |
||||
|
</div> |
||||
|
<section className={styles.buttonGroup}> |
||||
|
<div className={styles.button} onClick={onPaySubmit}>Pay</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
PayForm.propTypes = { |
||||
|
payform: PropTypes.object.isRequired, |
||||
|
currency: PropTypes.string.isRequired, |
||||
|
crypto: PropTypes.string.isRequired, |
||||
|
|
||||
|
isOnchain: PropTypes.bool.isRequired, |
||||
|
isLn: PropTypes.bool.isRequired, |
||||
|
currentAmount: PropTypes.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]).isRequired, |
||||
|
inputCaption: PropTypes.string.isRequired, |
||||
|
showPayLoadingScreen: PropTypes.bool.isRequired, |
||||
|
|
||||
|
setPayAmount: PropTypes.func.isRequired, |
||||
|
setPayInput: PropTypes.func.isRequired, |
||||
|
fetchInvoice: PropTypes.func.isRequired, |
||||
|
|
||||
|
onPaySubmit: PropTypes.func.isRequired |
||||
|
} |
||||
|
|
||||
|
export default PayForm |
@ -0,0 +1,59 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import CurrencyIcon from 'components/CurrencyIcon' |
||||
|
import styles from './RequestForm.scss' |
||||
|
|
||||
|
const RequestForm = ({ |
||||
|
requestform: { amount, memo }, |
||||
|
currency, |
||||
|
crypto, |
||||
|
|
||||
|
setRequestAmount, |
||||
|
setRequestMemo, |
||||
|
|
||||
|
onRequestSubmit |
||||
|
}) => ( |
||||
|
<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 => setRequestAmount(event.target.value)} |
||||
|
id='amount' |
||||
|
/> |
||||
|
</section> |
||||
|
<section className={styles.inputContainer}> |
||||
|
<label htmlFor='memo'>Request:</label> |
||||
|
<input |
||||
|
type='text' |
||||
|
placeholder='Dinner, Rent, etc' |
||||
|
value={memo} |
||||
|
onChange={event => setRequestMemo(event.target.value)} |
||||
|
id='memo' |
||||
|
/> |
||||
|
</section> |
||||
|
<section className={styles.buttonGroup}> |
||||
|
<div className={styles.button} onClick={onRequestSubmit}> |
||||
|
Request |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
RequestForm.propTypes = { |
||||
|
requestform: PropTypes.object.isRequired, |
||||
|
currency: PropTypes.string.isRequired, |
||||
|
crypto: PropTypes.string.isRequired, |
||||
|
|
||||
|
setRequestAmount: PropTypes.func.isRequired, |
||||
|
setRequestMemo: PropTypes.func.isRequired, |
||||
|
|
||||
|
onRequestSubmit: PropTypes.func.isRequired |
||||
|
} |
||||
|
|
||||
|
export default RequestForm |
@ -1,4 +1,4 @@ |
|||||
@import '../../../../../../../variables.scss'; |
@import '../../variables.scss'; |
||||
|
|
||||
.container { |
.container { |
||||
margin: 0 auto; |
margin: 0 auto; |
@ -0,0 +1,161 @@ |
|||||
|
import { createSelector } from 'reselect' |
||||
|
import bitcoin from 'bitcoinjs-lib' |
||||
|
import { tickerSelectors } from './ticker' |
||||
|
import { btc, bech32 } from '../utils' |
||||
|
|
||||
|
// Initial State
|
||||
|
const initialState = { |
||||
|
amount: '0', |
||||
|
payInput: '', |
||||
|
|
||||
|
invoice: { |
||||
|
payreq: '', |
||||
|
r_hash: '', |
||||
|
amount: '0' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Constants
|
||||
|
// ------------------------------------
|
||||
|
export const SET_PAY_AMOUNT = 'SET_PAY_AMOUNT' |
||||
|
export const SET_PAY_INPUT = 'SET_PAY_INPUT' |
||||
|
export const SET_PAY_INVOICE = 'SET_PAY_INVOICE' |
||||
|
|
||||
|
export const RESET_FORM = 'RESET_FORM' |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Actions
|
||||
|
// ------------------------------------
|
||||
|
export function setPayAmount(amount) { |
||||
|
return { |
||||
|
type: SET_PAY_AMOUNT, |
||||
|
amount |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function setPayInput(payInput) { |
||||
|
return { |
||||
|
type: SET_PAY_INPUT, |
||||
|
payInput |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function setPayInvoice(invoice) { |
||||
|
return { |
||||
|
type: SET_PAY_INVOICE, |
||||
|
invoice |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function resetPayForm() { |
||||
|
return { |
||||
|
type: RESET_FORM |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Action Handlers
|
||||
|
// ------------------------------------
|
||||
|
const ACTION_HANDLERS = { |
||||
|
[SET_PAY_AMOUNT]: (state, { amount }) => ({ ...state, amount }), |
||||
|
[SET_PAY_INPUT]: (state, { payInput }) => ({ ...state, payInput }), |
||||
|
[SET_PAY_INVOICE]: (state, { invoice }) => ({ ...state, invoice }), |
||||
|
|
||||
|
[RESET_FORM]: () => (initialState) |
||||
|
} |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Selector
|
||||
|
// ------------------------------------
|
||||
|
const payFormSelectors = {} |
||||
|
const payAmountSelector = state => state.payform.amount |
||||
|
const payInputSelector = state => state.payform.payInput |
||||
|
const payInvoiceSelector = state => state.payform.invoice |
||||
|
|
||||
|
// transaction
|
||||
|
const sendingTransactionSelector = state => state.transaction.sendingTransaction |
||||
|
|
||||
|
// transaction
|
||||
|
const sendingPaymentSelector = state => state.payment.sendingPayment |
||||
|
|
||||
|
// ticker
|
||||
|
const currencySelector = state => state.ticker.currency |
||||
|
|
||||
|
payFormSelectors.isOnchain = createSelector( |
||||
|
payInputSelector, |
||||
|
(input) => { |
||||
|
// TODO: work with bitcoin-js to fix p2wkh error and make testnet/mainnet dynamic
|
||||
|
try { |
||||
|
bitcoin.address.toOutputScript(input, bitcoin.networks.testnet) |
||||
|
return true |
||||
|
} catch (e) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
payFormSelectors.isLn = createSelector( |
||||
|
payInputSelector, |
||||
|
(input) => { |
||||
|
if (!input.startsWith('ln')) { return false } |
||||
|
|
||||
|
try { |
||||
|
bech32.decode(input) |
||||
|
return true |
||||
|
} catch (e) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
payFormSelectors.currentAmount = createSelector( |
||||
|
payFormSelectors.isLn, |
||||
|
payAmountSelector, |
||||
|
payInvoiceSelector, |
||||
|
currencySelector, |
||||
|
tickerSelectors.currentTicker, |
||||
|
(isLn, amount, invoice, currency, ticker) => { |
||||
|
if (isLn) { |
||||
|
return currency === 'usd' ? btc.satoshisToUsd((invoice.num_satoshis || 0), ticker.price_usd) : btc.satoshisToBtc((invoice.num_satoshis || 0)) |
||||
|
} |
||||
|
|
||||
|
return amount |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
payFormSelectors.inputCaption = createSelector( |
||||
|
payFormSelectors.isOnchain, |
||||
|
payFormSelectors.isLn, |
||||
|
payFormSelectors.currentAmount, |
||||
|
currencySelector, |
||||
|
(isOnchain, isLn, amount, currency) => { |
||||
|
if (!isOnchain && !isLn) { return '' } |
||||
|
|
||||
|
if (isOnchain) { |
||||
|
return `You're about to send ${amount} ${currency.toUpperCase()} on-chain which should take around 10 minutes` |
||||
|
} |
||||
|
|
||||
|
if (isLn) { |
||||
|
return `You're about to send ${amount} ${currency.toUpperCase()} over the Lightning Network which will be instant` |
||||
|
} |
||||
|
|
||||
|
return '' |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
payFormSelectors.showPayLoadingScreen = createSelector( |
||||
|
sendingTransactionSelector, |
||||
|
sendingPaymentSelector, |
||||
|
(sendingTransaction, sendingPayment) => sendingTransaction || sendingPayment |
||||
|
) |
||||
|
|
||||
|
export { payFormSelectors } |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Reducer
|
||||
|
// ------------------------------------
|
||||
|
export default function payFormReducer(state = initialState, action) { |
||||
|
const handler = ACTION_HANDLERS[action.type] |
||||
|
|
||||
|
return handler ? handler(state, action) : state |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
// Initial State
|
||||
|
const initialState = { |
||||
|
amount: '0', |
||||
|
memo: '' |
||||
|
} |
||||
|
|
||||
|
// Constants
|
||||
|
// ------------------------------------
|
||||
|
export const SET_REQUEST_AMOUNT = 'SET_REQUEST_AMOUNT' |
||||
|
export const SET_REQUEST_MEMO = 'SET_REQUEST_MEMO' |
||||
|
export const SET_PAY_INVOICE = 'SET_PAY_INVOICE' |
||||
|
|
||||
|
export const RESET_FORM = 'RESET_FORM' |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Actions
|
||||
|
// ------------------------------------
|
||||
|
export function setRequestAmount(amount) { |
||||
|
return { |
||||
|
type: SET_REQUEST_AMOUNT, |
||||
|
amount |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function setRequestMemo(memo) { |
||||
|
return { |
||||
|
type: SET_REQUEST_MEMO, |
||||
|
memo |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function resetRequestForm() { |
||||
|
return { |
||||
|
type: RESET_FORM |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Action Handlers
|
||||
|
// ------------------------------------
|
||||
|
const ACTION_HANDLERS = { |
||||
|
[SET_REQUEST_AMOUNT]: (state, { amount }) => ({ ...state, amount }), |
||||
|
[SET_REQUEST_MEMO]: (state, { memo }) => ({ ...state, memo }), |
||||
|
|
||||
|
[RESET_FORM]: () => (initialState) |
||||
|
} |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Reducer
|
||||
|
// ------------------------------------
|
||||
|
export default function payFormReducer(state = initialState, action) { |
||||
|
const handler = ACTION_HANDLERS[action.type] |
||||
|
|
||||
|
return handler ? handler(state, action) : state |
||||
|
} |
@ -1,93 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { MdClose } from 'react-icons/lib/md' |
|
||||
import Pay from './components/Pay' |
|
||||
import Request from './components/Request' |
|
||||
import styles from './Form.scss' |
|
||||
|
|
||||
const Form = ({ |
|
||||
form: { formType, amount, onchainAmount, message, payment_request }, |
|
||||
setAmount, |
|
||||
setOnchainAmount, |
|
||||
setMessage, |
|
||||
setPaymentRequest, |
|
||||
ticker: { currency, crypto }, |
|
||||
isOpen, |
|
||||
close, |
|
||||
createInvoice, |
|
||||
payInvoice, |
|
||||
sendCoins, |
|
||||
fetchInvoice, |
|
||||
formInvoice, |
|
||||
currentTicker, |
|
||||
isOnchain, |
|
||||
isLn, |
|
||||
sendingTransaction |
|
||||
}) => ( |
|
||||
<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 |
|
||||
sendingTransaction={sendingTransaction} |
|
||||
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} |
|
||||
/> |
|
||||
|
|
||||
} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
) |
|
||||
|
|
||||
Form.propTypes = { |
|
||||
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, |
|
||||
isOnchain: PropTypes.bool.isRequired, |
|
||||
isLn: PropTypes.bool.isRequired, |
|
||||
sendingTransaction: PropTypes.bool.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Form |
|
@ -1,159 +0,0 @@ |
|||||
@import '../../../../../variables.scss'; |
|
||||
|
|
||||
.formContainer { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
bottom: 0; |
|
||||
width: 100%; |
|
||||
height: 100vh; |
|
||||
background: $white; |
|
||||
z-index: 0; |
|
||||
opacity: 0; |
|
||||
transition: all 0.5s; |
|
||||
|
|
||||
&.open { |
|
||||
opacity: 1; |
|
||||
z-index: 10; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.container { |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.content { |
|
||||
width: 50%; |
|
||||
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%); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,149 +0,0 @@ |
|||||
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 { |
|
||||
sendingTransaction, |
|
||||
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> |
|
||||
{ |
|
||||
sendingTransaction ? |
|
||||
<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 = { |
|
||||
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, |
|
||||
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 |
|
@ -1,3 +0,0 @@ |
|||||
import Pay from './Pay' |
|
||||
|
|
||||
export default Pay |
|
@ -1,68 +0,0 @@ |
|||||
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 |
|
@ -1,3 +0,0 @@ |
|||||
import Request from './Request' |
|
||||
|
|
||||
export default Request |
|
@ -0,0 +1,150 @@ |
|||||
|
// Using bech32 here just without the 90 char length: https://github.com/bitcoinjs/bech32/blob/master/index.js
|
||||
|
|
||||
|
/* eslint-disable */ |
||||
|
|
||||
|
'use strict' |
||||
|
let ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' |
||||
|
|
||||
|
// pre-compute lookup table
|
||||
|
let ALPHABET_MAP = {} |
||||
|
for (let z = 0; z < ALPHABET.length; z++) { |
||||
|
let x = ALPHABET.charAt(z) |
||||
|
|
||||
|
if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous') |
||||
|
ALPHABET_MAP[x] = z |
||||
|
} |
||||
|
|
||||
|
function polymodStep (pre) { |
||||
|
let b = pre >> 25 |
||||
|
return ((pre & 0x1FFFFFF) << 5) ^ |
||||
|
(-((b >> 0) & 1) & 0x3b6a57b2) ^ |
||||
|
(-((b >> 1) & 1) & 0x26508e6d) ^ |
||||
|
(-((b >> 2) & 1) & 0x1ea119fa) ^ |
||||
|
(-((b >> 3) & 1) & 0x3d4233dd) ^ |
||||
|
(-((b >> 4) & 1) & 0x2a1462b3) |
||||
|
} |
||||
|
|
||||
|
function prefixChk (prefix) { |
||||
|
let chk = 1 |
||||
|
for (let i = 0; i < prefix.length; ++i) { |
||||
|
let c = prefix.charCodeAt(i) |
||||
|
if (c < 33 || c > 126) throw new Error('Invalid prefix (' + prefix + ')') |
||||
|
|
||||
|
chk = polymodStep(chk) ^ (c >> 5) |
||||
|
} |
||||
|
chk = polymodStep(chk) |
||||
|
|
||||
|
for (let i = 0; i < prefix.length; ++i) { |
||||
|
let v = prefix.charCodeAt(i) |
||||
|
chk = polymodStep(chk) ^ (v & 0x1f) |
||||
|
} |
||||
|
return chk |
||||
|
} |
||||
|
|
||||
|
function encode (prefix, words) { |
||||
|
// too long?
|
||||
|
if ((prefix.length + 7 + words.length) > 90) throw new TypeError('Exceeds Bech32 maximum length') |
||||
|
prefix = prefix.toLowerCase() |
||||
|
|
||||
|
// determine chk mod
|
||||
|
let chk = prefixChk(prefix) |
||||
|
let result = prefix + '1' |
||||
|
for (let i = 0; i < words.length; ++i) { |
||||
|
let x = words[i] |
||||
|
if ((x >> 5) !== 0) throw new Error('Non 5-bit word') |
||||
|
|
||||
|
chk = polymodStep(chk) ^ x |
||||
|
result += ALPHABET.charAt(x) |
||||
|
} |
||||
|
|
||||
|
for (let i = 0; i < 6; ++i) { |
||||
|
chk = polymodStep(chk) |
||||
|
} |
||||
|
chk ^= 1 |
||||
|
|
||||
|
for (let i = 0; i < 6; ++i) { |
||||
|
let v = (chk >> ((5 - i) * 5)) & 0x1f |
||||
|
result += ALPHABET.charAt(v) |
||||
|
} |
||||
|
|
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
function decode (str) { |
||||
|
if (str.length < 8) throw new TypeError(str + ' too short') |
||||
|
// LN payment requests can be longer than 90 chars
|
||||
|
// if (str.length > 90) throw new TypeError(str + ' too long')
|
||||
|
|
||||
|
// don't allow mixed case
|
||||
|
let lowered = str.toLowerCase() |
||||
|
let uppered = str.toUpperCase() |
||||
|
if (str !== lowered && str !== uppered) throw new Error('Mixed-case string ' + str) |
||||
|
str = lowered |
||||
|
|
||||
|
let split = str.lastIndexOf('1') |
||||
|
if (split === 0) throw new Error('Missing prefix for ' + str) |
||||
|
|
||||
|
let prefix = str.slice(0, split) |
||||
|
let wordChars = str.slice(split + 1) |
||||
|
if (wordChars.length < 6) throw new Error('Data too short') |
||||
|
|
||||
|
let chk = prefixChk(prefix) |
||||
|
let words = [] |
||||
|
for (let i = 0; i < wordChars.length; ++i) { |
||||
|
let c = wordChars.charAt(i) |
||||
|
let v = ALPHABET_MAP[c] |
||||
|
if (v === undefined) throw new Error('Unknown character ' + c) |
||||
|
chk = polymodStep(chk) ^ v |
||||
|
|
||||
|
// not in the checksum?
|
||||
|
if (i + 6 >= wordChars.length) continue |
||||
|
words.push(v) |
||||
|
} |
||||
|
|
||||
|
if (chk !== 1) throw new Error('Invalid checksum for ' + str) |
||||
|
return { prefix, words } |
||||
|
} |
||||
|
|
||||
|
function convert (data, inBits, outBits, pad) { |
||||
|
let value = 0 |
||||
|
let bits = 0 |
||||
|
let maxV = (1 << outBits) - 1 |
||||
|
|
||||
|
let result = [] |
||||
|
for (let i = 0; i < data.length; ++i) { |
||||
|
value = (value << inBits) | data[i] |
||||
|
bits += inBits |
||||
|
|
||||
|
while (bits >= outBits) { |
||||
|
bits -= outBits |
||||
|
result.push((value >> bits) & maxV) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (pad) { |
||||
|
if (bits > 0) { |
||||
|
result.push((value << (outBits - bits)) & maxV) |
||||
|
} |
||||
|
} else { |
||||
|
if (bits >= inBits) throw new Error('Excess padding') |
||||
|
if ((value << (outBits - bits)) & maxV) throw new Error('Non-zero padding') |
||||
|
} |
||||
|
|
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
function toWords (bytes) { |
||||
|
return convert(bytes, 8, 5, true) |
||||
|
} |
||||
|
|
||||
|
function fromWords (words) { |
||||
|
return convert(words, 5, 8, false) |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
decode, |
||||
|
encode, |
||||
|
toWords, |
||||
|
fromWords |
||||
|
} |
||||
|
|
@ -1,7 +1,9 @@ |
|||||
import btc from './btc' |
import btc from './btc' |
||||
import usd from './usd' |
import usd from './usd' |
||||
|
import bech32 from './bech32' |
||||
|
|
||||
export default { |
export default { |
||||
btc, |
btc, |
||||
usd |
usd, |
||||
|
bech32 |
||||
} |
} |
||||
|
@ -0,0 +1,60 @@ |
|||||
|
import React from 'react' |
||||
|
import { shallow } from 'enzyme' |
||||
|
|
||||
|
import Form from '../../app/components/Form' |
||||
|
import PayForm from '../../app/components/Form/PayForm' |
||||
|
import RequestForm from '../../app/components/Form/RequestForm' |
||||
|
|
||||
|
const payFormProps = { |
||||
|
payform: {}, |
||||
|
currency: 'BTC', |
||||
|
crypto: 'BTC', |
||||
|
|
||||
|
isOnchain: false, |
||||
|
isLn: false, |
||||
|
currentAmount: '0', |
||||
|
inputCaption: '', |
||||
|
showPayLoadingScreen: false, |
||||
|
|
||||
|
setPayAmount: () => {}, |
||||
|
setPayInput: () => {}, |
||||
|
fetchInvoice: () => {}, |
||||
|
|
||||
|
|
||||
|
onPaySubmit: () => {} |
||||
|
} |
||||
|
|
||||
|
const requestFormProps = { |
||||
|
requestform: {}, |
||||
|
currency: '', |
||||
|
crypto: '', |
||||
|
|
||||
|
setRequestAmount: () => {}, |
||||
|
setRequestMemo: () => {}, |
||||
|
|
||||
|
onRequestSubmit: () => {} |
||||
|
} |
||||
|
|
||||
|
const defaultProps = { |
||||
|
formType: '', |
||||
|
formProps: {}, |
||||
|
closeForm: () => {} |
||||
|
} |
||||
|
|
||||
|
describe('Form', () => { |
||||
|
describe('should show pay form when formType is PAY_FORM', () => { |
||||
|
const props = { ...defaultProps, formType: 'PAY_FORM', formProps: payFormProps } |
||||
|
const el = shallow(<Form {...props} />) |
||||
|
it('should contain PayForm', () => { |
||||
|
expect(el.find(PayForm)).toHaveLength(1) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('should show request form when formType is REQUEST_FORM', () => { |
||||
|
const props = { ...defaultProps, formType: 'REQUEST_FORM', formProps: requestFormProps } |
||||
|
const el = shallow(<Form {...props} />) |
||||
|
it('should contain RequestForm', () => { |
||||
|
expect(el.find(RequestForm)).toHaveLength(1) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
@ -1,85 +1,13 @@ |
|||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
|
||||
exports[`reducers formReducer should correctly resetForm 1`] = ` |
exports[`reducers formReducer should correctly setFormType 1`] = ` |
||||
Object { |
Object { |
||||
"amount": "0", |
"formType": "FOO", |
||||
"formType": "pay", |
|
||||
"message": "", |
|
||||
"modalOpen": false, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "", |
|
||||
"pubkey": "", |
|
||||
} |
|
||||
`; |
|
||||
|
|
||||
exports[`reducers formReducer should correctly setAmount 1`] = ` |
|
||||
Object { |
|
||||
"amount": 1, |
|
||||
"formType": "pay", |
|
||||
"message": "", |
|
||||
"modalOpen": false, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "", |
|
||||
"pubkey": "", |
|
||||
} |
|
||||
`; |
|
||||
|
|
||||
exports[`reducers formReducer should correctly setForm 1`] = ` |
|
||||
Object { |
|
||||
"amount": "0", |
|
||||
"formType": "foo", |
|
||||
"message": "", |
|
||||
"modalOpen": true, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "", |
|
||||
"pubkey": "", |
|
||||
} |
|
||||
`; |
|
||||
|
|
||||
exports[`reducers formReducer should correctly setMessage 1`] = ` |
|
||||
Object { |
|
||||
"amount": "0", |
|
||||
"formType": "pay", |
|
||||
"message": "foo", |
|
||||
"modalOpen": false, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "", |
|
||||
"pubkey": "", |
|
||||
} |
|
||||
`; |
|
||||
|
|
||||
exports[`reducers formReducer should correctly setPaymentRequest 1`] = ` |
|
||||
Object { |
|
||||
"amount": "0", |
|
||||
"formType": "pay", |
|
||||
"message": "", |
|
||||
"modalOpen": false, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "foo", |
|
||||
"pubkey": "", |
|
||||
} |
|
||||
`; |
|
||||
|
|
||||
exports[`reducers formReducer should correctly setPubkey 1`] = ` |
|
||||
Object { |
|
||||
"amount": "0", |
|
||||
"formType": "pay", |
|
||||
"message": "", |
|
||||
"modalOpen": false, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "", |
|
||||
"pubkey": "foo", |
|
||||
} |
} |
||||
`; |
`; |
||||
|
|
||||
exports[`reducers formReducer should handle initial state 1`] = ` |
exports[`reducers formReducer should handle initial state 1`] = ` |
||||
Object { |
Object { |
||||
"amount": "0", |
"formType": null, |
||||
"formType": "pay", |
|
||||
"message": "", |
|
||||
"modalOpen": false, |
|
||||
"onchainAmount": "0", |
|
||||
"payment_request": "", |
|
||||
"pubkey": "", |
|
||||
} |
} |
||||
`; |
`; |
||||
|
@ -1,64 +1,26 @@ |
|||||
import formReducer, { |
import formReducer, { |
||||
SET_FORM, |
SET_FORM_TYPE |
||||
SET_AMOUNT, |
|
||||
SET_MESSAGE, |
|
||||
SET_PUBKEY, |
|
||||
SET_PAYMENT_REQUEST, |
|
||||
RESET_FORM |
|
||||
} from '../../app/reducers/form' |
} from '../../app/reducers/form' |
||||
|
|
||||
|
// describe('reducers', () => {
|
||||
|
// describe('formReducer', () => {
|
||||
|
|
||||
|
|
||||
|
// }
|
||||
|
// }
|
||||
|
|
||||
describe('reducers', () => { |
describe('reducers', () => { |
||||
describe('formReducer', () => { |
describe('formReducer', () => { |
||||
it('should handle initial state', () => { |
it('should handle initial state', () => { |
||||
expect(formReducer(undefined, {})).toMatchSnapshot() |
expect(formReducer(undefined, {})).toMatchSnapshot() |
||||
}) |
}) |
||||
|
|
||||
it('should have SET_FORM', () => { |
it('should have SET_FORM_TYPE', () => { |
||||
expect(SET_FORM).toEqual('SET_FORM') |
expect(SET_FORM_TYPE).toEqual('SET_FORM_TYPE') |
||||
}) |
|
||||
|
|
||||
it('should have SET_AMOUNT', () => { |
|
||||
expect(SET_AMOUNT).toEqual('SET_AMOUNT') |
|
||||
}) |
|
||||
|
|
||||
it('should have SET_MESSAGE', () => { |
|
||||
expect(SET_MESSAGE).toEqual('SET_MESSAGE') |
|
||||
}) |
|
||||
|
|
||||
it('should have SET_PUBKEY', () => { |
|
||||
expect(SET_PUBKEY).toEqual('SET_PUBKEY') |
|
||||
}) |
|
||||
|
|
||||
it('should have SET_PAYMENT_REQUEST', () => { |
|
||||
expect(SET_PAYMENT_REQUEST).toEqual('SET_PAYMENT_REQUEST') |
|
||||
}) |
|
||||
|
|
||||
it('should have RESET_FORM', () => { |
|
||||
expect(RESET_FORM).toEqual('RESET_FORM') |
|
||||
}) |
|
||||
|
|
||||
it('should correctly setForm', () => { |
|
||||
expect(formReducer(undefined, { type: SET_FORM, modalOpen: true, formType: 'foo' })).toMatchSnapshot() |
|
||||
}) |
|
||||
|
|
||||
it('should correctly setAmount', () => { |
|
||||
expect(formReducer(undefined, { type: SET_AMOUNT, amount: 1 })).toMatchSnapshot() |
|
||||
}) |
|
||||
|
|
||||
it('should correctly setMessage', () => { |
|
||||
expect(formReducer(undefined, { type: SET_MESSAGE, message: 'foo' })).toMatchSnapshot() |
|
||||
}) |
|
||||
|
|
||||
it('should correctly setPubkey', () => { |
|
||||
expect(formReducer(undefined, { type: SET_PUBKEY, pubkey: 'foo' })).toMatchSnapshot() |
|
||||
}) |
|
||||
|
|
||||
it('should correctly setPaymentRequest', () => { |
|
||||
expect(formReducer(undefined, { type: SET_PAYMENT_REQUEST, payment_request: 'foo' })).toMatchSnapshot() |
|
||||
}) |
}) |
||||
|
|
||||
it('should correctly resetForm', () => { |
it('should correctly setFormType', () => { |
||||
expect(formReducer(undefined, { type: RESET_FORM })).toMatchSnapshot() |
expect(formReducer(undefined, { type: SET_FORM_TYPE, formType: 'FOO' })).toMatchSnapshot() |
||||
}) |
}) |
||||
}) |
}) |
||||
}) |
}) |
||||
|
Loading…
Reference in new issue