feat: admin panel (#29)

* feat: added kudos wall

* feat: add admin forms

* feat: add forfeit and allocate functionality

* fix: moved leaderboard out of admin panel
This commit is contained in:
Richard Ramos 2019-05-14 13:35:10 -04:00 committed by GitHub
parent 7233fb934b
commit 9d983c2b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 318 additions and 108 deletions

View File

@ -91,3 +91,7 @@ $input-icon-height: 20px;
.btn { .btn {
padding: 11px 36px; padding: 11px 36px;
} }
.text-small {
font-size: 13px;
}

View File

@ -8,6 +8,7 @@ import EmbarkJS from 'Embark/EmbarkJS';
import { isAdmin } from './services/Meritocracy'; import { isAdmin } from './services/Meritocracy';
import Header from './components/Header'; import Header from './components/Header';
import Home from './components/Home'; import Home from './components/Home';
import Leaderboard from './components/Leaderboard';
import Admin from './components/Admin'; import Admin from './components/Admin';
const MAINNET = 1; const MAINNET = 1;
@ -65,8 +66,8 @@ class App extends React.Component {
<Header isUserAdmin={isUserAdmin} /> <Header isUserAdmin={isUserAdmin} />
<Switch> <Switch>
<Route exact path="/" component={Home} /> <Route exact path="/" component={Home} />
<Route path="/leaderboard" component={Leaderboard} />
{isUserAdmin && <Route exact path="/admin" component={Admin} />} {isUserAdmin && <Route exact path="/admin" component={Admin} />}
<Redirect to="/404" /> <Redirect to="/404" />
</Switch> </Switch>
</ThemeProvider> </ThemeProvider>

View File

@ -1,21 +1,24 @@
/* global web3 */
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Button, Form, Alert, ListGroup, OverlayTrigger, Tooltip, Modal, Tabs, Tab, Table } from 'react-bootstrap'; import { Button, Form, Alert, ListGroup, OverlayTrigger, Tooltip, Modal, Tabs, Tab } from 'react-bootstrap';
import ValidatedForm from 'react-validation/build/form'; import ValidatedForm from 'react-validation/build/form';
import Input from 'react-validation/build/input'; import Input from 'react-validation/build/input';
import { required, isAddress } from '../validators'; import { required, isAddress, isNumber, higherThan } from '../validators';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { addContributor, getFormattedContributorList, removeContributor, getContributorData } from '../services/Meritocracy'; import {
import { sortByAlpha, sortByAttribute, sortNullableArray } from '../utils'; addContributor,
getFormattedContributorList,
removeContributor,
forfeitAllocation,
lastForfeited,
allocate
} from '../services/Meritocracy';
import { sortByAlpha } from '../utils';
import moment from 'moment';
import './admin.scss'; import './admin.scss';
const sort = (orderBy) => {
if(orderBy === 'praises') return sortNullableArray('praises');
if(orderBy === 'label') return sortByAlpha('label');
return sortByAttribute(orderBy);
};
class Admin extends React.Component { class Admin extends React.Component {
state = { state = {
contributorName: '', contributorName: '',
@ -27,7 +30,9 @@ class Admin extends React.Component {
showDeleteModal: false, showDeleteModal: false,
focusedContributorIndex: -1, focusedContributorIndex: -1,
sortBy: 'label', sortBy: 'label',
tab: 'admin' tab: 'admin',
sntPerContributor: 0,
lastForfeited: null
}; };
async componentDidMount() { async componentDidMount() {
@ -36,15 +41,7 @@ class Admin extends React.Component {
this.setState({ busy: false, contributorList }); this.setState({ busy: false, contributorList });
// TODO: this can be replaced by event sourcing this.getLastForfeitDate();
contributorList.forEach(contrib => {
getContributorData(contrib.value)
.then(data => {
contrib = Object.assign(contrib, data);
this.setState({contributorList});
});
});
} catch (error) { } catch (error) {
this.setState({ errorMsg: error.message || error }); this.setState({ errorMsg: error.message || error });
} }
@ -54,9 +51,14 @@ class Admin extends React.Component {
this.setState({ [name]: e.target.value }); this.setState({ [name]: e.target.value });
}; };
getLastForfeitDate = async () => {
const date = await lastForfeited();
this.setState({ lastForfeited: date });
};
addContributor = async e => { addContributor = async e => {
e.preventDefault(); e.preventDefault();
this.setState({ busy: true, successMsg: '' }); this.setState({ busy: true, successMsg: '', error: '' });
try { try {
await addContributor(this.state.contributorName, this.state.contributorAddress); await addContributor(this.state.contributorName, this.state.contributorAddress);
@ -69,6 +71,41 @@ class Admin extends React.Component {
} }
}; };
allocateFunds = async e => {
e.preventDefault();
/* eslint-disable-next-line no-alert*/
if (!confirm('Are you sure?')) return;
this.setState({ busy: true, successMsg: '', error: '' });
const { contributorList, sntPerContributor } = this.state;
const sntAmount = web3.utils.toWei((contributorList.length * parseInt(sntPerContributor, 10)).toString(), 'ether');
try {
await allocate(sntAmount);
this.setState({ busy: false, successMsg: 'Funds allocated!' });
} catch (error) {
this.setState({ error: error.message || error, busy: false });
}
};
forfeit = async e => {
e.preventDefault();
/* eslint-disable-next-line no-alert*/
if (!confirm('Are you sure?')) return;
this.setState({ busy: true, successMsg: '' });
try {
await forfeitAllocation();
await this.getLastForfeitDate();
this.setState({ busy: false, successMsg: 'Funds forfeited!' });
} catch (error) {
this.setState({ error: error.message || error, busy: false });
}
};
removeContributor = (e, contributorIndex) => { removeContributor = (e, contributorIndex) => {
e.preventDefault(); e.preventDefault();
this.setState({ focusedContributorIndex: contributorIndex, showDeleteModal: true }); this.setState({ focusedContributorIndex: contributorIndex, showDeleteModal: true });
@ -93,12 +130,9 @@ class Admin extends React.Component {
this.setState({ showDeleteModal: false }); this.setState({ showDeleteModal: false });
}; };
sortBy = (order) => () => {
this.setState({sortBy: order});
}
render() { render() {
const { const {
lastForfeited,
contributorAddress, contributorAddress,
contributorName, contributorName,
error, error,
@ -106,17 +140,19 @@ class Admin extends React.Component {
contributorList, contributorList,
successMsg, successMsg,
focusedContributorIndex, focusedContributorIndex,
sortBy, tab,
tab sntPerContributor
} = this.state; } = this.state;
const currentContributor = focusedContributorIndex > -1 ? contributorList[focusedContributorIndex] : {}; const currentContributor = focusedContributorIndex > -1 ? contributorList[focusedContributorIndex] : {};
const sortedContributorList = contributorList.sort(sort(sortBy)); const nextForfeit = (lastForfeited ? lastForfeited * 1000 : new Date().getTime()) + 86400 * 6 * 1000;
const nextForfeitDate =
new Date(nextForfeit).toLocaleDateString() + ' ' + new Date(nextForfeit).toLocaleTimeString();
return ( return (
<Fragment> <Fragment>
<Tabs className="home-tabs mb-3" activeKey={tab} onSelect={tab => this.setState({ tab })}> <Tabs className="home-tabs mb-3" activeKey={tab} onSelect={tab => this.setState({ tab })}>
<Tab eventKey="admin" title="Admin Panel" className="admin-panel"> <Tab eventKey="admin" title="Contributors" className="admin-panel">
<h2>Admin Panel</h2> <h2>Contributors</h2>
{error && <Alert variant="danger">{error}</Alert>} {error && <Alert variant="danger">{error}</Alert>}
{successMsg && <Alert variant="success">{successMsg}</Alert>} {successMsg && <Alert variant="success">{successMsg}</Alert>}
{busy && <Alert variant="primary">Working...</Alert>} {busy && <Alert variant="primary">Working...</Alert>}
@ -149,11 +185,13 @@ class Admin extends React.Component {
Add Add
</Button> </Button>
</ValidatedForm> </ValidatedForm>
<hr className="mt-5 mb-5" />
<h3>Contributor List</h3> <h3>Contributor List</h3>
<ListGroup> <ListGroup>
{contributorList.sort(sortByAlpha('label')).map((contributor, idx) => ( {contributorList.sort(sortByAlpha('label')).map((contributor, idx) => (
<ListGroup.Item key={contributor.value} action className="contributor-item"> <ListGroup.Item key={contributor.value} action className="contributor-item">
<span className="font-weight-bold">{contributor.label}:</span> {contributor.value} <span className="font-weight-bold">{contributor.label}:</span>{' '}
<span className="text-small">{contributor.value}</span>
<div className="contributor-controls float-right"> <div className="contributor-controls float-right">
<OverlayTrigger placement="top" overlay={<Tooltip>Delete contributor</Tooltip>}> <OverlayTrigger placement="top" overlay={<Tooltip>Delete contributor</Tooltip>}>
<FontAwesomeIcon <FontAwesomeIcon
@ -167,31 +205,44 @@ class Admin extends React.Component {
))} ))}
</ListGroup> </ListGroup>
</Tab> </Tab>
<Tab eventKey="leaderboard" title="Leaderboard" className="leaderboard-panel"> <Tab eventKey="allocation" title="Allocation" className="allocation-panel">
<Table striped bordered hover responsive size="sm"> <h2>Allocation</h2>
<thead> {error && <Alert variant="danger">{error}</Alert>}
<tr> {successMsg && <Alert variant="success">{successMsg}</Alert>}
<th onClick={this.sortBy('label')}>Contributor</th> {busy && <Alert variant="primary">Working...</Alert>}
<th onClick={this.sortBy('allocation')}>Allocation</th> <ValidatedForm>
<th onClick={this.sortBy('totalReceived')}>SNT Received</th> <Form.Group controlId="fundAllocation">
<th onClick={this.sortBy('totalForfeited')}>SNT Forfeited</th> <Form.Label>SNT per contributor</Form.Label>
<th onClick={this.sortBy('praises')}>Praises Received</th> <Form.Text className="text-muted">
</tr> Total: {contributorList.length * parseInt(sntPerContributor, 10) || 0} SNT
</thead> </Form.Text>
<tbody> <Input
{ type="text"
sortedContributorList.map((contrib, i) => ( placeholder="0"
<tr key={i}> value={sntPerContributor}
<td>{contrib.label}</td> onChange={e => this.onChange('sntPerContributor', e)}
<td>{contrib.allocation}</td> className="form-control"
<td>{contrib.totalReceived}</td> validations={[required, isNumber, higherThan.bind(null, 0)]}
<td>{contrib.totalForfeited}</td> />
<td>{contrib.praises ? contrib.praises.length : 0}</td> </Form.Group>
</tr> <Button variant="primary" onClick={this.allocateFunds}>
)) Allocate Funds
} </Button>
</tbody> </ValidatedForm>
</Table> <hr className="mt-5 mb-5" />
<ValidatedForm>
<Form.Group>
<Button variant="primary" disabled={nextForfeit > new Date().getTime()} onClick={this.forfeit}>
Forfeit Allocation
</Button>
{lastForfeited && (
<Form.Text className="text-muted">
Forfeited {moment.unix(lastForfeited).fromNow()}.<br />{' '}
{nextForfeit > new Date().getTime() && 'Can be forfeited on ' + nextForfeitDate}
</Form.Text>
)}
</Form.Group>
</ValidatedForm>
</Tab> </Tab>
</Tabs> </Tabs>
<Modal show={this.state.showDeleteModal} onHide={this.handleClose}> <Modal show={this.state.showDeleteModal} onHide={this.handleClose}>

View File

@ -10,18 +10,16 @@ const Header = ({ isUserAdmin }) => (
<img alt="Logo" src={logo} className="mr-3" /> <img alt="Logo" src={logo} className="mr-3" />
Status Meritocracy Status Meritocracy
</Navbar.Brand> </Navbar.Brand>
{isUserAdmin && ( <React.Fragment>
<React.Fragment> <Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Toggle aria-controls="basic-navbar-nav" /> <Navbar.Collapse id="basic-navbar-nav">
<Navbar.Collapse id="basic-navbar-nav"> <Nav className="mr-auto">
<Nav className="mr-auto"> <Nav.Link href="#/">Home</Nav.Link>
<Nav.Link href="#/">Home</Nav.Link> <Nav.Link href="#/leaderboard">Leaderboard</Nav.Link>
<Nav.Link href="#/admin">Admin</Nav.Link> {isUserAdmin && <Nav.Link href="#/admin">Admin</Nav.Link>}
<Nav.Link href="#/wall">The Wall</Nav.Link> </Nav>
</Nav> </Navbar.Collapse>
</Navbar.Collapse> </React.Fragment>
</React.Fragment>
)}
</Navbar> </Navbar>
); );

View File

@ -10,7 +10,8 @@ import Loading from './Loading';
import Complete from './Complete'; import Complete from './Complete';
import Error from './Error'; import Error from './Error';
import Withdrawal from './Withdrawal'; import Withdrawal from './Withdrawal';
import {sortByAlpha, sortByAttribute} from '../utils'; import { sortByAlpha, sortByAttribute } from '../utils';
import Praise from './Praise';
/* /*
TODO: TODO:
- list praise for contributor - list praise for contributor
@ -49,16 +50,15 @@ class Home extends React.Component {
async componentDidMount() { async componentDidMount() {
try { try {
const contributorList = (await getFormattedContributorList()); const contributorList = await getFormattedContributorList();
const currentContributor = await getCurrentContributorData(); const currentContributor = await getCurrentContributorData();
this.setState({ busy: false, currentContributor, contributorList: contributorList.sort(sortByAlpha('label'))}); this.setState({ busy: false, currentContributor, contributorList: contributorList.sort(sortByAlpha('label')) });
getAllPraises().then(praises => {
this.setState({praises: praises.sort(sortByAttribute('time'))});
});
getAllPraises().then(praises => {
this.setState({ praises: praises.sort(sortByAttribute('time')) });
});
} catch (error) { } catch (error) {
this.setState({ errorMsg: error.message || error }); this.setState({ errorMsg: error.message || error });
} }
@ -213,7 +213,7 @@ class Home extends React.Component {
onChangeAward={this.handleAwardChange} onChangeAward={this.handleAwardChange}
onSelectContributor={this.handleContributorSelection} onSelectContributor={this.handleContributorSelection}
onClickPlus5={this.handlePlus5} onClickPlus5={this.handlePlus5}
contributorList={contributorList} contributorList={contributorList.filter(x => x.value !== currentContributor.addr)}
selectedContributors={selectedContributors} selectedContributors={selectedContributors}
award={award} award={award}
isChecked={checkbox} isChecked={checkbox}
@ -239,14 +239,16 @@ class Home extends React.Component {
</Tab> </Tab>
<Tab eventKey="wall" title="Wall"> <Tab eventKey="wall" title="Wall">
<Container className="pt-4"> <Container className="pt-4">
{praises.map((item, i) => <Praise key={i} individual={false} contributorList={contributorList} item={item} />)} {praises.map((item, i) => (
<Praise key={i} individual={false} contributorList={contributorList} item={item} />
))}
</Container> </Container>
</Tab> </Tab>
<Tab eventKey="withdraw" title="Withdraw" className="withdraw-panel"> <Tab eventKey="withdraw" title="Withdraw" className="withdraw-panel">
{step === 'HOME' && ( {step === 'HOME' && (
<Withdrawal <Withdrawal
onClick={this.withdrawTokens} onClick={this.withdrawTokens}
totalReceived={currentContributor.totalReceived} received={currentContributor.received}
allocation={currentContributor.allocation} allocation={currentContributor.allocation}
contributorList={contributorList} contributorList={contributorList}
praises={currentContributor.praises} praises={currentContributor.praises}

View File

@ -0,0 +1,73 @@
import React from 'react';
import { Table } from 'react-bootstrap';
import { getFormattedContributorList, getContributorData } from '../services/Meritocracy';
import { sortByAlpha, sortByAttribute, sortNullableArray } from '../utils';
const sort = orderBy => {
if (orderBy === 'praises') return sortNullableArray('praises');
if (orderBy === 'label') return sortByAlpha('label');
return sortByAttribute(orderBy);
};
class Leaderboard extends React.Component {
state = {
contributorList: [],
sortBy: 'label',
errorMsg: ''
};
async componentDidMount() {
try {
const contributorList = await getFormattedContributorList();
this.setState({ contributorList });
// TODO: this can be replaced by event sourcing
contributorList.forEach(contrib => {
getContributorData(contrib.value).then(data => {
contrib = Object.assign(contrib, data);
this.setState({ contributorList });
});
});
} catch (error) {
this.setState({ errorMsg: error.message || error });
}
}
sortBy = order => () => {
this.setState({ sortBy: order });
};
render() {
const { contributorList, sortBy } = this.state;
const sortedContributorList = contributorList.sort(sort(sortBy));
return (
<React.Fragment>
<h2>Leaderboard</h2>
<Table striped bordered hover responsive size="sm">
<thead>
<tr>
<th onClick={this.sortBy('label')}>Contributor</th>
<th onClick={this.sortBy('allocation')}>Allocation</th>
<th onClick={this.sortBy('totalReceived')}>SNT Received</th>
<th onClick={this.sortBy('totalForfeited')}>SNT Forfeited</th>
<th onClick={this.sortBy('praises')}>Praises Received</th>
</tr>
</thead>
<tbody>
{sortedContributorList.map((contrib, i) => (
<tr key={i}>
<td>{contrib.label}</td>
<td>{contrib.allocation}</td>
<td>{contrib.totalReceived}</td>
<td>{contrib.totalForfeited}</td>
<td>{contrib.praises ? contrib.praises.length : 0}</td>
</tr>
))}
</tbody>
</Table>
</React.Fragment>
);
}
}
export default Leaderboard;

View File

@ -1,10 +1,11 @@
/* global web3 */ /* global web3 */
import React, {Fragment} from 'react'; import React, { Fragment } from 'react';
import moment from 'moment'; import moment from 'moment';
import Address from './Address'; import Address from './Address';
import { Row, Col } from 'react-bootstrap'; import { Row, Col } from 'react-bootstrap';
import PropTypes from 'prop-types';
const Praise = ({contributorList, item, individual}) => { const Praise = ({ contributorList, item, individual }) => {
const name = contributorList.find(x => x.value === item.author); const name = contributorList.find(x => x.value === item.author);
const date = moment.unix(item.time).fromNow(); const date = moment.unix(item.time).fromNow();
return ( return (
@ -12,14 +13,16 @@ const Praise = ({contributorList, item, individual}) => {
<Col className="mb-4 text-muted"> <Col className="mb-4 text-muted">
{!item.praise && ( {!item.praise && (
<Fragment> <Fragment>
{(name && name.label) || <Address value={item.author} compact={true} />} {individual ? "has sent you" : "sent"}{' '} {(name && name.label) || <Address value={item.author} compact={true} />}{' '}
{web3.utils.fromWei(item.amount, 'ether')} SNT {!individual && <span>to {item.destination}</span>}, <small>{date}</small> {individual ? 'has sent you' : 'sent'} {web3.utils.fromWei(item.amount, 'ether')} SNT{' '}
{!individual && <span>to {item.destination}</span>}, <small>{date}</small>
</Fragment> </Fragment>
)} )}
{item.praise && ( {item.praise && (
<Fragment> <Fragment>
{(name && name.label) || <Address value={item.author} compact={true} />}{!individual && <span> to {item.destination}</span>}, <small>{date}</small> {(name && name.label) || <Address value={item.author} compact={true} />}
{!individual && <span> to {item.destination}</span>}, <small>{date}</small>
<div className="chatBubble p-3"> <div className="chatBubble p-3">
&quot;{item.praise}&quot; &quot;{item.praise}&quot;
<small className="float-right">{web3.utils.fromWei(item.amount, 'ether')} SNT</small> <small className="float-right">{web3.utils.fromWei(item.amount, 'ether')} SNT</small>
@ -31,4 +34,10 @@ const Praise = ({contributorList, item, individual}) => {
); );
}; };
Praise.propTypes = {
contributorList: PropTypes.array,
individual: PropTypes.bool,
item: PropTypes.object
};
export default Praise; export default Praise;

View File

@ -5,12 +5,12 @@ import Praise from './Praise';
import './withdrawal.scss'; import './withdrawal.scss';
const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, praises }) => ( const Withdrawal = ({ received, allocation, onClick, contributorList, praises }) => (
<Fragment> <Fragment>
<div className="text-center p-4"> <div className="text-center p-4">
<p className="text-muted mb-0 mt-5">You have been awarded</p> <p className="text-muted mb-0 mt-5">You have been awarded</p>
<p className="awarded mb-0"> <p className="awarded mb-0">
{totalReceived || 0} <span className="text-muted">SNT</span> {received || 0} <span className="text-muted">SNT</span>
</p> </p>
<p className="text-muted">Available for withdraw</p> <p className="text-muted">Available for withdraw</p>
</div> </div>
@ -22,15 +22,15 @@ const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, prais
<p className="text-center"> <p className="text-center">
<Button <Button
variant={allocation !== '0' || totalReceived === '0' ? 'secondary' : 'primary'} variant={allocation !== '0' || received === '0' ? 'secondary' : 'primary'}
onClick={onClick} onClick={onClick}
disabled={allocation !== '0' || totalReceived === '0'} disabled={allocation !== '0' || received === '0'}
> >
Withdraw Withdraw
</Button> </Button>
</p> </p>
{totalReceived !== '0' && parseInt(allocation, 10) > 0 && ( {received !== '0' && parseInt(allocation, 10) > 0 && (
<div className="text-muted text-left border rounded p-2 mb-2 learn-more"> <div className="text-muted text-left border rounded p-2 mb-2 learn-more">
<img src={info} alt="" /> <img src={info} alt="" />
<p className="m-0 p-0"> <p className="m-0 p-0">

View File

@ -1,4 +1,5 @@
.chatBubble { .chatBubble {
background: #ECEFFC; background: #ECEFFC;
border-radius: 8px; border-radius: 8px;
word-wrap: break-word;
} }

View File

@ -1,5 +1,7 @@
/*global web3*/ /*global web3*/
import Meritocracy from 'Embark/contracts/Meritocracy'; import Meritocracy from 'Embark/contracts/Meritocracy';
import SNT from 'Embark/contracts/SNT';
import EmbarkJS from 'Embark/EmbarkJS'; import EmbarkJS from 'Embark/EmbarkJS';
let contributorList; let contributorList;
@ -82,9 +84,7 @@ export async function getFormattedContributorList(hash) {
list = list.map(prepareOptions); list = list.map(prepareOptions);
const registry = await Meritocracy.methods.getRegistry().call({ from: mainAccount }); const registry = await Meritocracy.methods.getRegistry().call({ from: mainAccount });
list = list.filter( list = list.filter(contributorData => registry.includes(contributorData.value));
contributorData => registry.includes(contributorData.value) && contributorData.value !== mainAccount
);
resolve(list); resolve(list);
} catch (error) { } catch (error) {
@ -96,6 +96,79 @@ export async function getFormattedContributorList(hash) {
}); });
} }
export function forfeitAllocation() {
return new Promise(async (resolve, reject) => {
const mainAccount = web3.eth.defaultAccount;
try {
const toSend = Meritocracy.methods.forfeitAllocations();
let gas = await toSend.estimateGas({ from: mainAccount });
const receipt = await toSend.send({ from: mainAccount, gas: gas + 1000 });
resolve(receipt);
} catch (error) {
const message = 'Error forfeiting allocation';
console.error(message);
console.error(error);
reject(message);
}
});
}
export function allocate(sntAmount) {
return new Promise(async (resolve, reject) => {
const mainAccount = web3.eth.defaultAccount;
try {
let toSend, gas;
const balance = web3.utils.toBN(await SNT.methods.balanceOf(mainAccount).call());
const allowance = web3.utils.toBN(await SNT.methods.allowance(mainAccount, Meritocracy.options.address).call());
if (balance.lt(web3.utils.toBN(sntAmount))) {
throw new Error('Not enough SNT');
}
if (allowance.gt(web3.utils.toBN('0')) && allowance.lt(web3.utils.toBN(sntAmount))) {
alert('Reset allowance to 0');
toSend = SNT.methods.approve(Meritocracy.options.address, '0');
gas = await toSend.estimateGas({ from: mainAccount });
await toSend.send({ from: mainAccount, gas: gas + 1000 });
}
if (allowance.eq(web3.utils.toBN('0'))) {
alert(`Approving ${web3.utils.fromWei(sntAmount, 'ether')} to meritocracy contract`);
toSend = SNT.methods.approve(Meritocracy.options.address, sntAmount);
gas = await toSend.estimateGas({ from: mainAccount });
await toSend.send({ from: mainAccount, gas: gas + 1000 });
}
alert('Allocating SNT');
toSend = Meritocracy.methods.allocate(sntAmount);
gas = await toSend.estimateGas({ from: mainAccount });
await toSend.send({ from: mainAccount, gas: gas + 1000 });
resolve(true);
} catch (error) {
let message;
if (error.message === 'Not enough SNT') {
message = 'Not enough SNT';
} else {
message = 'Error forfeiting allocation';
}
console.error(message);
console.error(error);
reject(message);
}
});
}
export function lastForfeited() {
return new Promise(async resolve => {
const date = await Meritocracy.methods.lastForfeit().call();
resolve(date);
});
}
const prepareOptions = option => { const prepareOptions = option => {
if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) { if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) {
// Address // Address
@ -110,7 +183,7 @@ const prepareOptions = option => {
return option; return option;
}; };
export async function getCurrentContributorData(){ export async function getCurrentContributorData() {
const mainAccount = web3.eth.defaultAccount; const mainAccount = web3.eth.defaultAccount;
const contribData = await getContributorData(mainAccount); const contribData = await getContributorData(mainAccount);
return contribData; return contribData;
@ -120,7 +193,7 @@ export async function getContributorData(_address) {
const currentContributor = await getContributor(_address); const currentContributor = await getContributor(_address);
let praises = []; let praises = [];
for(let i = 0; i < currentContributor.praiseNum; i++){ for (let i = 0; i < currentContributor.praiseNum; i++) {
praises.push(Meritocracy.methods.getStatus(_address, i).call()); praises.push(Meritocracy.methods.getStatus(_address, i).call());
} }
@ -129,30 +202,29 @@ export async function getContributorData(_address) {
} }
const contribData = contributorList.find(x => x.value === _address); const contribData = contributorList.find(x => x.value === _address);
if(contribData) currentContributor.name = contribData.label; if (contribData) currentContributor.name = contribData.label;
currentContributor.praises = await Promise.all(praises); currentContributor.praises = await Promise.all(praises);
currentContributor.allocation = web3.utils.fromWei(currentContributor.allocation, "ether"); currentContributor.allocation = web3.utils.fromWei(currentContributor.allocation, 'ether');
currentContributor.totalForfeited = web3.utils.fromWei(currentContributor.totalForfeited, "ether"); currentContributor.totalForfeited = web3.utils.fromWei(currentContributor.totalForfeited, 'ether');
currentContributor.totalReceived = web3.utils.fromWei(currentContributor.totalReceived, "ether"); currentContributor.totalReceived = web3.utils.fromWei(currentContributor.totalReceived, 'ether');
currentContributor.received = web3.utils.fromWei(currentContributor.received, "ether"); currentContributor.received = web3.utils.fromWei(currentContributor.received, 'ether');
return currentContributor; return currentContributor;
} }
export function getAllPraises() { export function getAllPraises() {
return new Promise(function(resolve) { return new Promise(function(resolve) {
let praisesPromises = []; let praisesPromises = [];
let praiseNumPromises = []; let praiseNumPromises = [];
for(let i = 0; i < contributorList.length; i++){ for (let i = 0; i < contributorList.length; i++) {
praiseNumPromises.push(Meritocracy.methods.getStatusLength(contributorList[i].value).call()); praiseNumPromises.push(Meritocracy.methods.getStatusLength(contributorList[i].value).call());
} }
Promise.all(praiseNumPromises).then(praiseNums => { Promise.all(praiseNumPromises).then(praiseNums => {
for(let i = 0; i < contributorList.length; i++){ for (let i = 0; i < contributorList.length; i++) {
let currPraises = []; let currPraises = [];
for(let j = 0; j < praiseNums[i]; j++){ for (let j = 0; j < praiseNums[i]; j++) {
currPraises.push(Meritocracy.methods.getStatus(contributorList[i].value, j).call()); currPraises.push(Meritocracy.methods.getStatus(contributorList[i].value, j).call());
} }
praisesPromises.push(currPraises); praisesPromises.push(currPraises);
@ -165,17 +237,16 @@ export function getAllPraises() {
); );
allPromises.then(praises => { allPromises.then(praises => {
for(let i = 0; i < praises.length; i++){ for (let i = 0; i < praises.length; i++) {
praises[i] = praises[i].map(x => { praises[i] = praises[i].map(x => {
x.destination = contributorList[i].label; x.destination = contributorList[i].label;
return x; return x;
}); });
} }
resolve(praises.flat()); resolve(praises.flat());
}); });
}); });
}); });
} }
export async function getContributor(_address) { export async function getContributor(_address) {

View File

@ -24,7 +24,7 @@ export const isInteger = value => {
}; };
export const isNumber = value => { export const isNumber = value => {
if (Number.isNaN(value)) { if (isNaN(value)) {
return ( return (
<Form.Control.Feedback type="invalid" className="d-block"> <Form.Control.Feedback type="invalid" className="d-block">
This field needs to be an number This field needs to be an number