ilp-plugin-virtual
Version:
ILP virtual ledger plugin for directly transacting connectors
287 lines (244 loc) • 10 kB
JavaScript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { LinkContainer, IndexLinkContainer } from 'react-router-bootstrap'
import { Link } from 'react-router'
import Helmet from 'react-helmet'
import LoadingBar from 'react-redux-loading-bar'
import ReactFitText from 'react-fittext'
import Alert from 'react-bootstrap/lib/Alert'
import Navbar from 'react-bootstrap/lib/Navbar'
import Nav from 'react-bootstrap/lib/Nav'
import NavItem from 'react-bootstrap/lib/NavItem'
import NavDropdown from 'react-bootstrap/lib/NavDropdown'
import MenuItem from 'react-bootstrap/lib/MenuItem'
import Label from 'react-bootstrap/lib/Label'
import Amount from 'components/Amount/Amount'
import hotkeys from 'decorators/hotkeys'
import { isLoaded as isAuthLoaded, load as loadAuth, loadConfig, logout, updateBalance, verify, resendVerificationEmail } from 'redux/actions/auth'
import { routeActions } from 'react-router-redux'
import { addActivity } from 'redux/actions/activity'
import { asyncConnect } from 'redux-connect'
import classNames from 'classnames/bind'
import styles from './App.scss'
const cx = classNames.bind(styles)
([{
promise: ({store: {dispatch, getState}}) => {
const promises = []
if (!isAuthLoaded(getState())) {
promises.push(dispatch(loadAuth()))
}
return Promise.all(promises)
}
}])
(
state => ({
user: state.auth.user,
config: state.auth.config,
loading: state.auth.loading,
advancedMode: state.auth.advancedMode,
verificationEmailSent: state.auth.verificationEmailSent
}),
{logout, pushState: routeActions.push, addActivity, updateBalance, verify, loadConfig, resendVerificationEmail})
()
export default class App extends Component {
static propTypes = {
children: PropTypes.object.isRequired,
user: PropTypes.object,
config: PropTypes.object,
logout: PropTypes.func.isRequired,
addActivity: PropTypes.func.isRequired,
updateBalance: PropTypes.func.isRequired,
pushState: PropTypes.func.isRequired,
params: PropTypes.object,
// TODO:UI add loading screen
advancedMode: PropTypes.bool,
loadConfig: PropTypes.func,
// User verification
verified: PropTypes.bool,
verify: PropTypes.func,
verificationEmailSent: PropTypes.bool,
resendVerificationEmail: PropTypes.func
}
static contextTypes = {
store: PropTypes.object.isRequired,
router: PropTypes.object
}
state = {
navExpanded: false
}
componentWillMount () {
if (!this.props.config.ledgerUri) {
this.props.loadConfig()
}
}
componentDidMount () {
const params = this.props.params
this.connect()
if (params.username) {
// Email verification
if (params.verifyCode) {
this.props.verify(params.username, params.verifyCode)
}
}
// Disable scaling
document.addEventListener('touchmove', function(event) {
event = event.originalEvent || event
if (event.scale !== 1) {
event.preventDefault()
}
}, false)
this.fixAppHeight()
}
componentWillReceiveProps (nextProps) {
if (!this.props.user && nextProps.user) {
// login or register
this.props.pushState('/')
this.connect(nextProps)
} else if (this.props.user && !nextProps.user) {
// logout
this.props.pushState('/')
}
}
fixAppHeight = () => {
// Viewport height
const height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
document.getElementsByClassName(cx('App'))[0].style['min-height'] = height + 'px'
window.addEventListener('resize', this.fixAppHeight, false)
}
connect = (props = this.props) => {
if (!props.user || !props.user.username) return
if (socket) {
socket.connect()
socket.on('activity', props.addActivity)
socket.on('balance', props.updateBalance)
socket.on('reconnect', () => socket.emit('subscribe', props.user.username))
socket.emit('subscribe', props.user.username)
}
}
onNavItemClick = () => {
this.setState({ navExpanded: false })
}
onNavbarToggle = () => {
this.setState({ navExpanded: !this.state.navExpanded })
}
handleLogout = (event) => {
event.preventDefault()
// TODO don't disconnect, just unsubscribe
socket.disconnect()
this.props.logout()
}
resendVerification = (event) => {
event.preventDefault()
this.props.resendVerificationEmail(this.props.user.username)
}
render () {
const { user, config = {}, advancedMode, verified, verificationEmailSent } = this.props
return (
<div className={cx('App', !user && 'guest')}>
<Helmet defaultTitle={config.title} titleTemplate={config.title && '%s - ' + config.title}>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<meta property='og:type' content='website' />
<meta property='og:title' content={config.title} />
<meta name='twitter:title' content={config.title} />
<meta name='twitter:card' content='summary' />
<meta itemProp='name' content={config.title} />
</Helmet>
<LoadingBar className={cx('loadingBar')} />
{user &&
<Navbar inverse expanded={this.state.navExpanded} onToggle={this.onNavbarToggle}>
<Navbar.Header>
<Navbar.Brand className={cx('brand')}>
{config.title} {advancedMode && <Label bsStyle='warning'>Advanced Mode</Label>}
</Navbar.Brand>
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
<IndexLinkContainer to='/'>
<NavItem onClick={this.onNavItemClick}>Home</NavItem>
</IndexLinkContainer>
{user.isAdmin &&
<LinkContainer to='/users'>
<NavItem onClick={this.onNavItemClick}>Users</NavItem>
</LinkContainer>}
{user.isAdmin &&
<LinkContainer to='/withdrawals'>
<NavItem onClick={this.onNavItemClick}>Withdrawals</NavItem>
</LinkContainer>}
{user.isAdmin &&
<LinkContainer to='/invites'>
<NavItem onClick={this.onNavItemClick}>Invites</NavItem>
</LinkContainer>}
{user.isAdmin &&
<LinkContainer to='/peers'>
<NavItem onClick={this.onNavItemClick}>Peers</NavItem>
</LinkContainer>}
{user.isAdmin &&
<NavDropdown id='settlementDropdown' title='Settlements'>
<LinkContainer to='/settlements/user'>
<MenuItem>User Settlements</MenuItem>
</LinkContainer>
<LinkContainer to='/settlements/peer'>
<MenuItem>Peer Settlements</MenuItem>
</LinkContainer>
<LinkContainer to='/settlements/settings'>
<MenuItem>Configure</MenuItem>
</LinkContainer>
</NavDropdown>}
<NavDropdown id='navDropdown' title={<span>{user.profile_picture && <img className={cx('profilePic')} src={user.profile_picture} />} {user.displayName}</span>}>
{!user.github_id &&
<LinkContainer to='/settings'>
<MenuItem>Settings</MenuItem>
</LinkContainer>}
<MenuItem href='https://interledgerjs.github.io/ilp-kit/apidoc/' target='_blank'>API Docs</MenuItem>
<MenuItem onClick={this.handleLogout}>Logout</MenuItem>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Navbar>}
{user && user.email && (!user.email_verified || verified) &&
<Alert bsStyle={verified ? 'success' : 'info'} className={cx('topNotificationBox')}>
<div className={cx('container')}>
{verified && <span>Your email has been verified!</span>}
{!verified &&
<span>
An email has been sent to <strong>{user.email}</strong>.
Please follow the steps in the message to confirm your email address.
{!verificationEmailSent && <a href='' onClick={this.resendVerification}>Resend the message</a>}
{verificationEmailSent && <strong>Verification email sent!</strong>}
</span>}
</div>
</Alert>}
{user && user.isAdmin && config.versions && config.versions.current !== config.versions.latest &&
<Alert bsStyle={'info'} className={cx('topNotificationBox')}>
<div className={cx('container')}>
Your ILP Kit (v. {config.versions.current}) is outdated. Please upgrade to the <a href='https://github.com/interledgerjs/ilp-kit/tree/release' target='_blank'>latest version</a> (v. {config.versions.latest}).
</div>
</Alert>}
{user && user.balance !== undefined &&
<div className={cx('mobileHeader')}>
<ReactFitText compressor={0.8}>
<div className={cx('balanceContainer')}>
<Amount amount={user.balance} currencySymbol={config.currencySymbol} />
</div>
</ReactFitText>
</div>}
<div className={cx('container')}>
{this.props.children}
</div>
{user &&
<div className={cx('mobileFooter')}>
<Link to='/' className={cx('activity')}>Activity</Link>
<Link to='/send' className={cx('send')}>Send</Link>
<Link to='/settings' className={cx('settings')}>
<i className={cx('fa', 'fa-gear')} />
</Link>
<Link to='/' onClick={this.handleLogout} className={cx('logout')}>
<i className={cx('fa', 'fa-sign-out')} />
</Link>
</div>}
{advancedMode && <div className={cx('version')}>Version: {config.versions && (config.versions.current + '-' + config.versions.hash)}</div>}
</div>
)
}
}