diff --git a/app/components/Form/Pay.js b/app/components/Form/Pay.js index ced4d19d..57f4bc94 100644 --- a/app/components/Form/Pay.js +++ b/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 (
{showPayLoadingScreen && } @@ -122,18 +138,21 @@ class Pay extends Component { readOnly={isLn} />
-
- BTC +
setCurrencyFilters(!showCurrencyFilters)}> + {currencyName}
-
    -
  • Bits
  • -
  • Satoshis
  • +
      + { + currentCurrencyFilters.map(filter => +
    • onCurrencyFilterClick(filter.key)}>{filter.name}
    • + ) + }
- {`≈ ${usdAmount} USD`} + {`≈ ${usdAmount || 0} USD`}
@@ -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, diff --git a/app/components/Form/Pay.scss b/app/components/Form/Pay.scss index 52997373..55a8b954 100644 --- a/app/components/Form/Pay.scss +++ b/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; + } + } } } diff --git a/app/reducers/payform.js b/app/reducers/payform.js index da66ecc4..4fc2221e 100644 --- a/app/reducers/payform.js +++ b/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) { - return btc.satoshisToBtc((invoice.num_satoshis || 0)) + 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) } - return btc.btcToUsd(amount, 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' + } } ) diff --git a/app/reducers/ticker.js b/app/reducers/ticker.js index 32665a5a..2a9aee11 100644 --- a/app/reducers/ticker.js +++ b/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) { diff --git a/app/routes/app/containers/AppContainer.js b/app/routes/app/containers/AppContainer.js index bd39879d..1e89057e 100644 --- a/app/routes/app/containers/AppContainer.js +++ b/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 diff --git a/app/utils/btc.js b/app/utils/btc.js index 32c470d1..945cccf7 100644 --- a/app/utils/btc.js +++ b/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 }