Browse Source

feature(payform): new pay form MVP

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
04798ac7f9
  1. 21
      app/components/Form/Form.js
  2. 58
      app/components/Form/Form.scss
  3. 156
      app/components/Form/Pay.js
  4. 123
      app/components/Form/Pay.scss
  5. 5
      app/components/Wallet/Wallet.js
  6. 1
      app/icons/link.svg
  7. 16
      app/icons/paper_plane.svg
  8. 1
      app/icons/x.svg
  9. 1
      app/icons/zap.svg
  10. 4
      app/main.dev.js
  11. 19
      app/reducers/payform.js
  12. 2
      app/routes/app/containers/AppContainer.js
  13. 2
      app/utils/btc.js

21
app/components/Form/Form.js

@ -1,15 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg'
import { MdClose } from 'react-icons/lib/md'
import Pay from './Pay'
import PayForm from './PayForm'
import RequestForm from './RequestForm'
import x from 'icons/x.svg'
import styles from './Form.scss'
const FORM_TYPES = {
PAY_FORM: PayForm,
// PAY_FORM: PayForm,
PAY_FORM: Pay,
REQUEST_FORM: RequestForm
}
@ -18,16 +22,13 @@ const Form = ({ formType, formProps, closeForm }) => {
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 className={`${styles.container} ${formType && styles.open}`}>
<div className={styles.closeContainer}>
<span onClick={closeForm}>
<Isvg src={x} />
</span>
</div>
<FormComponent {...formProps} />
</div>
)
}

58
app/components/Form/Form.scss

@ -1,59 +1,27 @@
@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: 20;
}
}
.innercontainer {
.container {
position: relative;
height: 100vh;
margin: 5%;
background: $spaceblue;
}
.esc {
position: absolute;
top: 0;
right: 0;
color: $darkestgrey;
cursor: pointer;
padding: 20px;
border-radius: 50%;
.closeContainer {
text-align: right;
padding: 20px 40px 0px;
&:hover {
color: $bluegrey;
background: $darkgrey;
}
span {
cursor: pointer;
opacity: 1.0;
transition: 0.25s all;
&:active {
color: $white;
background: $main;
&:hover {
opacity: 0.5;
}
}
svg {
width: 32px;
height: 32px;
color: $white;
}
}
.content {
width: 50%;
margin: 0 auto;
display: flex;
flex-direction: column;
height: 75vh;
justify-content: center;
align-items: center;
}

156
app/components/Form/Pay.js

@ -0,0 +1,156 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg'
import paperPlane from 'icons/paper_plane.svg'
import { FaBolt, FaChain, FaAngleDown } from 'react-icons/lib/fa'
import LoadingBolt from 'components/LoadingBolt'
import CurrencyIcon from 'components/CurrencyIcon'
import styles from './Pay.scss'
class Pay 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, showErrors },
currency,
crypto,
isOnchain,
isLn,
currentAmount,
usdAmount,
inputCaption,
showPayLoadingScreen,
payFormIsValid: { errors, isValid },
setPayAmount,
onPayAmountBlur,
setPayInput,
onPayInputBlur,
onPaySubmit
} = this.props
console.log('usdAmount: ', usdAmount)
return (
<div className={styles.container}>
{showPayLoadingScreen && <LoadingBolt />}
<header className={styles.header}>
<Isvg src={paperPlane} />
<h1>Make Payment</h1>
</header>
<div className={styles.content}>
<section className={styles.destination}>
<div className={styles.top}>
<label htmlFor='destination'>Destination</label>
<span>
</span>
</div>
<div className={styles.bottom}>
<textarea
type='text'
placeholder='Payment request or bitcoin address'
value={payInput}
onChange={event => setPayInput(event.target.value)}
onBlur={onPayInputBlur}
id='destination'
rows='4'
/>
</div>
</section>
<section className={styles.amount}>
<div className={styles.top}>
<label>Amount</label>
<span></span>
</div>
<div className={styles.bottom}>
<input
type='number'
min='0'
ref={(input) => { this.amountInput = input }}
size=''
placeholder='0.00000000'
value={currentAmount || ''}
onChange={event => setPayAmount(event.target.value)}
onBlur={onPayAmountBlur}
id='amount'
readOnly={isLn}
/>
<div className={styles.currency}>
<section className={styles.currentCurrency}>
<span>BTC</span><span><FaAngleDown /></span>
</section>
<ul>
<li>Bits</li>
<li>Satoshis</li>
</ul>
</div>
</div>
<div className={styles.usdAmount}>
{`${usdAmount} USD`}
</div>
</section>
<section className={styles.submit}>
<div className={`${styles.button} ${isValid && styles.active}`} onClick={onPaySubmit}>Pay</div>
</section>
</div>
</div>
)
}
}
Pay.propTypes = {
payform: PropTypes.shape({
amount: PropTypes.string.isRequired,
payInput: PropTypes.string.isRequired,
showErrors: PropTypes.object.isRequired
}).isRequired,
currency: PropTypes.string.isRequired,
crypto: PropTypes.string.isRequired,
isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired,
currentAmount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
inputCaption: PropTypes.string.isRequired,
showPayLoadingScreen: PropTypes.bool.isRequired,
payFormIsValid: PropTypes.shape({
errors: PropTypes.object,
isValid: PropTypes.bool
}).isRequired,
setPayAmount: PropTypes.func.isRequired,
onPayAmountBlur: PropTypes.func.isRequired,
setPayInput: PropTypes.func.isRequired,
onPayInputBlur: PropTypes.func.isRequired,
fetchInvoice: PropTypes.func.isRequired,
onPaySubmit: PropTypes.func.isRequired
}
export default Pay

123
app/components/Form/Pay.scss

@ -0,0 +1,123 @@
@import '../../variables.scss';
.container {
padding: 0 40px;
font-family: Roboto;
}
.header {
text-align: center;
padding-bottom: 20px;
color: $white;
border-bottom: 1px solid $spaceborder;
h1 {
font-size: 22px;
font-weight: 100;
margin-top: 10px;
letter-spacing: 1.5px;
}
}
.content {
margin-top: 50px;
color: $white;
.destination {
margin-bottom: 25px;
}
.amount .bottom {
display: flex;
flex-direction: row;
align-items: center;
input {
font-size: 40px;
max-width: 250px;
}
}
.top {
margin-bottom: 30px;
label {
font-size: 14px;
}
}
.bottom {
input, textarea {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
font-size: 12px;
width: 100%;
font-weight: 200;
}
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
}
.currency {
display: flex;
flex-direction: row;
align-items: center;
.currentCurrency {
cursor: pointer;
transition: 0.25s all;
&:hover {
opacity: 0.5;
}
span {
font-size: 14px;
&:nth-child(1) {
font-weight: bold;
}
}
}
ul {
visibility: hidden;
position: absolute;
}
}
.usdAmount {
margin-top: 20px;
}
.submit {
margin-top: 50px;
text-align: center;
.button {
width: 235px;
margin: 0 auto;
padding: 20px 10px;
background: #31343f;
opacity: 0.5;
cursor: pointer;
transition: 0.25s all;
&.active {
background: $gold;
opacity: 1.0;
&:hover {
background: darken($gold, 5%);
}
}
}
}
}

5
app/components/Wallet/Wallet.js

@ -35,6 +35,7 @@ class Wallet extends Component {
} = this.props
const { modalOpen, qrCodeType } = this.state
const usdAmount = btc.satoshisToUsd((parseInt(balance.walletBalance, 10) + parseInt(balance.channelBalance, 10)), currentTicker.price_usd)
const changeQrCode = () => {
const qrCodeNum = this.state.qrCodeType === 1 ? 2 : 1
@ -89,6 +90,7 @@ class Wallet extends Component {
<Isvg className={styles.bitcoinLogo} src={qrCode} />
</span>
</h1>
<span className={styles.usdValue}> ${usdAmount ? usdAmount.toLocaleString() : ''}</span>
<div className={styles.tickerButtons}>
<section className={ticker.currency === 'btc' && styles.active} onClick={() => setCurrency('btc')}>
BTC
@ -99,9 +101,6 @@ class Wallet extends Component {
<section className={ticker.currency === 'sats' && styles.active} onClick={() => setCurrency('sats')}>
Satoshis
</section>
<section className={ticker.currency === 'usd' && styles.active} onClick={() => setCurrency('usd')}>
USD
</section>
</div>
</div>
</div>

1
app/icons/link.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>

After

Width:  |  Height:  |  Size: 371 B

16
app/icons/paper_plane.svg

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>Shape</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Pay-(Onchain)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-502.000000, -68.000000)" stroke-linecap="round" stroke-linejoin="round">
<g id="Group-3" transform="translate(423.000000, 69.000000)" stroke="#FFFFFF" stroke-width="0.75">
<g id="Group">
<g id="send" transform="translate(79.000000, 0.000000)">
<polygon id="Shape" points="21 0 13.65 21 9.45 11.55 0 7.35"></polygon>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 897 B

1
app/icons/x.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>

After

Width:  |  Height:  |  Size: 299 B

1
app/icons/zap.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 282 B

4
app/main.dev.js

@ -152,8 +152,8 @@ const startLnd = (alias, autopilot) => {
'--bitcoin.active',
'--bitcoin.testnet',
'--bitcoin.node=neutrino',
'--neutrino.connect=btcd.jackmallers.com:18333',
'--neutrino.addpeer=188.166.148.62:18333',
'--neutrino.connect=188.166.148.62:18333',
'--neutrino.addpeer=btcd.jackmallers.com:18333',
'--neutrino.addpeer=159.65.48.139:18333',
'--neutrino.connect=127.0.0.1:18333',
'--debuglevel=debug',

19
app/reducers/payform.js

@ -135,17 +135,32 @@ payFormSelectors.isLn = createSelector(
)
payFormSelectors.currentAmount = createSelector(
payFormSelectors.isLn,
payAmountSelector,
payInvoiceSelector,
(isLn, amount, invoice) => {
if (isLn) {
return btc.satoshisToBtc((invoice.num_satoshis || 0))
}
return amount > 0 ? amount : null
}
)
payFormSelectors.usdAmount = createSelector(
payFormSelectors.isLn,
payAmountSelector,
payInvoiceSelector,
currencySelector,
tickerSelectors.currentTicker,
(isLn, amount, invoice, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false }
if (isLn) {
return currency === 'usd' ? btc.satoshisToUsd((invoice.num_satoshis || 0), ticker.price_usd) : btc.satoshisToBtc((invoice.num_satoshis || 0))
return btc.satoshisToUsd((invoice.num_satoshis || 0), ticker.price_usd)
}
return amount
return btc.btcToUsd(amount, ticker.price_usd)
}
)

2
app/routes/app/containers/AppContainer.js

@ -133,6 +133,7 @@ const mapStateToProps = state => ({
isOnchain: payFormSelectors.isOnchain(state),
isLn: payFormSelectors.isLn(state),
currentAmount: payFormSelectors.currentAmount(state),
usdAmount: payFormSelectors.usdAmount(state),
inputCaption: payFormSelectors.inputCaption(state),
showPayLoadingScreen: payFormSelectors.showPayLoadingScreen(state),
payFormIsValid: payFormSelectors.payFormIsValid(state),
@ -159,6 +160,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
isOnchain: stateProps.isOnchain,
isLn: stateProps.isLn,
currentAmount: stateProps.currentAmount,
usdAmount: stateProps.usdAmount,
inputCaption: stateProps.inputCaption,
showPayLoadingScreen: stateProps.showPayLoadingScreen,
payFormIsValid: stateProps.payFormIsValid,

2
app/utils/btc.js

@ -39,8 +39,6 @@ export function renderCurrency(currency) {
return 'bits'
case 'sats':
return 'satoshis'
case 'usd':
return 'USD'
default:
return 'satoshis'
}

Loading…
Cancel
Save