Browse Source

feature(payform): add multi-currency support for payform

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
0883bbd486
  1. 48
      app/components/Form/Pay.js
  2. 18
      app/components/Form/Pay.scss
  3. 55
      app/reducers/payform.js
  4. 34
      app/reducers/ticker.js
  5. 12
      app/routes/app/containers/AppContainer.js
  6. 99
      app/utils/btc.js

48
app/components/Form/Pay.js

@ -5,8 +5,9 @@ import find from 'lodash/find'
import Isvg from 'react-inlinesvg'
import paperPlane from 'icons/paper_plane.svg'
import link from 'icons/link.svg'
import { FaAngleDown } from 'react-icons/lib/fa'
import { btc } from 'utils'
import LoadingBolt from 'components/LoadingBolt'
import CurrencyIcon from 'components/CurrencyIcon'
@ -29,10 +30,11 @@ class Pay extends Component {
render() {
const {
payform: { amount, payInput, showErrors, invoice },
payform: { amount, payInput, showErrors, invoice, showCurrencyFilters },
currency,
crypto,
nodes,
ticker,
isOnchain,
isLn,
@ -41,6 +43,9 @@ class Pay extends Component {
inputCaption,
showPayLoadingScreen,
payFormIsValid: { errors, isValid },
payInputMin,
currentCurrencyFilters,
currencyName,
setPayAmount,
onPayAmountBlur,
@ -48,20 +53,31 @@ class Pay extends Component {
setPayInput,
onPayInputBlur,
onPaySubmit
setCurrencyFilters,
onPaySubmit,
setCurrency
} = this.props
const displayNodeName = (pubkey) => {
console.log('nodes: ', nodes)
console.log('pubkey: ', pubkey)
const node = find(nodes, n => n.pub_key === pubkey)
console.log('node: ', node)
if (node && node.alias.length) { return node.alias }
return pubkey.substring(0, 10)
}
const onCurrencyFilterClick = (currency) => {
if (!isLn) {
// change the input amount
setPayAmount(btc.convert(ticker.currency, currency, currentAmount))
}
setCurrency(currency)
setCurrencyFilters(false)
}
return (
<div className={styles.container}>
{showPayLoadingScreen && <LoadingBolt />}
@ -122,18 +138,21 @@ class Pay extends Component {
readOnly={isLn}
/>
<div className={styles.currency}>
<section className={styles.currentCurrency}>
<span>BTC</span><span><FaAngleDown /></span>
<section className={styles.currentCurrency} onClick={() => setCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
</section>
<ul>
<li>Bits</li>
<li>Satoshis</li>
<ul className={showCurrencyFilters && styles.active}>
{
currentCurrencyFilters.map(filter =>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>
)
}
</ul>
</div>
</div>
<div className={styles.usdAmount}>
{`${usdAmount} USD`}
{`${usdAmount || 0} USD`}
</div>
</section>
@ -149,7 +168,10 @@ class Pay extends Component {
Pay.propTypes = {
payform: PropTypes.shape({
amount: PropTypes.string.isRequired,
amount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
payInput: PropTypes.string.isRequired,
showErrors: PropTypes.object.isRequired
}).isRequired,

18
app/components/Form/Pay.scss

@ -89,6 +89,7 @@
}
.currency {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
@ -114,6 +115,23 @@
ul {
visibility: hidden;
position: absolute;
top: 30px;
&.active {
visibility: visible;
}
li {
padding: 8px 15px;
background: #191919;
cursor: pointer;
transition: 0.25s hover;
border-bottom: 1px solid #0f0f0f;
&:hover {
background: #0f0f0f;
}
}
}
}

55
app/reducers/payform.js

@ -9,7 +9,7 @@ import { btc, bech32 } from '../utils'
// Initial State
const initialState = {
amount: '0',
amount: '',
payInput: '',
invoice: {
@ -20,6 +20,8 @@ const initialState = {
destination: ''
},
showCurrencyFilters: false,
showErrors: {
amount: false,
payInput: false
@ -32,6 +34,8 @@ 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 SET_CURRENCY_FILTERS = 'SET_CURRENCY_FILTERS'
export const UPDATE_PAY_ERRORS = 'UPDATE_PAY_ERRORS'
export const RESET_FORM = 'RESET_FORM'
@ -60,6 +64,13 @@ export function setPayInvoice(invoice) {
}
}
export function setCurrencyFilters(showCurrencyFilters) {
return {
type: SET_CURRENCY_FILTERS,
showCurrencyFilters
}
}
export function updatePayErrors(errorsObject) {
return {
type: UPDATE_PAY_ERRORS,
@ -87,6 +98,7 @@ const ACTION_HANDLERS = {
[SET_PAY_AMOUNT]: (state, { amount }) => ({ ...state, amount, showErrors: Object.assign(state.showErrors, { amount: false }) }),
[SET_PAY_INPUT]: (state, { payInput }) => ({ ...state, payInput, showErrors: Object.assign(state.showErrors, { payInput: false }) }),
[SET_PAY_INVOICE]: (state, { invoice }) => ({ ...state, invoice, showErrors: Object.assign(state.showErrors, { amount: false }) }),
[SET_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({ ...state, showCurrencyFilters }),
[UPDATE_PAY_ERRORS]: (state, { errorsObject }) => ({ ...state, showErrors: Object.assign(state.showErrors, errorsObject) }),
@ -140,12 +152,23 @@ payFormSelectors.currentAmount = createSelector(
payFormSelectors.isLn,
payAmountSelector,
payInvoiceSelector,
(isLn, amount, invoice) => {
currencySelector,
(isLn, amount, invoice, currency) => {
if (isLn) {
switch (currency) {
case 'btc':
return btc.satoshisToBtc((invoice.num_satoshis || 0))
case 'bits':
return btc.satoshisToBits((invoice.num_satoshis || 0))
case 'sats':
return invoice.num_satoshis
default:
return invoice.num_satoshis
}
}
return amount > 0 ? amount : null
return amount
}
)
@ -162,7 +185,33 @@ payFormSelectors.usdAmount = createSelector(
return btc.satoshisToUsd((invoice.num_satoshis || 0), ticker.price_usd)
}
switch (currency) {
case 'btc':
return btc.btcToUsd(amount, ticker.price_usd)
case 'bits':
return btc.bitsToUsd(amount, ticker.price_usd)
case 'sats':
return btc.satoshisToUsd(amount, ticker.price_usd)
default:
return ''
}
}
)
payFormSelectors.payInputMin = createSelector(
currencySelector,
(currency) => {
switch (currency) {
case 'btc':
return '0.00000001'
case 'bits':
return '0.01'
case 'sats':
return '1'
default:
return '0'
}
}
)

34
app/reducers/ticker.js

@ -77,6 +77,8 @@ const ACTION_HANDLERS = {
// Selectors
const tickerSelectors = {}
const cryptoSelector = state => state.ticker.crypto
const currencyFiltersSelector = state => state.ticker.currencyFilters
const currencySelector = state => state.ticker.currency
const bitcoinTickerSelector = state => state.ticker.btcTicker
const litecoinTickerSelector = state => state.ticker.ltcTicker
@ -87,6 +89,22 @@ tickerSelectors.currentTicker = createSelector(
(crypto, btcTicker, ltcTicker) => (crypto === 'btc' ? btcTicker : ltcTicker)
)
tickerSelectors.currentCurrencyFilters = createSelector(
currencySelector,
currencyFiltersSelector,
(currency, filters) => filters.filter(f => f.key !== currency)
)
tickerSelectors.currencyName = createSelector(
currencySelector,
(currency) => {
if (currency === 'btc') { return 'BTC' }
if (currency === 'sats') { return 'satohis' }
return currency
}
)
export { tickerSelectors }
// ------------------------------------
@ -97,7 +115,21 @@ const initialState = {
currency: '',
crypto: '',
btcTicker: null,
ltcTicker: null
ltcTicker: null,
currencyFilters: [
{
key: 'btc',
name: 'BTC'
},
{
key: 'bits',
name: 'bits'
},
{
key: 'sats',
name: 'satoshis'
}
]
}
export default function tickerReducer(state = initialState, action) {

12
app/routes/app/containers/AppContainer.js

@ -11,7 +11,7 @@ import { showModal, hideModal } from 'reducers/modal'
import { setFormType } from 'reducers/form'
import { setPayAmount, setPayInput, updatePayErrors, payFormSelectors } from 'reducers/payform'
import { setPayAmount, setPayInput, setCurrencyFilters, updatePayErrors, payFormSelectors } from 'reducers/payform'
import { setRequestAmount, setRequestMemo } from 'reducers/requestform'
@ -69,6 +69,7 @@ const mapDispatchToProps = {
setPayAmount,
setPayInput,
setCurrencyFilters,
updatePayErrors,
setRequestAmount,
@ -130,6 +131,8 @@ const mapStateToProps = state => ({
network: state.network,
currentTicker: tickerSelectors.currentTicker(state),
currentCurrencyFilters: tickerSelectors.currentCurrencyFilters(state),
currencyName: tickerSelectors.currencyName(state),
isOnchain: payFormSelectors.isOnchain(state),
isLn: payFormSelectors.isLn(state),
currentAmount: payFormSelectors.currentAmount(state),
@ -137,6 +140,7 @@ const mapStateToProps = state => ({
inputCaption: payFormSelectors.inputCaption(state),
showPayLoadingScreen: payFormSelectors.showPayLoadingScreen(state),
payFormIsValid: payFormSelectors.payFormIsValid(state),
payInputMin: payFormSelectors.payInputMin(state),
syncPercentage: lndSelectors.syncPercentage(state),
filteredNetworkNodes: contactFormSelectors.filteredNetworkNodes(state),
@ -157,6 +161,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
currency: stateProps.ticker.currency,
crypto: stateProps.ticker.crypto,
nodes: stateProps.network.nodes,
ticker: stateProps.ticker,
isOnchain: stateProps.isOnchain,
isLn: stateProps.isLn,
@ -165,10 +170,15 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
inputCaption: stateProps.inputCaption,
showPayLoadingScreen: stateProps.showPayLoadingScreen,
payFormIsValid: stateProps.payFormIsValid,
payInputMin: stateProps.payInputMin,
currentCurrencyFilters: stateProps.currentCurrencyFilters,
currencyName: stateProps.currencyName,
setPayAmount: dispatchProps.setPayAmount,
setPayInput: dispatchProps.setPayInput,
setCurrencyFilters: dispatchProps.setCurrencyFilters,
fetchInvoice: dispatchProps.fetchInvoice,
setCurrency: dispatchProps.setCurrency,
onPayAmountBlur: () => {
// If the amount is now valid and showErrors was on, turn it off

99
app/utils/btc.js

@ -1,11 +1,52 @@
import sb from 'satoshi-bitcoin'
//////////////////////
// BTC to things /////
/////////////////////
export function btcToSatoshis(btc) {
if (btc === undefined || btc === null || btc === '') return null
return sb.toSatoshi(btc)
}
export function btcToBits(btc) {
if (btc === undefined || btc === null || btc === '') return null
return satoshisToBits(sb.toSatoshi(btc))
}
export function btcToUsd(btc, price) {
const amount = parseFloat(btc * price).toFixed(2)
return (btc > 0 && amount <= 0) ? '< 0.01' : amount.toLocaleString('en')
}
////////////////////////////
// bits to things /////////
//////////////////////////
export function bitsToBtc(bits, price) {
if (bits === undefined || bits === null || bits === '') return null
const sats = bits * 100
return satoshisToBtc(sats, price)
}
export function bitsToSatoshis(bits, price) {
if (bits === undefined || bits === null || bits === '') return null
return bits * 100
}
export function bitsToUsd(bits, price) {
if (bits === undefined || bits === null || bits === '') return null
const sats = bits * 100
return satoshisToUsd(sats, price)
}
////////////////////////////
// satoshis to things /////
//////////////////////////
export function satoshisToBtc(satoshis) {
if (satoshis === undefined || satoshis === null || satoshis === '') return null
@ -20,17 +61,13 @@ export function satoshisToBits(satoshis) {
return bitsAmount > 0 ? bitsAmount : bitsAmount * -1
}
export function btcToUsd(btc, price) {
const amount = parseFloat(btc * price).toFixed(2)
return (btc > 0 && amount <= 0) ? '< 0.01' : amount.toLocaleString('en')
}
export function satoshisToUsd(satoshis, price) {
if (satoshis === undefined || satoshis === null || satoshis === '') return null
return btcToUsd(satoshisToBtc(satoshis), price)
}
export function renderCurrency(currency) {
switch (currency) {
case 'btc':
@ -44,11 +81,57 @@ export function renderCurrency(currency) {
}
}
export function convert(from, to, amount, price) {
switch (from) {
case 'btc':
switch (to) {
case 'bits':
return btcToBits(amount)
case 'sats':
return btcToSatoshis(amount)
case 'usd':
return btcToUsd(amount, price)
}
break
case 'bits':
switch (to) {
case 'btc':
return bitsToBtc(amount)
case 'sats':
return bitsToSatoshis(amount)
case 'usd':
return bitsToUsd(amount, price)
}
break
case 'sats':
switch (to) {
case 'btc':
return satoshisToBtc(amount)
case 'bits':
return satoshisToBits(amount)
case 'usd':
return satoshisToUsd(amount, price)
}
break
default:
return ''
}
}
export default {
btcToSatoshis,
btcToBits,
btcToUsd,
bitsToBtc,
bitsToSatoshis,
bitsToUsd,
satoshisToBtc,
satoshisToBits,
satoshisToUsd,
btcToUsd,
renderCurrency
renderCurrency,
convert
}

Loading…
Cancel
Save