embark-dtwitter-workshop/app/js/components/Header.js

245 lines
7.5 KiB
JavaScript

import { NavLink, withRouter } from 'react-router-dom'
import { Button, Image, Modal, Navbar, ButtonToolbar, Dropdown, Glyphicon, MenuItem, Overlay, Tooltip } from 'react-bootstrap';
import React, { Component } from 'react';
import DoTweet from './DoTweet';
import Search from './Search';
import { limitLength, limitAddressLength } from '../utils';
import Spinner from 'react-spinkit';
import FieldGroup from './FieldGroup';
import imgAvatar from '../../img/avatar-default.png';
/**
* Class representing the header of the page that handles
* commone functions such as navigation, searching of users,
* link to create account, and modal to tweet
*
* @extends React.Component
*/
class Header extends Component {
//#region Constructor
constructor(props, context) {
super(props, context);
this.state = {
showModal: false,
showTooltip: false
};
}
//#endregion
//#region Component events
/**
* Hides the tweet modal
*/
_handleClose() {
this.setState({ showModal: false });
}
/**
* Shows the tweet modal
*/
_handleShow() {
this.setState({ showModal: true });
}
/**
* Toggles the current account address tooltip
*/
_handleToggle() {
this.setState({ showTooltip: !this.state.showTooltip });
}
/**
* Handles the event when the user selects a different account from
* the dropdown
* @param {Event} e - the DOM event fired when the account was changed
*/
_handleAcctChange(e) {
if (e.target.tagName !== 'A') e.target = e.target.parentElement;
web3.eth.defaultAccount = e.target.attributes.value.value;
this.props.onAfterUserUpdate();
if (e.target.attributes.username.value) {
this.props.history.push('/update/@' + e.target.attributes.username.value);
}
else {
this.props.history.push('/create');
}
}
/**
* Formats an ethereum balance for display
* @param {*} balance to be formatted
*/
_formatBalance(balance) {
return 'Ξ' + limitLength(
parseFloat(
balance
).toFixed(4), 6, '', true
);
}
//#endregion
//#region React lifecycle events
render() {
const { picture, username, description } = this.props.user;
const isEditable = Boolean(username);
const isError = this.props.error && this.props.error.message;
const isLoading = !Boolean(this.props.account) && !isError;
const tooltipProps = {
container: this,
target: this.tooltipTarget,
show: this.state.showTooltip
};
let navClasses = [];
if (isError) navClasses.push('error');
if (!isEditable) navClasses.push('logged-out');
// generate the menuitems for the accounts to populate
// the accounts dropdown
const accts = this.props.userAccounts.map((userAccount, index) => {
const isCurrUser = userAccount.address === this.props.account;
const hasUser = Boolean(userAccount.user.username);
return <MenuItem
key={index}
eventKey={index}
active={isCurrUser}
value={userAccount.address}
username={userAccount.user.username}
onSelect={(key, e) => this._handleAcctChange(e, key)}
>
{hasUser ?
<React.Fragment><Image
src={userAccount.user.picture}
alt={userAccount.user.username}
width={30}
circle
className='profile'
></Image>
<span className='username'>{userAccount.user.username}</span></React.Fragment>
:
<React.Fragment>
<Glyphicon glyph="question-sign" />
<span className='address'>{limitAddressLength(userAccount.address, 4)}</span>
</React.Fragment>
}
<React.Fragment>
<small className='balance'>{this._formatBalance(userAccount.balance)}</small>
</React.Fragment>
</MenuItem>
});
let states = {};
// state when we are waiting for the App component to finish loading
// the current account (address) from web3.eth.getAccounts()
states.isLoading = <Spinner name="pacman" color="white" fadeIn='none' />;
states.isError = <span className='error'>ERROR!</span>;
// state when our account has loaded, and it was determined that that
// account (address) has not been mapped to an owner/user in the contract
// (This happens in the App component)
states.isNotEditable = <React.Fragment>
<span className='profile-link'>
<Glyphicon glyph="question-sign" />
<span
onMouseEnter={(e) => this._handleToggle(e)}
onMouseLeave={(e) => this._handleToggle(e)}
className='username'
ref={(span) => this.tooltipTarget = span}
>{limitAddressLength(this.props.account, 4)}
</span>
</span>
<small className='balance'>{this._formatBalance(this.props.balance)}</small>
<Overlay {...tooltipProps} placement="bottom">
<Tooltip id="overload-bottom">{this.props.account}</Tooltip>
</Overlay>
</React.Fragment>;
// state when our account has loaded, and it was determined that the
// account (address) has been mapped to an owner/user in the contract
// (This happens in the App component)
states.isEditable = <React.Fragment>
<span className='profile-link'>
<Image
src={picture}
alt={username}
width={60}
circle
className='profile'
></Image>
<span className='username'>{username}</span>
</span>
<small className='balance'>{this._formatBalance(this.props.balance)}</small>
</React.Fragment>;
// state for showing the tweet button and associated modal
states.tweet = <React.Fragment>
<Button bsStyle="primary" onClick={(e) => this._handleShow(e)}>
Tweet
</Button>
<Modal show={this.state.showModal} onHide={(e) => this._handleClose(e)}>
<Modal.Header closeButton>
<Modal.Title>New tweet</Modal.Title>
</Modal.Header>
<Modal.Body>
<DoTweet username={username} onAfterTweet={(e) => this._handleClose()}></DoTweet>
</Modal.Body>
<Modal.Footer>
<Button onClick={(e) => this._handleClose(e)}>Close</Button>
</Modal.Footer>
</Modal>
</React.Fragment>;
return (
<Navbar collapseOnSelect className={navClasses.join(' ')}>
<Navbar.Header>
<Navbar.Brand>
<NavLink exact to="/">dTwitter <small>embark by Status</small></NavLink>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<div className='navbar-right'>
<Navbar.Form>
<Search />
</Navbar.Form>
{isLoading ?
states.isLoading
:
isError ?
states.isError
:
<React.Fragment>
<ButtonToolbar>
<Dropdown id="dropdown-accounts">
<Dropdown.Toggle>
{isEditable ?
states.isEditable
:
states.isNotEditable
}
</Dropdown.Toggle>
<Dropdown.Menu className="accounts-list">
{accts}
</Dropdown.Menu>
</Dropdown>
</ButtonToolbar>
{isEditable ? states.tweet : ''}
</React.Fragment>
}
</div>
</Navbar.Collapse>
</Navbar>
);
}
//#endregion
}
export default withRouter(Header)