JimmyMow
7 years ago
committed by
GitHub
84 changed files with 3033 additions and 1730 deletions
@ -1,22 +1,3 @@ |
|||||
{ |
{ |
||||
"rules": { |
"rules": {} |
||||
"flowtype/boolean-style": [2, "boolean"], |
|
||||
"flowtype/define-flow-type": 1, |
|
||||
"flowtype/delimiter-dangle": [2, "never"], |
|
||||
"flowtype/generic-spacing": [2, "never"], |
|
||||
"flowtype/no-primitive-constructor-types": 2, |
|
||||
"flowtype/no-weak-types": 1, |
|
||||
"flowtype/object-type-delimiter": [2, "comma"], |
|
||||
"flowtype/require-parameter-type": 0, |
|
||||
"flowtype/require-return-type": 0, |
|
||||
"flowtype/require-valid-file-annotation": 0, |
|
||||
"flowtype/semi": [2, "always"], |
|
||||
"flowtype/space-after-type-colon": [2, "always"], |
|
||||
"flowtype/space-before-generic-bracket": [2, "never"], |
|
||||
"flowtype/space-before-type-colon": [2, "never"], |
|
||||
"flowtype/union-intersection-spacing": [2, "always"], |
|
||||
"flowtype/use-flow-type": 2, |
|
||||
"flowtype/valid-syntax": 2, |
|
||||
"flowtype-errors/show-errors": 2 |
|
||||
} |
|
||||
} |
} |
||||
|
@ -0,0 +1,57 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
import x from 'icons/x.svg' |
||||
|
|
||||
|
import TransactionModal from './TransactionModal' |
||||
|
import PaymentModal from './PaymentModal' |
||||
|
import InvoiceModal from './InvoiceModal' |
||||
|
|
||||
|
import styles from './ActivityModal.scss' |
||||
|
|
||||
|
const ActivityModal = ({ |
||||
|
modalType, |
||||
|
modalProps, |
||||
|
ticker, |
||||
|
currentTicker, |
||||
|
|
||||
|
hideActivityModal, |
||||
|
toggleCurrencyProps |
||||
|
}) => { |
||||
|
const MODAL_COMPONENTS = { |
||||
|
TRANSACTION: TransactionModal, |
||||
|
PAYMENT: PaymentModal, |
||||
|
INVOICE: InvoiceModal |
||||
|
} |
||||
|
|
||||
|
if (!modalType) { return null } |
||||
|
|
||||
|
const SpecificModal = MODAL_COMPONENTS[modalType] |
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<div className={styles.closeContainer}> |
||||
|
<span onClick={() => hideActivityModal()}> |
||||
|
<Isvg src={x} /> |
||||
|
</span> |
||||
|
</div> |
||||
|
<SpecificModal |
||||
|
{...modalProps} |
||||
|
ticker={ticker} |
||||
|
currentTicker={currentTicker} |
||||
|
toggleCurrencyProps={toggleCurrencyProps} |
||||
|
/> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
ActivityModal.propTypes = { |
||||
|
ticker: PropTypes.object.isRequired, |
||||
|
currentTicker: PropTypes.object.isRequired, |
||||
|
toggleCurrencyProps: PropTypes.object.isRequired, |
||||
|
|
||||
|
modalType: PropTypes.string, |
||||
|
modalProps: PropTypes.object.isRequired, |
||||
|
hideActivityModal: PropTypes.func.isRequired |
||||
|
} |
||||
|
|
||||
|
export default ActivityModal |
@ -0,0 +1,27 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
position: relative; |
||||
|
height: 100vh; |
||||
|
background: $bluegrey; |
||||
|
} |
||||
|
|
||||
|
.closeContainer { |
||||
|
text-align: right; |
||||
|
padding: 20px 40px 0px; |
||||
|
|
||||
|
span { |
||||
|
cursor: pointer; |
||||
|
opacity: 1.0; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
color: $white; |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,94 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import styles from './Countdown.scss' |
||||
|
|
||||
|
class Countdown extends React.Component { |
||||
|
constructor(props) { |
||||
|
super(props) |
||||
|
|
||||
|
this.state = { |
||||
|
days: null, |
||||
|
hours: null, |
||||
|
minutes: null, |
||||
|
seconds: null, |
||||
|
expired: false, |
||||
|
interval: null |
||||
|
} |
||||
|
|
||||
|
this.timerInterval = this.timerInterval.bind(this) |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
const interval = setInterval(this.timerInterval, 1000) |
||||
|
// store interval in the state so it can be accessed later
|
||||
|
this.setState({ interval }) |
||||
|
} |
||||
|
|
||||
|
componentWillUnmount() { |
||||
|
// use interval from the state to clear the interval
|
||||
|
clearInterval(this.state.interval) |
||||
|
} |
||||
|
|
||||
|
timerInterval() { |
||||
|
const convertTwoDigits = n => (n > 9 ? n : `0${n}`.slice(-2)) |
||||
|
|
||||
|
const now = new Date().getTime() |
||||
|
const distance = (this.props.countDownDate * 1000) - now |
||||
|
|
||||
|
if (distance <= 0) { |
||||
|
this.setState({ expired: true }) |
||||
|
clearInterval(this.state.interval) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const days = convertTwoDigits(Math.floor(distance / (1000 * 60 * 60 * 24))) |
||||
|
const hours = convertTwoDigits(Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))) |
||||
|
const minutes = convertTwoDigits(Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))) |
||||
|
const seconds = convertTwoDigits(Math.floor((distance % (1000 * 60)) / 1000)) |
||||
|
|
||||
|
this.setState({ |
||||
|
days, |
||||
|
hours, |
||||
|
minutes, |
||||
|
seconds |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { |
||||
|
days, |
||||
|
hours, |
||||
|
minutes, |
||||
|
seconds, |
||||
|
expired |
||||
|
} = this.state |
||||
|
|
||||
|
if (expired) { return <span className={`${styles.container} ${styles.expired}`}>Expired</span> } |
||||
|
if (!days && !hours && !minutes && !seconds) { return <span className={styles.container} /> } |
||||
|
|
||||
|
return ( |
||||
|
<span className={styles.container}> |
||||
|
<i className={styles.caption}>Expires in</i> |
||||
|
<i> |
||||
|
{days > 0 && `${days}:`} |
||||
|
</i> |
||||
|
<i> |
||||
|
{hours > 0 && `${hours}:`} |
||||
|
</i> |
||||
|
<i> |
||||
|
{minutes > 0 && `${minutes}:`} |
||||
|
</i> |
||||
|
<i> |
||||
|
{seconds >= 0 && `${seconds}`} |
||||
|
</i> |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Countdown.propTypes = { |
||||
|
countDownDate: PropTypes.number.isRequired |
||||
|
} |
||||
|
|
||||
|
export default Countdown |
@ -0,0 +1,16 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
display: block; |
||||
|
text-align: center; |
||||
|
font-size: 12px; |
||||
|
min-height: 12px; |
||||
|
|
||||
|
&.expired { |
||||
|
color: $red; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.caption { |
||||
|
margin-right: 4px; |
||||
|
} |
@ -0,0 +1,107 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import Moment from 'react-moment' |
||||
|
import 'moment-timezone' |
||||
|
|
||||
|
import QRCode from 'qrcode.react' |
||||
|
import copy from 'copy-to-clipboard' |
||||
|
import { showNotification } from 'notifications' |
||||
|
|
||||
|
import { FaAngleDown } from 'react-icons/lib/fa' |
||||
|
|
||||
|
import Value from 'components/Value' |
||||
|
import Countdown from './Countdown' |
||||
|
|
||||
|
import styles from './InvoiceModal.scss' |
||||
|
|
||||
|
const InvoiceModal = ({ |
||||
|
invoice, |
||||
|
ticker, |
||||
|
currentTicker, |
||||
|
|
||||
|
toggleCurrencyProps: { |
||||
|
setActivityModalCurrencyFilters, |
||||
|
showCurrencyFilters, |
||||
|
currencyName, |
||||
|
currentCurrencyFilters, |
||||
|
onCurrencyFilterClick |
||||
|
} |
||||
|
}) => { |
||||
|
const copyPaymentRequest = () => { |
||||
|
copy(invoice.payment_request) |
||||
|
showNotification('Noice', 'Successfully copied to clipboard') |
||||
|
} |
||||
|
|
||||
|
const countDownDate = (parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)) |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<div className={styles.content}> |
||||
|
<section className={styles.left}> |
||||
|
<h2>Payment Request</h2> |
||||
|
<QRCode |
||||
|
value={invoice.payment_request} |
||||
|
renderAs='svg' |
||||
|
size={150} |
||||
|
bgColor='transparent' |
||||
|
fgColor='white' |
||||
|
level='L' |
||||
|
className={styles.qrcode} |
||||
|
/> |
||||
|
<Countdown countDownDate={countDownDate} /> |
||||
|
</section> |
||||
|
<section className={styles.right}> |
||||
|
<div className={styles.details}> |
||||
|
<section className={styles.amount}> |
||||
|
<h1> |
||||
|
<Value value={invoice.value} currency={ticker.currency} currentTicker={currentTicker} /> |
||||
|
</h1> |
||||
|
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}> |
||||
|
<span>{currencyName}</span><span><FaAngleDown /></span> |
||||
|
</section> |
||||
|
<ul className={showCurrencyFilters && styles.active}> |
||||
|
{ |
||||
|
currentCurrencyFilters.map(filter => |
||||
|
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) |
||||
|
} |
||||
|
</ul> |
||||
|
</section> |
||||
|
<section className={styles.date}> |
||||
|
<p> |
||||
|
<Moment format='MM/DD/YYYY'>{invoice.creation_date * 1000}</Moment> |
||||
|
</p> |
||||
|
<p className={styles.notPaid}> |
||||
|
{!invoice.settled && 'Not Paid'} |
||||
|
</p> |
||||
|
</section> |
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.memo}> |
||||
|
<h4>Memo</h4> |
||||
|
<p>{invoice.memo}</p> |
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.request}> |
||||
|
<h4>Request</h4> |
||||
|
<p>{invoice.payment_request}</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.actions}> |
||||
|
<div>Save as image</div> |
||||
|
<div onClick={copyPaymentRequest}>Copy Request</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
InvoiceModal.propTypes = { |
||||
|
invoice: PropTypes.object.isRequired, |
||||
|
ticker: PropTypes.object.isRequired, |
||||
|
currentTicker: PropTypes.object.isRequired, |
||||
|
toggleCurrencyProps: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default InvoiceModal |
@ -0,0 +1,152 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
color: $white; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
background: $spaceblue; |
||||
|
width: 85%; |
||||
|
margin: 50px auto; |
||||
|
padding: 30px 0; |
||||
|
|
||||
|
.left { |
||||
|
width: 25%; |
||||
|
padding: 0 60px; |
||||
|
|
||||
|
h2 { |
||||
|
text-align: center; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.qrcode { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.right { |
||||
|
width: 75%; |
||||
|
min-height: 220px; |
||||
|
border-left: 1px solid $spaceborder; |
||||
|
padding: 10px 60px; |
||||
|
|
||||
|
.details { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 40px; |
||||
|
|
||||
|
.amount { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
position: relative; |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 40px; |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
top: 40px; |
||||
|
right: -50px; |
||||
|
|
||||
|
&.active { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
padding: 8px 15px; |
||||
|
background: #191919; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s hover; |
||||
|
border-bottom: 1px solid #0f0f0f; |
||||
|
|
||||
|
&:hover { |
||||
|
background: #0f0f0f; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.date { |
||||
|
font-size: 12px; |
||||
|
text-align: right; |
||||
|
|
||||
|
.notPaid { |
||||
|
color: #FF8A65; |
||||
|
margin-top: 5px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.memo, .request { |
||||
|
h4 { |
||||
|
font-size: 10px; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
word-wrap: break-word; |
||||
|
max-width: 450px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.memo { |
||||
|
margin-bottom: 40px; |
||||
|
|
||||
|
p { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.request p { |
||||
|
font-size: 10px; |
||||
|
max-width: 450px; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.actions { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: center; |
||||
|
|
||||
|
div { |
||||
|
text-align: center; |
||||
|
margin: 35px 10px; |
||||
|
width: 235px; |
||||
|
padding: 20px 10px; |
||||
|
background: #31343f; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&:hover { |
||||
|
background: darken(#31343f, 5%); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,83 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import Moment from 'react-moment' |
||||
|
import 'moment-timezone' |
||||
|
|
||||
|
import { FaAngleDown } from 'react-icons/lib/fa' |
||||
|
|
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
import paperPlane from 'icons/paper_plane.svg' |
||||
|
import zap from 'icons/zap.svg' |
||||
|
|
||||
|
import Value from 'components/Value' |
||||
|
|
||||
|
import styles from './PaymentModal.scss' |
||||
|
|
||||
|
const PaymentModal = ({ |
||||
|
payment, |
||||
|
ticker, |
||||
|
currentTicker, |
||||
|
|
||||
|
toggleCurrencyProps: { |
||||
|
setActivityModalCurrencyFilters, |
||||
|
showCurrencyFilters, |
||||
|
currencyName, |
||||
|
currentCurrencyFilters, |
||||
|
onCurrencyFilterClick |
||||
|
} |
||||
|
}) => ( |
||||
|
<div className={styles.container}> |
||||
|
<header className={styles.header}> |
||||
|
<section> |
||||
|
<Isvg src={paperPlane} /> |
||||
|
<span>Sent</span> |
||||
|
</section> |
||||
|
<section className={styles.details}> |
||||
|
<div> |
||||
|
<Isvg src={zap} /> |
||||
|
<span className={styles.zap}>Lightning Network</span> |
||||
|
</div> |
||||
|
<div> |
||||
|
<Value value={payment.fee} currency={ticker.currency} currentTicker={currentTicker} /> |
||||
|
<span> {currencyName} fee</span> |
||||
|
</div> |
||||
|
</section> |
||||
|
</header> |
||||
|
|
||||
|
<div className={styles.amount}> |
||||
|
<h1> |
||||
|
<i className={`${styles.symbol} ${payment.value > 0 && styles.active}`}>-</i> |
||||
|
<Value value={payment.value} currency={ticker.currency} currentTicker={currentTicker} /> |
||||
|
</h1> |
||||
|
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}> |
||||
|
<span>{currencyName}</span><span><FaAngleDown /></span> |
||||
|
<ul className={showCurrencyFilters && styles.active}> |
||||
|
{ |
||||
|
currentCurrencyFilters.map(filter => |
||||
|
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) |
||||
|
} |
||||
|
</ul> |
||||
|
</section> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.date}> |
||||
|
<Moment format='LLL'>{payment.creation_date * 1000}</Moment> |
||||
|
</div> |
||||
|
|
||||
|
<footer className={styles.footer}> |
||||
|
<p>{payment.payment_preimage}</p> |
||||
|
</footer> |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
PaymentModal.propTypes = { |
||||
|
payment: PropTypes.object.isRequired, |
||||
|
ticker: PropTypes.object.isRequired, |
||||
|
currentTicker: PropTypes.object.isRequired, |
||||
|
|
||||
|
toggleCurrencyProps: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default PaymentModal |
@ -0,0 +1,126 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
color: $white; |
||||
|
font-size: 12px; |
||||
|
width: 75%; |
||||
|
margin: 0 auto; |
||||
|
background: $spaceblue; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
padding: 20px; |
||||
|
|
||||
|
section { |
||||
|
&:nth-child(1) { |
||||
|
font-size: 16px; |
||||
|
color: $green; |
||||
|
|
||||
|
svg { |
||||
|
width: 16px; |
||||
|
height: 16px; |
||||
|
vertical-align: top; |
||||
|
fill: $green; |
||||
|
} |
||||
|
|
||||
|
span:nth-child(2) { |
||||
|
margin-left: 5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.details { |
||||
|
text-align: right; |
||||
|
|
||||
|
div:nth-child(1) { |
||||
|
margin-bottom: 5px; |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
width: 12px; |
||||
|
height: 12px; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.zap { |
||||
|
margin-left: 5px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.25s; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.amount { |
||||
|
margin-top: 50px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 20px; |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 40px; |
||||
|
} |
||||
|
|
||||
|
section { |
||||
|
font-size: 20px; |
||||
|
margin-left: 10px; |
||||
|
position: relative; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
&:hover { |
||||
|
span { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
transition: all 0.25s; |
||||
|
} |
||||
|
|
||||
|
ul { |
||||
|
visibility: hidden; |
||||
|
position: absolute; |
||||
|
top: 40px; |
||||
|
right: -50px; |
||||
|
font-size: 12px; |
||||
|
|
||||
|
&.active { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
padding: 8px 15px; |
||||
|
background: #191919; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s hover; |
||||
|
border-bottom: 1px solid #0f0f0f; |
||||
|
|
||||
|
&:hover { |
||||
|
background: #0f0f0f; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.date { |
||||
|
text-align: center; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.footer { |
||||
|
background: #31343f; |
||||
|
margin: 20px 0 50px 0; |
||||
|
padding: 20px; |
||||
|
text-align: center; |
||||
|
|
||||
|
p { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import Moment from 'react-moment' |
||||
|
import 'moment-timezone' |
||||
|
|
||||
|
import { FaAngleDown } from 'react-icons/lib/fa' |
||||
|
|
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
import paperPlane from 'icons/paper_plane.svg' |
||||
|
import link from 'icons/link.svg' |
||||
|
import { blockExplorer } from 'utils' |
||||
|
|
||||
|
import Value from 'components/Value' |
||||
|
|
||||
|
import styles from './TransactionModal.scss' |
||||
|
|
||||
|
const TransactionModal = ({ |
||||
|
transaction, |
||||
|
ticker, |
||||
|
currentTicker, |
||||
|
|
||||
|
toggleCurrencyProps: { |
||||
|
setActivityModalCurrencyFilters, |
||||
|
showCurrencyFilters, |
||||
|
currencyName, |
||||
|
currentCurrencyFilters, |
||||
|
onCurrencyFilterClick |
||||
|
} |
||||
|
}) => ( |
||||
|
<div className={styles.container}> |
||||
|
<header className={styles.header}> |
||||
|
<section> |
||||
|
<Isvg src={paperPlane} /> |
||||
|
<span>Sent</span> |
||||
|
</section> |
||||
|
<section className={styles.details}> |
||||
|
<div> |
||||
|
<Isvg src={link} /> |
||||
|
<span className={styles.link} onClick={() => blockExplorer.showTransaction(transaction.tx_hash)}>On-Chain</span> |
||||
|
</div> |
||||
|
<div> |
||||
|
<Value value={transaction.total_fees} currency={ticker.currency} currentTicker={currentTicker} /> |
||||
|
<span> {currencyName} fee</span> |
||||
|
</div> |
||||
|
</section> |
||||
|
</header> |
||||
|
|
||||
|
<div className={styles.amount}> |
||||
|
<h1> |
||||
|
<i className={`${styles.symbol} ${transaction.amount > 0 && styles.active}`}> |
||||
|
{ |
||||
|
transaction.amount > 0 ? |
||||
|
'+' |
||||
|
: |
||||
|
'-' |
||||
|
} |
||||
|
</i> |
||||
|
<Value value={transaction.amount} currency={ticker.currency} currentTicker={currentTicker} /> |
||||
|
</h1> |
||||
|
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}> |
||||
|
<span>{currencyName}</span><span><FaAngleDown /></span> |
||||
|
<ul className={showCurrencyFilters && styles.active}> |
||||
|
{ |
||||
|
currentCurrencyFilters.map(filter => |
||||
|
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) |
||||
|
} |
||||
|
</ul> |
||||
|
</section> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.date}> |
||||
|
<Moment format='LLL'>{transaction.time_stamp * 1000}</Moment> |
||||
|
</div> |
||||
|
|
||||
|
<footer className={styles.footer}> |
||||
|
<p onClick={() => blockExplorer.showTransaction(transaction.tx_hash)}>{transaction.tx_hash}</p> |
||||
|
</footer> |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
TransactionModal.propTypes = { |
||||
|
transaction: PropTypes.object.isRequired, |
||||
|
ticker: PropTypes.object.isRequired, |
||||
|
currentTicker: PropTypes.object.isRequired, |
||||
|
|
||||
|
toggleCurrencyProps: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default TransactionModal |
@ -0,0 +1,135 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
color: $white; |
||||
|
font-size: 12px; |
||||
|
width: 75%; |
||||
|
margin: 0 auto; |
||||
|
background: $spaceblue; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
padding: 20px; |
||||
|
|
||||
|
section { |
||||
|
&:nth-child(1) { |
||||
|
font-size: 16px; |
||||
|
color: $green; |
||||
|
|
||||
|
svg { |
||||
|
width: 16px; |
||||
|
height: 16px; |
||||
|
vertical-align: top; |
||||
|
fill: $green; |
||||
|
} |
||||
|
|
||||
|
span:nth-child(2) { |
||||
|
margin-left: 5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.details { |
||||
|
text-align: right; |
||||
|
|
||||
|
div:nth-child(1) { |
||||
|
margin-bottom: 5px; |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
width: 12px; |
||||
|
height: 12px; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.link { |
||||
|
text-decoration: underline; |
||||
|
margin-left: 5px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.25s; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.amount { |
||||
|
margin-top: 50px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 20px; |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 40px; |
||||
|
} |
||||
|
|
||||
|
section { |
||||
|
font-size: 20px; |
||||
|
margin-left: 10px; |
||||
|
position: relative; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
&:hover { |
||||
|
span { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
transition: all 0.25s; |
||||
|
} |
||||
|
|
||||
|
ul { |
||||
|
visibility: hidden; |
||||
|
position: absolute; |
||||
|
top: 40px; |
||||
|
right: -50px; |
||||
|
font-size: 12px; |
||||
|
|
||||
|
&.active { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
padding: 8px 15px; |
||||
|
background: #191919; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s hover; |
||||
|
border-bottom: 1px solid #0f0f0f; |
||||
|
|
||||
|
&:hover { |
||||
|
background: #0f0f0f; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.date { |
||||
|
text-align: center; |
||||
|
margin: 20px 0 50px 0; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.footer { |
||||
|
background: #31343f; |
||||
|
padding: 20px; |
||||
|
text-align: center; |
||||
|
|
||||
|
p { |
||||
|
text-decoration: underline; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.25s; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,219 @@ |
|||||
|
import React, { Component } from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
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 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: { |
||||
|
payInput, |
||||
|
showErrors, |
||||
|
invoice, |
||||
|
showCurrencyFilters |
||||
|
}, |
||||
|
nodes, |
||||
|
ticker, |
||||
|
|
||||
|
isOnchain, |
||||
|
isLn, |
||||
|
currentAmount, |
||||
|
usdAmount, |
||||
|
payFormIsValid: { errors, isValid }, |
||||
|
currentCurrencyFilters, |
||||
|
currencyName, |
||||
|
|
||||
|
setPayAmount, |
||||
|
onPayAmountBlur, |
||||
|
|
||||
|
setPayInput, |
||||
|
onPayInputBlur, |
||||
|
|
||||
|
setCurrencyFilters, |
||||
|
|
||||
|
onPaySubmit, |
||||
|
|
||||
|
setCurrency |
||||
|
} = this.props |
||||
|
|
||||
|
const displayNodeName = (pubkey) => { |
||||
|
const node = find(nodes, n => n.pub_key === pubkey) |
||||
|
|
||||
|
if (node && node.alias.length) { return node.alias } |
||||
|
|
||||
|
return pubkey ? 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}> |
||||
|
<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='paymentRequest'>Destination</label> |
||||
|
<span className={`${styles.description} ${(isOnchain || isLn) && styles.active}`}> |
||||
|
{isOnchain && |
||||
|
<i> |
||||
|
<Isvg src={link} /> |
||||
|
<span>On-Chain (~10 minutes)</span> |
||||
|
</i> |
||||
|
} |
||||
|
{isLn && |
||||
|
<i> |
||||
|
<span> |
||||
|
{displayNodeName(invoice.destination)} ({invoice.description}) |
||||
|
</span> |
||||
|
</i> |
||||
|
} |
||||
|
</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='paymentRequest' |
||||
|
rows='2' |
||||
|
/> |
||||
|
<section className={`${styles.errorMessage} ${showErrors.payInput && styles.active}`}> |
||||
|
{showErrors.payInput && |
||||
|
<span>{errors.payInput}</span> |
||||
|
} |
||||
|
</section> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section className={styles.amount}> |
||||
|
<div className={styles.top}> |
||||
|
<label htmlFor='amount'>Amount</label> |
||||
|
<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} onClick={() => setCurrencyFilters(!showCurrencyFilters)}> |
||||
|
<span>{currencyName}</span><span><FaAngleDown /></span> |
||||
|
</section> |
||||
|
<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 || 0} USD`} |
||||
|
</div> |
||||
|
|
||||
|
<section className={`${styles.errorMessage} ${styles.amount} ${showErrors.amount && styles.active}`}> |
||||
|
{showErrors.amount && |
||||
|
<span>{errors.amount}</span> |
||||
|
} |
||||
|
</section> |
||||
|
</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.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]), |
||||
|
payInput: PropTypes.string.isRequired, |
||||
|
invoice: PropTypes.object.isRequired, |
||||
|
showErrors: PropTypes.object.isRequired |
||||
|
}).isRequired, |
||||
|
currencyName: PropTypes.string.isRequired, |
||||
|
|
||||
|
isOnchain: PropTypes.bool.isRequired, |
||||
|
isLn: PropTypes.bool.isRequired, |
||||
|
currentAmount: PropTypes.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]), |
||||
|
usdAmount: PropTypes.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]), |
||||
|
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, |
||||
|
setCurrencyFilters: PropTypes.func.isRequired, |
||||
|
setCurrency: PropTypes.func.isRequired, |
||||
|
|
||||
|
ticker: PropTypes.object.isRequired, |
||||
|
|
||||
|
nodes: PropTypes.array.isRequired, |
||||
|
currentCurrencyFilters: PropTypes.array.isRequired |
||||
|
} |
||||
|
|
||||
|
export default Pay |
@ -0,0 +1,182 @@ |
|||||
|
@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; |
||||
|
|
||||
|
.description { |
||||
|
font-size: 12px; |
||||
|
line-height: 14px; |
||||
|
padding: 10px 15px; |
||||
|
min-height: 14px; |
||||
|
|
||||
|
&.active { |
||||
|
background: #23252f; |
||||
|
border-radius: 10px; |
||||
|
min-height: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
width: 10px; |
||||
|
height: 10px; |
||||
|
line-height: 14px; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.amount .bottom { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
|
||||
|
input { |
||||
|
font-size: 40px; |
||||
|
max-width: 230px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.top { |
||||
|
margin-bottom: 25px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
|
||||
|
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 { |
||||
|
position: relative; |
||||
|
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; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.usdAmount { |
||||
|
margin-top: 20px; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
.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%); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.errorMessage { |
||||
|
color: $red; |
||||
|
font-size: 12px; |
||||
|
min-height: 20px; |
||||
|
opacity: 0; |
||||
|
transition: all 0.25s ease; |
||||
|
|
||||
|
&.amount { |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
|
||||
|
&.active { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
@ -1,152 +0,0 @@ |
|||||
import React, { Component } from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
|
|
||||
import { FaBolt, FaChain } from 'react-icons/lib/fa' |
|
||||
import LoadingBolt from 'components/LoadingBolt' |
|
||||
import CurrencyIcon from 'components/CurrencyIcon' |
|
||||
|
|
||||
import styles from './PayForm.scss' |
|
||||
|
|
||||
class PayForm 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, |
|
||||
inputCaption, |
|
||||
showPayLoadingScreen, |
|
||||
payFormIsValid: { errors, isValid }, |
|
||||
|
|
||||
setPayAmount, |
|
||||
onPayAmountBlur, |
|
||||
|
|
||||
setPayInput, |
|
||||
onPayInputBlur, |
|
||||
|
|
||||
onPaySubmit |
|
||||
} = this.props |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.container}> |
|
||||
{showPayLoadingScreen && <LoadingBolt />} |
|
||||
|
|
||||
<section className={`${styles.amountContainer} ${isLn ? styles.ln : ''} ${showErrors.amount && styles.error}`}> |
|
||||
<label htmlFor='amount'> |
|
||||
<CurrencyIcon currency={currency} crypto={crypto} /> |
|
||||
</label> |
|
||||
<input |
|
||||
type='number' |
|
||||
min='0' |
|
||||
ref={(input) => { this.amountInput = input }} |
|
||||
size='' |
|
||||
style={ |
|
||||
isLn ? |
|
||||
{ width: '75%', fontSize: '85px' } |
|
||||
: |
|
||||
{ width: `${amount.length > 1 ? (amount.length * 20) - 5 : 35}%`, fontSize: `${190 - (amount.length ** 2)}px` } |
|
||||
} |
|
||||
value={currentAmount} |
|
||||
onChange={event => setPayAmount(event.target.value)} |
|
||||
onBlur={onPayAmountBlur} |
|
||||
id='amount' |
|
||||
readOnly={isLn} |
|
||||
/> |
|
||||
</section> |
|
||||
<section className={`${styles.errorMessage} ${showErrors.amount && styles.active}`}> |
|
||||
{showErrors.amount && |
|
||||
<span>{errors.amount}</span> |
|
||||
} |
|
||||
</section> |
|
||||
<div className={styles.inputContainer}> |
|
||||
<div className={styles.info}> |
|
||||
<span>{inputCaption}</span> |
|
||||
</div> |
|
||||
<aside className={styles.paymentIcon}> |
|
||||
{isOnchain && |
|
||||
<i> |
|
||||
<span>on-chain</span> |
|
||||
<FaChain /> |
|
||||
</i> |
|
||||
} |
|
||||
{isLn && |
|
||||
<i> |
|
||||
<span>lightning network</span> |
|
||||
<FaBolt /> |
|
||||
</i> |
|
||||
} |
|
||||
</aside> |
|
||||
<section className={`${styles.input} ${showErrors.payInput && styles.error}`}> |
|
||||
<input |
|
||||
type='text' |
|
||||
placeholder='Payment request or bitcoin address' |
|
||||
value={payInput} |
|
||||
onChange={event => setPayInput(event.target.value)} |
|
||||
onBlur={onPayInputBlur} |
|
||||
id='paymentRequest' |
|
||||
/> |
|
||||
</section> |
|
||||
<section className={`${styles.errorMessage} ${showErrors.payInput && styles.active}`}> |
|
||||
{showErrors.payInput && |
|
||||
<span>{errors.payInput}</span> |
|
||||
} |
|
||||
</section> |
|
||||
</div> |
|
||||
<section className={styles.buttonGroup}> |
|
||||
<div className={`buttonPrimary ${styles.button} ${isValid && styles.active}`} onClick={onPaySubmit}>Pay</div> |
|
||||
</section> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
PayForm.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 |
|
||||
]).isRequired, |
|
||||
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 PayForm |
|
@ -1,166 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
.container { |
|
||||
margin: 0 auto; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 75vh; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
.amountContainer { |
|
||||
position: relative; |
|
||||
color: $main; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
min-height: 175px; |
|
||||
border-bottom: 1px solid transparent; |
|
||||
|
|
||||
&.ln { |
|
||||
opacity: 0.75; |
|
||||
} |
|
||||
|
|
||||
&.error { |
|
||||
border-color: $red; |
|
||||
} |
|
||||
|
|
||||
label, input[type=number], input[type=text] { |
|
||||
color: inherit; |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
label { |
|
||||
svg { |
|
||||
width: 85px; |
|
||||
height: 85px; |
|
||||
} |
|
||||
|
|
||||
svg[data-icon='ltc'] { |
|
||||
margin-right: 10px; |
|
||||
|
|
||||
g { |
|
||||
transform: scale(1.75) translate(-5px, -5px); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
input[type=number], input[type=text] { |
|
||||
width: 100px; |
|
||||
font-size: 180px; |
|
||||
border: none; |
|
||||
outline: 0; |
|
||||
-webkit-appearance: none; |
|
||||
font-weight: 200; |
|
||||
} |
|
||||
|
|
||||
input[type=number] { |
|
||||
&::-webkit-inner-spin-button, &::-webkit-outer-spin-button { |
|
||||
-webkit-appearance: none; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.inputContainer { |
|
||||
position: relative; |
|
||||
width: 100%; |
|
||||
padding: 40px 0; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
.info { |
|
||||
margin-bottom: 10px; |
|
||||
min-height: 19px; |
|
||||
} |
|
||||
|
|
||||
.paymentIcon { |
|
||||
position: absolute; |
|
||||
width: 20%; |
|
||||
left: calc(-12.5% - 75px); |
|
||||
top: 42px; |
|
||||
color: $main; |
|
||||
font-size: 50px; |
|
||||
text-align: center; |
|
||||
|
|
||||
span { |
|
||||
text-transform: uppercase; |
|
||||
display: block; |
|
||||
font-size: 12px; |
|
||||
font-weight: 200; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.input { |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
font-size: 18px; |
|
||||
height: auto; |
|
||||
min-height: 55px; |
|
||||
border: 1px solid $traditionalgrey; |
|
||||
border-radius: 6px; |
|
||||
position: relative; |
|
||||
padding: 0 20px; |
|
||||
|
|
||||
&.error { |
|
||||
border-color: $red; |
|
||||
} |
|
||||
|
|
||||
label, input[type=number], input[type=text] { |
|
||||
font-size: inherit; |
|
||||
} |
|
||||
|
|
||||
label { |
|
||||
padding-top: 19px; |
|
||||
padding-bottom: 12px; |
|
||||
color: $traditionalgrey; |
|
||||
} |
|
||||
|
|
||||
input[type=text] { |
|
||||
width: 100%; |
|
||||
border: none; |
|
||||
outline: 0; |
|
||||
-webkit-appearance: none; |
|
||||
height: 55px; |
|
||||
padding: 0 10px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.errorMessage { |
|
||||
margin: 10px 0; |
|
||||
min-height: 20px; |
|
||||
color: $red; |
|
||||
opacity: 0; |
|
||||
transition: all 0.25s ease; |
|
||||
|
|
||||
&.active { |
|
||||
opacity: 1; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.buttonGroup { |
|
||||
width: 100%; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
border-radius: 6px; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
.button { |
|
||||
width: 100%; |
|
||||
margin-bottom: 20px; |
|
||||
font-weight: bold; |
|
||||
cursor: pointer; |
|
||||
text-transform: uppercase; |
|
||||
letter-spacing: .2px; |
|
||||
|
|
||||
&:first-child { |
|
||||
border-right: 1px solid lighten($main, 20%); |
|
||||
} |
|
||||
|
|
||||
&.active { |
|
||||
opacity: 1; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,124 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
import hand from 'icons/hand.svg' |
||||
|
import { FaAngleDown } from 'react-icons/lib/fa' |
||||
|
|
||||
|
import { btc } from 'utils' |
||||
|
import styles from './Request.scss' |
||||
|
|
||||
|
const Request = ({ |
||||
|
requestform: { amount, memo, showCurrencyFilters }, |
||||
|
ticker, |
||||
|
|
||||
|
setRequestAmount, |
||||
|
setRequestMemo, |
||||
|
setCurrency, |
||||
|
setRequestCurrencyFilters, |
||||
|
currencyName, |
||||
|
requestUsdAmount, |
||||
|
|
||||
|
currentCurrencyFilters, |
||||
|
|
||||
|
onRequestSubmit |
||||
|
}) => { |
||||
|
const onCurrencyFilterClick = (currency) => { |
||||
|
// change the input amount
|
||||
|
setRequestAmount(btc.convert(ticker.currency, currency, amount)) |
||||
|
|
||||
|
setCurrency(currency) |
||||
|
setRequestCurrencyFilters(false) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<header className={styles.header}> |
||||
|
<Isvg src={hand} /> |
||||
|
<h1>Request Payment</h1> |
||||
|
</header> |
||||
|
|
||||
|
<div className={styles.content}> |
||||
|
<section className={styles.memo}> |
||||
|
<div className={styles.top}> |
||||
|
<label htmlFor='memo'>Memo</label> |
||||
|
</div> |
||||
|
<div className={styles.bottom}> |
||||
|
<input |
||||
|
type='text' |
||||
|
placeholder='Dinner, Rent, etc' |
||||
|
value={memo} |
||||
|
onChange={event => setRequestMemo(event.target.value)} |
||||
|
id='memo' |
||||
|
/> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section className={styles.amount}> |
||||
|
<div className={styles.top}> |
||||
|
<label htmlFor='amount'>Amount</label> |
||||
|
<span /> |
||||
|
</div> |
||||
|
<div className={styles.bottom}> |
||||
|
<input |
||||
|
type='number' |
||||
|
value={amount || ''} |
||||
|
onChange={event => setRequestAmount(event.target.value)} |
||||
|
id='amount' |
||||
|
placeholder='0.00000000' |
||||
|
/> |
||||
|
<div className={styles.currency}> |
||||
|
<section className={styles.currentCurrency} onClick={() => setRequestCurrencyFilters(!showCurrencyFilters)}> |
||||
|
<span>{currencyName}</span><span><FaAngleDown /></span> |
||||
|
</section> |
||||
|
<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}> |
||||
|
{`≈ ${requestUsdAmount || 0} USD`} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section className={styles.submit}> |
||||
|
<div className={`${styles.button} ${amount > 0 && styles.active}`} onClick={onRequestSubmit}> |
||||
|
Request |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Request.propTypes = { |
||||
|
requestform: PropTypes.shape({ |
||||
|
amount: PropTypes.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]), |
||||
|
memo: PropTypes.string |
||||
|
}).isRequired, |
||||
|
|
||||
|
requestUsdAmount: PropTypes.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]), |
||||
|
currencyName: PropTypes.string.isRequired, |
||||
|
|
||||
|
currentCurrencyFilters: PropTypes.array.isRequired, |
||||
|
|
||||
|
setRequestAmount: PropTypes.func.isRequired, |
||||
|
setRequestMemo: PropTypes.func.isRequired, |
||||
|
onRequestSubmit: PropTypes.func.isRequired, |
||||
|
setCurrency: PropTypes.func.isRequired, |
||||
|
setRequestCurrencyFilters: PropTypes.func.isRequired, |
||||
|
|
||||
|
ticker: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default Request |
@ -0,0 +1,147 @@ |
|||||
|
@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; |
||||
|
|
||||
|
.memo { |
||||
|
margin-bottom: 25px; |
||||
|
} |
||||
|
|
||||
|
.amount .bottom { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
|
||||
|
input { |
||||
|
font-size: 40px; |
||||
|
max-width: 230px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.top { |
||||
|
margin-bottom: 25px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
|
||||
|
label { |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.bottom { |
||||
|
input { |
||||
|
background: transparent; |
||||
|
outline: none; |
||||
|
border: 0; |
||||
|
color: $gold; |
||||
|
-webkit-text-fill-color: $white; |
||||
|
font-size: 20px; |
||||
|
width: 100%; |
||||
|
font-weight: 200; |
||||
|
} |
||||
|
|
||||
|
input::-webkit-input-placeholder { |
||||
|
text-shadow: none; |
||||
|
-webkit-text-fill-color: initial; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.currency { |
||||
|
position: relative; |
||||
|
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; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.usdAmount { |
||||
|
margin-top: 20px; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
.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%); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
@ -1,64 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import CurrencyIcon from 'components/CurrencyIcon' |
|
||||
import styles from './RequestForm.scss' |
|
||||
|
|
||||
const RequestForm = ({ |
|
||||
requestform: { amount, memo }, |
|
||||
currency, |
|
||||
crypto, |
|
||||
|
|
||||
setRequestAmount, |
|
||||
setRequestMemo, |
|
||||
|
|
||||
onRequestSubmit |
|
||||
}) => ( |
|
||||
<div className={styles.container}> |
|
||||
<section className={styles.amountContainer}> |
|
||||
<label htmlFor='amount'> |
|
||||
<CurrencyIcon currency={currency} crypto={crypto} /> |
|
||||
</label> |
|
||||
<input |
|
||||
type='text' |
|
||||
size='' |
|
||||
style={{ width: `${amount.length > 1 ? (amount.length * 15) - 5 : 25}%`, fontSize: `${190 - (amount.length ** 2)}px` }} |
|
||||
value={amount} |
|
||||
onChange={event => setRequestAmount(event.target.value)} |
|
||||
id='amount' |
|
||||
/> |
|
||||
</section> |
|
||||
<section className={styles.inputContainer}> |
|
||||
<label htmlFor='memo'> |
|
||||
Request: |
|
||||
</label> |
|
||||
<input |
|
||||
type='text' |
|
||||
placeholder='Dinner, Rent, etc' |
|
||||
value={memo} |
|
||||
onChange={event => setRequestMemo(event.target.value)} |
|
||||
id='memo' |
|
||||
/> |
|
||||
</section> |
|
||||
<section className={styles.buttonGroup}> |
|
||||
<div className={`buttonPrimary ${styles.button}`} onClick={onRequestSubmit}> |
|
||||
Request |
|
||||
</div> |
|
||||
</section> |
|
||||
</div> |
|
||||
) |
|
||||
|
|
||||
RequestForm.propTypes = { |
|
||||
requestform: PropTypes.shape({ |
|
||||
amount: PropTypes.string.isRequired, |
|
||||
memo: PropTypes.string |
|
||||
}).isRequired, |
|
||||
currency: PropTypes.string.isRequired, |
|
||||
crypto: PropTypes.string.isRequired, |
|
||||
|
|
||||
setRequestAmount: PropTypes.func.isRequired, |
|
||||
setRequestMemo: PropTypes.func.isRequired, |
|
||||
|
|
||||
onRequestSubmit: PropTypes.func.isRequired |
|
||||
} |
|
||||
|
|
||||
export default RequestForm |
|
@ -1,103 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
.container { |
|
||||
margin: 0 auto; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 75vh; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
.amountContainer { |
|
||||
color: $main; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
min-height: 120px; |
|
||||
margin-bottom: 20px; |
|
||||
|
|
||||
label, input[type=text] { |
|
||||
color: inherit; |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
label { |
|
||||
svg { |
|
||||
width: 85px; |
|
||||
height: 85px; |
|
||||
} |
|
||||
|
|
||||
svg[data-icon='ltc'] { |
|
||||
margin-right: 10px; |
|
||||
|
|
||||
g { |
|
||||
transform: scale(1.75) translate(-5px, -5px); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
input[type=text] { |
|
||||
width: 100px; |
|
||||
font-size: 180px; |
|
||||
border: none; |
|
||||
outline: 0; |
|
||||
-webkit-appearance: none; |
|
||||
font-weight: 200; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.inputContainer { |
|
||||
width: 100%; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
font-size: 18px; |
|
||||
height: auto; |
|
||||
min-height: 55px; |
|
||||
margin-bottom: 20px; |
|
||||
border: 1px solid $traditionalgrey; |
|
||||
border-radius: 6px; |
|
||||
position: relative; |
|
||||
padding: 0 20px; |
|
||||
|
|
||||
label, input[type=text] { |
|
||||
font-size: inherit; |
|
||||
} |
|
||||
|
|
||||
label { |
|
||||
padding-top: 19px; |
|
||||
padding-bottom: 12px; |
|
||||
color: $traditionalgrey; |
|
||||
} |
|
||||
|
|
||||
input[type=text] { |
|
||||
width: 100%; |
|
||||
border: none; |
|
||||
outline: 0; |
|
||||
-webkit-appearance: none; |
|
||||
height: 55px; |
|
||||
padding: 0 10px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.buttonGroup { |
|
||||
width: 100%; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
border-radius: 6px; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
.button { |
|
||||
width: 100%; |
|
||||
margin-bottom: 20px; |
|
||||
font-weight: bold; |
|
||||
cursor: pointer; |
|
||||
text-transform: uppercase; |
|
||||
letter-spacing: .2px; |
|
||||
|
|
||||
&:first-child { |
|
||||
border-right: 1px solid lighten($main, 20%); |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,22 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import { btc } from 'utils' |
||||
|
|
||||
|
const Value = ({ value, currency, currentTicker }) => { |
||||
|
if (currency === 'sats') { return <i>{value > 0 ? value : value * -1}</i> } |
||||
|
|
||||
|
return ( |
||||
|
<i>{btc.convert('sats', currency, value, currentTicker.price_usd)}</i> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Value.propTypes = { |
||||
|
value: PropTypes.oneOfType([ |
||||
|
PropTypes.string, |
||||
|
PropTypes.number |
||||
|
]), |
||||
|
currency: PropTypes.string.isRequired, |
||||
|
currentTicker: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default Value |
@ -0,0 +1,3 @@ |
|||||
|
import Value from './Value' |
||||
|
|
||||
|
export default Value |
@ -1,103 +1,111 @@ |
|||||
import React from 'react' |
import React from 'react' |
||||
import PropTypes from 'prop-types' |
import PropTypes from 'prop-types' |
||||
import ReactModal from 'react-modal' |
|
||||
import copy from 'copy-to-clipboard' |
import copy from 'copy-to-clipboard' |
||||
import QRCode from 'qrcode.react' |
import QRCode from 'qrcode.react' |
||||
|
import copyIcon from 'icons/copy.svg' |
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
|
||||
|
import x from 'icons/x.svg' |
||||
import { showNotification } from 'notifications' |
import { showNotification } from 'notifications' |
||||
import { FaCopy } from 'react-icons/lib/fa' |
|
||||
import { MdClose } from 'react-icons/lib/md' |
|
||||
import styles from './ReceiveModal.scss' |
import styles from './ReceiveModal.scss' |
||||
|
|
||||
const ReceiveModal = ({ |
class ReceiveModal extends React.Component { |
||||
isOpen, hideActivityModal, pubkey, address, newAddress, qrCodeType, changeQrCode |
constructor(props) { |
||||
}) => { |
super(props) |
||||
const customStyles = { |
|
||||
overlay: { |
this.state = { |
||||
cursor: 'pointer' |
qrCodeType: 1 |
||||
}, |
|
||||
content: { |
|
||||
top: 'auto', |
|
||||
left: '0', |
|
||||
right: '0', |
|
||||
bottom: 'auto', |
|
||||
width: '40%', |
|
||||
margin: '50px auto', |
|
||||
borderRadius: 'none', |
|
||||
padding: '0' |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
const copyOnClick = (data) => { |
render() { |
||||
copy(data) |
const copyOnClick = (data) => { |
||||
showNotification('Noice', 'Successfully copied to clipboard') |
copy(data) |
||||
} |
showNotification('Noice', 'Successfully copied to clipboard') |
||||
|
} |
||||
|
|
||||
return ( |
const changeQrCode = () => { |
||||
<ReactModal |
if (this.state.qrCodeType === 1) { |
||||
isOpen={isOpen} |
this.setState({ qrCodeType: 2 }) |
||||
ariaHideApp |
} else { |
||||
shouldCloseOnOverlayClick |
this.setState({ qrCodeType: 1 }) |
||||
contentLabel='No Overlay Click Modal' |
} |
||||
onRequestClose={() => hideActivityModal()} |
} |
||||
parentSelector={() => document.body} |
|
||||
style={customStyles} |
const { |
||||
> |
isOpen, |
||||
<div className={styles.closeContainer}> |
pubkey, |
||||
<span onClick={() => hideActivityModal()}> |
address, |
||||
<MdClose /> |
closeReceiveModal |
||||
</span> |
} = this.props |
||||
</div> |
|
||||
|
|
||||
|
const { qrCodeType } = this.state |
||||
|
|
||||
|
if (!isOpen) { return null } |
||||
|
|
||||
|
return ( |
||||
<div className={styles.container}> |
<div className={styles.container}> |
||||
<header> |
<div className={styles.closeContainer}> |
||||
<div className={styles.qrcodes}> |
<span onClick={closeReceiveModal}> |
||||
<QRCode value={qrCodeType === 1 ? address : pubkey} /> |
<Isvg src={x} /> |
||||
</div> |
</span> |
||||
|
</div> |
||||
<ul className={styles.tabs}> |
|
||||
<li className={qrCodeType === 1 && styles.active} onClick={changeQrCode}> |
<div className={styles.content}> |
||||
Wallet address |
<section className={styles.left}> |
||||
</li> |
<header className={styles.header}> |
||||
<li className={qrCodeType === 2 && styles.active} onClick={changeQrCode}> |
<h2>JimmyMow</h2> |
||||
Node pubkey |
|
||||
</li> |
<div className={styles.qrCodeOptions}> |
||||
</ul> |
<div className={qrCodeType === 1 && styles.active} onClick={changeQrCode}>Node Pubkey</div> |
||||
</header> |
<div className={qrCodeType === 2 && styles.active} onClick={changeQrCode}>Deposit Address</div> |
||||
<section> |
</div> |
||||
<div className={styles.addressHeader}> |
</header> |
||||
<h4>Deposit Address</h4> |
|
||||
<span className={styles.newAddress} onClick={() => newAddress('np2wkh')}>New Address</span> |
<div className={styles.qrCodeContainer}> |
||||
</div> |
<QRCode |
||||
<p> |
value={qrCodeType === 1 ? pubkey : address} |
||||
<span>{address}</span> |
renderAs='svg' |
||||
<span onClick={() => copyOnClick(address)} className='hint--left' data-hint='Copy address'> |
size={150} |
||||
<FaCopy /> |
bgColor='transparent' |
||||
</span> |
fgColor='white' |
||||
</p> |
level='L' |
||||
</section> |
/> |
||||
|
</div> |
||||
<section> |
</section> |
||||
<h4>Node Public Key</h4> |
<section className={styles.right}> |
||||
<p> |
<div className={styles.pubkey}> |
||||
<span>{pubkey}</span> |
<h4>Node Public Key</h4> |
||||
<span onClick={() => copyOnClick(pubkey)} className='hint--left' data-hint='Copy pubkey'> |
<p> |
||||
<FaCopy /> |
<span className={styles.data}>{pubkey}</span> |
||||
</span> |
<span onClick={() => copyOnClick(pubkey)} className={`${styles.copy} hint--left`} data-hint='Copy pubkey'> |
||||
</p> |
<Isvg src={copyIcon} /> |
||||
</section> |
</span> |
||||
|
</p> |
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.address}> |
||||
|
<h4>Deposit Address</h4> |
||||
|
<p> |
||||
|
<span className={styles.data}>{address}</span> |
||||
|
<span onClick={() => copyOnClick(address)} className={`${styles.copy} hint--left`} data-hint='Copy address'> |
||||
|
<Isvg src={copyIcon} /> |
||||
|
</span> |
||||
|
</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
</div> |
</div> |
||||
</ReactModal> |
) |
||||
) |
} |
||||
} |
} |
||||
|
|
||||
ReceiveModal.propTypes = { |
ReceiveModal.propTypes = { |
||||
isOpen: PropTypes.bool.isRequired, |
isOpen: PropTypes.bool.isRequired, |
||||
hideActivityModal: PropTypes.func.isRequired, |
|
||||
pubkey: PropTypes.string.isRequired, |
pubkey: PropTypes.string.isRequired, |
||||
address: PropTypes.string.isRequired, |
address: PropTypes.string.isRequired, |
||||
newAddress: PropTypes.func.isRequired, |
closeReceiveModal: PropTypes.func.isRequired |
||||
changeQrCode: PropTypes.func.isRequired, |
|
||||
qrCodeType: PropTypes.number.isRequired |
|
||||
} |
} |
||||
|
|
||||
export default ReceiveModal |
export default ReceiveModal |
||||
|
@ -1,106 +1,129 @@ |
|||||
@import '../../variables.scss'; |
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
position: relative; |
||||
|
height: 100vh; |
||||
|
background: $bluegrey; |
||||
|
} |
||||
|
|
||||
.closeContainer { |
.closeContainer { |
||||
background: $lightgrey; |
|
||||
text-align: right; |
text-align: right; |
||||
padding: 10px; |
padding: 20px 40px 0px; |
||||
|
|
||||
span { |
span { |
||||
color: $darkestgrey; |
|
||||
font-size: 20px; |
|
||||
cursor: pointer; |
cursor: pointer; |
||||
} |
opacity: 1.0; |
||||
} |
transition: 0.25s all; |
||||
|
|
||||
.container { |
&:hover { |
||||
header { |
opacity: 0.5; |
||||
background: $lightgrey; |
|
||||
padding: 10px 40px 40px; |
|
||||
text-align: center; |
|
||||
|
|
||||
.qrcodes { |
|
||||
text-align: center; |
|
||||
margin-top: 20px; |
|
||||
} |
} |
||||
|
} |
||||
|
|
||||
.tabs { |
svg { |
||||
display: flex; |
color: $white; |
||||
flex-direction: row; |
} |
||||
align-items: center; |
} |
||||
justify-content: center; |
|
||||
margin-top: 20px; |
|
||||
|
|
||||
li { |
|
||||
margin: 0 20px; |
|
||||
color: $darkestgrey; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
.content { |
||||
color: $black; |
display: flex; |
||||
} |
flex-direction: row; |
||||
|
align-items: center; |
||||
|
background: $spaceblue; |
||||
|
width: 85%; |
||||
|
margin: 10% auto 50px auto; |
||||
|
color: $white; |
||||
|
|
||||
|
.left { |
||||
|
width: 25%; |
||||
|
padding: 30px 40px; |
||||
|
|
||||
|
.header { |
||||
|
|
||||
|
h2 { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
&.active { |
.qrCodeOptions { |
||||
color: $black; |
display: flex; |
||||
font-weight: bold; |
flex-direction: row; |
||||
|
justify-content: space-around; |
||||
|
margin: 20px 0; |
||||
|
|
||||
|
div { |
||||
|
font-size: 10px; |
||||
|
opacity: 0.5; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.25s; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.75; |
||||
|
} |
||||
|
|
||||
|
&.active { |
||||
|
opacity: 1.0; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
.qrCodeContainer { |
||||
|
text-align: center; |
||||
|
} |
||||
} |
} |
||||
|
|
||||
section { |
.right { |
||||
margin: 25px 0; |
width: 75%; |
||||
padding: 25px; |
min-height: 220px; |
||||
|
border-left: 1px solid $spaceborder; |
||||
|
padding: 30px 40px; |
||||
|
|
||||
h4 { |
.pubkey, .address { |
||||
font-size: 14px; |
padding: 25px; |
||||
font-weight: bold; |
|
||||
letter-spacing: 1.5px; |
|
||||
margin-bottom: 10px; |
|
||||
|
|
||||
span { |
h4 { |
||||
color: $blue; |
font-size: 12px; |
||||
cursor: pointer; |
margin-bottom: 10px; |
||||
|
|
||||
|
span { |
||||
|
cursor: pointer; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
p { |
p { |
||||
display: flex; |
display: flex; |
||||
flex-direction: row; |
flex-direction: row; |
||||
|
align-items: center; |
||||
font-family: 'Roboto'; |
font-family: 'Roboto'; |
||||
font-size: 14px; |
font-size: 10px; |
||||
font-weight: 200; |
font-weight: 200; |
||||
background: $lightgrey; |
background: $bluegrey; |
||||
|
|
||||
span { |
.data, .copy { |
||||
padding: 15px; |
padding: 15px; |
||||
} |
} |
||||
|
|
||||
span:nth-child(1) { |
.data { |
||||
flex: 9; |
flex: 9; |
||||
overflow-x: scroll; |
overflow-x: scroll; |
||||
} |
} |
||||
|
|
||||
span:nth-child(2) { |
.copy { |
||||
background: $darkgrey; |
background: #383b47; |
||||
color: $black; |
color: $white; |
||||
cursor: pointer; |
cursor: pointer; |
||||
transition: all 0.25s; |
transition: all 0.25s; |
||||
|
|
||||
&:hover { |
&:hover { |
||||
background: $darkestgrey; |
background: lighten(#383b47, 5%); |
||||
} |
} |
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.addressHeader { |
svg { |
||||
display: flex; |
height: 12px; |
||||
flex-direction: row; |
width: 12px; |
||||
justify-content: space-between; |
} |
||||
|
} |
||||
.newAddress { |
} |
||||
text-decoration: underline; |
|
||||
font-size: 12px; |
|
||||
} |
} |
||||
} |
} |
||||
|
@ -1,111 +1,105 @@ |
|||||
import React, { Component } from 'react' |
import React from 'react' |
||||
import PropTypes from 'prop-types' |
import PropTypes from 'prop-types' |
||||
import { FaAngleDown } from 'react-icons/lib/fa' |
import { FaAngleDown } from 'react-icons/lib/fa' |
||||
import Isvg from 'react-inlinesvg' |
import Isvg from 'react-inlinesvg' |
||||
|
|
||||
import { btc } from 'utils' |
import { btc } from 'utils' |
||||
|
import Value from 'components/Value' |
||||
|
import AnimatedCheckmark from 'components/AnimatedCheckmark' |
||||
|
|
||||
import bitcoinIcon from 'icons/bitcoin.svg' |
import bitcoinIcon from 'icons/bitcoin.svg' |
||||
import zapLogo from 'icons/zap_logo.svg' |
import zapLogo from 'icons/zap_logo.svg' |
||||
import qrCode from 'icons/qrcode.svg' |
import qrCode from 'icons/qrcode.svg' |
||||
import ReceiveModal from './ReceiveModal' |
|
||||
|
|
||||
import styles from './Wallet.scss' |
import styles from './Wallet.scss' |
||||
|
|
||||
class Wallet extends Component { |
const Wallet = ({ |
||||
constructor(props) { |
balance, |
||||
super(props) |
info, |
||||
|
openReceiveModal, |
||||
this.state = { |
ticker, |
||||
modalOpen: false, |
currentTicker, |
||||
qrCodeType: 1 |
openPayForm, |
||||
} |
openRequestForm, |
||||
} |
showPayLoadingScreen, |
||||
|
showSuccessPayScreen |
||||
render() { |
}) => { |
||||
const { |
const usdAmount = btc.satoshisToUsd((parseInt(balance.walletBalance, 10) + parseInt(balance.channelBalance, 10)), currentTicker.price_usd) |
||||
balance, |
|
||||
address, |
|
||||
info, |
|
||||
newAddress, |
|
||||
currentTicker, |
|
||||
openPayForm, |
|
||||
openRequestForm |
|
||||
} = 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 |
|
||||
|
|
||||
this.setState({ qrCodeType: qrCodeNum }) |
return ( |
||||
} |
<div className={styles.wallet}> |
||||
|
<div className={styles.content}> |
||||
|
<header className={styles.header}> |
||||
|
<section className={styles.logo}> |
||||
|
<Isvg className={styles.bitcoinLogo} src={zapLogo} /> |
||||
|
</section> |
||||
|
|
||||
return ( |
<section className={styles.user}> |
||||
<div className={styles.wallet}> |
<div> |
||||
{ |
<span>{info.data.alias}</span> |
||||
( |
<FaAngleDown /> |
||||
modalOpen && |
</div> |
||||
<ReceiveModal |
</section> |
||||
isOpen={modalOpen} |
</header> |
||||
hideActivityModal={() => this.setState({ modalOpen: false })} |
|
||||
pubkey={info.data.identity_pubkey} |
|
||||
address={address} |
|
||||
newAddress={newAddress} |
|
||||
qrCodeType={qrCodeType} |
|
||||
changeQrCode={changeQrCode} |
|
||||
/> |
|
||||
) |
|
||||
} |
|
||||
<div className={styles.content}> |
|
||||
<header className={styles.header}> |
|
||||
<section className={styles.logo}> |
|
||||
<Isvg className={styles.bitcoinLogo} src={zapLogo} /> |
|
||||
</section> |
|
||||
|
|
||||
<section className={styles.user}> |
|
||||
<div> |
|
||||
<span>{info.data.alias}</span> |
|
||||
<FaAngleDown /> |
|
||||
</div> |
|
||||
</section> |
|
||||
</header> |
|
||||
|
|
||||
<div className={styles.left}> |
<div className={styles.left}> |
||||
<div className={styles.leftContent}> |
<div className={styles.leftContent}> |
||||
<Isvg className={styles.bitcoinLogo} src={bitcoinIcon} /> |
<Isvg className={styles.bitcoinLogo} src={bitcoinIcon} /> |
||||
<div className={styles.details}> |
<div className={styles.details}> |
||||
<h1> |
<h1> |
||||
<span> |
<span> |
||||
{btc.satoshisToBtc(parseFloat(balance.walletBalance) + parseFloat(balance.channelBalance))}BTC |
<Value |
||||
</span> |
value={parseFloat(balance.walletBalance) + parseFloat(balance.channelBalance)} |
||||
<span onClick={() => this.setState({ modalOpen: true })}> |
currency={ticker.currency} |
||||
<Isvg className={styles.bitcoinLogo} src={qrCode} /> |
currentTicker={currentTicker} |
||||
</span> |
/> |
||||
</h1> |
<i className={styles.currency}>{btc.renderCurrency(ticker.currency)}</i> |
||||
<span className={styles.usdValue}>≈ ${usdAmount ? usdAmount.toLocaleString() : ''}</span> |
</span> |
||||
</div> |
<span onClick={openReceiveModal}> |
||||
|
<Isvg className={styles.bitcoinLogo} src={qrCode} /> |
||||
|
</span> |
||||
|
</h1> |
||||
|
<span className={styles.usdValue}>≈ ${usdAmount ? usdAmount.toLocaleString() : ''}</span> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
<div className={styles.right}> |
</div> |
||||
<div className={styles.rightContent}> |
<div className={styles.right}> |
||||
<div className={styles.pay} onClick={openPayForm}>Pay</div> |
<div className={styles.rightContent}> |
||||
<div className={styles.request} onClick={openRequestForm}>Request</div> |
<div className={styles.pay} onClick={openPayForm}>Pay</div> |
||||
</div> |
<div className={styles.request} onClick={openRequestForm}>Request</div> |
||||
|
</div> |
||||
|
<div className={styles.notificationBox}> |
||||
|
{ |
||||
|
showPayLoadingScreen && |
||||
|
<span> |
||||
|
<section className={`${styles.spinner} ${styles.icon}`} /> |
||||
|
<section>Sending your lightning payment...</section> |
||||
|
</span> |
||||
|
} |
||||
|
{ |
||||
|
showSuccessPayScreen && |
||||
|
<span> |
||||
|
<section className={styles.icon}><AnimatedCheckmark /></section> |
||||
|
<section>Successfully sent payment</section> |
||||
|
</span> |
||||
|
} |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
) |
</div> |
||||
} |
) |
||||
} |
} |
||||
|
|
||||
Wallet.propTypes = { |
Wallet.propTypes = { |
||||
balance: PropTypes.object.isRequired, |
balance: PropTypes.object.isRequired, |
||||
address: PropTypes.string.isRequired, |
|
||||
info: PropTypes.object.isRequired, |
info: PropTypes.object.isRequired, |
||||
newAddress: PropTypes.func.isRequired, |
ticker: PropTypes.object.isRequired, |
||||
currentTicker: PropTypes.object.isRequired, |
currentTicker: PropTypes.object.isRequired, |
||||
openPayForm: PropTypes.func.isRequired, |
openPayForm: PropTypes.func.isRequired, |
||||
openRequestForm: PropTypes.func.isRequired |
openRequestForm: PropTypes.func.isRequired, |
||||
|
openReceiveModal: PropTypes.func.isRequired, |
||||
|
showPayLoadingScreen: PropTypes.bool.isRequired, |
||||
|
showSuccessPayScreen: PropTypes.bool.isRequired |
||||
} |
} |
||||
|
|
||||
export default Wallet |
export default Wallet |
||||
|
After Width: | Height: | Size: 351 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 803 B |
After Width: | Height: | Size: 299 B |
After Width: | Height: | Size: 282 B |
@ -1,74 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
|
|
||||
import Moment from 'react-moment' |
|
||||
import 'moment-timezone' |
|
||||
|
|
||||
import QRCode from 'qrcode.react' |
|
||||
import copy from 'copy-to-clipboard' |
|
||||
import { showNotification } from 'notifications' |
|
||||
|
|
||||
import { FaCircle, FaCopy } from 'react-icons/lib/fa' |
|
||||
|
|
||||
import { btc } from 'utils' |
|
||||
|
|
||||
import styles from './Invoice.scss' |
|
||||
|
|
||||
|
|
||||
const Invoice = ({ invoice, ticker, currentTicker }) => { |
|
||||
const copyPaymentRequest = () => { |
|
||||
copy(invoice.payment_request) |
|
||||
showNotification('Noice', 'Successfully copied to clipboard') |
|
||||
} |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.container}> |
|
||||
<div className={styles.settled}> |
|
||||
{ |
|
||||
!invoice.settled && |
|
||||
<p> |
|
||||
<FaCircle /> |
|
||||
<span>Not Paid</span> |
|
||||
</p> |
|
||||
} |
|
||||
</div> |
|
||||
<header> |
|
||||
<h3>{invoice.memo}</h3> |
|
||||
<h1> |
|
||||
<span className={styles.value}> |
|
||||
{ |
|
||||
ticker.currency === 'usd' ? |
|
||||
btc.satoshisToUsd(invoice.value, currentTicker.price_usd) |
|
||||
: |
|
||||
btc.satoshisToBtc(invoice.value) |
|
||||
} |
|
||||
</span> |
|
||||
<i>BTC</i> |
|
||||
</h1> |
|
||||
</header> |
|
||||
<div className={styles.qrcode}> |
|
||||
<QRCode value={invoice.payment_request} size={150} /> |
|
||||
</div> |
|
||||
<div className={styles.input}> |
|
||||
<p className={styles.invoiceAddress}> |
|
||||
<span>{invoice.payment_request}</span> |
|
||||
<span onClick={copyPaymentRequest} className='hint--left' data-hint='Copy Invoice'> |
|
||||
<FaCopy /> |
|
||||
</span> |
|
||||
</p> |
|
||||
</div> |
|
||||
<p className={styles.date}> |
|
||||
Created on |
|
||||
<Moment format='MMM Do'>{invoice.creation_date * 1000}</Moment> |
|
||||
</p> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Invoice.propTypes = { |
|
||||
invoice: PropTypes.object.isRequired, |
|
||||
ticker: PropTypes.object.isRequired, |
|
||||
currentTicker: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Invoice |
|
@ -1,106 +0,0 @@ |
|||||
@import '../../../../../../variables.scss'; |
|
||||
|
|
||||
.container { |
|
||||
.settled { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
padding: 10px 0 10px 40px; |
|
||||
color: $darkestgrey; |
|
||||
|
|
||||
svg { |
|
||||
line-height: 20px; |
|
||||
font-size: 10px; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
font-size: 12px; |
|
||||
margin-left: 5px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
header { |
|
||||
background: $lightgrey; |
|
||||
padding-bottom: 20px; |
|
||||
} |
|
||||
|
|
||||
h3 { |
|
||||
font-size: 20px; |
|
||||
color: $black; |
|
||||
font-weight: bold; |
|
||||
padding: 10px 40px; |
|
||||
} |
|
||||
|
|
||||
h1 { |
|
||||
color: $main; |
|
||||
padding: 10px 40px; |
|
||||
|
|
||||
.value { |
|
||||
font-size: 30px; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
margin-left: 2px; |
|
||||
vertical-align: top; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.qrcode { |
|
||||
text-align: center; |
|
||||
margin-top: 40px; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
.input { |
|
||||
padding: 10px 40px; |
|
||||
.paymentRequest { |
|
||||
text-align: center; |
|
||||
font-size: 12px; |
|
||||
padding: 15px; |
|
||||
background: $lightgrey; |
|
||||
border: 1px solid transparent; |
|
||||
display: block; |
|
||||
width: 90%; |
|
||||
margin: 20px auto 0 auto; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.date { |
|
||||
text-align: center; |
|
||||
padding-bottom: 40px; |
|
||||
margin-top: 20px; |
|
||||
|
|
||||
time { |
|
||||
margin-left: 3px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.invoiceAddress { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
font-family: 'Roboto'; |
|
||||
font-size: 14px; |
|
||||
font-weight: 200; |
|
||||
background: $lightgrey; |
|
||||
|
|
||||
span { |
|
||||
padding: 15px; |
|
||||
} |
|
||||
|
|
||||
span:nth-child(1) { |
|
||||
flex: 9; |
|
||||
overflow-x: scroll; |
|
||||
} |
|
||||
|
|
||||
span:nth-child(2) { |
|
||||
background: $darkgrey; |
|
||||
color: $black; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
background: $darkestgrey; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
import Invoice from './Invoice' |
|
||||
|
|
||||
export default Invoice |
|
@ -1,69 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import ReactModal from 'react-modal' |
|
||||
import { MdClose } from 'react-icons/lib/md' |
|
||||
|
|
||||
import Transaction from './Transaction' |
|
||||
import Payment from './Payment' |
|
||||
import Invoice from './Invoice' |
|
||||
|
|
||||
import styles from './Modal.scss' |
|
||||
|
|
||||
const Modal = ({ |
|
||||
modalType, modalProps, hideActivityModal, ticker, currentTicker |
|
||||
}) => { |
|
||||
const MODAL_COMPONENTS = { |
|
||||
TRANSACTION: Transaction, |
|
||||
PAYMENT: Payment, |
|
||||
INVOICE: Invoice |
|
||||
} |
|
||||
|
|
||||
const customStyles = { |
|
||||
overlay: { |
|
||||
cursor: 'pointer' |
|
||||
}, |
|
||||
content: { |
|
||||
top: 'auto', |
|
||||
left: '0', |
|
||||
right: '0', |
|
||||
bottom: 'auto', |
|
||||
width: '40%', |
|
||||
margin: '50px auto', |
|
||||
borderRadius: 'none', |
|
||||
padding: '0' |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!modalType) { return null } |
|
||||
|
|
||||
const SpecificModal = MODAL_COMPONENTS[modalType] |
|
||||
|
|
||||
return ( |
|
||||
<ReactModal |
|
||||
isOpen |
|
||||
ariaHideApp |
|
||||
shouldCloseOnOverlayClick |
|
||||
contentLabel='No Overlay Click Modal' |
|
||||
onRequestClose={() => hideActivityModal()} |
|
||||
parentSelector={() => document.body} |
|
||||
style={customStyles} |
|
||||
> |
|
||||
<div className={styles.closeContainer}> |
|
||||
<span onClick={() => hideActivityModal()}> |
|
||||
<MdClose /> |
|
||||
</span> |
|
||||
</div> |
|
||||
<SpecificModal {...modalProps} ticker={ticker} currentTicker={currentTicker} /> |
|
||||
</ReactModal> |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Modal.propTypes = { |
|
||||
modalType: PropTypes.string, |
|
||||
modalProps: PropTypes.object.isRequired, |
|
||||
hideActivityModal: PropTypes.func.isRequired, |
|
||||
ticker: PropTypes.object.isRequired, |
|
||||
currentTicker: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Modal |
|
@ -1,13 +0,0 @@ |
|||||
@import '../../../../../variables.scss'; |
|
||||
|
|
||||
.closeContainer { |
|
||||
background: $lightgrey; |
|
||||
text-align: right; |
|
||||
padding: 10px; |
|
||||
|
|
||||
span { |
|
||||
color: $darkestgrey; |
|
||||
font-size: 20px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
} |
|
@ -1,52 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
|
|
||||
import Moment from 'react-moment' |
|
||||
import 'moment-timezone' |
|
||||
|
|
||||
import { btc } from 'utils' |
|
||||
|
|
||||
import styles from './Payment.scss' |
|
||||
|
|
||||
|
|
||||
const Payment = ({ payment, ticker, currentTicker }) => ( |
|
||||
<div className={styles.container}> |
|
||||
<header> |
|
||||
<div className={styles.title}> |
|
||||
<h2>Sent</h2> |
|
||||
<h1> |
|
||||
<span className={styles.value}> |
|
||||
{ |
|
||||
ticker.currency === 'usd' ? |
|
||||
btc.satoshisToUsd(payment.value, currentTicker.price_usd) |
|
||||
: |
|
||||
btc.satoshisToBtc(payment.value) |
|
||||
} |
|
||||
</span> |
|
||||
<i> |
|
||||
BTC |
|
||||
</i> |
|
||||
</h1> |
|
||||
</div> |
|
||||
<h3>{payment.payment_hash}</h3> |
|
||||
</header> |
|
||||
<dl> |
|
||||
<dt>Fee</dt> |
|
||||
<dd>{payment.fee}</dd> |
|
||||
<dt>Hops</dt> |
|
||||
<dd>{payment.path.length}</dd> |
|
||||
<dt>Date</dt> |
|
||||
<dd> |
|
||||
<Moment format='MMM Do'>{payment.creation_date * 1000}</Moment> |
|
||||
</dd> |
|
||||
</dl> |
|
||||
</div> |
|
||||
) |
|
||||
|
|
||||
Payment.propTypes = { |
|
||||
payment: PropTypes.object.isRequired, |
|
||||
ticker: PropTypes.object.isRequired, |
|
||||
currentTicker: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Payment |
|
@ -1,61 +0,0 @@ |
|||||
@import '../../../../../../variables.scss'; |
|
||||
|
|
||||
.container { |
|
||||
header { |
|
||||
padding: 5px 40px 20px 40px; |
|
||||
background: $lightgrey; |
|
||||
|
|
||||
.title { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
margin-bottom: 30px; |
|
||||
|
|
||||
h2 { |
|
||||
text-transform: uppercase; |
|
||||
font-size: 24px; |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h1 { |
|
||||
color: $main; |
|
||||
|
|
||||
.value { |
|
||||
font-size: 24px; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
margin-left: 2px; |
|
||||
vertical-align: top; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h3 { |
|
||||
font-size: 14px; |
|
||||
text-align: center; |
|
||||
color: $darkestgrey; |
|
||||
} |
|
||||
|
|
||||
dl { |
|
||||
padding: 40px 40px 40px 40px; |
|
||||
} |
|
||||
|
|
||||
dt { |
|
||||
text-align: left; |
|
||||
float: left; |
|
||||
clear: left; |
|
||||
font-weight: 500; |
|
||||
padding: 20px 35px 19px 0; |
|
||||
color: $black; |
|
||||
font-weight: bold; |
|
||||
} |
|
||||
|
|
||||
dd { |
|
||||
text-align: right; |
|
||||
font-weight: 400; |
|
||||
padding: 30px 0 10px 0; |
|
||||
margin-left: 0; |
|
||||
border-bottom: 1px solid $darkgrey; |
|
||||
} |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
import Payment from './Payment' |
|
||||
|
|
||||
export default Payment |
|
@ -1,64 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
|
|
||||
import Moment from 'react-moment' |
|
||||
import 'moment-timezone' |
|
||||
|
|
||||
import { btc, blockExplorer } from 'utils' |
|
||||
|
|
||||
import styles from './Transaction.scss' |
|
||||
|
|
||||
|
|
||||
const Transaction = ({ transaction, ticker, currentTicker }) => ( |
|
||||
<div className={styles.container}> |
|
||||
<header> |
|
||||
<div className={styles.title}> |
|
||||
<h2> |
|
||||
{ |
|
||||
transaction.amount < 0 ? |
|
||||
'Sent' |
|
||||
: |
|
||||
'Received' |
|
||||
} |
|
||||
</h2> |
|
||||
<h1> |
|
||||
<span className={styles.value}> |
|
||||
{ |
|
||||
ticker.currency === 'usd' ? |
|
||||
btc.satoshisToUsd(transaction.amount, currentTicker.price_usd) |
|
||||
: |
|
||||
btc.satoshisToBtc(transaction.amount) |
|
||||
} |
|
||||
</span> |
|
||||
<i>BTC</i> |
|
||||
</h1> |
|
||||
</div> |
|
||||
<h3 onClick={() => blockExplorer.showTransaction(transaction.tx_hash)}>{transaction.tx_hash}</h3> |
|
||||
</header> |
|
||||
<dl> |
|
||||
<dt>Confirmations</dt> |
|
||||
<dd>{transaction.num_confirmations}</dd> |
|
||||
<dt>Fee</dt> |
|
||||
<dd> |
|
||||
{ |
|
||||
ticker.currency === 'usd' ? |
|
||||
btc.satoshisToUsd(transaction.total_fees) |
|
||||
: |
|
||||
btc.satoshisToBtc(transaction.total_fees) |
|
||||
} |
|
||||
</dd> |
|
||||
<dt>Date</dt> |
|
||||
<dd> |
|
||||
<Moment format='MMM Do'>{transaction.time_stamp * 1000}</Moment> |
|
||||
</dd> |
|
||||
</dl> |
|
||||
</div> |
|
||||
) |
|
||||
|
|
||||
Transaction.propTypes = { |
|
||||
transaction: PropTypes.object.isRequired, |
|
||||
ticker: PropTypes.object.isRequired, |
|
||||
currentTicker: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Transaction |
|
@ -1,72 +0,0 @@ |
|||||
@import '../../../../../../variables.scss'; |
|
||||
|
|
||||
.container { |
|
||||
header { |
|
||||
padding: 5px 40px 20px 40px; |
|
||||
background: $lightgrey; |
|
||||
|
|
||||
.title { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
|
|
||||
h2 { |
|
||||
text-transform: uppercase; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h1 { |
|
||||
text-align: center; |
|
||||
color: $main; |
|
||||
|
|
||||
.value { |
|
||||
font-size: 24px; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
margin-left: 2px; |
|
||||
vertical-align: top; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h3 { |
|
||||
font-size: 14px; |
|
||||
text-align: center; |
|
||||
color: $darkestgrey; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
&:hover { |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h2 { |
|
||||
text-align: center; |
|
||||
margin-right: 10px; |
|
||||
margin-bottom: 30px; |
|
||||
text-transform: uppercase; |
|
||||
font-size: 24px; |
|
||||
} |
|
||||
|
|
||||
dl { |
|
||||
padding: 40px 40px 40px 40px; |
|
||||
} |
|
||||
|
|
||||
dt { |
|
||||
text-align: left; |
|
||||
float: left; |
|
||||
clear: left; |
|
||||
font-weight: 500; |
|
||||
padding: 20px 35px 19px 0; |
|
||||
color: $black; |
|
||||
font-weight: bold; |
|
||||
} |
|
||||
|
|
||||
dd { |
|
||||
text-align: right; |
|
||||
font-weight: 400; |
|
||||
padding: 30px 0 10px 0; |
|
||||
margin-left: 0; |
|
||||
border-bottom: 1px solid $darkgrey; |
|
||||
} |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
import Transaction from './Transaction' |
|
||||
|
|
||||
export default Transaction |
|
@ -1,3 +0,0 @@ |
|||||
import Modal from './Modal' |
|
||||
|
|
||||
export default Modal |
|
@ -1,63 +1,67 @@ |
|||||
import React from 'react' |
import React from 'react' |
||||
import { shallow } from 'enzyme' |
import { shallow } from 'enzyme' |
||||
|
|
||||
import PayForm from '../../../app/components/Form/PayForm' |
import Pay from '../../../app/components/Form/Pay' |
||||
|
|
||||
const defaultProps = { |
const defaultProps = { |
||||
payform: { |
payform: { |
||||
amount: '', |
amount: 0, |
||||
payInput: '', |
payInput: '', |
||||
|
invoice: {}, |
||||
showErrors: {} |
showErrors: {} |
||||
}, |
}, |
||||
currency: 'BTC', |
currency: {}, |
||||
crypto: 'BTC', |
crypto: {}, |
||||
|
nodes: [], |
||||
|
ticker: {}, |
||||
|
|
||||
isOnchain: false, |
isOnchain: false, |
||||
isLn: false, |
isLn: true, |
||||
currentAmount: '0', |
currentAmount: 0, |
||||
|
usdAmount: 0, |
||||
inputCaption: '', |
inputCaption: '', |
||||
showPayLoadingScreen: false, |
showPayLoadingScreen: true, |
||||
payFormIsValid: {}, |
payFormIsValid: {}, |
||||
|
currentCurrencyFilters: [], |
||||
|
currencyName: '', |
||||
|
|
||||
setPayAmount: () => {}, |
setPayAmount: () => {}, |
||||
onPayAmountBlur: () => {}, |
|
||||
setPayInput: () => {}, |
setPayInput: () => {}, |
||||
onPayInputBlur: () => {}, |
setCurrencyFilters: () => {}, |
||||
fetchInvoice: () => {}, |
fetchInvoice: () => {}, |
||||
|
setCurrency: () => {}, |
||||
|
|
||||
|
onPayAmountBlur: () => {}, |
||||
|
|
||||
|
onPayInputBlur: () => {}, |
||||
|
|
||||
onPaySubmit: () => {} |
onPaySubmit: () => {} |
||||
} |
} |
||||
|
|
||||
describe('Form', () => { |
describe('Form', () => { |
||||
describe('should show the form without an input', () => { |
describe('should show the form without an input', () => { |
||||
const el = shallow(<PayForm {...defaultProps} />) |
const el = shallow(<Pay {...defaultProps} />) |
||||
|
|
||||
it('should contain PayForm', () => { |
it('should contain Pay', () => { |
||||
expect(el.find('input#paymentRequest').props.value).toBe(undefined) |
expect(el.find('input#paymentRequest').props.value).toBe(undefined) |
||||
expect(el.contains('lightning network')).toBe(false) |
|
||||
expect(el.contains('on-chain')).toBe(false) |
|
||||
}) |
}) |
||||
}) |
}) |
||||
|
|
||||
describe('should show lightning with a lightning input', () => { |
describe('should show lightning with a lightning input', () => { |
||||
const props = { ...defaultProps, isLn: true } |
const props = { ...defaultProps, isLn: true } |
||||
const el = shallow(<PayForm {...props} />) |
const el = shallow(<Pay {...props} />) |
||||
|
|
||||
it('should contain PayForm', () => { |
it('should contain Pay', () => { |
||||
expect(el.find('input#paymentRequest').props.value).toBe(undefined) |
expect(el.find('input#paymentRequest').props.value).toBe(undefined) |
||||
expect(el.contains('lightning network')).toBe(true) |
|
||||
expect(el.contains('on-chain')).toBe(false) |
|
||||
}) |
}) |
||||
}) |
}) |
||||
|
|
||||
describe('should show on-chain with an on-chain input', () => { |
describe('should show on-chain with an on-chain input', () => { |
||||
const props = { ...defaultProps, isOnchain: true } |
const props = { ...defaultProps, isOnchain: true } |
||||
const el = shallow(<PayForm {...props} />) |
const el = shallow(<Pay {...props} />) |
||||
|
|
||||
it('should contain PayForm', () => { |
it('should contain Pay', () => { |
||||
expect(el.find('input#paymentRequest').props.value).toBe(undefined) |
expect(el.find('input#paymentRequest').props.value).toBe(undefined) |
||||
expect(el.contains('lightning network')).toBe(false) |
|
||||
expect(el.contains('on-chain')).toBe(true) |
|
||||
}) |
}) |
||||
}) |
}) |
||||
}) |
}) |
Loading…
Reference in new issue