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:
parent
7233fb934b
commit
9d983c2b9d
|
@ -91,3 +91,7 @@ $input-icon-height: 20px;
|
|||
.btn {
|
||||
padding: 11px 36px;
|
||||
}
|
||||
|
||||
.text-small {
|
||||
font-size: 13px;
|
||||
}
|
|
@ -8,6 +8,7 @@ import EmbarkJS from 'Embark/EmbarkJS';
|
|||
import { isAdmin } from './services/Meritocracy';
|
||||
import Header from './components/Header';
|
||||
import Home from './components/Home';
|
||||
import Leaderboard from './components/Leaderboard';
|
||||
import Admin from './components/Admin';
|
||||
|
||||
const MAINNET = 1;
|
||||
|
@ -65,8 +66,8 @@ class App extends React.Component {
|
|||
<Header isUserAdmin={isUserAdmin} />
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/leaderboard" component={Leaderboard} />
|
||||
{isUserAdmin && <Route exact path="/admin" component={Admin} />}
|
||||
|
||||
<Redirect to="/404" />
|
||||
</Switch>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
/* global web3 */
|
||||
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 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 { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { addContributor, getFormattedContributorList, removeContributor, getContributorData } from '../services/Meritocracy';
|
||||
import { sortByAlpha, sortByAttribute, sortNullableArray } from '../utils';
|
||||
import {
|
||||
addContributor,
|
||||
getFormattedContributorList,
|
||||
removeContributor,
|
||||
forfeitAllocation,
|
||||
lastForfeited,
|
||||
allocate
|
||||
} from '../services/Meritocracy';
|
||||
import { sortByAlpha } from '../utils';
|
||||
import moment from 'moment';
|
||||
|
||||
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 {
|
||||
state = {
|
||||
contributorName: '',
|
||||
|
@ -27,7 +30,9 @@ class Admin extends React.Component {
|
|||
showDeleteModal: false,
|
||||
focusedContributorIndex: -1,
|
||||
sortBy: 'label',
|
||||
tab: 'admin'
|
||||
tab: 'admin',
|
||||
sntPerContributor: 0,
|
||||
lastForfeited: null
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
|
@ -36,15 +41,7 @@ class Admin extends React.Component {
|
|||
|
||||
this.setState({ busy: false, 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});
|
||||
});
|
||||
});
|
||||
|
||||
this.getLastForfeitDate();
|
||||
} catch (error) {
|
||||
this.setState({ errorMsg: error.message || error });
|
||||
}
|
||||
|
@ -54,9 +51,14 @@ class Admin extends React.Component {
|
|||
this.setState({ [name]: e.target.value });
|
||||
};
|
||||
|
||||
getLastForfeitDate = async () => {
|
||||
const date = await lastForfeited();
|
||||
this.setState({ lastForfeited: date });
|
||||
};
|
||||
|
||||
addContributor = async e => {
|
||||
e.preventDefault();
|
||||
this.setState({ busy: true, successMsg: '' });
|
||||
this.setState({ busy: true, successMsg: '', error: '' });
|
||||
try {
|
||||
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) => {
|
||||
e.preventDefault();
|
||||
this.setState({ focusedContributorIndex: contributorIndex, showDeleteModal: true });
|
||||
|
@ -93,12 +130,9 @@ class Admin extends React.Component {
|
|||
this.setState({ showDeleteModal: false });
|
||||
};
|
||||
|
||||
sortBy = (order) => () => {
|
||||
this.setState({sortBy: order});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
lastForfeited,
|
||||
contributorAddress,
|
||||
contributorName,
|
||||
error,
|
||||
|
@ -106,17 +140,19 @@ class Admin extends React.Component {
|
|||
contributorList,
|
||||
successMsg,
|
||||
focusedContributorIndex,
|
||||
sortBy,
|
||||
tab
|
||||
tab,
|
||||
sntPerContributor
|
||||
} = this.state;
|
||||
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 (
|
||||
<Fragment>
|
||||
<Tabs className="home-tabs mb-3" activeKey={tab} onSelect={tab => this.setState({ tab })}>
|
||||
<Tab eventKey="admin" title="Admin Panel" className="admin-panel">
|
||||
<h2>Admin Panel</h2>
|
||||
<Tab eventKey="admin" title="Contributors" className="admin-panel">
|
||||
<h2>Contributors</h2>
|
||||
{error && <Alert variant="danger">{error}</Alert>}
|
||||
{successMsg && <Alert variant="success">{successMsg}</Alert>}
|
||||
{busy && <Alert variant="primary">Working...</Alert>}
|
||||
|
@ -149,11 +185,13 @@ class Admin extends React.Component {
|
|||
Add
|
||||
</Button>
|
||||
</ValidatedForm>
|
||||
<hr className="mt-5 mb-5" />
|
||||
<h3>Contributor List</h3>
|
||||
<ListGroup>
|
||||
{contributorList.sort(sortByAlpha('label')).map((contributor, idx) => (
|
||||
<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">
|
||||
<OverlayTrigger placement="top" overlay={<Tooltip>Delete contributor</Tooltip>}>
|
||||
<FontAwesomeIcon
|
||||
|
@ -167,31 +205,44 @@ class Admin extends React.Component {
|
|||
))}
|
||||
</ListGroup>
|
||||
</Tab>
|
||||
<Tab eventKey="leaderboard" title="Leaderboard" className="leaderboard-panel">
|
||||
<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>
|
||||
<Tab eventKey="allocation" title="Allocation" className="allocation-panel">
|
||||
<h2>Allocation</h2>
|
||||
{error && <Alert variant="danger">{error}</Alert>}
|
||||
{successMsg && <Alert variant="success">{successMsg}</Alert>}
|
||||
{busy && <Alert variant="primary">Working...</Alert>}
|
||||
<ValidatedForm>
|
||||
<Form.Group controlId="fundAllocation">
|
||||
<Form.Label>SNT per contributor</Form.Label>
|
||||
<Form.Text className="text-muted">
|
||||
Total: {contributorList.length * parseInt(sntPerContributor, 10) || 0} SNT
|
||||
</Form.Text>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="0"
|
||||
value={sntPerContributor}
|
||||
onChange={e => this.onChange('sntPerContributor', e)}
|
||||
className="form-control"
|
||||
validations={[required, isNumber, higherThan.bind(null, 0)]}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant="primary" onClick={this.allocateFunds}>
|
||||
Allocate Funds
|
||||
</Button>
|
||||
</ValidatedForm>
|
||||
<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>
|
||||
</Tabs>
|
||||
<Modal show={this.state.showDeleteModal} onHide={this.handleClose}>
|
||||
|
|
|
@ -10,18 +10,16 @@ const Header = ({ isUserAdmin }) => (
|
|||
<img alt="Logo" src={logo} className="mr-3" />
|
||||
Status Meritocracy
|
||||
</Navbar.Brand>
|
||||
{isUserAdmin && (
|
||||
<React.Fragment>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="mr-auto">
|
||||
<Nav.Link href="#/">Home</Nav.Link>
|
||||
<Nav.Link href="#/admin">Admin</Nav.Link>
|
||||
<Nav.Link href="#/wall">The Wall</Nav.Link>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<React.Fragment>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="mr-auto">
|
||||
<Nav.Link href="#/">Home</Nav.Link>
|
||||
<Nav.Link href="#/leaderboard">Leaderboard</Nav.Link>
|
||||
{isUserAdmin && <Nav.Link href="#/admin">Admin</Nav.Link>}
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</React.Fragment>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ import Loading from './Loading';
|
|||
import Complete from './Complete';
|
||||
import Error from './Error';
|
||||
import Withdrawal from './Withdrawal';
|
||||
import {sortByAlpha, sortByAttribute} from '../utils';
|
||||
import { sortByAlpha, sortByAttribute } from '../utils';
|
||||
import Praise from './Praise';
|
||||
/*
|
||||
TODO:
|
||||
- list praise for contributor
|
||||
|
@ -49,16 +50,15 @@ class Home extends React.Component {
|
|||
|
||||
async componentDidMount() {
|
||||
try {
|
||||
const contributorList = (await getFormattedContributorList());
|
||||
const contributorList = await getFormattedContributorList();
|
||||
|
||||
const currentContributor = await getCurrentContributorData();
|
||||
|
||||
this.setState({ busy: false, currentContributor, contributorList: contributorList.sort(sortByAlpha('label'))});
|
||||
|
||||
getAllPraises().then(praises => {
|
||||
this.setState({praises: praises.sort(sortByAttribute('time'))});
|
||||
});
|
||||
this.setState({ busy: false, currentContributor, contributorList: contributorList.sort(sortByAlpha('label')) });
|
||||
|
||||
getAllPraises().then(praises => {
|
||||
this.setState({ praises: praises.sort(sortByAttribute('time')) });
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({ errorMsg: error.message || error });
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ class Home extends React.Component {
|
|||
onChangeAward={this.handleAwardChange}
|
||||
onSelectContributor={this.handleContributorSelection}
|
||||
onClickPlus5={this.handlePlus5}
|
||||
contributorList={contributorList}
|
||||
contributorList={contributorList.filter(x => x.value !== currentContributor.addr)}
|
||||
selectedContributors={selectedContributors}
|
||||
award={award}
|
||||
isChecked={checkbox}
|
||||
|
@ -239,14 +239,16 @@ class Home extends React.Component {
|
|||
</Tab>
|
||||
<Tab eventKey="wall" title="Wall">
|
||||
<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>
|
||||
</Tab>
|
||||
<Tab eventKey="withdraw" title="Withdraw" className="withdraw-panel">
|
||||
{step === 'HOME' && (
|
||||
<Withdrawal
|
||||
onClick={this.withdrawTokens}
|
||||
totalReceived={currentContributor.totalReceived}
|
||||
received={currentContributor.received}
|
||||
allocation={currentContributor.allocation}
|
||||
contributorList={contributorList}
|
||||
praises={currentContributor.praises}
|
||||
|
|
|
@ -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;
|
|
@ -1,10 +1,11 @@
|
|||
/* global web3 */
|
||||
import React, {Fragment} from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import moment from 'moment';
|
||||
import Address from './Address';
|
||||
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 date = moment.unix(item.time).fromNow();
|
||||
return (
|
||||
|
@ -12,14 +13,16 @@ const Praise = ({contributorList, item, individual}) => {
|
|||
<Col className="mb-4 text-muted">
|
||||
{!item.praise && (
|
||||
<Fragment>
|
||||
{(name && name.label) || <Address value={item.author} compact={true} />} {individual ? "has sent you" : "sent"}{' '}
|
||||
{web3.utils.fromWei(item.amount, 'ether')} SNT {!individual && <span>to {item.destination}</span>}, <small>{date}</small>
|
||||
{(name && name.label) || <Address value={item.author} compact={true} />}{' '}
|
||||
{individual ? 'has sent you' : 'sent'} {web3.utils.fromWei(item.amount, 'ether')} SNT{' '}
|
||||
{!individual && <span>to {item.destination}</span>}, <small>{date}</small>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{item.praise && (
|
||||
<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">
|
||||
"{item.praise}"
|
||||
<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;
|
||||
|
|
|
@ -5,12 +5,12 @@ import Praise from './Praise';
|
|||
|
||||
import './withdrawal.scss';
|
||||
|
||||
const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, praises }) => (
|
||||
const Withdrawal = ({ received, 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>
|
||||
{received || 0} <span className="text-muted">SNT</span>
|
||||
</p>
|
||||
<p className="text-muted">Available for withdraw</p>
|
||||
</div>
|
||||
|
@ -22,15 +22,15 @@ const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, prais
|
|||
|
||||
<p className="text-center">
|
||||
<Button
|
||||
variant={allocation !== '0' || totalReceived === '0' ? 'secondary' : 'primary'}
|
||||
variant={allocation !== '0' || received === '0' ? 'secondary' : 'primary'}
|
||||
onClick={onClick}
|
||||
disabled={allocation !== '0' || totalReceived === '0'}
|
||||
disabled={allocation !== '0' || received === '0'}
|
||||
>
|
||||
Withdraw
|
||||
</Button>
|
||||
</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">
|
||||
<img src={info} alt="" />
|
||||
<p className="m-0 p-0">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.chatBubble {
|
||||
background: #ECEFFC;
|
||||
border-radius: 8px;
|
||||
word-wrap: break-word;
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
/*global web3*/
|
||||
import Meritocracy from 'Embark/contracts/Meritocracy';
|
||||
import SNT from 'Embark/contracts/SNT';
|
||||
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
|
||||
let contributorList;
|
||||
|
@ -82,9 +84,7 @@ export async function getFormattedContributorList(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
|
||||
);
|
||||
list = list.filter(contributorData => registry.includes(contributorData.value));
|
||||
|
||||
resolve(list);
|
||||
} 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 => {
|
||||
if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) {
|
||||
// Address
|
||||
|
@ -110,7 +183,7 @@ const prepareOptions = option => {
|
|||
return option;
|
||||
};
|
||||
|
||||
export async function getCurrentContributorData(){
|
||||
export async function getCurrentContributorData() {
|
||||
const mainAccount = web3.eth.defaultAccount;
|
||||
const contribData = await getContributorData(mainAccount);
|
||||
return contribData;
|
||||
|
@ -120,7 +193,7 @@ export async function getContributorData(_address) {
|
|||
const currentContributor = await getContributor(_address);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -129,30 +202,29 @@ export async function getContributorData(_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.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;
|
||||
}
|
||||
|
||||
export function getAllPraises() {
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
let praisesPromises = [];
|
||||
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());
|
||||
}
|
||||
|
||||
Promise.all(praiseNumPromises).then(praiseNums => {
|
||||
for(let i = 0; i < contributorList.length; i++){
|
||||
for (let i = 0; i < contributorList.length; i++) {
|
||||
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());
|
||||
}
|
||||
praisesPromises.push(currPraises);
|
||||
|
@ -165,17 +237,16 @@ export function getAllPraises() {
|
|||
);
|
||||
|
||||
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 => {
|
||||
x.destination = contributorList[i].label;
|
||||
return x;
|
||||
});
|
||||
});
|
||||
}
|
||||
resolve(praises.flat());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export async function getContributor(_address) {
|
||||
|
|
|
@ -24,7 +24,7 @@ export const isInteger = value => {
|
|||
};
|
||||
|
||||
export const isNumber = value => {
|
||||
if (Number.isNaN(value)) {
|
||||
if (isNaN(value)) {
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be an number
|
||||
|
|
Loading…
Reference in New Issue