feat: withdrawal screens + prettier

This commit is contained in:
Richard Ramos 2019-04-27 18:10:34 -04:00
parent ae884d278c
commit 64fdef7697
22 changed files with 676 additions and 416 deletions

8
app/images/red-info.svg Normal file
View File

@ -0,0 +1,8 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 14.3612C11 14.9135 10.5522 15.3612 10 15.3612C9.44775 15.3612 9 14.9135 9 14.3612V9.44031C9 9.44031 9 8.40991 10 8.40991C10.8345 8.40991 11 9.29419 11 9.44031V14.3612Z" fill="#FF2D55"/>
<path d="M11 6.40991C11 6.96216 10.5522 7.40991 10 7.40991C9.44775 7.40991 9 6.96216 9 6.40991C9 5.85767 9.44775 5.40991 10 5.40991C10.5522 5.40991 11 5.85767 11 6.40991Z" fill="#FF2D55"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10.4099C0 15.9327 4.47705 20.4099 10 20.4099C15.5229 20.4099 20 15.9327 20 10.4099C20 4.88708 15.5229 0.409912 10 0.409912C4.47705 0.409912 0 4.88708 0 10.4099ZM18 10.4099C18 14.8282 14.4182 18.4099 10 18.4099C5.58179 18.4099 2 14.8282 2 10.4099C2 5.99158 5.58179 2.40991 10 2.40991C14.4182 2.40991 18 5.99158 18 10.4099Z" fill="#FF2D55"/>
<path d="M11 14.3612C11 14.9135 10.5522 15.3612 10 15.3612C9.44775 15.3612 9 14.9135 9 14.3612V9.44031C9 9.44031 9 8.40991 10 8.40991C10.8345 8.40991 11 9.29419 11 9.44031V14.3612Z" stroke="#FFEAEE"/>
<path d="M11 6.40991C11 6.96216 10.5522 7.40991 10 7.40991C9.44775 7.40991 9 6.96216 9 6.40991C9 5.85767 9.44775 5.40991 10 5.40991C10.5522 5.40991 11 5.85767 11 6.40991Z" stroke="#FFEAEE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10.4099C0 15.9327 4.47705 20.4099 10 20.4099C15.5229 20.4099 20 15.9327 20 10.4099C20 4.88708 15.5229 0.409912 10 0.409912C4.47705 0.409912 0 4.88708 0 10.4099ZM18 10.4099C18 14.8282 14.4182 18.4099 10 18.4099C5.58179 18.4099 2 14.8282 2 10.4099C2 5.99158 5.58179 2.40991 10 2.40991C14.4182 2.40991 18 5.99158 18 10.4099Z" stroke="#FFEAEE"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,11 +1,11 @@
/*global web3*/
import React, {Fragment} from 'react';
import {HashRouter, Route, Redirect, Switch} from "react-router-dom";
import React from 'react';
import { HashRouter, Route, Redirect, Switch } from 'react-router-dom';
import ThemeProvider from 'react-bootstrap/ThemeProvider';
import EmbarkJS from 'Embark/EmbarkJS';
import {isAdmin} from './services/Meritocracy';
import { isAdmin } from './services/Meritocracy';
import Header from './components/Header';
import Home from './components/Home';
import Admin from './components/Admin';
@ -14,7 +14,6 @@ const MAINNET = 1;
const TESTNET = 3;
class App extends React.Component {
state = {
error: null,
loading: true,
@ -22,52 +21,57 @@ class App extends React.Component {
};
componentDidMount() {
EmbarkJS.onReady(async (err) => {
EmbarkJS.onReady(async err => {
if (err) {
return this.setState({error: err.message || err});
return this.setState({ error: err.message || err });
}
const netId = await web3.eth.net.getId();
if (EmbarkJS.environment === 'testnet' && netId !== TESTNET) {
return this.setState({error: 'Please connect to Ropsten'});
return this.setState({ error: 'Please connect to Ropsten' });
}
if (EmbarkJS.environment === 'livenet' && netId !== MAINNET) {
return this.setState({error: 'Please connect to Mainnet'});
return this.setState({ error: 'Please connect to Mainnet' });
}
const isUserAdmin = await isAdmin(web3.eth.defaultAccount);
this.setState({loading: false, isUserAdmin})
this.setState({ loading: false, isUserAdmin });
});
}
render() {
const {error, loading, isUserAdmin} = this.state;
const { error, loading, isUserAdmin } = this.state;
if (error) {
return (<div>
<div>Something went wrong connecting to Ethereum. Please make sure you have a node running or are using Metamask
to connect to the Ethereum network:
return (
<div>
<div>
Something went wrong connecting to Ethereum. Please make sure you have a node running or are using Metamask
to connect to the Ethereum network:
</div>
<div>{error}</div>
</div>
<div>{error}</div>
</div>);
);
}
if (loading) {
return <p>Loading, please wait</p>;
}
return (<HashRouter>
<ThemeProvider prefixes={{ btn: 'my-btn' }}>
<Header isUserAdmin={isUserAdmin}/>
<Switch>
<Route exact path="/" component={Home}/>
{isUserAdmin && <Route exact path="/admin" component={Admin}/>}
return (
<HashRouter>
<ThemeProvider prefixes={{ btn: 'my-btn' }}>
<Header isUserAdmin={isUserAdmin} />
<Switch>
<Route exact path="/" component={Home} />
{isUserAdmin && <Route exact path="/admin" component={Admin} />}
<Redirect to="/404"/>
</Switch>
</ThemeProvider>
</HashRouter>);
<Redirect to="/404" />
</Switch>
</ThemeProvider>
</HashRouter>
);
}
}

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
function compactAddress(addr) {
return addr.substring(0, 6) + '...' + addr.substring(38);
}
class Address extends Component {
constructor(props) {
super(props);
this.state = {
addressHovered: false,
fixed: false
};
}
mouseOverAddress = () => {
this.setState({ addressHovered: true });
};
mouseOutAddress = () => {
this.setState({ addressHovered: false });
};
handleClick = () => {
this.setState({ fixed: !this.state.fixed });
};
render() {
const address =
this.props.compact || (!this.state.fixed && !this.state.addressHovered)
? compactAddress(this.props.value)
: this.props.value;
return (
<span
title={this.props.value}
onClick={this.handleClick}
onMouseOver={this.mouseOverAddress}
onMouseOut={this.mouseOutAddress}
>
{address}
</span>
);
}
}
Address.defaultProps = {
compact: false
};
Address.propTypes = {
value: PropTypes.string,
compact: PropTypes.bool
};
export default Address;

View File

@ -1,13 +1,12 @@
/*global web3*/
import React, {Fragment} from 'react';
import {Button, Form, Alert, ListGroup, OverlayTrigger, Tooltip, Modal} from 'react-bootstrap';
import React, { Fragment } from 'react';
import { Button, Form, Alert, ListGroup, OverlayTrigger, Tooltip, Modal } from 'react-bootstrap';
import ValidatedForm from 'react-validation/build/form';
import Input from 'react-validation/build/input';
import {required, isAddress} from '../validators';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faTrash} from "@fortawesome/free-solid-svg-icons";
import { required, isAddress } from '../validators';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import {addContributor, getFormattedContributorList, removeContributor} from '../services/Meritocracy';
import { addContributor, getFormattedContributorList, removeContributor } from '../services/Meritocracy';
import './admin.scss';
@ -27,121 +26,139 @@ class Admin extends React.Component {
try {
const contributorList = await getFormattedContributorList();
this.setState({busy: false, contributorList});
} catch (e) {
this.setState({errorMsg: e.message || e});
this.setState({ busy: false, contributorList });
} catch (error) {
this.setState({ errorMsg: error.message || error });
}
}
onChange = (name, e) => {
this.setState({[name]: e.target.value});
this.setState({ [name]: e.target.value });
};
addContributor = async (e) => {
addContributor = async e => {
e.preventDefault();
this.setState({busy: true, successMsg: ''});
this.setState({ busy: true, successMsg: '' });
try {
await addContributor(this.state.contributorName, this.state.contributorAddress);
const contributorList = this.state.contributorList;
contributorList.push({label: this.state.contributorName, value: this.state.contributorAddress});
contributorList.push({ label: this.state.contributorName, value: this.state.contributorAddress });
this.setState({busy: false, successMsg: 'Contributor added!'});
} catch (e) {
this.setState({error: e.message || e, busy: false});
this.setState({ busy: false, successMsg: 'Contributor added!' });
} catch (error) {
this.setState({ error: error.message || error, busy: false });
}
};
removeContributor = (e, contributorIndex) => {
e.preventDefault();
this.setState({focusedContributorIndex: contributorIndex, showDeleteModal: true});
this.setState({ focusedContributorIndex: contributorIndex, showDeleteModal: true });
};
doRemove = async () => {
const idx = this.state.focusedContributorIndex;
this.setState({focusedContributorIndex: -1, showDeleteModal: false, busy: true});
this.setState({ focusedContributorIndex: -1, showDeleteModal: false, busy: true });
try {
await removeContributor(this.state.contributorList[idx].value);
const contributorList = this.state.contributorList;
contributorList.splice(idx, 1);
this.setState({contributorList, busy: false, successMsg: 'Contributor removed!'});
} catch (e) {
this.setState({error: e.message || e, busy: false});
this.setState({ contributorList, busy: false, successMsg: 'Contributor removed!' });
} catch (error) {
this.setState({ error: error.message || error, busy: false });
}
};
handleClose = () => {
this.setState({showDeleteModal: false});
this.setState({ showDeleteModal: false });
};
render() {
const {contributorAddress, contributorName, error, busy, contributorList, successMsg, focusedContributorIndex} = this.state;
const {
contributorAddress,
contributorName,
error,
busy,
contributorList,
successMsg,
focusedContributorIndex
} = this.state;
const currentContributor = focusedContributorIndex > -1 ? contributorList[focusedContributorIndex] : {};
return (<Fragment>
<h2>Admin Panel</h2>
{error && <Alert variant="danger">{error}</Alert>}
{successMsg && <Alert variant="success">{successMsg}</Alert>}
{busy && <Alert variant="primary">Working...</Alert>}
<h3>Add a contributor</h3>
<ValidatedForm onSubmit={(e) => this.addContributor(e)}>
<Form.Group controlId="formContributor">
<Form.Label>Contributor name</Form.Label>
<Input type="text" placeholder="Name" value={contributorName}
onChange={(e) => this.onChange('contributorName', e)}
className="form-control"
validations={[required]}/>
</Form.Group>
return (
<Fragment>
<h2>Admin Panel</h2>
{error && <Alert variant="danger">{error}</Alert>}
{successMsg && <Alert variant="success">{successMsg}</Alert>}
{busy && <Alert variant="primary">Working...</Alert>}
<h3>Add a contributor</h3>
<ValidatedForm onSubmit={e => this.addContributor(e)}>
<Form.Group controlId="formContributor">
<Form.Label>Contributor name</Form.Label>
<Input
type="text"
placeholder="Name"
value={contributorName}
onChange={e => this.onChange('contributorName', e)}
className="form-control"
validations={[required]}
/>
</Form.Group>
<Form.Group controlId="formAddress">
<Form.Label>Contributor address</Form.Label>
<Input type="text" placeholder="0x" value={contributorAddress}
onChange={(e) => this.onChange('contributorAddress', e)}
className="form-control"
validations={[required, isAddress]}/>
</Form.Group>
<Button variant="primary" onClick={(e) => this.addContributor(e)}>Add</Button>
</ValidatedForm>
<h3>Contributor List</h3>
<ListGroup>
{contributorList.map((contributor, idx) => (
<ListGroup.Item key={contributor.value} action className="contributor-item">
<span className="font-weight-bold">{contributor.label}:</span> {contributor.value}
<div className="contributor-controls float-right">
<OverlayTrigger placement="top"
overlay={
<Tooltip>
Delete contributor
</Tooltip>
}>
<FontAwesomeIcon icon={faTrash} className="text-danger icon" onClick={(e) => this.removeContributor(e, idx)}/>
</OverlayTrigger>
</div>
</ListGroup.Item>
))}
</ListGroup>
<Modal show={this.state.showDeleteModal} onHide={this.handleClose}>
<Modal.Header closeButton>
<Modal.Title>Are you sure you want to remove this contributor?</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Name: {currentContributor.label}</p>
<p>Address: {currentContributor.value}</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.handleClose}>
Cancel
<Form.Group controlId="formAddress">
<Form.Label>Contributor address</Form.Label>
<Input
type="text"
placeholder="0x"
value={contributorAddress}
onChange={e => this.onChange('contributorAddress', e)}
className="form-control"
validations={[required, isAddress]}
/>
</Form.Group>
<Button variant="primary" onClick={e => this.addContributor(e)}>
Add
</Button>
<Button variant="danger" onClick={this.doRemove}>
Remove
</Button>
</Modal.Footer>
</Modal>
</Fragment>);
</ValidatedForm>
<h3>Contributor List</h3>
<ListGroup>
{contributorList.map((contributor, idx) => (
<ListGroup.Item key={contributor.value} action className="contributor-item">
<span className="font-weight-bold">{contributor.label}:</span> {contributor.value}
<div className="contributor-controls float-right">
<OverlayTrigger placement="top" overlay={<Tooltip>Delete contributor</Tooltip>}>
<FontAwesomeIcon
icon={faTrash}
className="text-danger icon"
onClick={e => this.removeContributor(e, idx)}
/>
</OverlayTrigger>
</div>
</ListGroup.Item>
))}
</ListGroup>
<Modal show={this.state.showDeleteModal} onHide={this.handleClose}>
<Modal.Header closeButton>
<Modal.Title>Are you sure you want to remove this contributor?</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Name: {currentContributor.label}</p>
<p>Address: {currentContributor.value}</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.handleClose}>
Cancel
</Button>
<Button variant="danger" onClick={this.doRemove}>
Remove
</Button>
</Modal.Footer>
</Modal>
</Fragment>
);
}
}

View File

@ -1,40 +1,44 @@
import React, {Component} from 'react';
import React, { Component } from 'react';
import info from '../../images/info.svg';
import downArrow from '../../images/down-arrow.svg';
import "./allocation.scss";
class Allocation extends Component {
state = {
showHelp: false
}
};
handleClickHelp = (e) => {
handleClickHelp = e => {
e.preventDefault();
this.setState(prevState => ({ showHelp: !prevState.showHelp }));
}
};
render() {
const {value} = this.props;
const {showHelp} = this.state;
const { value } = this.props;
const { showHelp } = this.state;
return (
<div className="text-center p-4 allocation">
<p className="text-muted mb-2">Reward Status contributors for all the times they impressed you.</p>
<p className="mb-2">
<a href="#" onClick={this.handleClickHelp}>Learn more <img src={downArrow} alt="" className="ml-2" /></a>
<a href="#" onClick={this.handleClickHelp}>
Learn more <img src={downArrow} alt="" className="ml-2" />
</a>
</p>
{showHelp && (
<div className="text-muted text-left border rounded p-2 mb-2 learn-more">
<img src={info} alt="" />
<p className="m-0 p-0">
Status Meritocracy is an SNT Reward System that allows a Contributor in the registry to
award allocated SNT, along with praise, to other Contributors.<br />
<a href="https://github.com/status-im/meritocracy/blob/master/register.md">Register</a> to
receive a budget and participate.</p>
Status Meritocracy is an SNT Reward System that allows a Contributor in the registry to award allocated
SNT, along with praise, to other Contributors.
<br />
<a href="https://github.com/status-im/meritocracy/blob/master/register.md">Register</a> to receive a
budget and participate.
</p>
</div>
)}
<p className="allocation mb-0">{value} <span className="text-muted">SNT</span></p>
<p className="allocation mb-0">
{value} <span className="text-muted">SNT</span>
</p>
<p className="text-muted">Available</p>
</div>
);

View File

@ -1,14 +1,18 @@
import React from 'react';
import CompleteIcon from '../../images/complete.png';
import {Button} from 'react-bootstrap';
import { Button } from 'react-bootstrap';
const Complete = ({onClick}) => (
const Complete = ({ onClick }) => (
<div className="text-center mt-5 pt-5">
<img src={CompleteIcon} alt="" width="160" height="160" className="mt-5" />
<h4 className="text-center pr-5 pl-5 mt-3">Thank you</h4>
<p className="text-muted">Your SNT has been awarded.</p>
<p><Button onClick={onClick} variant="link">Back</Button></p>
<p>
<Button onClick={onClick} variant="link">
Back
</Button>
</p>
</div>
)
);
export default Complete;

View File

@ -1,25 +1,31 @@
import React, {Fragment} from 'react';
import React, { Fragment } from 'react';
import Select from 'react-select';
import {Form} from 'react-bootstrap';
import { Form } from 'react-bootstrap';
import Allocation from './Allocation';
import statusLogo from '../../images/status-logo.svg';
import "./contributor-selector.scss";
import './contributor-selector.scss';
const sortByAlpha = (a,b) => {
const sortByAlpha = (a, b) => {
if (a.label < b.label) return -1;
if (a.label > b.label) return 1;
return 0;
}
};
const ContributorSelection = ({allocation, contributorList, selectedContributors, onSelectContributor, onChangeAward, onClickPlus5, award}) => (
const ContributorSelection = ({
allocation,
contributorList,
selectedContributors,
onSelectContributor,
onChangeAward,
onClickPlus5,
award
}) => (
<Fragment>
<Allocation value={allocation - award * selectedContributors.length} />
<div className="container">
<div className="row mb-2">
<div className="col-10 label">
Enter contributors and award SNT
</div>
<div className="col-10 label">Enter contributors and award SNT</div>
<div className="col-2">
<div className="plus-5" title="Add +5" onClick={onClickPlus5}>
<img alt="+5" src={statusLogo} />
@ -37,33 +43,28 @@ const ContributorSelection = ({allocation, contributorList, selectedContributors
options={contributorList.sort(sortByAlpha)}
placeholder="Choose Contributor(s)..."
className="mb-2 contributorSelector"
theme={(theme) => ({
theme={theme => ({
...theme,
borderRadius: '4px',
border: 'none',
padding: '10px',
colors: {
...theme.colors,
...theme.colors,
neutral0: '#EEF2F5',
neutral10: '#EEF2F5',
neutral10: '#EEF2F5'
},
spacing: {
...theme.spacing,
controlHeight: 50,
controlHeight: 50
}
})}
/>
</div>
<div className="col-2 p-0">
<Form.Control
type="number"
step="1"
onChange={onChangeAward}
value={award}
/>
<Form.Control type="number" step="1" onChange={onChangeAward} value={award} />
</div>
</div>
</div>
</div>
</Fragment>
);

View File

@ -1,14 +1,18 @@
import React from 'react';
import ErrorIcon from '../../images/error.png';
import {Button} from 'react-bootstrap';
import { Button } from 'react-bootstrap';
const Error = ({onClick, title, message}) => (
const Error = ({ onClick, title, message }) => (
<div className="text-center mt-5 pt-5">
<img src={ErrorIcon} alt="" width="160" height="160" className="mt-5" />
<h4 className="text-center pr-5 pl-5 mt-3">{title}</h4>
<p className="text-muted">{message}</p>
<p><Button onClick={onClick} variant="link">Back</Button></p>
<p>
<Button onClick={onClick} variant="link">
Back
</Button>
</p>
</div>
)
);
export default Error;

View File

@ -1,19 +1,25 @@
import React from 'react'
import {Navbar, Nav} from 'react-bootstrap';
import React from 'react';
import { Navbar, Nav } from 'react-bootstrap';
import './header.scss'
import './header.scss';
import logo from '../../images/logo.png';
const Header = ({isUserAdmin}) => (<Navbar expand="lg" className="header border-bottom mb-3">
<Navbar.Brand href="/#/"><img alt="Logo" src={logo} className="mr-3"/>Status Meritocracy</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
{isUserAdmin && <Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="/#/">Home</Nav.Link>
<Nav.Link href="/#/admin">Admin</Nav.Link>
</Nav>
</Navbar.Collapse>}
const Header = ({ isUserAdmin }) => (
<Navbar expand="lg" className="header border-bottom mb-3">
<Navbar.Brand href="/#/">
<img alt="Logo" src={logo} className="mr-3" />
Status Meritocracy
</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
{isUserAdmin && (
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="/#/">Home</Nav.Link>
<Nav.Link href="/#/admin">Admin</Nav.Link>
</Nav>
</Navbar.Collapse>
)}
</Navbar>
);
export default Header
export default Header;

View File

@ -1,9 +0,0 @@
import React from 'react';
const History = ({value}) => (
<div className="text-center p-4">
</div>
);
export default Allocation;

View File

@ -1,14 +1,15 @@
/*global web3*/
import React, {Fragment} from 'react';
import {Row, Col, Alert, Button, Container, Form, Tabs, Tab} from 'react-bootstrap';
import React, { Fragment } from 'react';
import { Tabs, Tab } from 'react-bootstrap';
import Meritocracy from 'Embark/contracts/Meritocracy';
import arrowLeft from '../../images/arrow-left.svg';
import {getFormattedContributorList, getCurrentContributorData} from '../services/Meritocracy';
import { getFormattedContributorList, getCurrentContributorData } from '../services/Meritocracy';
import './home.scss';
import Step1 from './Step1';
import Step2 from './Step2';
import Loading from './Loading';
import Complete from './Complete';
import Error from './Error';
import Withdrawal from './Withdrawal';
/*
TODO:
@ -17,10 +18,8 @@ TODO:
*/
class Home extends React.Component {
state = {
errorMsg: null,
busy: true,
selectedContributors: [],
contributorList: [],
currentContributor: {
@ -32,8 +31,9 @@ class Home extends React.Component {
},
award: 0,
praise: '',
step: 'ERROR',
step: 'HOME',
checkbox: false,
tab: 'reward'
};
constructor(props) {
@ -52,9 +52,9 @@ class Home extends React.Component {
const currentContributor = await getCurrentContributorData();
this.setState({busy: false, currentContributor, contributorList});
} catch (e) {
this.setState({errorMsg: e.message || e});
this.setState({ busy: false, currentContributor, contributorList });
} catch (error) {
this.setState({ errorMsg: error.message || error });
}
}
@ -65,8 +65,8 @@ class Home extends React.Component {
}
handleAwardChange(e) {
if(e.target.value.trim() === "") {
this.setState({award: ""});
if (e.target.value.trim() === '') {
this.setState({ award: '' });
return;
}
this._setAward(e.target.value);
@ -74,49 +74,54 @@ class Home extends React.Component {
handlePlus5 = () => {
this._setAward(this.state.award + 5);
}
_setAward = (value) => {
let _amount = parseInt(value, 10);
if(_amount < 0 || isNaN(_amount)) _amount = 0;
const { currentContributor: {allocation}, selectedContributors} = this.state;
const maxAllocation = selectedContributors.length ? Math.floor(allocation / selectedContributors.length) : 0;
const award = (_amount <= maxAllocation ? _amount : maxAllocation );
};
this.setState({award});
}
_setAward = value => {
let _amount = parseInt(value, 10);
if (_amount < 0 || isNaN(_amount)) _amount = 0;
const {
currentContributor: { allocation },
selectedContributors
} = this.state;
const maxAllocation = selectedContributors.length > 0 ? Math.floor(allocation / selectedContributors.length) : 0;
const award = _amount <= maxAllocation ? _amount : maxAllocation;
this.setState({ award });
};
handlePraiseChange(e) {
this.setState({ praise: e.target.value });
}
handleCheckbox = (e) => {
handleCheckbox = () => {
this.setState(prevState => ({ checkbox: !prevState.checkbox }));
}
};
resetUIFields(){
resetUIFields() {
this.setState({
praise: '',
selectedContributors: [],
errorMsg: '',
award: 0
award: 0,
checkbox: false
});
}
async awardTokens(e) {
const {award, selectedContributors, praise} = this.state;
async awardTokens() {
const { award, selectedContributors, praise } = this.state;
this.moveStep('BUSY');
this.moveStep('BUSY')();
let addresses = selectedContributors.map(a => a.value);
const sntAmount = web3.utils.toWei(award.toString(), "ether");
const sntAmount = web3.utils.toWei(award.toString(), 'ether');
let toSend;
switch(addresses.length) {
switch (addresses.length) {
case 0:
this.setState({errorMsg: 'No Contributor Selected'});
this.setState({ errorMsg: 'No Contributor Selected' });
return;
case 1:
toSend = Meritocracy.methods.award(addresses[0], sntAmount, praise);
@ -127,69 +132,74 @@ class Home extends React.Component {
}
try {
const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount});
const receipt = await toSend.send({from: web3.eth.defaultAccount, gas: estimatedGas + 1000});
const estimatedGas = await toSend.estimateGas({ from: web3.eth.defaultAccount });
await toSend.send({ from: web3.eth.defaultAccount, gas: estimatedGas + 1000 });
this.resetUIFields();
const currentContributor = await getCurrentContributorData();
this.setState({currentContributor});
this.setState({ currentContributor });
this.moveStep('COMPLETE')();
} catch(e) {
this.setState({errorMsg: 'tx failed? got enough tokens to award?'});
console.error(e);
} finally {
} catch (error) {
this.setState({ errorMsg: 'tx failed? got enough tokens to award?' });
console.error(error);
}
}
async withdrawTokens(e) {
const {currentContributor} = this.state;
async withdrawTokens() {
const { currentContributor } = this.state;
if (currentContributor.received === 0) {
this.setState({errorMsg: 'can only call withdraw when you have tokens'});
this.setState({ errorMsg: 'can only call withdraw when you have tokens' });
return;
}
if ( currentContributor.allocation > 0 ) {
this.setState({errorMsg: 'you must allocate all your tokens'});
if (currentContributor.allocation > 0) {
this.setState({ errorMsg: 'you must allocate all your tokens' });
return;
}
this.moveStep('BUSY')();
const toSend = Meritocracy.methods.withdraw();
try {
this.setState({busy: true});
this.setState({ busy: true });
const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount});
const receipt = await toSend.send({from: web3.eth.defaultAccount, gas: estimatedGas + 1000});
const estimatedGas = await toSend.estimateGas({ from: web3.eth.defaultAccount });
await toSend.send({ from: web3.eth.defaultAccount, gas: estimatedGas + 1000 });
const currentContributor = await getCurrentContributorData();
this.setState({currentContributor});
} catch(e) {
this.setState({errorMsg: 'tx failed? Did you allocate all your tokens first?'});
console.error(e);
} finally {
this.setState({busy: false});
this.setState({ currentContributor });
this.moveStep('COMPLETE')();
} catch (error) {
console.error(error);
this.setState({ errorMsg: 'tx failed? Did you allocate all your tokens first?' });
}
}
moveStep = nexStep => () => {
this.setState({step: nexStep});
}
this.setState({ step: nexStep, errorMsg: '' });
};
render() {
const { selectedContributors, contributorList, award, currentContributor, praise, busy, errorMsg, step, checkbox } = this.state;
const {
selectedContributors,
contributorList,
award,
currentContributor,
praise,
errorMsg,
step,
checkbox,
tab
} = this.state;
if(errorMsg) return <Error title="Error" value={errorMsg} />;
if (errorMsg) return <Error title="Error" message={errorMsg} onClick={this.moveStep('HOME')} />;
return (
<Fragment>
<Tabs defaultActiveKey="reward" className="home-tabs mb-3">
<Tabs className="home-tabs mb-3" activeKey={tab} onSelect={tab => this.setState({ tab })}>
<Tab eventKey="reward" title="Reward" className="reward-panel">
{step === 'HOME' && (
<Step1
allocation={currentContributor.allocation}
@ -206,54 +216,35 @@ class Home extends React.Component {
)}
{step === 'PRAISE' && (
<div>
<p className="text-center mt-5 text-muted">Research shows that a note of praise and learning how much our work helped others, increases motivation.</p>
<p className="mb-0">
<span className="font-weight-bold">{ selectedContributors.map(x => x.label).join(', ') }</span>
<span className="float-right text-muted">SNT <b>{award * selectedContributors.length}</b></span>
</p>
<Form>
<Form.Label className="small-text">Add note</Form.Label>
<Form.Control disabled={busy} as="textarea" rows="5" onChange={this.handlePraiseChange}
value={praise} className="p-2"/>
</Form>
<div className="fixed-bottom bg-white">
<Button onClick={this.moveStep('HOME')} variant="link"><img src={arrowLeft} alt="" className="mr-2" /> Back</Button>
<Button disabled={busy} variant="primary" className="float-right mr-2 mb-2" onClick={this.awardTokens}>Award</Button>
</div>
</div>
<Step2
selectedContributors={selectedContributors}
award={award}
praise={praise}
onChangeNote={this.handlePraiseChange}
onClickBack={this.moveStep('HOME')}
onClickAward={this.awardTokens}
/>
)}
{ step === 'BUSY' && <Loading /> }
{ step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} /> }
{ step === 'ERROR' && <Error onClick={this.moveStep('PRAISE')} title="Error" message="Your transaction could not be processed" /> }
{step === 'BUSY' && <Loading />}
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
</Tab>
<Tab eventKey="withdraw" title="Withdraw">
<p>Your Total Received Kudos: {currentContributor.totalReceived || 0} SNT</p>
<p>Your Total Forfeited Kudos: {currentContributor.totalForfeited || 0} SNT</p>
<h4>Your Kudos History</h4>
<p>Your Received Kudos: <b>{currentContributor.received} SNT</b></p>
<p className="text-center">
<Button variant="outline-primary" onClick={this.withdrawTokens} disabled={busy}>
Withdraw
</Button>
</p>
<Container>
<Row>
{currentContributor.praises && currentContributor.praises.map((item, i) => {
const name = options.find(x => x.value === item.author);
return <Col key={i}>{(name && name.label) || item.author} has sent
you {web3.utils.fromWei(item.amount, "ether")} SNT {item.praise && "\"" + item.praise + "\""}</Col>;
})}
</Row>
</Container>
<Tab eventKey="withdraw" title="Withdraw" className="withdraw-panel">
{step === 'HOME' && (
<Withdrawal
onClick={this.withdrawTokens}
totalReceived={currentContributor.totalReceived}
allocation={currentContributor.allocation}
contributorList={contributorList}
praises={currentContributor.praises}
/>
)}
{step === 'BUSY' && <Loading />}
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
</Tab>
</Tabs>
</Fragment>

View File

@ -7,6 +7,6 @@ const Loading = () => (
<img src={spinner} alt="" className="mt-5" />
<h5 className="text-muted text-center pr-5 pl-5">Waiting for the confirmation from miners</h5>
</div>
)
);
export default Loading;

View File

@ -1,11 +1,22 @@
import React, {Fragment} from 'react';
import React, { Fragment } from 'react';
import arrowRight from '../../images/arrow-right.svg';
import ContributorSelection from './ContributorSelection';
import {Button, Form} from 'react-bootstrap';
import { Button, Form } from 'react-bootstrap';
const Step1 = ({allocation, onChangeAward, onSelectContributor, onClickPlus5, contributorList, selectedContributors, award, isChecked, onClickCheckbox, onClickNext}) => (
<Fragment>
<ContributorSelection
const Step1 = ({
allocation,
onChangeAward,
onSelectContributor,
onClickPlus5,
contributorList,
selectedContributors,
award,
isChecked,
onClickCheckbox,
onClickNext
}) => (
<Fragment>
<ContributorSelection
allocation={allocation}
onChangeAward={onChangeAward}
onSelectContributor={onSelectContributor}
@ -15,14 +26,27 @@ const Step1 = ({allocation, onChangeAward, onSelectContributor, onClickPlus5, co
award={award}
/>
<Form.Group>
<Form.Check type="checkbox" className="TOC pl-5 pr-2 mt-4" checked={isChecked} onChange={onClickCheckbox} label="I understand that I only receive rewards if I spend my entire reward budget." />
</Form.Group>
<Form.Group>
<Form.Check
type="checkbox"
className="TOC pl-5 pr-2 mt-4"
checked={isChecked}
onChange={onClickCheckbox}
label="I understand that I only receive rewards if I spend my entire reward budget."
/>
</Form.Group>
<div className="fixed-bottom bg-white">
<Button disabled={selectedContributors.length === 0 || !(award > 0) || !isChecked} onClick={onClickNext} variant="link" className="float-right p-3">Next <img src={arrowRight} alt="" className="ml-2" /></Button>
</div>
</Fragment>
<div className="fixed-bottom bg-white">
<Button
disabled={selectedContributors.length === 0 || !(award > 0) || !isChecked}
onClick={onClickNext}
variant="link"
className="float-right p-3"
>
Next <img src={arrowRight} alt="" className="ml-2" />
</Button>
</div>
</Fragment>
);
export default Step1;
export default Step1;

View File

@ -0,0 +1,31 @@
import React from 'react';
import { Button, Form } from 'react-bootstrap';
import arrowLeft from '../../images/arrow-left.svg';
const Step2 = ({ selectedContributors, award, praise, onChangeNote, onClickBack, onClickAward }) => (
<div>
<p className="text-center mt-5 text-muted">
Research shows that a note of praise and learning how much our work helped others, increases motivation.
</p>
<p className="mb-0">
<span className="font-weight-bold">{selectedContributors.map(x => x.label).join(', ')}</span>
<span className="float-right text-muted">
SNT <b>{award * selectedContributors.length}</b>
</span>
</p>
<Form>
<Form.Label className="small-text">Add note</Form.Label>
<Form.Control as="textarea" rows="5" onChange={onChangeNote} value={praise} className="p-2" />
</Form>
<div className="fixed-bottom bg-white">
<Button onClick={onClickBack} variant="link">
<img src={arrowLeft} alt="" className="mr-2" /> Back
</Button>
<Button variant="primary" className="float-right mr-2 mb-2" onClick={onClickAward}>
Award
</Button>
</div>
</div>
);
export default Step2;

View File

@ -0,0 +1,72 @@
/* global web3 */
import React, { Fragment } from 'react';
import { Row, Col, Button, Container } from 'react-bootstrap';
import moment from 'moment';
import info from '../../images/red-info.svg';
import Address from './Address';
import './withdrawal.scss';
const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, praises }) => (
<Fragment>
<div className="text-center p-4">
<p className="text-muted mb-0 mt-5">You have been awarded</p>
<p className="awarded mb-0">
{totalReceived || 0} <span className="text-muted">SNT</span>
</p>
<p className="text-muted">Available for withdraw</p>
</div>
<Container>
{praises &&
praises.map((item, i) => {
const name = contributorList.find(x => x.value === item.author);
const date = moment.unix(item.time).fromNow();
return (
<Row key={i}>
<Col className="mb-4 text-muted">
{!item.praise && (
<Fragment>
{(name && name.label) || <Address value={item.author} compact={true} />} has sent you{' '}
{web3.utils.fromWei(item.amount, 'ether')} SNT <small>{date}</small>
</Fragment>
)}
{item.praise && (
<Fragment>
{(name && name.label) || item.author}, <small>{date}</small>
<div className="chatBubble p-3">
&quot;{item.praise}&quot;
<small className="float-right">{web3.utils.fromWei(item.amount, 'ether')} SNT</small>
</div>
</Fragment>
)}
</Col>
</Row>
);
})}
</Container>
<p className="text-center">
<Button
variant={allocation !== '0' || totalReceived === '0' ? 'secondary' : 'primary'}
onClick={onClick}
disabled={allocation !== '0' || totalReceived === '0'}
>
Withdraw
</Button>
</p>
{allocation !== '0' && (
<div className="text-muted text-left border rounded p-2 mb-2 learn-more">
<img src={info} alt="" />
<p className="m-0 p-0">
Your budget wasnt fullly rewarded to others. Note that you can only withdraw your own reward if youve spend
your full budget to reward others.
</p>
</div>
)}
</Fragment>
);
export default Withdrawal;

View File

@ -1,15 +0,0 @@
.learn-more {
display: flex;
img {
width: 25px;
margin: 0 10px 0 0;
object-fit: contain;
align-self: flex-start;
}
p {
flex: 1 1 auto;
}
}

View File

@ -37,3 +37,26 @@
font-size: 13px;
}
}
.withdraw-panel {
.awarded {
font-size: 32px;
}
}
.learn-more {
display: flex;
img {
width: 25px;
margin: 0 10px 0 0;
object-fit: contain;
align-self: flex-start;
}
p {
flex: 1 1 auto;
}
}

View File

@ -0,0 +1,4 @@
.chatBubble {
background: #ECEFFC;
border-radius: 8px;
}

View File

@ -1,49 +1,49 @@
const contributors = [
{ 'label' : 'Andreas S.', 'value' : '0x4923121411e884a4af66ec025712eba600a782d3' },
{ 'label' : 'andrey.dev', 'value' : '0xA4EcA293cb578a68b190e3e07c2B170dc753fe44' },
{ 'label' : 'Anna', 'value': '0x2e1ce0f514387a188f4aeff4ceb6c2c0dea66ca7'},
{ 'label' : 'barry', 'value' : '0xa46b0546481a04b7de049a8a20f8a9b2b2c5cc05' },
{ 'label' : 'BrianXV', 'value' : '0x03b832b3fa819d7a4b6c819e4df1e60a173e739a' },
{ 'label' : 'cammellos', 'value' : '0xd0ec8a940fe9712c1521c2190f41604ecaa7ec9e' },
{ 'label' : 'Carl', 'value': '0x9fb937ab76b68dae29e3de20cf797d44f00e6410'},
{ 'label' : 'ceri', 'value' : '0x68f47e153e1aa7d6529e078feff86eada87ddee3' },
{ 'label' : 'Dani', 'value' : '0x89c010bc7085eb150b66582f13681f9e36904bea' },
{ 'label' : 'dmitryn', 'value' : '0x6b0d7ba67aa3d84122749dc7906b8e7f25ed1af8' },
{ 'label' : 'gravityblast', 'value' : '0xb5a2c17c7fd72070fcf078bb8458f2f595441066' },
{ 'label' : 'guylouis.stateofus.eth', 'value' : '0x6913f3bdbb7c303977d6244c0e0071b4ebc6f359' },
{ 'label' : 'Hester', 'value' : '0x8c4f71b3cf6a76de2cc239a6fa84e1a80e589598' },
{ 'label' : 'Hutch', 'value' : '0x34a4b73100d11815ee4bb0ebcc86ba5824b12134' },
{ 'label' : 'igor.stateofus.eth', 'value' : '0xff91043525391cd3a316450bc80ef874fb0ef446' },
{ 'label' : 'jakubgs.eth', 'value' : '0x9b64770c9485A5188D238C305E53627A67C05D7D'},
{ 'label' : 'Jinho', 'value' : '0x7407bF49004ee99d9B2caA2fb90B476bfF2DbCaf' },
{ 'label' : 'Jonathan Barker', 'value' : '0xf23d05F375A8367b150f7Ad1A37DFd9E3c35eE56' },
{ 'label' : 'Jonathan Rainville', 'value' : '0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9' },
{ 'label' : 'Jonny Z', 'value' : '0xa40b07ac80d1f89b233b74e78d254c90906c33ee' },
{ 'label' : 'Julien', 'value' : '0x6c618ddbf53aa9540c279e3670d4d26fb367fd4e' },
{ 'label' : 'Maciej', 'value' : '0x227612e69b1d06250e7035c1c12840561ebf3c56' },
{ 'label' : 'michele', 'value' : '0x658a1d2c105b35d9aaad38480dbbfe47b9054962' },
{ 'label' : 'Nabil', 'value' : '0x528c9e62bb0e7083f4b42802297b38ba237776a0' },
{ 'label' : 'Oskar', 'value' : '0x3fd6e2dfa535ce8b1e7eb7116a009eba3890b6bd' },
{ 'label' : 'PascalPrecht', 'value' : '0x6f490165DdD8d604b52dB9D9BF9b63aE997DC11C' },
{ 'label' : 'pedro.stateofus.eth', 'value' : '0x78EA50b13de394671474314aA261556717bF9185' },
{ 'label' : 'Rachel', 'value' : '0x4b9ba5B0dEE90f5B84Bcbfbf921cF02e1C8da113' },
{ 'label' : 'Rajanie', 'value' : '0x8af0d6fabc4a90ea0b95f80ab62beb816ed32a69' },
{ 'label' : 'Ricardo Schmidt <3esmit>', 'value' : '0x3D597789ea16054a084ac84ce87F50df9198F415' },
{ 'label' : 'Sergey', 'value' : '0xb9f914fe1c6edae2351fb42276868470083a3cd2' },
{ 'label' : 'shemnon', 'value' : '0x82ad1b2419fd71dfe2d5db9b3c832c60ec96c53b' },
{ 'label' : 'sonja.stateofus.eth', 'value' : '0xCF03738e9605C0B38cEAa7349bF6926463f01A25' },
{ 'label' : 'Swader', 'value' : '0x9702797d92e2a06070b446e49a594a943686e28f' },
{ 'label' : 'yenda', 'value' : '0xe829f7947175fe6a338344e70aa770a8c134372c' },
{ 'label' : 'petty', 'value' : '0x2942577508e060ea092c0CD7802ae42c1CEA2BAe' },
{ 'label' : 'chu', 'value' : '0xd21DB0e43048AcB94f428eD61dC244c82f1ff2a8' },
{ 'label' : 'Yessin', 'value' : '0xbaba92b7822a56c05554ab5d1bc1d0b7e212499d' },
{ 'label' : 'michaelb', 'value' : '0xdba0bade45727776bbb0d93176ee1ddba830f319' },
{ 'label' : 'cryptowanderer', 'value' : '0x406abd306b633b6460666b4092784a3330370c7b' },
{ 'label' : 'adam.stateofus.eth', 'value' : '0x074032269ca1775896c92304d45f80b5a67a5bcb' },
{ 'label' : 'André Medeiros', 'value' : '0xf062E478870B17B55d1dC64888914B82aD9808B4' },
{ 'label' : 'rramos', 'value' : '0xc379330ae48716b81d7411813c3250cd89271788' },
{ 'label' : 'emizzle', 'value' : '0x91Ef8ef20Adf13E42757a3Ed6Ff2b1249bE15544' },
{ 'label' : 'jason.stateofus.eth', 'value' : '0x4636fb2F6D1DC335EA655795064c2092c89148aB' }
{ label: 'Andreas S.', value: '0x4923121411e884a4af66ec025712eba600a782d3' },
{ label: 'andrey.dev', value: '0xA4EcA293cb578a68b190e3e07c2B170dc753fe44' },
{ label: 'Anna', value: '0x2e1ce0f514387a188f4aeff4ceb6c2c0dea66ca7' },
{ label: 'barry', value: '0xa46b0546481a04b7de049a8a20f8a9b2b2c5cc05' },
{ label: 'BrianXV', value: '0x03b832b3fa819d7a4b6c819e4df1e60a173e739a' },
{ label: 'cammellos', value: '0xd0ec8a940fe9712c1521c2190f41604ecaa7ec9e' },
{ label: 'Carl', value: '0x9fb937ab76b68dae29e3de20cf797d44f00e6410' },
{ label: 'ceri', value: '0x68f47e153e1aa7d6529e078feff86eada87ddee3' },
{ label: 'Dani', value: '0x89c010bc7085eb150b66582f13681f9e36904bea' },
{ label: 'dmitryn', value: '0x6b0d7ba67aa3d84122749dc7906b8e7f25ed1af8' },
{ label: 'gravityblast', value: '0xb5a2c17c7fd72070fcf078bb8458f2f595441066' },
{ label: 'guylouis.stateofus.eth', value: '0x6913f3bdbb7c303977d6244c0e0071b4ebc6f359' },
{ label: 'Hester', value: '0x8c4f71b3cf6a76de2cc239a6fa84e1a80e589598' },
{ label: 'Hutch', value: '0x34a4b73100d11815ee4bb0ebcc86ba5824b12134' },
{ label: 'igor.stateofus.eth', value: '0xff91043525391cd3a316450bc80ef874fb0ef446' },
{ label: 'jakubgs.eth', value: '0x9b64770c9485A5188D238C305E53627A67C05D7D' },
{ label: 'Jinho', value: '0x7407bF49004ee99d9B2caA2fb90B476bfF2DbCaf' },
{ label: 'Jonathan Barker', value: '0xf23d05F375A8367b150f7Ad1A37DFd9E3c35eE56' },
{ label: 'Jonathan Rainville', value: '0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9' },
{ label: 'Jonny Z', value: '0xa40b07ac80d1f89b233b74e78d254c90906c33ee' },
{ label: 'Julien', value: '0x6c618ddbf53aa9540c279e3670d4d26fb367fd4e' },
{ label: 'Maciej', value: '0x227612e69b1d06250e7035c1c12840561ebf3c56' },
{ label: 'michele', value: '0x658a1d2c105b35d9aaad38480dbbfe47b9054962' },
{ label: 'Nabil', value: '0x528c9e62bb0e7083f4b42802297b38ba237776a0' },
{ label: 'Oskar', value: '0x3fd6e2dfa535ce8b1e7eb7116a009eba3890b6bd' },
{ label: 'PascalPrecht', value: '0x6f490165DdD8d604b52dB9D9BF9b63aE997DC11C' },
{ label: 'pedro.stateofus.eth', value: '0x78EA50b13de394671474314aA261556717bF9185' },
{ label: 'Rachel', value: '0x4b9ba5B0dEE90f5B84Bcbfbf921cF02e1C8da113' },
{ label: 'Rajanie', value: '0x8af0d6fabc4a90ea0b95f80ab62beb816ed32a69' },
{ label: 'Ricardo Schmidt <3esmit>', value: '0x3D597789ea16054a084ac84ce87F50df9198F415' },
{ label: 'Sergey', value: '0xb9f914fe1c6edae2351fb42276868470083a3cd2' },
{ label: 'shemnon', value: '0x82ad1b2419fd71dfe2d5db9b3c832c60ec96c53b' },
{ label: 'sonja.stateofus.eth', value: '0xCF03738e9605C0B38cEAa7349bF6926463f01A25' },
{ label: 'Swader', value: '0x9702797d92e2a06070b446e49a594a943686e28f' },
{ label: 'yenda', value: '0xe829f7947175fe6a338344e70aa770a8c134372c' },
{ label: 'petty', value: '0x2942577508e060ea092c0CD7802ae42c1CEA2BAe' },
{ label: 'chu', value: '0xd21DB0e43048AcB94f428eD61dC244c82f1ff2a8' },
{ label: 'Yessin', value: '0xbaba92b7822a56c05554ab5d1bc1d0b7e212499d' },
{ label: 'michaelb', value: '0xdba0bade45727776bbb0d93176ee1ddba830f319' },
{ label: 'cryptowanderer', value: '0x406abd306b633b6460666b4092784a3330370c7b' },
{ label: 'adam.stateofus.eth', value: '0x074032269ca1775896c92304d45f80b5a67a5bcb' },
{ label: 'André Medeiros', value: '0xf062E478870B17B55d1dC64888914B82aD9808B4' },
{ label: 'rramos', value: '0xc379330ae48716b81d7411813c3250cd89271788' },
{ label: 'emizzle', value: '0x91Ef8ef20Adf13E42757a3Ed6Ff2b1249bE15544' },
{ label: 'jason.stateofus.eth', value: '0x4636fb2F6D1DC335EA655795064c2092c89148aB' }
];
module.exports = contributors;

View File

@ -1,13 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import '../css/fonts/Inter/inter.css';
import '../css/bootstrap-overrides.scss';
import '../css/index.scss';
ReactDOM.render(
<App/>,
document.getElementById('app')
);
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import '../css/fonts/Inter/inter.css';
import '../css/bootstrap-overrides.scss';
import '../css/index.scss';
ReactDOM.render(<App />, document.querySelector('#app'));

View File

@ -2,7 +2,6 @@
import Meritocracy from 'Embark/contracts/Meritocracy';
import EmbarkJS from 'Embark/EmbarkJS';
let contributorList;
export function addContributor(name, address) {
@ -10,19 +9,19 @@ export function addContributor(name, address) {
const mainAccount = web3.eth.defaultAccount;
try {
const list = await getContributorList();
list.push({label: name, value: address});
list.push({ label: name, value: address });
const newHash = await saveContributorList(list);
const addContributor = Meritocracy.methods.addContributor(address, web3.utils.toHex(newHash));
let gas = await addContributor.estimateGas({from: mainAccount});
const receipt = await addContributor.send({from: mainAccount, gas: gas + 1000});
let gas = await addContributor.estimateGas({ from: mainAccount });
const receipt = await addContributor.send({ from: mainAccount, gas: gas + 1000 });
resolve(receipt);
} catch (e) {
} catch (error) {
const message = 'Error adding contributor';
console.error(message);
console.error(e);
console.error(error);
reject(message);
}
});
@ -32,7 +31,7 @@ export function removeContributor(address) {
return new Promise(async (resolve, reject) => {
const mainAccount = web3.eth.defaultAccount;
try {
const registry = await Meritocracy.methods.getRegistry().call({from: mainAccount});
const registry = await Meritocracy.methods.getRegistry().call({ from: mainAccount });
let index = registry.indexOf(address);
const list = await getContributorList();
@ -42,14 +41,14 @@ export function removeContributor(address) {
const newHash = await saveContributorList(list);
const removeContributor = Meritocracy.methods.removeContributor(index, web3.utils.toHex(newHash));
let gas = await removeContributor.estimateGas({from: mainAccount});
const receipt = await removeContributor.send({from: mainAccount, gas: gas + 1000});
let gas = await removeContributor.estimateGas({ from: mainAccount });
const receipt = await removeContributor.send({ from: mainAccount, gas: gas + 1000 });
resolve(receipt);
} catch (e) {
} catch (error) {
const message = 'Error removing contributor';
console.error(message);
console.error(e);
console.error(error);
reject(message);
}
});
@ -66,10 +65,10 @@ export function getContributorList(hash) {
const content = await EmbarkJS.Storage.get(hash);
contributorList = JSON.parse(content);
resolve(contributorList);
} catch (e) {
} catch (error) {
const message = 'Error getting contributor file on IPFS';
console.error(message);
console.error(e);
console.error(error);
reject(message);
}
});
@ -82,23 +81,27 @@ export async function getFormattedContributorList(hash) {
let list = await getContributorList(hash);
list = list.map(prepareOptions);
const registry = await Meritocracy.methods.getRegistry().call({from: mainAccount});
list = list.filter(contributorData => registry.includes(contributorData.value) && contributorData.value !== mainAccount);
const registry = await Meritocracy.methods.getRegistry().call({ from: mainAccount });
list = list.filter(
contributorData => registry.includes(contributorData.value) && contributorData.value !== mainAccount
);
resolve(list);
} catch (e) {
} catch (error) {
const message = 'Error getting formatted contributor file on IPFS';
console.error(message);
console.error(e);
console.error(error);
reject(message);
}
});
}
const prepareOptions = option => {
if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) { // Address
if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) {
// Address
option.value = web3.utils.toChecksumAddress(option.value);
} else { // ENS Name
} else {
// ENS Name
// TODO: resolve ENS names
// EmbarkJS.Names.resolve("ethereum.eth").then(address => {
// console.log("the address for ethereum.eth is: " + address);
@ -107,12 +110,12 @@ const prepareOptions = option => {
return option;
};
export async function getCurrentContributorData(){
export async function getCurrentContributorData() {
const mainAccount = web3.eth.defaultAccount;
const currentContributor = await getContributor(mainAccount);
let praises = [];
for(let i = 0; i < currentContributor.praiseNum; i++){
for (let i = 0; i < currentContributor.praiseNum; i++) {
praises.push(Meritocracy.methods.getStatus(mainAccount, i).call());
}
@ -121,13 +124,13 @@ export async function getCurrentContributorData(){
}
const contribData = contributorList.find(x => x.value === mainAccount);
if(contribData) currentContributor.name = contribData.label;
if (contribData) currentContributor.name = contribData.label;
currentContributor.praises = await Promise.all(praises);
currentContributor.allocation = web3.utils.fromWei(currentContributor.allocation, "ether");
currentContributor.totalForfeited = web3.utils.fromWei(currentContributor.totalForfeited, "ether");
currentContributor.totalReceived = web3.utils.fromWei(currentContributor.totalReceived, "ether");
currentContributor.received = web3.utils.fromWei(currentContributor.received, "ether");
currentContributor.allocation = web3.utils.fromWei(currentContributor.allocation, 'ether');
currentContributor.totalForfeited = web3.utils.fromWei(currentContributor.totalForfeited, 'ether');
currentContributor.totalReceived = web3.utils.fromWei(currentContributor.totalReceived, 'ether');
currentContributor.received = web3.utils.fromWei(currentContributor.received, 'ether');
return currentContributor;
}
@ -144,10 +147,10 @@ export function saveContributorList(list) {
contributorList = list;
const newHash = await EmbarkJS.Storage.saveText(JSON.stringify(list));
resolve(newHash);
} catch (e) {
} catch (error) {
const message = 'Error saving contributor file on IPFS';
console.error(message);
console.error(e);
console.error(error);
reject(message);
}
});
@ -158,12 +161,11 @@ export function isAdmin(address) {
try {
const result = await Meritocracy.methods.admins(address).call();
resolve(result);
} catch (e) {
} catch (error) {
const message = 'Could not get status of user';
console.error(message);
console.error(e);
console.error(error);
reject(message);
}
});
}

View File

@ -1,60 +1,96 @@
/*global Web3*/
import React from 'react';
import {Form} from 'react-bootstrap';
import { Form } from 'react-bootstrap';
export const required = (value) => {
if (!value.toString().trim().length) {
return <Form.Control.Feedback type="invalid" className="d-block">This field is required</Form.Control.Feedback>;
export const required = value => {
if (value.toString().trim().length === 0) {
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field is required
</Form.Control.Feedback>
);
}
};
export const isInteger = (value) => {
export const isInteger = value => {
value = parseFloat(value);
if (!Number.isInteger(value)) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be an integer</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be an integer
</Form.Control.Feedback>
);
}
};
export const isNumber = (value) => {
export const isNumber = value => {
if (Number.isNaN(value)) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be an number</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be an number
</Form.Control.Feedback>
);
}
};
export const lowerThan = (max, value) => {
if (value >= max) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be lower than {max}</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be lower than {max}
</Form.Control.Feedback>
);
}
};
export const lowerEqThan = (max, value) => {
if (value > max) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be lower or equal than {max}</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be lower or equal than {max}
</Form.Control.Feedback>
);
}
};
export const higherThan = (min, value) => {
if (value <= min) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be higher than {min}</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be higher than {min}
</Form.Control.Feedback>
);
}
};
export const higherEqThan = (min, value) => {
if (value < min) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be higher or equal than {min}</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be higher or equal than {min}
</Form.Control.Feedback>
);
}
};
export const isAddress = (value) => {
export const isAddress = value => {
if (!Web3.utils.isAddress(value)) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be a valid Ethereum address</Form.Control.Feedback>;
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be a valid Ethereum address
</Form.Control.Feedback>
);
}
};
export const isJSON = (value) => {
export const isJSON = value => {
try {
JSON.parse(value);
} catch (e) {
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be a valid JSON string</Form.Control.Feedback>;
} catch (error) {
return (
<Form.Control.Feedback type="invalid" className="d-block">
This field needs to be a valid JSON string
</Form.Control.Feedback>
);
}
};