From 907b81da8c2c64f024c7b21136ad18c3d6e95c82 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Thu, 1 Feb 2018 21:31:42 -0600 Subject: [PATCH] feature(header, activity): new activity and header --- app/components/Wallet/Wallet.js | 50 ++++++-- app/components/Wallet/Wallet.scss | 110 +++++++++++------- app/icons/bitcoin.svg | 16 +++ app/icons/check_circle.svg | 1 + app/icons/clock.svg | 1 + app/icons/qrcode.svg | 27 +++++ app/icons/zap_logo.svg | 16 +++ app/main.dev.js | 3 +- app/reducers/activity.js | 48 ++++++-- app/routes/activity/components/Activity.js | 50 ++++---- app/routes/activity/components/Activity.scss | 99 ++++++---------- .../components/components/Activity.scss | 79 ++++++++----- .../components/components/Invoice/Invoice.js | 34 +++--- .../components/components/Payment/Payment.js | 16 +-- .../components/Transaction/Transaction.js | 16 +-- .../activity/containers/ActivityContainer.js | 21 +++- app/routes/app/components/App.js | 11 +- app/routes/app/components/App.scss | 2 +- app/routes/app/containers/AppContainer.js | 4 - app/utils/btc.js | 3 +- app/variables.scss | 5 +- 21 files changed, 379 insertions(+), 233 deletions(-) create mode 100644 app/icons/bitcoin.svg create mode 100644 app/icons/check_circle.svg create mode 100644 app/icons/clock.svg create mode 100644 app/icons/qrcode.svg create mode 100644 app/icons/zap_logo.svg diff --git a/app/components/Wallet/Wallet.js b/app/components/Wallet/Wallet.js index 08ec36a1..469290fa 100644 --- a/app/components/Wallet/Wallet.js +++ b/app/components/Wallet/Wallet.js @@ -1,9 +1,11 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import { FaQrcode } from 'react-icons/lib/fa' +import { FaQrcode, FaAngleDown } from 'react-icons/lib/fa' import Isvg from 'react-inlinesvg' import { btc } from 'utils' -import skinnyBitcoinIcon from 'icons/skinny_bitcoin.svg' +import bitcoinIcon from 'icons/bitcoin.svg' +import zapLogo from 'icons/zap_logo.svg' +import qrCode from 'icons/qrcode.svg' import ReceiveModal from './ReceiveModal' import styles from './Wallet.scss' @@ -23,10 +25,14 @@ class Wallet extends Component { balance, address, info, - newAddress + newAddress, + currentTicker, + openPayForm, + openRequestForm } = this.props const { modalOpen, qrCodeType } = this.state + const usdAmount = parseFloat(btc.satoshisToUsd(balance.walletBalance, currentTicker.price_usd)).toLocaleString() const changeQrCode = () => { const qrCodeNum = this.state.qrCodeType === 1 ? 2 : 1 @@ -51,22 +57,39 @@ class Wallet extends Component { ) }
+
+
+ +
+ +
+
+ {info.data.alias} + +
+
+
+
- +
-

{btc.satoshisToBtc(parseFloat(balance.walletBalance) + parseFloat(balance.channelBalance))} BTC

- {btc.satoshisToBtc(balance.walletBalance)} available - {btc.satoshisToBtc(balance.channelBalance)} in channels +

+ + {btc.satoshisToBtc(parseFloat(balance.walletBalance) + parseFloat(balance.channelBalance))}BTC + + this.setState({ modalOpen: true })}> + + +

+ ≈ ${usdAmount}
-
this.setState({ modalOpen: true })}> - - Address -
+
Pay
+
Request
@@ -79,7 +102,10 @@ Wallet.propTypes = { balance: PropTypes.object.isRequired, address: PropTypes.string.isRequired, info: PropTypes.object.isRequired, - newAddress: PropTypes.func.isRequired + newAddress: PropTypes.func.isRequired, + currentTicker: PropTypes.object.isRequired, + openPayForm: PropTypes.func.isRequired, + openRequestForm: PropTypes.func.isRequired } export default Wallet diff --git a/app/components/Wallet/Wallet.scss b/app/components/Wallet/Wallet.scss index 80ff9dd4..db643089 100644 --- a/app/components/Wallet/Wallet.scss +++ b/app/components/Wallet/Wallet.scss @@ -1,10 +1,31 @@ @import '../../variables.scss'; .wallet { - cursor: pointer; - background: $lightgrey; + background: $bluegrey; + color: $white; transition: background 0.25s; height: 150px; + padding: 20px 40px; +} + +.header { + display: flex; + flex-direction: row; + justify-content: space-between; + + .logo span svg { + width: 64px; + height: 24px; + } + + .user { + cursor: pointer; + transition: all 0.25s; + + &:hover { + opacity: 0.5; + } + } } .left, .right { @@ -14,7 +35,7 @@ height: 150px; .leftContent, .rightContent { - padding: 25px; + padding: 25px 0; } } @@ -23,8 +44,8 @@ flex-direction: row; .bitcoinLogo svg { - width: 100px; - height: 100px; + width: 32px; + height: 32px; } .details { @@ -33,14 +54,37 @@ justify-content: center; h1 { - font-size: 24px; - font-weight: bold; - margin-bottom: 10px; - letter-spacing: 1.5px; + display: flex; + flex-direction: row; + + span:nth-child(1) { + font-size: 24px; + line-height: 32px; + font-weight: 500; + margin-left: 10px; + margin-bottom: 5px; + letter-spacing: 1.5px; + } + + span:nth-child(2) svg { + color: $white; + width: 20px; + height: 32px; + opacity: 1; + margin-left: 5px; + cursor: pointer; + transition: all 0.25s; + + &:hover { + opacity: 0.5; + } + } } - span { - margin: 2.5px 0; + .usdValue { + font-size: 12px; + margin-left: 10px; + font-style: italic; } } @@ -56,42 +100,24 @@ justify-content: flex-end; align-items: right; - .addressButton { - box-shadow: none; + .pay, .request { + font-size: 16px; + font-weight: bold; + color: $white; + background: $spaceblue; + padding: 15px; + width: 100px; + text-align: center; + border-radius: 5px; + cursor: pointer; transition: all 0.25s; - padding-top: 12px; - padding-bottom: 10px; - font-size: 14px; - - &:hover { - background: darken($main, 10%); - } - - span { - display: inline-block; - vertical-align: top; - - &:nth-child(1) svg { - width: 14px; - height: 14px; - margin-right: 5px; - } - } - } - - div { - padding: 7px 20px; - background: $main; - transition: background 0.25s; - color: $black; &:hover { - background: darken($main, 10%); + opacity: 0.5; } - svg { - font-size: 35px; - margin-right: 10px; + &:nth-child(1) { + margin-right: 20px; } } } diff --git a/app/icons/bitcoin.svg b/app/icons/bitcoin.svg new file mode 100644 index 00000000..63b9304f --- /dev/null +++ b/app/icons/bitcoin.svg @@ -0,0 +1,16 @@ + + + + Bitcoin + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/app/icons/check_circle.svg b/app/icons/check_circle.svg new file mode 100644 index 00000000..d95153c8 --- /dev/null +++ b/app/icons/check_circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/icons/clock.svg b/app/icons/clock.svg new file mode 100644 index 00000000..833c0608 --- /dev/null +++ b/app/icons/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/icons/qrcode.svg b/app/icons/qrcode.svg new file mode 100644 index 00000000..fe674bc6 --- /dev/null +++ b/app/icons/qrcode.svg @@ -0,0 +1,27 @@ + + + + Group 23 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/icons/zap_logo.svg b/app/icons/zap_logo.svg new file mode 100644 index 00000000..2e0032a4 --- /dev/null +++ b/app/icons/zap_logo.svg @@ -0,0 +1,16 @@ + + + + Group 5 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/app/main.dev.js b/app/main.dev.js index d8f8621b..662fe1a0 100644 --- a/app/main.dev.js +++ b/app/main.dev.js @@ -146,7 +146,8 @@ const startLnd = () => { '--neutrino.connect=127.0.0.1:18333', '--autopilot.active', '--debuglevel=debug', - '--noencryptwallet' + '--noencryptwallet', + '--alias=jimmymow' ] ) .on('error', error => console.log(`lnd error: ${error}`)) diff --git a/app/reducers/activity.js b/app/reducers/activity.js index e9b672ae..64b3e61c 100644 --- a/app/reducers/activity.js +++ b/app/reducers/activity.js @@ -7,11 +7,11 @@ const initialState = { filterPulldown: false, filter: { key: 'ALL_ACTIVITY', name: 'All Activity' }, filters: [ - { key: 'ALL_ACTIVITY', name: 'All Activity' }, - { key: 'LN_ACTIVITY', name: 'LN Activity' }, - { key: 'PAYMENT_ACTIVITY', name: 'LN Payments' }, - { key: 'INVOICE_ACTIVITY', name: 'LN Invoices' }, - { key: 'TRANSACTION_ACTIVITY', name: 'On-chain Activity' } + { key: 'ALL_ACTIVITY', name: 'All' }, + { key: 'SENT_ACTIVITY', name: 'Sent' }, + { key: 'REQUESTED_ACTIVITY', name: 'Requested' }, + { key: 'PENDING_ACTIVITY', name: 'Pending' }, + { key: 'FUNDED_ACTIVITY', name: 'Funding Transactions' } ], modal: { modalType: null, @@ -91,6 +91,7 @@ const searchSelector = state => state.activity.searchText const paymentsSelector = state => state.payment.payments const invoicesSelector = state => state.invoice.invoices const transactionsSelector = state => state.transaction.transactions +const channelsSelector = state => state.channels.channels const allActivity = createSelector( searchSelector, @@ -138,12 +139,41 @@ const transactionActivity = createSelector( transactions => transactions ) +const sentActivity = createSelector( + transactionsSelector, + paymentsSelector, + (transactions, payments) => { + const sentTransactions = transactions.filter(transaction => transaction.amount < 0) + return [...sentTransactions, ...payments].sort((a, b) => { + const aTimestamp = Object.prototype.hasOwnProperty.call(a, 'time_stamp') ? a.time_stamp : a.creation_date + const bTimestamp = Object.prototype.hasOwnProperty.call(b, 'time_stamp') ? b.time_stamp : b.creation_date + + return bTimestamp - aTimestamp + }) + } +) + +const pendingActivity = createSelector( + invoicesSelector, + invoices => invoices.filter(invoice => !invoice.settled) +) + +const fundedActivity = createSelector( + transactionsSelector, + channelsSelector, + (transactions, channels) => { + const fundingTxIds = channels.map(channel => channel.channel_point.split(':')[0]) + + return transactions.filter(transaction => fundingTxIds.includes(transaction.tx_hash)) + } +) + const FILTERS = { ALL_ACTIVITY: allActivity, - LN_ACTIVITY: lnActivity, - PAYMENT_ACTIVITY: paymentActivity, - INVOICE_ACTIVITY: invoiceActivity, - TRANSACTION_ACTIVITY: transactionActivity + SENT_ACTIVITY: sentActivity, + REQUESTED_ACTIVITY: invoiceActivity, + PENDING_ACTIVITY: pendingActivity, + FUNDED_ACTIVITY: fundedActivity } activitySelectors.currentActivity = createSelector( diff --git a/app/routes/activity/components/Activity.js b/app/routes/activity/components/Activity.js index 60c83d5d..229cd284 100644 --- a/app/routes/activity/components/Activity.js +++ b/app/routes/activity/components/Activity.js @@ -21,13 +21,14 @@ class Activity extends Component { componentWillMount() { const { - fetchPayments, fetchInvoices, fetchTransactions, fetchBalance + fetchPayments, fetchInvoices, fetchTransactions, fetchBalance, fetchChannels } = this.props fetchBalance() fetchPayments() fetchInvoices() fetchTransactions() + fetchChannels() } renderActivity(activity) { @@ -54,13 +55,15 @@ class Activity extends Component { info, payment: { paymentLoading }, currentTicker, - activity: { modal, filter, filterPulldown, searchText }, + activity: { modal, filters, filter, filterPulldown, searchText }, hideActivityModal, changeFilter, toggleFilterPulldown, currentActivity, nonActiveFilters, - newAddress + newAddress, + openPayForm, + openRequestForm } = this.props if (invoiceLoading || paymentLoading) { return } @@ -68,7 +71,7 @@ class Activity extends Component { if (!balance.channelBalance || !balance.walletBalance) { return } return ( -
+
- - -
- - updateSearchText(event.target.value)} - className={`${styles.text} ${styles.input}`} - placeholder='Search by hash...' - type='text' - id='invoiceSearch' - /> -
+
-

- {filter.name} -

-
    +
      { - nonActiveFilters.map(f => ( -
    • changeFilter(f)}> - {f.name} + filters.map(f => ( +
    • changeFilter(f)}> + {f.name} + +
    • )) } @@ -142,6 +138,8 @@ Activity.propTypes = { changeFilter: PropTypes.func.isRequired, newAddress: PropTypes.func.isRequired, toggleFilterPulldown: PropTypes.func.isRequired, + openPayForm: PropTypes.func.isRequired, + openRequestForm: PropTypes.func.isRequired, activity: PropTypes.object.isRequired, currentActivity: PropTypes.array.isRequired, diff --git a/app/routes/activity/components/Activity.scss b/app/routes/activity/components/Activity.scss index 38e9caff..c75e875e 100644 --- a/app/routes/activity/components/Activity.scss +++ b/app/routes/activity/components/Activity.scss @@ -32,91 +32,66 @@ .activities { background: $white; + height: 100%; .header { - padding: 60px 0 20px 0; + background: $spaceblue; + color: $white; margin: 0 auto; + padding: 0 40px; + border-bottom: 1px solid $spaceborder; - section { - position: relative; - margin-left: auto; - margin-right: auto; - padding-left: 100px; - padding-right: 100px; - max-width: 964px; - - h2 { - color: $bluegrey; - } - } - h2, h2 span { - color: $bluegrey; - cursor: pointer; - transition: color 0.25s; + .filters { + display: flex; + flex-direction: row; + + li { + position: relative; + margin: 0 15px; + opacity: 0.5; + font-size: 14px; + cursor: pointer; + padding: 20px 0; - &:hover { - color: lighten($bluegrey, 10%); + &.activeFilter { + opacity: 1; + } + + &:nth-child(1) { + margin-left: 0; + } } - } - - h2, .filters li { - text-transform: uppercase; - letter-spacing: 1.5px; - color: $darkestgrey; - font-size: 14px; - font-weight: 400; - } - - h2 span.pulldown { - color: $main; - } - - .filters { - display: none; - - &.active { - display: block; + + .activeBorder { + width: 100%; + height: 1px; + background: white; position: absolute; - bottom: -100px; - z-index: 10; - - li { - margin: 5px 0; - cursor: pointer; - - &:hover { - color: $main; - } - } - } - } + bottom: -1px; + } + } } } .activityContainer { - background: $white; + background: $spaceblue; transition: opacity 0.25s; padding-bottom: 50px; + height: 100%; + overflow-y: scroll; + padding-top: 20px; &.pulldown { opacity: 0.15; } } -.activityList { - width: 75%; - margin: 0 auto; - background: $white; -} - .activity { - padding: 0 100px; + padding: 0 40px; &:hover { - background-color: #f0f0f0; - transition-delay: 0s; - outline: $grey solid 1px; + } .left, .center, .right { diff --git a/app/routes/activity/components/components/Activity.scss b/app/routes/activity/components/components/Activity.scss index 993f309b..07cc2b56 100644 --- a/app/routes/activity/components/components/Activity.scss +++ b/app/routes/activity/components/components/Activity.scss @@ -4,15 +4,12 @@ display: flex; flex-direction: row; cursor: pointer; - max-width: 960px; - margin: 0 auto; height: 76px; align-items: center; - border-bottom: 1px solid $grey; font-size: 14px; transition: background-color .1s linear; transition-delay: .1s; - color: $darkestgrey; + color: $white; position: relative; } @@ -37,22 +34,20 @@ justify-content: center; align-items: center; padding-right: 50px; + min-width: 90px; - time { - text-transform: uppercase; + section { + margin: 2.5px 0; + } - &:nth-child(1) { - display: flex; - align-items: center; - height: 38px; - font-size: 18px; - margin-bottom: 5px; - } + svg { + width: 12.5px; + height: 12.5px; + } - &:nth-child(2) { - font-size: 12px; - } - } + time { + font-size: 12px; + } } .data { @@ -61,6 +56,10 @@ flex: 6; justify-content: space-evenly; + &:nth-child(2) { + font-size: 10px; + } + .title { margin-bottom: 5px; } @@ -70,23 +69,24 @@ } h3, span { - font-size: 18px; + font-size: 14px; font-weight: bold; + letter-spacing: 1.2px; } .icon { display: inline-block; flex: none; position: relative; - width: 36px; - height: 36px; - border: 1px solid $darkestgrey; + width: 20px; + height: 20px; + background: $main; border-radius: 50%; - margin-right: 15px; + margin-right: 5px; svg { - color: $main; - font-size: 16px; + color: $spaceblue; + font-size: 10px; vertical-align: middle; display: flex; top: 0; @@ -116,16 +116,33 @@ flex-direction: column; flex: 1; text-align: right; - font-size: 16px; - - &.positive span:nth-child(1) { - font-weight: bold; - color: $main; - } + font-size: 12px; + color: $white; span { + &:nth-child(1) { + margin-bottom: 5px; + } + &:nth-child(2) { - font-size: 12px; + font-size: 10px; + opacity: 0.5; + } + + .plus, .minus { + margin-right: 2px; + } + + .plus { + color: $green; + } + + .minus { + color: $red; } } } + +.unpaid svg { + opacity: 0.5; +} diff --git a/app/routes/activity/components/components/Invoice/Invoice.js b/app/routes/activity/components/components/Invoice/Invoice.js index 78f040c4..ea5c4f5a 100644 --- a/app/routes/activity/components/components/Invoice/Invoice.js +++ b/app/routes/activity/components/components/Invoice/Invoice.js @@ -2,31 +2,31 @@ import React from 'react' import PropTypes from 'prop-types' import Moment from 'react-moment' import 'moment-timezone' +import Isvg from 'react-inlinesvg' import { FaBolt, FaClockO } from 'react-icons/lib/fa' import { btc } from 'utils' +import checkmarkIcon from 'icons/check_circle.svg' +import clockIcon from 'icons/clock.svg' import styles from '../Activity.scss' const Invoice = ({ invoice, ticker, currentTicker, showActivityModal }) => (
      showActivityModal('INVOICE', { invoice })}> - { - !invoice.settled ? -
      - - - -
      - : - null - }
      - - {invoice.creation_date * 1000} - - - {invoice.creation_date * 1000} - +
      + { + invoice.settled ? + + : + + + + } +
      +
      + {invoice.creation_date * 1000} {invoice.creation_date * 1000} +
      @@ -46,7 +46,7 @@ const Invoice = ({
      - + + + { ticker.currency === 'usd' ? btc.satoshisToUsd(invoice.value, currentTicker.price_usd) diff --git a/app/routes/activity/components/components/Payment/Payment.js b/app/routes/activity/components/components/Payment/Payment.js index ecf5ceed..2b97e060 100644 --- a/app/routes/activity/components/components/Payment/Payment.js +++ b/app/routes/activity/components/components/Payment/Payment.js @@ -2,8 +2,10 @@ import React from 'react' import PropTypes from 'prop-types' import Moment from 'react-moment' import 'moment-timezone' +import Isvg from 'react-inlinesvg' import { FaBolt } from 'react-icons/lib/fa' import { btc } from 'utils' +import checkmarkIcon from 'icons/check_circle.svg' import styles from '../Activity.scss' const Payment = ({ @@ -11,12 +13,12 @@ const Payment = ({ }) => (
      showActivityModal('PAYMENT', { payment })}>
      - - {payment.creation_date * 1000} - - - {payment.creation_date * 1000} - +
      + +
      +
      + {payment.creation_date * 1000} {payment.creation_date * 1000} +
      @@ -36,7 +38,7 @@ const Payment = ({
      - - + - { ticker.currency === 'usd' ? btc.satoshisToUsd(payment.value, currentTicker.price_usd) diff --git a/app/routes/activity/components/components/Transaction/Transaction.js b/app/routes/activity/components/components/Transaction/Transaction.js index 36fd7fff..1660946f 100644 --- a/app/routes/activity/components/components/Transaction/Transaction.js +++ b/app/routes/activity/components/components/Transaction/Transaction.js @@ -2,8 +2,10 @@ import React from 'react' import PropTypes from 'prop-types' import Moment from 'react-moment' import 'moment-timezone' +import Isvg from 'react-inlinesvg' import { FaChain } from 'react-icons/lib/fa' import { btc } from 'utils' +import checkmarkIcon from 'icons/check_circle.svg' import styles from '../Activity.scss' const Transaction = ({ @@ -11,12 +13,12 @@ const Transaction = ({ }) => (
      showActivityModal('TRANSACTION', { transaction })}>
      - - {transaction.time_stamp * 1000} - - - {transaction.time_stamp * 1000} - +
      + +
      +
      + {transaction.time_stamp * 1000} {transaction.time_stamp * 1000} +
      @@ -36,7 +38,7 @@ const Transaction = ({
      0 ? styles.positive : styles.negative}`}> - { transaction.amount > 0 ? '+' : '' } + 0 ? styles.plus : styles.minus}>{ transaction.amount > 0 ? '+' : '-' } { ticker.currency === 'usd' ? btc.satoshisToUsd(transaction.amount, currentTicker.price_usd) diff --git a/app/routes/activity/containers/ActivityContainer.js b/app/routes/activity/containers/ActivityContainer.js index 1caa3a50..724f8cc7 100644 --- a/app/routes/activity/containers/ActivityContainer.js +++ b/app/routes/activity/containers/ActivityContainer.js @@ -21,6 +21,8 @@ import { updateSearchText } from 'reducers/activity' import { newAddress } from 'reducers/address' +import { setFormType } from 'reducers/form' +import { fetchChannels } from 'reducers/channels' import Activity from '../components/Activity' @@ -36,7 +38,9 @@ const mapDispatchToProps = { toggleFilterPulldown, newAddress, fetchBalance, - updateSearchText + updateSearchText, + setFormType, + fetchChannels } const mapStateToProps = state => ({ @@ -62,4 +66,17 @@ const mapStateToProps = state => ({ nonActiveFilters: activitySelectors.nonActiveFilters(state) }) -export default connect(mapStateToProps, mapDispatchToProps)(Activity) +const mergeProps = (stateProps, dispatchProps, ownProps) => { + return { + ...stateProps, + ...dispatchProps, + ...ownProps, + + // action to open the pay form + openPayForm: () => dispatchProps.setFormType('PAY_FORM'), + // action to open the request form + openRequestForm: () => dispatchProps.setFormType('REQUEST_FORM'), + } +} + +export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Activity) diff --git a/app/routes/app/components/App.js b/app/routes/app/components/App.js index 67333e36..482bd017 100644 --- a/app/routes/app/components/App.js +++ b/app/routes/app/components/App.js @@ -24,8 +24,6 @@ class App extends Component { currentTicker, form, - openPayForm, - openRequestForm, formProps, closeForm, @@ -38,7 +36,7 @@ class App extends Component { if (!currentTicker) { return } return ( -
      +
      -