From 04798ac7f99f47fe4b61c7c5c1cf7fc789d4a69d Mon Sep 17 00:00:00 2001
From: Jack Mallers <jimmymowschess@gmail.com>
Date: Wed, 21 Feb 2018 11:39:07 -0600
Subject: [PATCH] feature(payform): new pay form MVP

---
 app/components/Form/Form.js               |  21 +--
 app/components/Form/Form.scss             |  58 ++------
 app/components/Form/Pay.js                | 156 ++++++++++++++++++++++
 app/components/Form/Pay.scss              | 123 +++++++++++++++++
 app/components/Wallet/Wallet.js           |   5 +-
 app/icons/link.svg                        |   1 +
 app/icons/paper_plane.svg                 |  16 +++
 app/icons/x.svg                           |   1 +
 app/icons/zap.svg                         |   1 +
 app/main.dev.js                           |   4 +-
 app/reducers/payform.js                   |  19 ++-
 app/routes/app/containers/AppContainer.js |   2 +
 app/utils/btc.js                          |   2 -
 13 files changed, 345 insertions(+), 64 deletions(-)
 create mode 100644 app/components/Form/Pay.js
 create mode 100644 app/components/Form/Pay.scss
 create mode 100644 app/icons/link.svg
 create mode 100644 app/icons/paper_plane.svg
 create mode 100644 app/icons/x.svg
 create mode 100644 app/icons/zap.svg

diff --git a/app/components/Form/Form.js b/app/components/Form/Form.js
index dfc9476b..cbaece23 100644
--- a/app/components/Form/Form.js
+++ b/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>
   )
 }
diff --git a/app/components/Form/Form.scss b/app/components/Form/Form.scss
index 25a54ff0..d0fa3879 100644
--- a/app/components/Form/Form.scss
+++ b/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;
-}
diff --git a/app/components/Form/Pay.js b/app/components/Form/Pay.js
new file mode 100644
index 00000000..a88dd950
--- /dev/null
+++ b/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
diff --git a/app/components/Form/Pay.scss b/app/components/Form/Pay.scss
new file mode 100644
index 00000000..98ec2b17
--- /dev/null
+++ b/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%);
+        }
+      }
+    }
+  }
+}
diff --git a/app/components/Wallet/Wallet.js b/app/components/Wallet/Wallet.js
index 6bb2195a..703bc554 100644
--- a/app/components/Wallet/Wallet.js
+++ b/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>
diff --git a/app/icons/link.svg b/app/icons/link.svg
new file mode 100644
index 00000000..c89dd41c
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/app/icons/paper_plane.svg b/app/icons/paper_plane.svg
new file mode 100644
index 00000000..46ab5ce7
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/app/icons/x.svg b/app/icons/x.svg
new file mode 100644
index 00000000..7d5875ca
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/app/icons/zap.svg b/app/icons/zap.svg
new file mode 100644
index 00000000..8fdafa93
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/app/main.dev.js b/app/main.dev.js
index e16f893a..e2f4f46f 100644
--- a/app/main.dev.js
+++ b/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',
diff --git a/app/reducers/payform.js b/app/reducers/payform.js
index f626af99..db17dbf2 100644
--- a/app/reducers/payform.js
+++ b/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)
   }
 )
 
diff --git a/app/routes/app/containers/AppContainer.js b/app/routes/app/containers/AppContainer.js
index b1e4ab65..f0cc5faa 100644
--- a/app/routes/app/containers/AppContainer.js
+++ b/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,
diff --git a/app/utils/btc.js b/app/utils/btc.js
index 93e9edc9..32c470d1 100644
--- a/app/utils/btc.js
+++ b/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'
   }